Spaces:
Runtime error
Runtime error
| # Copyright 2017 The TensorFlow Authors. All Rights Reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # ============================================================================== | |
| r"""Constructs model, inputs, and training environment.""" | |
| from __future__ import absolute_import | |
| from __future__ import division | |
| from __future__ import print_function | |
| import copy | |
| import functools | |
| import os | |
| import tensorflow.compat.v1 as tf | |
| import tensorflow.compat.v2 as tf2 | |
| import tf_slim as slim | |
| from object_detection import eval_util | |
| from object_detection import exporter as exporter_lib | |
| from object_detection import inputs | |
| from object_detection.builders import graph_rewriter_builder | |
| from object_detection.builders import model_builder | |
| from object_detection.builders import optimizer_builder | |
| from object_detection.core import standard_fields as fields | |
| from object_detection.utils import config_util | |
| from object_detection.utils import label_map_util | |
| from object_detection.utils import ops | |
| from object_detection.utils import shape_utils | |
| from object_detection.utils import variables_helper | |
| from object_detection.utils import visualization_utils as vis_utils | |
| # pylint: disable=g-import-not-at-top | |
| try: | |
| from tensorflow.contrib import learn as contrib_learn | |
| from tensorflow.contrib import tpu as contrib_tpu | |
| except ImportError: | |
| # TF 2.0 doesn't ship with contrib. | |
| pass | |
| # pylint: enable=g-import-not-at-top | |
| # A map of names to methods that help build the model. | |
| MODEL_BUILD_UTIL_MAP = { | |
| 'get_configs_from_pipeline_file': | |
| config_util.get_configs_from_pipeline_file, | |
| 'create_pipeline_proto_from_configs': | |
| config_util.create_pipeline_proto_from_configs, | |
| 'merge_external_params_with_configs': | |
| config_util.merge_external_params_with_configs, | |
| 'create_train_input_fn': | |
| inputs.create_train_input_fn, | |
| 'create_eval_input_fn': | |
| inputs.create_eval_input_fn, | |
| 'create_predict_input_fn': | |
| inputs.create_predict_input_fn, | |
| 'detection_model_fn_base': model_builder.build, | |
| } | |
| def _prepare_groundtruth_for_eval(detection_model, class_agnostic, | |
| max_number_of_boxes): | |
| """Extracts groundtruth data from detection_model and prepares it for eval. | |
| Args: | |
| detection_model: A `DetectionModel` object. | |
| class_agnostic: Whether the detections are class_agnostic. | |
| max_number_of_boxes: Max number of groundtruth boxes. | |
| Returns: | |
| A tuple of: | |
| groundtruth: Dictionary with the following fields: | |
| 'groundtruth_boxes': [batch_size, num_boxes, 4] float32 tensor of boxes, | |
| in normalized coordinates. | |
| 'groundtruth_classes': [batch_size, num_boxes] int64 tensor of 1-indexed | |
| classes. | |
| 'groundtruth_masks': 4D float32 tensor of instance masks (if provided in | |
| groundtruth) | |
| 'groundtruth_is_crowd': [batch_size, num_boxes] bool tensor indicating | |
| is_crowd annotations (if provided in groundtruth). | |
| 'groundtruth_area': [batch_size, num_boxes] float32 tensor indicating | |
| the area (in the original absolute coordinates) of annotations (if | |
| provided in groundtruth). | |
| 'num_groundtruth_boxes': [batch_size] tensor containing the maximum number | |
| of groundtruth boxes per image.. | |
| 'groundtruth_keypoints': [batch_size, num_boxes, num_keypoints, 2] float32 | |
| tensor of keypoints (if provided in groundtruth). | |
| 'groundtruth_group_of': [batch_size, num_boxes] bool tensor indicating | |
| group_of annotations (if provided in groundtruth). | |
| 'groundtruth_labeled_classes': [batch_size, num_classes] int64 | |
| tensor of 1-indexed classes. | |
| class_agnostic: Boolean indicating whether detections are class agnostic. | |
| """ | |
| input_data_fields = fields.InputDataFields() | |
| groundtruth_boxes = tf.stack( | |
| detection_model.groundtruth_lists(fields.BoxListFields.boxes)) | |
| groundtruth_boxes_shape = tf.shape(groundtruth_boxes) | |
| # For class-agnostic models, groundtruth one-hot encodings collapse to all | |
| # ones. | |
| if class_agnostic: | |
| groundtruth_classes_one_hot = tf.ones( | |
| [groundtruth_boxes_shape[0], groundtruth_boxes_shape[1], 1]) | |
| else: | |
| groundtruth_classes_one_hot = tf.stack( | |
| detection_model.groundtruth_lists(fields.BoxListFields.classes)) | |
| label_id_offset = 1 # Applying label id offset (b/63711816) | |
| groundtruth_classes = ( | |
| tf.argmax(groundtruth_classes_one_hot, axis=2) + label_id_offset) | |
| groundtruth = { | |
| input_data_fields.groundtruth_boxes: groundtruth_boxes, | |
| input_data_fields.groundtruth_classes: groundtruth_classes | |
| } | |
| if detection_model.groundtruth_has_field(fields.BoxListFields.masks): | |
| groundtruth[input_data_fields.groundtruth_instance_masks] = tf.stack( | |
| detection_model.groundtruth_lists(fields.BoxListFields.masks)) | |
| if detection_model.groundtruth_has_field(fields.BoxListFields.is_crowd): | |
| groundtruth[input_data_fields.groundtruth_is_crowd] = tf.stack( | |
| detection_model.groundtruth_lists(fields.BoxListFields.is_crowd)) | |
| if detection_model.groundtruth_has_field(input_data_fields.groundtruth_area): | |
| groundtruth[input_data_fields.groundtruth_area] = tf.stack( | |
| detection_model.groundtruth_lists(input_data_fields.groundtruth_area)) | |
| if detection_model.groundtruth_has_field(fields.BoxListFields.keypoints): | |
| groundtruth[input_data_fields.groundtruth_keypoints] = tf.stack( | |
| detection_model.groundtruth_lists(fields.BoxListFields.keypoints)) | |
| if detection_model.groundtruth_has_field( | |
| fields.BoxListFields.keypoint_visibilities): | |
| groundtruth[input_data_fields.groundtruth_keypoint_visibilities] = tf.stack( | |
| detection_model.groundtruth_lists( | |
| fields.BoxListFields.keypoint_visibilities)) | |
| if detection_model.groundtruth_has_field(fields.BoxListFields.group_of): | |
| groundtruth[input_data_fields.groundtruth_group_of] = tf.stack( | |
| detection_model.groundtruth_lists(fields.BoxListFields.group_of)) | |
| if detection_model.groundtruth_has_field( | |
| fields.InputDataFields.groundtruth_labeled_classes): | |
| labeled_classes_list = detection_model.groundtruth_lists( | |
| fields.InputDataFields.groundtruth_labeled_classes) | |
| labeled_classes = [ | |
| tf.where(x)[:, 0] + label_id_offset for x in labeled_classes_list | |
| ] | |
| if len(labeled_classes) > 1: | |
| num_classes = labeled_classes_list[0].shape[0] | |
| padded_labeled_classes = [] | |
| for x in labeled_classes: | |
| padding = num_classes - tf.shape(x)[0] | |
| padded_labeled_classes.append(tf.pad(x, [[0, padding]])) | |
| groundtruth[input_data_fields.groundtruth_labeled_classes] = tf.stack( | |
| padded_labeled_classes) | |
| else: | |
| groundtruth[input_data_fields.groundtruth_labeled_classes] = tf.stack( | |
| labeled_classes) | |
| groundtruth[input_data_fields.num_groundtruth_boxes] = ( | |
| tf.tile([max_number_of_boxes], multiples=[groundtruth_boxes_shape[0]])) | |
| return groundtruth | |
| def unstack_batch(tensor_dict, unpad_groundtruth_tensors=True): | |
| """Unstacks all tensors in `tensor_dict` along 0th dimension. | |
| Unstacks tensor from the tensor dict along 0th dimension and returns a | |
| tensor_dict containing values that are lists of unstacked, unpadded tensors. | |
| Tensors in the `tensor_dict` are expected to be of one of the three shapes: | |
| 1. [batch_size] | |
| 2. [batch_size, height, width, channels] | |
| 3. [batch_size, num_boxes, d1, d2, ... dn] | |
| When unpad_groundtruth_tensors is set to true, unstacked tensors of form 3 | |
| above are sliced along the `num_boxes` dimension using the value in tensor | |
| field.InputDataFields.num_groundtruth_boxes. | |
| Note that this function has a static list of input data fields and has to be | |
| kept in sync with the InputDataFields defined in core/standard_fields.py | |
| Args: | |
| tensor_dict: A dictionary of batched groundtruth tensors. | |
| unpad_groundtruth_tensors: Whether to remove padding along `num_boxes` | |
| dimension of the groundtruth tensors. | |
| Returns: | |
| A dictionary where the keys are from fields.InputDataFields and values are | |
| a list of unstacked (optionally unpadded) tensors. | |
| Raises: | |
| ValueError: If unpad_tensors is True and `tensor_dict` does not contain | |
| `num_groundtruth_boxes` tensor. | |
| """ | |
| unbatched_tensor_dict = { | |
| key: tf.unstack(tensor) for key, tensor in tensor_dict.items() | |
| } | |
| if unpad_groundtruth_tensors: | |
| if (fields.InputDataFields.num_groundtruth_boxes not in | |
| unbatched_tensor_dict): | |
| raise ValueError('`num_groundtruth_boxes` not found in tensor_dict. ' | |
| 'Keys available: {}'.format( | |
| unbatched_tensor_dict.keys())) | |
| unbatched_unpadded_tensor_dict = {} | |
| unpad_keys = set([ | |
| # List of input data fields that are padded along the num_boxes | |
| # dimension. This list has to be kept in sync with InputDataFields in | |
| # standard_fields.py. | |
| fields.InputDataFields.groundtruth_instance_masks, | |
| fields.InputDataFields.groundtruth_classes, | |
| fields.InputDataFields.groundtruth_boxes, | |
| fields.InputDataFields.groundtruth_keypoints, | |
| fields.InputDataFields.groundtruth_keypoint_visibilities, | |
| fields.InputDataFields.groundtruth_group_of, | |
| fields.InputDataFields.groundtruth_difficult, | |
| fields.InputDataFields.groundtruth_is_crowd, | |
| fields.InputDataFields.groundtruth_area, | |
| fields.InputDataFields.groundtruth_weights | |
| ]).intersection(set(unbatched_tensor_dict.keys())) | |
| for key in unpad_keys: | |
| unpadded_tensor_list = [] | |
| for num_gt, padded_tensor in zip( | |
| unbatched_tensor_dict[fields.InputDataFields.num_groundtruth_boxes], | |
| unbatched_tensor_dict[key]): | |
| tensor_shape = shape_utils.combined_static_and_dynamic_shape( | |
| padded_tensor) | |
| slice_begin = tf.zeros([len(tensor_shape)], dtype=tf.int32) | |
| slice_size = tf.stack( | |
| [num_gt] + [-1 if dim is None else dim for dim in tensor_shape[1:]]) | |
| unpadded_tensor = tf.slice(padded_tensor, slice_begin, slice_size) | |
| unpadded_tensor_list.append(unpadded_tensor) | |
| unbatched_unpadded_tensor_dict[key] = unpadded_tensor_list | |
| unbatched_tensor_dict.update(unbatched_unpadded_tensor_dict) | |
| return unbatched_tensor_dict | |
| def provide_groundtruth(model, labels): | |
| """Provides the labels to a model as groundtruth. | |
| This helper function extracts the corresponding boxes, classes, | |
| keypoints, weights, masks, etc. from the labels, and provides it | |
| as groundtruth to the models. | |
| Args: | |
| model: The detection model to provide groundtruth to. | |
| labels: The labels for the training or evaluation inputs. | |
| """ | |
| gt_boxes_list = labels[fields.InputDataFields.groundtruth_boxes] | |
| gt_classes_list = labels[fields.InputDataFields.groundtruth_classes] | |
| gt_masks_list = None | |
| if fields.InputDataFields.groundtruth_instance_masks in labels: | |
| gt_masks_list = labels[ | |
| fields.InputDataFields.groundtruth_instance_masks] | |
| gt_keypoints_list = None | |
| if fields.InputDataFields.groundtruth_keypoints in labels: | |
| gt_keypoints_list = labels[fields.InputDataFields.groundtruth_keypoints] | |
| gt_keypoint_visibilities_list = None | |
| if fields.InputDataFields.groundtruth_keypoint_visibilities in labels: | |
| gt_keypoint_visibilities_list = labels[ | |
| fields.InputDataFields.groundtruth_keypoint_visibilities] | |
| gt_weights_list = None | |
| if fields.InputDataFields.groundtruth_weights in labels: | |
| gt_weights_list = labels[fields.InputDataFields.groundtruth_weights] | |
| gt_confidences_list = None | |
| if fields.InputDataFields.groundtruth_confidences in labels: | |
| gt_confidences_list = labels[ | |
| fields.InputDataFields.groundtruth_confidences] | |
| gt_is_crowd_list = None | |
| if fields.InputDataFields.groundtruth_is_crowd in labels: | |
| gt_is_crowd_list = labels[fields.InputDataFields.groundtruth_is_crowd] | |
| gt_group_of_list = None | |
| if fields.InputDataFields.groundtruth_group_of in labels: | |
| gt_group_of_list = labels[fields.InputDataFields.groundtruth_group_of] | |
| gt_area_list = None | |
| if fields.InputDataFields.groundtruth_area in labels: | |
| gt_area_list = labels[fields.InputDataFields.groundtruth_area] | |
| gt_labeled_classes = None | |
| if fields.InputDataFields.groundtruth_labeled_classes in labels: | |
| gt_labeled_classes = labels[ | |
| fields.InputDataFields.groundtruth_labeled_classes] | |
| model.provide_groundtruth( | |
| groundtruth_boxes_list=gt_boxes_list, | |
| groundtruth_classes_list=gt_classes_list, | |
| groundtruth_confidences_list=gt_confidences_list, | |
| groundtruth_labeled_classes=gt_labeled_classes, | |
| groundtruth_masks_list=gt_masks_list, | |
| groundtruth_keypoints_list=gt_keypoints_list, | |
| groundtruth_keypoint_visibilities_list=gt_keypoint_visibilities_list, | |
| groundtruth_weights_list=gt_weights_list, | |
| groundtruth_is_crowd_list=gt_is_crowd_list, | |
| groundtruth_group_of_list=gt_group_of_list, | |
| groundtruth_area_list=gt_area_list) | |
| def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False, | |
| postprocess_on_cpu=False): | |
| """Creates a model function for `Estimator`. | |
| Args: | |
| detection_model_fn: Function that returns a `DetectionModel` instance. | |
| configs: Dictionary of pipeline config objects. | |
| hparams: `HParams` object. | |
| use_tpu: Boolean indicating whether model should be constructed for | |
| use on TPU. | |
| postprocess_on_cpu: When use_tpu and postprocess_on_cpu is true, postprocess | |
| is scheduled on the host cpu. | |
| Returns: | |
| `model_fn` for `Estimator`. | |
| """ | |
| train_config = configs['train_config'] | |
| eval_input_config = configs['eval_input_config'] | |
| eval_config = configs['eval_config'] | |
| def model_fn(features, labels, mode, params=None): | |
| """Constructs the object detection model. | |
| Args: | |
| features: Dictionary of feature tensors, returned from `input_fn`. | |
| labels: Dictionary of groundtruth tensors if mode is TRAIN or EVAL, | |
| otherwise None. | |
| mode: Mode key from tf.estimator.ModeKeys. | |
| params: Parameter dictionary passed from the estimator. | |
| Returns: | |
| An `EstimatorSpec` that encapsulates the model and its serving | |
| configurations. | |
| """ | |
| params = params or {} | |
| total_loss, train_op, detections, export_outputs = None, None, None, None | |
| is_training = mode == tf.estimator.ModeKeys.TRAIN | |
| # Make sure to set the Keras learning phase. True during training, | |
| # False for inference. | |
| tf.keras.backend.set_learning_phase(is_training) | |
| # Set policy for mixed-precision training with Keras-based models. | |
| if use_tpu and train_config.use_bfloat16: | |
| from tensorflow.python.keras.engine import base_layer_utils # pylint: disable=g-import-not-at-top | |
| # Enable v2 behavior, as `mixed_bfloat16` is only supported in TF 2.0. | |
| base_layer_utils.enable_v2_dtype_behavior() | |
| tf2.keras.mixed_precision.experimental.set_policy( | |
| 'mixed_bfloat16') | |
| detection_model = detection_model_fn( | |
| is_training=is_training, add_summaries=(not use_tpu)) | |
| scaffold_fn = None | |
| if mode == tf.estimator.ModeKeys.TRAIN: | |
| labels = unstack_batch( | |
| labels, | |
| unpad_groundtruth_tensors=train_config.unpad_groundtruth_tensors) | |
| elif mode == tf.estimator.ModeKeys.EVAL: | |
| # For evaling on train data, it is necessary to check whether groundtruth | |
| # must be unpadded. | |
| boxes_shape = ( | |
| labels[fields.InputDataFields.groundtruth_boxes].get_shape() | |
| .as_list()) | |
| unpad_groundtruth_tensors = boxes_shape[1] is not None and not use_tpu | |
| labels = unstack_batch( | |
| labels, unpad_groundtruth_tensors=unpad_groundtruth_tensors) | |
| if mode in (tf.estimator.ModeKeys.TRAIN, tf.estimator.ModeKeys.EVAL): | |
| provide_groundtruth(detection_model, labels) | |
| preprocessed_images = features[fields.InputDataFields.image] | |
| side_inputs = detection_model.get_side_inputs(features) | |
| if use_tpu and train_config.use_bfloat16: | |
| with contrib_tpu.bfloat16_scope(): | |
| prediction_dict = detection_model.predict( | |
| preprocessed_images, | |
| features[fields.InputDataFields.true_image_shape], **side_inputs) | |
| prediction_dict = ops.bfloat16_to_float32_nested(prediction_dict) | |
| else: | |
| prediction_dict = detection_model.predict( | |
| preprocessed_images, | |
| features[fields.InputDataFields.true_image_shape], **side_inputs) | |
| def postprocess_wrapper(args): | |
| return detection_model.postprocess(args[0], args[1]) | |
| if mode in (tf.estimator.ModeKeys.EVAL, tf.estimator.ModeKeys.PREDICT): | |
| if use_tpu and postprocess_on_cpu: | |
| detections = contrib_tpu.outside_compilation( | |
| postprocess_wrapper, | |
| (prediction_dict, | |
| features[fields.InputDataFields.true_image_shape])) | |
| else: | |
| detections = postprocess_wrapper(( | |
| prediction_dict, | |
| features[fields.InputDataFields.true_image_shape])) | |
| if mode == tf.estimator.ModeKeys.TRAIN: | |
| load_pretrained = hparams.load_pretrained if hparams else False | |
| if train_config.fine_tune_checkpoint and load_pretrained: | |
| if not train_config.fine_tune_checkpoint_type: | |
| # train_config.from_detection_checkpoint field is deprecated. For | |
| # backward compatibility, set train_config.fine_tune_checkpoint_type | |
| # based on train_config.from_detection_checkpoint. | |
| if train_config.from_detection_checkpoint: | |
| train_config.fine_tune_checkpoint_type = 'detection' | |
| else: | |
| train_config.fine_tune_checkpoint_type = 'classification' | |
| asg_map = detection_model.restore_map( | |
| fine_tune_checkpoint_type=train_config.fine_tune_checkpoint_type, | |
| load_all_detection_checkpoint_vars=( | |
| train_config.load_all_detection_checkpoint_vars)) | |
| available_var_map = ( | |
| variables_helper.get_variables_available_in_checkpoint( | |
| asg_map, | |
| train_config.fine_tune_checkpoint, | |
| include_global_step=False)) | |
| if use_tpu: | |
| def tpu_scaffold(): | |
| tf.train.init_from_checkpoint(train_config.fine_tune_checkpoint, | |
| available_var_map) | |
| return tf.train.Scaffold() | |
| scaffold_fn = tpu_scaffold | |
| else: | |
| tf.train.init_from_checkpoint(train_config.fine_tune_checkpoint, | |
| available_var_map) | |
| if mode in (tf.estimator.ModeKeys.TRAIN, tf.estimator.ModeKeys.EVAL): | |
| if (mode == tf.estimator.ModeKeys.EVAL and | |
| eval_config.use_dummy_loss_in_eval): | |
| total_loss = tf.constant(1.0) | |
| losses_dict = {'Loss/total_loss': total_loss} | |
| else: | |
| losses_dict = detection_model.loss( | |
| prediction_dict, features[fields.InputDataFields.true_image_shape]) | |
| losses = [loss_tensor for loss_tensor in losses_dict.values()] | |
| if train_config.add_regularization_loss: | |
| regularization_losses = detection_model.regularization_losses() | |
| if use_tpu and train_config.use_bfloat16: | |
| regularization_losses = ops.bfloat16_to_float32_nested( | |
| regularization_losses) | |
| if regularization_losses: | |
| regularization_loss = tf.add_n( | |
| regularization_losses, name='regularization_loss') | |
| losses.append(regularization_loss) | |
| losses_dict['Loss/regularization_loss'] = regularization_loss | |
| total_loss = tf.add_n(losses, name='total_loss') | |
| losses_dict['Loss/total_loss'] = total_loss | |
| if 'graph_rewriter_config' in configs: | |
| graph_rewriter_fn = graph_rewriter_builder.build( | |
| configs['graph_rewriter_config'], is_training=is_training) | |
| graph_rewriter_fn() | |
| # TODO(rathodv): Stop creating optimizer summary vars in EVAL mode once we | |
| # can write learning rate summaries on TPU without host calls. | |
| global_step = tf.train.get_or_create_global_step() | |
| training_optimizer, optimizer_summary_vars = optimizer_builder.build( | |
| train_config.optimizer) | |
| if mode == tf.estimator.ModeKeys.TRAIN: | |
| if use_tpu: | |
| training_optimizer = contrib_tpu.CrossShardOptimizer(training_optimizer) | |
| # Optionally freeze some layers by setting their gradients to be zero. | |
| trainable_variables = None | |
| include_variables = ( | |
| train_config.update_trainable_variables | |
| if train_config.update_trainable_variables else None) | |
| exclude_variables = ( | |
| train_config.freeze_variables | |
| if train_config.freeze_variables else None) | |
| trainable_variables = slim.filter_variables( | |
| tf.trainable_variables(), | |
| include_patterns=include_variables, | |
| exclude_patterns=exclude_variables) | |
| clip_gradients_value = None | |
| if train_config.gradient_clipping_by_norm > 0: | |
| clip_gradients_value = train_config.gradient_clipping_by_norm | |
| if not use_tpu: | |
| for var in optimizer_summary_vars: | |
| tf.summary.scalar(var.op.name, var) | |
| summaries = [] if use_tpu else None | |
| if train_config.summarize_gradients: | |
| summaries = ['gradients', 'gradient_norm', 'global_gradient_norm'] | |
| train_op = slim.optimizers.optimize_loss( | |
| loss=total_loss, | |
| global_step=global_step, | |
| learning_rate=None, | |
| clip_gradients=clip_gradients_value, | |
| optimizer=training_optimizer, | |
| update_ops=detection_model.updates(), | |
| variables=trainable_variables, | |
| summaries=summaries, | |
| name='') # Preventing scope prefix on all variables. | |
| if mode == tf.estimator.ModeKeys.PREDICT: | |
| exported_output = exporter_lib.add_output_tensor_nodes(detections) | |
| export_outputs = { | |
| tf.saved_model.signature_constants.PREDICT_METHOD_NAME: | |
| tf.estimator.export.PredictOutput(exported_output) | |
| } | |
| eval_metric_ops = None | |
| scaffold = None | |
| if mode == tf.estimator.ModeKeys.EVAL: | |
| class_agnostic = ( | |
| fields.DetectionResultFields.detection_classes not in detections) | |
| groundtruth = _prepare_groundtruth_for_eval( | |
| detection_model, class_agnostic, | |
| eval_input_config.max_number_of_boxes) | |
| use_original_images = fields.InputDataFields.original_image in features | |
| if use_original_images: | |
| eval_images = features[fields.InputDataFields.original_image] | |
| true_image_shapes = tf.slice( | |
| features[fields.InputDataFields.true_image_shape], [0, 0], [-1, 3]) | |
| original_image_spatial_shapes = features[fields.InputDataFields | |
| .original_image_spatial_shape] | |
| else: | |
| eval_images = features[fields.InputDataFields.image] | |
| true_image_shapes = None | |
| original_image_spatial_shapes = None | |
| eval_dict = eval_util.result_dict_for_batched_example( | |
| eval_images, | |
| features[inputs.HASH_KEY], | |
| detections, | |
| groundtruth, | |
| class_agnostic=class_agnostic, | |
| scale_to_absolute=True, | |
| original_image_spatial_shapes=original_image_spatial_shapes, | |
| true_image_shapes=true_image_shapes) | |
| if fields.InputDataFields.image_additional_channels in features: | |
| eval_dict[fields.InputDataFields.image_additional_channels] = features[ | |
| fields.InputDataFields.image_additional_channels] | |
| if class_agnostic: | |
| category_index = label_map_util.create_class_agnostic_category_index() | |
| else: | |
| category_index = label_map_util.create_category_index_from_labelmap( | |
| eval_input_config.label_map_path) | |
| vis_metric_ops = None | |
| if not use_tpu and use_original_images: | |
| keypoint_edges = [ | |
| (kp.start, kp.end) for kp in eval_config.keypoint_edge] | |
| eval_metric_op_vis = vis_utils.VisualizeSingleFrameDetections( | |
| category_index, | |
| max_examples_to_draw=eval_config.num_visualizations, | |
| max_boxes_to_draw=eval_config.max_num_boxes_to_visualize, | |
| min_score_thresh=eval_config.min_score_threshold, | |
| use_normalized_coordinates=False, | |
| keypoint_edges=keypoint_edges or None) | |
| vis_metric_ops = eval_metric_op_vis.get_estimator_eval_metric_ops( | |
| eval_dict) | |
| # Eval metrics on a single example. | |
| eval_metric_ops = eval_util.get_eval_metric_ops_for_evaluators( | |
| eval_config, list(category_index.values()), eval_dict) | |
| for loss_key, loss_tensor in iter(losses_dict.items()): | |
| eval_metric_ops[loss_key] = tf.metrics.mean(loss_tensor) | |
| for var in optimizer_summary_vars: | |
| eval_metric_ops[var.op.name] = (var, tf.no_op()) | |
| if vis_metric_ops is not None: | |
| eval_metric_ops.update(vis_metric_ops) | |
| eval_metric_ops = {str(k): v for k, v in eval_metric_ops.items()} | |
| if eval_config.use_moving_averages: | |
| variable_averages = tf.train.ExponentialMovingAverage(0.0) | |
| variables_to_restore = variable_averages.variables_to_restore() | |
| keep_checkpoint_every_n_hours = ( | |
| train_config.keep_checkpoint_every_n_hours) | |
| saver = tf.train.Saver( | |
| variables_to_restore, | |
| keep_checkpoint_every_n_hours=keep_checkpoint_every_n_hours) | |
| scaffold = tf.train.Scaffold(saver=saver) | |
| # EVAL executes on CPU, so use regular non-TPU EstimatorSpec. | |
| if use_tpu and mode != tf.estimator.ModeKeys.EVAL: | |
| return contrib_tpu.TPUEstimatorSpec( | |
| mode=mode, | |
| scaffold_fn=scaffold_fn, | |
| predictions=detections, | |
| loss=total_loss, | |
| train_op=train_op, | |
| eval_metrics=eval_metric_ops, | |
| export_outputs=export_outputs) | |
| else: | |
| if scaffold is None: | |
| keep_checkpoint_every_n_hours = ( | |
| train_config.keep_checkpoint_every_n_hours) | |
| saver = tf.train.Saver( | |
| sharded=True, | |
| keep_checkpoint_every_n_hours=keep_checkpoint_every_n_hours, | |
| save_relative_paths=True) | |
| tf.add_to_collection(tf.GraphKeys.SAVERS, saver) | |
| scaffold = tf.train.Scaffold(saver=saver) | |
| return tf.estimator.EstimatorSpec( | |
| mode=mode, | |
| predictions=detections, | |
| loss=total_loss, | |
| train_op=train_op, | |
| eval_metric_ops=eval_metric_ops, | |
| export_outputs=export_outputs, | |
| scaffold=scaffold) | |
| return model_fn | |
| def create_estimator_and_inputs(run_config, | |
| hparams, | |
| pipeline_config_path, | |
| config_override=None, | |
| train_steps=None, | |
| sample_1_of_n_eval_examples=1, | |
| sample_1_of_n_eval_on_train_examples=1, | |
| model_fn_creator=create_model_fn, | |
| use_tpu_estimator=False, | |
| use_tpu=False, | |
| num_shards=1, | |
| params=None, | |
| override_eval_num_epochs=True, | |
| save_final_config=False, | |
| postprocess_on_cpu=False, | |
| export_to_tpu=None, | |
| **kwargs): | |
| """Creates `Estimator`, input functions, and steps. | |
| Args: | |
| run_config: A `RunConfig`. | |
| hparams: A `HParams`. | |
| pipeline_config_path: A path to a pipeline config file. | |
| config_override: A pipeline_pb2.TrainEvalPipelineConfig text proto to | |
| override the config from `pipeline_config_path`. | |
| train_steps: Number of training steps. If None, the number of training steps | |
| is set from the `TrainConfig` proto. | |
| sample_1_of_n_eval_examples: Integer representing how often an eval example | |
| should be sampled. If 1, will sample all examples. | |
| sample_1_of_n_eval_on_train_examples: Similar to | |
| `sample_1_of_n_eval_examples`, except controls the sampling of training | |
| data for evaluation. | |
| model_fn_creator: A function that creates a `model_fn` for `Estimator`. | |
| Follows the signature: | |
| * Args: | |
| * `detection_model_fn`: Function that returns `DetectionModel` instance. | |
| * `configs`: Dictionary of pipeline config objects. | |
| * `hparams`: `HParams` object. | |
| * Returns: | |
| `model_fn` for `Estimator`. | |
| use_tpu_estimator: Whether a `TPUEstimator` should be returned. If False, | |
| an `Estimator` will be returned. | |
| use_tpu: Boolean, whether training and evaluation should run on TPU. Only | |
| used if `use_tpu_estimator` is True. | |
| num_shards: Number of shards (TPU cores). Only used if `use_tpu_estimator` | |
| is True. | |
| params: Parameter dictionary passed from the estimator. Only used if | |
| `use_tpu_estimator` is True. | |
| override_eval_num_epochs: Whether to overwrite the number of epochs to 1 for | |
| eval_input. | |
| save_final_config: Whether to save final config (obtained after applying | |
| overrides) to `estimator.model_dir`. | |
| postprocess_on_cpu: When use_tpu and postprocess_on_cpu are true, | |
| postprocess is scheduled on the host cpu. | |
| export_to_tpu: When use_tpu and export_to_tpu are true, | |
| `export_savedmodel()` exports a metagraph for serving on TPU besides the | |
| one on CPU. | |
| **kwargs: Additional keyword arguments for configuration override. | |
| Returns: | |
| A dictionary with the following fields: | |
| 'estimator': An `Estimator` or `TPUEstimator`. | |
| 'train_input_fn': A training input function. | |
| 'eval_input_fns': A list of all evaluation input functions. | |
| 'eval_input_names': A list of names for each evaluation input. | |
| 'eval_on_train_input_fn': An evaluation-on-train input function. | |
| 'predict_input_fn': A prediction input function. | |
| 'train_steps': Number of training steps. Either directly from input or from | |
| configuration. | |
| """ | |
| get_configs_from_pipeline_file = MODEL_BUILD_UTIL_MAP[ | |
| 'get_configs_from_pipeline_file'] | |
| merge_external_params_with_configs = MODEL_BUILD_UTIL_MAP[ | |
| 'merge_external_params_with_configs'] | |
| create_pipeline_proto_from_configs = MODEL_BUILD_UTIL_MAP[ | |
| 'create_pipeline_proto_from_configs'] | |
| create_train_input_fn = MODEL_BUILD_UTIL_MAP['create_train_input_fn'] | |
| create_eval_input_fn = MODEL_BUILD_UTIL_MAP['create_eval_input_fn'] | |
| create_predict_input_fn = MODEL_BUILD_UTIL_MAP['create_predict_input_fn'] | |
| detection_model_fn_base = MODEL_BUILD_UTIL_MAP['detection_model_fn_base'] | |
| configs = get_configs_from_pipeline_file( | |
| pipeline_config_path, config_override=config_override) | |
| kwargs.update({ | |
| 'train_steps': train_steps, | |
| 'use_bfloat16': configs['train_config'].use_bfloat16 and use_tpu | |
| }) | |
| if sample_1_of_n_eval_examples >= 1: | |
| kwargs.update({ | |
| 'sample_1_of_n_eval_examples': sample_1_of_n_eval_examples | |
| }) | |
| if override_eval_num_epochs: | |
| kwargs.update({'eval_num_epochs': 1}) | |
| tf.logging.warning( | |
| 'Forced number of epochs for all eval validations to be 1.') | |
| configs = merge_external_params_with_configs( | |
| configs, hparams, kwargs_dict=kwargs) | |
| model_config = configs['model'] | |
| train_config = configs['train_config'] | |
| train_input_config = configs['train_input_config'] | |
| eval_config = configs['eval_config'] | |
| eval_input_configs = configs['eval_input_configs'] | |
| eval_on_train_input_config = copy.deepcopy(train_input_config) | |
| eval_on_train_input_config.sample_1_of_n_examples = ( | |
| sample_1_of_n_eval_on_train_examples) | |
| if override_eval_num_epochs and eval_on_train_input_config.num_epochs != 1: | |
| tf.logging.warning('Expected number of evaluation epochs is 1, but ' | |
| 'instead encountered `eval_on_train_input_config' | |
| '.num_epochs` = ' | |
| '{}. Overwriting `num_epochs` to 1.'.format( | |
| eval_on_train_input_config.num_epochs)) | |
| eval_on_train_input_config.num_epochs = 1 | |
| # update train_steps from config but only when non-zero value is provided | |
| if train_steps is None and train_config.num_steps != 0: | |
| train_steps = train_config.num_steps | |
| detection_model_fn = functools.partial( | |
| detection_model_fn_base, model_config=model_config) | |
| # Create the input functions for TRAIN/EVAL/PREDICT. | |
| train_input_fn = create_train_input_fn( | |
| train_config=train_config, | |
| train_input_config=train_input_config, | |
| model_config=model_config) | |
| eval_input_fns = [ | |
| create_eval_input_fn( | |
| eval_config=eval_config, | |
| eval_input_config=eval_input_config, | |
| model_config=model_config) for eval_input_config in eval_input_configs | |
| ] | |
| eval_input_names = [ | |
| eval_input_config.name for eval_input_config in eval_input_configs | |
| ] | |
| eval_on_train_input_fn = create_eval_input_fn( | |
| eval_config=eval_config, | |
| eval_input_config=eval_on_train_input_config, | |
| model_config=model_config) | |
| predict_input_fn = create_predict_input_fn( | |
| model_config=model_config, predict_input_config=eval_input_configs[0]) | |
| # Read export_to_tpu from hparams if not passed. | |
| if export_to_tpu is None: | |
| export_to_tpu = hparams.get('export_to_tpu', False) | |
| tf.logging.info('create_estimator_and_inputs: use_tpu %s, export_to_tpu %s', | |
| use_tpu, export_to_tpu) | |
| model_fn = model_fn_creator(detection_model_fn, configs, hparams, use_tpu, | |
| postprocess_on_cpu) | |
| if use_tpu_estimator: | |
| estimator = contrib_tpu.TPUEstimator( | |
| model_fn=model_fn, | |
| train_batch_size=train_config.batch_size, | |
| # For each core, only batch size 1 is supported for eval. | |
| eval_batch_size=num_shards * 1 if use_tpu else 1, | |
| use_tpu=use_tpu, | |
| config=run_config, | |
| export_to_tpu=export_to_tpu, | |
| eval_on_tpu=False, # Eval runs on CPU, so disable eval on TPU | |
| params=params if params else {}) | |
| else: | |
| estimator = tf.estimator.Estimator(model_fn=model_fn, config=run_config) | |
| # Write the as-run pipeline config to disk. | |
| if run_config.is_chief and save_final_config: | |
| pipeline_config_final = create_pipeline_proto_from_configs(configs) | |
| config_util.save_pipeline_config(pipeline_config_final, estimator.model_dir) | |
| return dict( | |
| estimator=estimator, | |
| train_input_fn=train_input_fn, | |
| eval_input_fns=eval_input_fns, | |
| eval_input_names=eval_input_names, | |
| eval_on_train_input_fn=eval_on_train_input_fn, | |
| predict_input_fn=predict_input_fn, | |
| train_steps=train_steps) | |
| def create_train_and_eval_specs(train_input_fn, | |
| eval_input_fns, | |
| eval_on_train_input_fn, | |
| predict_input_fn, | |
| train_steps, | |
| eval_on_train_data=False, | |
| final_exporter_name='Servo', | |
| eval_spec_names=None): | |
| """Creates a `TrainSpec` and `EvalSpec`s. | |
| Args: | |
| train_input_fn: Function that produces features and labels on train data. | |
| eval_input_fns: A list of functions that produce features and labels on eval | |
| data. | |
| eval_on_train_input_fn: Function that produces features and labels for | |
| evaluation on train data. | |
| predict_input_fn: Function that produces features for inference. | |
| train_steps: Number of training steps. | |
| eval_on_train_data: Whether to evaluate model on training data. Default is | |
| False. | |
| final_exporter_name: String name given to `FinalExporter`. | |
| eval_spec_names: A list of string names for each `EvalSpec`. | |
| Returns: | |
| Tuple of `TrainSpec` and list of `EvalSpecs`. If `eval_on_train_data` is | |
| True, the last `EvalSpec` in the list will correspond to training data. The | |
| rest EvalSpecs in the list are evaluation datas. | |
| """ | |
| train_spec = tf.estimator.TrainSpec( | |
| input_fn=train_input_fn, max_steps=train_steps) | |
| if eval_spec_names is None: | |
| eval_spec_names = [str(i) for i in range(len(eval_input_fns))] | |
| eval_specs = [] | |
| for index, (eval_spec_name, eval_input_fn) in enumerate( | |
| zip(eval_spec_names, eval_input_fns)): | |
| # Uses final_exporter_name as exporter_name for the first eval spec for | |
| # backward compatibility. | |
| if index == 0: | |
| exporter_name = final_exporter_name | |
| else: | |
| exporter_name = '{}_{}'.format(final_exporter_name, eval_spec_name) | |
| exporter = tf.estimator.FinalExporter( | |
| name=exporter_name, serving_input_receiver_fn=predict_input_fn) | |
| eval_specs.append( | |
| tf.estimator.EvalSpec( | |
| name=eval_spec_name, | |
| input_fn=eval_input_fn, | |
| steps=None, | |
| exporters=exporter)) | |
| if eval_on_train_data: | |
| eval_specs.append( | |
| tf.estimator.EvalSpec( | |
| name='eval_on_train', input_fn=eval_on_train_input_fn, steps=None)) | |
| return train_spec, eval_specs | |
| def _evaluate_checkpoint(estimator, | |
| input_fn, | |
| checkpoint_path, | |
| name, | |
| max_retries=0): | |
| """Evaluates a checkpoint. | |
| Args: | |
| estimator: Estimator object to use for evaluation. | |
| input_fn: Input function to use for evaluation. | |
| checkpoint_path: Path of the checkpoint to evaluate. | |
| name: Namescope for eval summary. | |
| max_retries: Maximum number of times to retry the evaluation on encountering | |
| a tf.errors.InvalidArgumentError. If negative, will always retry the | |
| evaluation. | |
| Returns: | |
| Estimator evaluation results. | |
| """ | |
| always_retry = True if max_retries < 0 else False | |
| retries = 0 | |
| while always_retry or retries <= max_retries: | |
| try: | |
| return estimator.evaluate( | |
| input_fn=input_fn, | |
| steps=None, | |
| checkpoint_path=checkpoint_path, | |
| name=name) | |
| except tf.errors.InvalidArgumentError as e: | |
| if always_retry or retries < max_retries: | |
| tf.logging.info('Retrying checkpoint evaluation after exception: %s', e) | |
| retries += 1 | |
| else: | |
| raise e | |
| def continuous_eval(estimator, | |
| model_dir, | |
| input_fn, | |
| train_steps, | |
| name, | |
| max_retries=0): | |
| """Perform continuous evaluation on checkpoints written to a model directory. | |
| Args: | |
| estimator: Estimator object to use for evaluation. | |
| model_dir: Model directory to read checkpoints for continuous evaluation. | |
| input_fn: Input function to use for evaluation. | |
| train_steps: Number of training steps. This is used to infer the last | |
| checkpoint and stop evaluation loop. | |
| name: Namescope for eval summary. | |
| max_retries: Maximum number of times to retry the evaluation on encountering | |
| a tf.errors.InvalidArgumentError. If negative, will always retry the | |
| evaluation. | |
| """ | |
| def terminate_eval(): | |
| tf.logging.info('Terminating eval after 180 seconds of no checkpoints') | |
| return True | |
| for ckpt in tf.train.checkpoints_iterator( | |
| model_dir, min_interval_secs=180, timeout=None, | |
| timeout_fn=terminate_eval): | |
| tf.logging.info('Starting Evaluation.') | |
| try: | |
| eval_results = _evaluate_checkpoint( | |
| estimator=estimator, | |
| input_fn=input_fn, | |
| checkpoint_path=ckpt, | |
| name=name, | |
| max_retries=max_retries) | |
| tf.logging.info('Eval results: %s' % eval_results) | |
| # Terminate eval job when final checkpoint is reached | |
| current_step = int(os.path.basename(ckpt).split('-')[1]) | |
| if current_step >= train_steps: | |
| tf.logging.info( | |
| 'Evaluation finished after training step %d' % current_step) | |
| break | |
| except tf.errors.NotFoundError: | |
| tf.logging.info( | |
| 'Checkpoint %s no longer exists, skipping checkpoint' % ckpt) | |
| def populate_experiment(run_config, | |
| hparams, | |
| pipeline_config_path, | |
| train_steps=None, | |
| eval_steps=None, | |
| model_fn_creator=create_model_fn, | |
| **kwargs): | |
| """Populates an `Experiment` object. | |
| EXPERIMENT CLASS IS DEPRECATED. Please switch to | |
| tf.estimator.train_and_evaluate. As an example, see model_main.py. | |
| Args: | |
| run_config: A `RunConfig`. | |
| hparams: A `HParams`. | |
| pipeline_config_path: A path to a pipeline config file. | |
| train_steps: Number of training steps. If None, the number of training steps | |
| is set from the `TrainConfig` proto. | |
| eval_steps: Number of evaluation steps per evaluation cycle. If None, the | |
| number of evaluation steps is set from the `EvalConfig` proto. | |
| model_fn_creator: A function that creates a `model_fn` for `Estimator`. | |
| Follows the signature: | |
| * Args: | |
| * `detection_model_fn`: Function that returns `DetectionModel` instance. | |
| * `configs`: Dictionary of pipeline config objects. | |
| * `hparams`: `HParams` object. | |
| * Returns: | |
| `model_fn` for `Estimator`. | |
| **kwargs: Additional keyword arguments for configuration override. | |
| Returns: | |
| An `Experiment` that defines all aspects of training, evaluation, and | |
| export. | |
| """ | |
| tf.logging.warning('Experiment is being deprecated. Please use ' | |
| 'tf.estimator.train_and_evaluate(). See model_main.py for ' | |
| 'an example.') | |
| train_and_eval_dict = create_estimator_and_inputs( | |
| run_config, | |
| hparams, | |
| pipeline_config_path, | |
| train_steps=train_steps, | |
| eval_steps=eval_steps, | |
| model_fn_creator=model_fn_creator, | |
| save_final_config=True, | |
| **kwargs) | |
| estimator = train_and_eval_dict['estimator'] | |
| train_input_fn = train_and_eval_dict['train_input_fn'] | |
| eval_input_fns = train_and_eval_dict['eval_input_fns'] | |
| predict_input_fn = train_and_eval_dict['predict_input_fn'] | |
| train_steps = train_and_eval_dict['train_steps'] | |
| export_strategies = [ | |
| contrib_learn.utils.saved_model_export_utils.make_export_strategy( | |
| serving_input_fn=predict_input_fn) | |
| ] | |
| return contrib_learn.Experiment( | |
| estimator=estimator, | |
| train_input_fn=train_input_fn, | |
| eval_input_fn=eval_input_fns[0], | |
| train_steps=train_steps, | |
| eval_steps=None, | |
| export_strategies=export_strategies, | |
| eval_delay_secs=120, | |
| ) | |