Unverified Commit 31ae57eb authored by pkulzc's avatar pkulzc Committed by GitHub
Browse files

Minor fixes for object detection (#5613)

* Internal change.

PiperOrigin-RevId: 213914693

* Add original_image_spatial_shape tensor in input dictionary to store shape of the original input image

PiperOrigin-RevId: 214018767

* Remove "groundtruth_confidences" from decoders use "groundtruth_weights" to indicate label confidence.

This also solves a bug that only surfaced now - random crop routines in core/preprocessor.py did not correctly handle "groundtruth_weight" tensors returned by the decoders.

PiperOrigin-RevId: 214091843

* Update CocoMaskEvaluator to allow for a batch of image info, rather than a single image.

PiperOrigin-RevId: 214295305

* Adding the option to be able to summarize gradients.

PiperOrigin-RevId: 214310875

* Adds FasterRCNN inference on CPU

1. Adds a flag use_static_shapes_for_eval to restrict to the ops that guarantees static shape.
2. No filtering of overlapping anchors while clipping the anchors when use_static_shapes_for_eval is set to True.
3. Adds test for faster_rcnn_meta_arch for predict and postprocess in inference mode for first and second stages.

PiperOrigin-RevId: 214329565

* Fix model_lib eval_spec_names assignment (integer->string).

PiperOrigin-RevId: 214335461

* Refactor Mask HEAD to optionally upsample after applying convolutions on ROI crops.

PiperOrigin-RevId: 214338440

* Uses final_exporter_name as exporter_name for the first eval spec for backward compatibility.

PiperOrigin-RevId: 214522032

* Add reshaped `mask_predictions` tensor to the prediction dictionary in `_predict_third_stage` method to allow computing mask loss in eval job.

PiperOrigin-RevId: 214620716

* Add support for fully conv training to fpn.

PiperOrigin-RevId: 214626274

* Fix the proprocess() function in Resnet v1 to make it work for any number of input channels.

Note: If the #channels != 3, this will simply skip the mean subtraction in preprocess() function.
PiperOrigin-RevId: 214635428

* Wrap result_dict_for_single_example in eval_util to run for batched examples.

PiperOrigin-RevId: 214678514

* Adds PNASNet-based (ImageNet model) feature extractor for SSD.

PiperOrigin-RevId: 214988331

* Update documentation

PiperOrigin-RevId: 215243502

* Correct index used to compute number of groundtruth/detection boxes in COCOMaskEvaluator.

Due to an incorrect indexing in cl/214295305 only the first detection mask and first groundtruth mask for a given image are fed to the COCO Mask evaluation library. Since groundtruth masks are arranged in no particular order, the first and highest scoring detection mask (detection masks are ordered by score) won't match the the first and only groundtruth retained in all cases. This is I think why mask evaluation metrics do not get better than ~11 mAP. Note that this code path is only active when using model_main.py binary for evaluation.

This change fixes the indices and modifies an existing test case to cover it.

PiperOrigin-RevId: 215275936

* Fixing grayscale_image_resizer to accept mask as input.

PiperOrigin-RevId: 215345836

* Add an option not to clip groundtruth boxes during preprocessing. Clipping boxes adversely affects training for partially occluded or large objects, especially for fully conv models. Clipping already occurs during postprocessing, and should not occur during training.

PiperOrigin-RevId: 215613379

* Always return recalls and precisions with length equal to the number of classes.

The previous behavior of ObjectDetectionEvaluation was somewhat dangerous: when no groundtruth boxes were present, the lists of per-class precisions and recalls were simply truncated. Unless you were aware of this phenomenon (and consulted the `num_gt_instances_per_class` vector) it was difficult to associate each metric with each class.

PiperOrigin-RevId: 215633711

* Expose the box feature node in SSD.

PiperOrigin-RevId: 215653316

* Fix ssd mobilenet v2 _CONV_DEFS overwriting issue.

PiperOrigin-RevId: 215654160

* More documentation updates

PiperOrigin-RevId: 215656580

* Add pooling + residual option in multi_resolution_feature_maps. It adds an average pooling and a residual layer between feature maps with matching depth. Designed to be used with WeightSharedBoxPredictor.

PiperOrigin-RevId: 215665619

* Only call create_modificed_mobilenet_config on init if use_depthwise is true.

PiperOrigin-RevId: 215784290

* Only call create_modificed_mobilenet_config on init if use_depthwise is true.

PiperOrigin-RevId: 215837524

* Don't prune keypoints if clip_boxes is false.

PiperOrigin-RevId: 216187642

* Makes sure "key" field exists in the result dictionary.

PiperOrigin-RevId: 216456543

* Add add_background_class parameter to allow disabling the inclusion of a background class.

PiperOrigin-RevId: 216567612

* Update expected_classification_loss_under_sampling to better account for expected sampling.

PiperOrigin-RevId: 216712287

* Let the evaluation receive a evaluation class in its constructor.

PiperOrigin-RevId: 216769374

* This CL adds model building & training support for end-to-end Keras-based SSD models. If a Keras feature extractor's name is specified in the model config (e.g. 'ssd_mobilenet_v2_keras'), the model will use that feature extractor and a corresponding Keras-based box predictor.

This CL makes sure regularization losses & batch norm updates work correctly when training models that have Keras-based components. It also updates the default hyperparameter settings of the keras-based mobilenetV2 (when not overriding hyperparams) to more closely match the legacy Slim training scope.

PiperOrigin-RevId: 216938707

* Adding the ability in the coco evaluator to indicate whether an image has been annotated. For a non-annotated image, detections and groundtruth are not supplied.

PiperOrigin-RevId: 217316342

* Release the 8k minival dataset ids for MSCOCO, used in Huang et al. "Speed/accuracy trade-offs for modern convolutional object detectors" (https://arxiv.org/abs/1611.10012)

PiperOrigin-RevId: 217549353

* Exposes weighted_sigmoid_focal loss for faster rcnn classifier

PiperOrigin-RevId: 217601740

* Add detection_features to output nodes. The shape of the feature is [batch_size, max_detections, depth].

PiperOrigin-RevId: 217629905

* FPN uses a custom NN resize op for TPU-compatibility. Replace this op with the Tensorflow version at export time for TFLite-compatibility.

PiperOrigin-RevId: 217721184

* Compute `num_groundtruth_boxes` in inputs.tranform_input_data_fn after data augmentation instead of decoders.

PiperOrigin-RevId: 217733432

* 1. Stop gradients from flowing into groundtruth masks with zero paddings.
2. Normalize pixelwise cross entropy loss across the whole batch.

PiperOrigin-RevId: 217735114

* Optimize Input pipeline for Mask R-CNN on TPU with blfoat16: improve the step time from:
1663.6 ms -> 1184.2 ms, about 28.8% improvement.

PiperOrigin-RevId: 217748833

* Fixes to export a TPU compatible model

Adds nodes to each of the output tensor. Also increments the value of class labels by 1.

PiperOrigin-RevId: 217856760

* API changes:
 - change the interface of target assigner to return per-class weights.
 - change the interface of classification loss to take per-class weights.

PiperOrigin-RevId: 217968393

* Add an option to override pipeline config in export_saved_model using command line arg

PiperOrigin-RevId: 218429292

* Include Quantized trained MobileNet V2 SSD and FaceSsd in model zoo.

PiperOrigin-RevId: 218530947

* Write final config to disk in `train` mode only.

PiperOrigin-RevId: 218735512
parent 0b0c9cfd
...@@ -19,7 +19,6 @@ models. ...@@ -19,7 +19,6 @@ models.
""" """
from abc import abstractmethod from abc import abstractmethod
import re
import tensorflow as tf import tensorflow as tf
from object_detection.core import box_list from object_detection.core import box_list
...@@ -116,6 +115,25 @@ class SSDFeatureExtractor(object): ...@@ -116,6 +115,25 @@ class SSDFeatureExtractor(object):
""" """
raise NotImplementedError raise NotImplementedError
def restore_from_classification_checkpoint_fn(self, feature_extractor_scope):
"""Returns a map of variables to load from a foreign checkpoint.
Args:
feature_extractor_scope: A scope name for the feature extractor.
Returns:
A dict mapping variable names (to load from a checkpoint) to variables in
the model graph.
"""
variables_to_restore = {}
for variable in tf.global_variables():
var_name = variable.op.name
if var_name.startswith(feature_extractor_scope + '/'):
var_name = var_name.replace(feature_extractor_scope + '/', '')
variables_to_restore[var_name] = variable
return variables_to_restore
class SSDKerasFeatureExtractor(tf.keras.Model): class SSDKerasFeatureExtractor(tf.keras.Model):
"""SSD Feature Extractor definition.""" """SSD Feature Extractor definition."""
...@@ -218,6 +236,25 @@ class SSDKerasFeatureExtractor(tf.keras.Model): ...@@ -218,6 +236,25 @@ class SSDKerasFeatureExtractor(tf.keras.Model):
def call(self, inputs, **kwargs): def call(self, inputs, **kwargs):
return self._extract_features(inputs) return self._extract_features(inputs)
def restore_from_classification_checkpoint_fn(self, feature_extractor_scope):
"""Returns a map of variables to load from a foreign checkpoint.
Args:
feature_extractor_scope: A scope name for the feature extractor.
Returns:
A dict mapping variable names (to load from a checkpoint) to variables in
the model graph.
"""
variables_to_restore = {}
for variable in tf.global_variables():
var_name = variable.op.name
if var_name.startswith(feature_extractor_scope + '/'):
var_name = var_name.replace(feature_extractor_scope + '/', '')
variables_to_restore[var_name] = variable
return variables_to_restore
class SSDMetaArch(model.DetectionModel): class SSDMetaArch(model.DetectionModel):
"""SSD Meta-architecture definition.""" """SSD Meta-architecture definition."""
...@@ -333,13 +370,15 @@ class SSDMetaArch(model.DetectionModel): ...@@ -333,13 +370,15 @@ class SSDMetaArch(model.DetectionModel):
# Slim feature extractors get an explicit naming scope # Slim feature extractors get an explicit naming scope
self._extract_features_scope = 'FeatureExtractor' self._extract_features_scope = 'FeatureExtractor'
# TODO(jonathanhuang): handle agnostic mode if self._add_background_class and encode_background_as_zeros:
# weights
self._unmatched_class_label = tf.constant([1] + self.num_classes * [0],
tf.float32)
if encode_background_as_zeros:
self._unmatched_class_label = tf.constant((self.num_classes + 1) * [0], self._unmatched_class_label = tf.constant((self.num_classes + 1) * [0],
tf.float32) tf.float32)
elif self._add_background_class:
self._unmatched_class_label = tf.constant([1] + self.num_classes * [0],
tf.float32)
else:
self._unmatched_class_label = tf.constant(self.num_classes * [0],
tf.float32)
self._target_assigner = target_assigner_instance self._target_assigner = target_assigner_instance
...@@ -606,14 +645,22 @@ class SSDMetaArch(model.DetectionModel): ...@@ -606,14 +645,22 @@ class SSDMetaArch(model.DetectionModel):
detection_boxes = tf.identity(detection_boxes, 'raw_box_locations') detection_boxes = tf.identity(detection_boxes, 'raw_box_locations')
detection_boxes = tf.expand_dims(detection_boxes, axis=2) detection_boxes = tf.expand_dims(detection_boxes, axis=2)
detection_scores_with_background = self._score_conversion_fn( detection_scores = self._score_conversion_fn(class_predictions)
class_predictions) detection_scores = tf.identity(detection_scores, 'raw_box_scores')
detection_scores_with_background = tf.identity( if self._add_background_class:
detection_scores_with_background, 'raw_box_scores') detection_scores = tf.slice(detection_scores, [0, 0, 1], [-1, -1, -1])
detection_scores = tf.slice(detection_scores_with_background, [0, 0, 1],
[-1, -1, -1])
additional_fields = None additional_fields = None
batch_size = (
shape_utils.combined_static_and_dynamic_shape(preprocessed_images)[0])
if 'feature_maps' in prediction_dict:
feature_map_list = []
for feature_map in prediction_dict['feature_maps']:
feature_map_list.append(tf.reshape(feature_map, [batch_size, -1]))
box_features = tf.concat(feature_map_list, 1)
box_features = tf.identity(box_features, 'raw_box_features')
if detection_keypoints is not None: if detection_keypoints is not None:
additional_fields = { additional_fields = {
fields.BoxListFields.keypoints: detection_keypoints} fields.BoxListFields.keypoints: detection_keypoints}
...@@ -683,17 +730,20 @@ class SSDMetaArch(model.DetectionModel): ...@@ -683,17 +730,20 @@ class SSDMetaArch(model.DetectionModel):
self.groundtruth_lists(fields.BoxListFields.boxes), match_list) self.groundtruth_lists(fields.BoxListFields.boxes), match_list)
if self._random_example_sampler: if self._random_example_sampler:
batch_cls_per_anchor_weights = tf.reduce_mean(
batch_cls_weights, axis=-1)
batch_sampled_indicator = tf.to_float( batch_sampled_indicator = tf.to_float(
shape_utils.static_or_dynamic_map_fn( shape_utils.static_or_dynamic_map_fn(
self._minibatch_subsample_fn, self._minibatch_subsample_fn,
[batch_cls_targets, batch_cls_weights], [batch_cls_targets, batch_cls_per_anchor_weights],
dtype=tf.bool, dtype=tf.bool,
parallel_iterations=self._parallel_iterations, parallel_iterations=self._parallel_iterations,
back_prop=True)) back_prop=True))
batch_reg_weights = tf.multiply(batch_sampled_indicator, batch_reg_weights = tf.multiply(batch_sampled_indicator,
batch_reg_weights) batch_reg_weights)
batch_cls_weights = tf.multiply(batch_sampled_indicator, batch_cls_weights = tf.multiply(
batch_cls_weights) tf.expand_dims(batch_sampled_indicator, -1),
batch_cls_weights)
losses_mask = None losses_mask = None
if self.groundtruth_has_field(fields.InputDataFields.is_annotated): if self.groundtruth_has_field(fields.InputDataFields.is_annotated):
...@@ -713,16 +763,32 @@ class SSDMetaArch(model.DetectionModel): ...@@ -713,16 +763,32 @@ class SSDMetaArch(model.DetectionModel):
losses_mask=losses_mask) losses_mask=losses_mask)
if self._expected_classification_loss_under_sampling: if self._expected_classification_loss_under_sampling:
# Need to compute losses for assigned targets against the
# unmatched_class_label as well as their assigned targets.
# simplest thing (but wasteful) is just to calculate all losses
# twice
batch_size, num_anchors, num_classes = batch_cls_targets.get_shape()
unmatched_targets = tf.ones([batch_size, num_anchors, 1
]) * self._unmatched_class_label
unmatched_cls_losses = self._classification_loss(
prediction_dict['class_predictions_with_background'],
unmatched_targets,
weights=batch_cls_weights,
losses_mask=losses_mask)
if cls_losses.get_shape().ndims == 3: if cls_losses.get_shape().ndims == 3:
batch_size, num_anchors, num_classes = cls_losses.get_shape() batch_size, num_anchors, num_classes = cls_losses.get_shape()
cls_losses = tf.reshape(cls_losses, [batch_size, -1]) cls_losses = tf.reshape(cls_losses, [batch_size, -1])
unmatched_cls_losses = tf.reshape(unmatched_cls_losses,
[batch_size, -1])
batch_cls_targets = tf.reshape( batch_cls_targets = tf.reshape(
batch_cls_targets, [batch_size, num_anchors * num_classes, -1]) batch_cls_targets, [batch_size, num_anchors * num_classes, -1])
batch_cls_targets = tf.concat( batch_cls_targets = tf.concat(
[1 - batch_cls_targets, batch_cls_targets], axis=-1) [1 - batch_cls_targets, batch_cls_targets], axis=-1)
cls_losses = self._expected_classification_loss_under_sampling( cls_losses = self._expected_classification_loss_under_sampling(
batch_cls_targets, cls_losses) batch_cls_targets, cls_losses, unmatched_cls_losses)
classification_loss = tf.reduce_sum(cls_losses) classification_loss = tf.reduce_sum(cls_losses)
localization_loss = tf.reduce_sum(location_losses) localization_loss = tf.reduce_sum(location_losses)
...@@ -971,6 +1037,26 @@ class SSDMetaArch(model.DetectionModel): ...@@ -971,6 +1037,26 @@ class SSDMetaArch(model.DetectionModel):
[combined_shape[0], combined_shape[1], 4])) [combined_shape[0], combined_shape[1], 4]))
return decoded_boxes, decoded_keypoints return decoded_boxes, decoded_keypoints
def regularization_losses(self):
"""Returns a list of regularization losses for this model.
Returns a list of regularization losses for this model that the estimator
needs to use during training/optimization.
Returns:
A list of regularization loss tensors.
"""
losses = []
slim_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
# Copy the slim losses to avoid modifying the collection
if slim_losses:
losses.extend(slim_losses)
if self._box_predictor.is_keras_model:
losses.extend(self._box_predictor.losses)
if self._feature_extractor.is_keras_model:
losses.extend(self._feature_extractor.losses)
return losses
def restore_map(self, def restore_map(self,
fine_tune_checkpoint_type='detection', fine_tune_checkpoint_type='detection',
load_all_detection_checkpoint_vars=False): load_all_detection_checkpoint_vars=False):
...@@ -997,18 +1083,44 @@ class SSDMetaArch(model.DetectionModel): ...@@ -997,18 +1083,44 @@ class SSDMetaArch(model.DetectionModel):
if fine_tune_checkpoint_type not in ['detection', 'classification']: if fine_tune_checkpoint_type not in ['detection', 'classification']:
raise ValueError('Not supported fine_tune_checkpoint_type: {}'.format( raise ValueError('Not supported fine_tune_checkpoint_type: {}'.format(
fine_tune_checkpoint_type)) fine_tune_checkpoint_type))
variables_to_restore = {}
for variable in tf.global_variables(): if fine_tune_checkpoint_type == 'classification':
var_name = variable.op.name return self._feature_extractor.restore_from_classification_checkpoint_fn(
if (fine_tune_checkpoint_type == 'detection' and self._extract_features_scope)
load_all_detection_checkpoint_vars):
variables_to_restore[var_name] = variable if fine_tune_checkpoint_type == 'detection':
else: variables_to_restore = {}
if var_name.startswith(self._extract_features_scope): for variable in tf.global_variables():
if fine_tune_checkpoint_type == 'classification': var_name = variable.op.name
var_name = ( if load_all_detection_checkpoint_vars:
re.split('^' + self._extract_features_scope + '/',
var_name)[-1])
variables_to_restore[var_name] = variable variables_to_restore[var_name] = variable
else:
if var_name.startswith(self._extract_features_scope):
variables_to_restore[var_name] = variable
return variables_to_restore return variables_to_restore
def updates(self):
"""Returns a list of update operators for this model.
Returns a list of update operators for this model that must be executed at
each training step. The estimator's train op needs to have a control
dependency on these updates.
Returns:
A list of update operators.
"""
update_ops = []
slim_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
# Copy the slim ops to avoid modifying the collection
if slim_update_ops:
update_ops.extend(slim_update_ops)
if self._box_predictor.is_keras_model:
update_ops.extend(self._box_predictor.get_updates_for(None))
update_ops.extend(self._box_predictor.get_updates_for(
self._box_predictor.inputs))
if self._feature_extractor.is_keras_model:
update_ops.extend(self._feature_extractor.get_updates_for(None))
update_ops.extend(self._feature_extractor.get_updates_for(
self._feature_extractor.inputs))
return update_ops
...@@ -42,7 +42,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase, ...@@ -42,7 +42,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase,
random_example_sampling=False, random_example_sampling=False,
weight_regression_loss_by_score=False, weight_regression_loss_by_score=False,
use_expected_classification_loss_under_sampling=False, use_expected_classification_loss_under_sampling=False,
minimum_negative_sampling=1, min_num_negative_samples=1,
desired_negative_sampling_ratio=3, desired_negative_sampling_ratio=3,
use_keras=False, use_keras=False,
predict_mask=False, predict_mask=False,
...@@ -57,7 +57,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase, ...@@ -57,7 +57,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase,
weight_regression_loss_by_score=weight_regression_loss_by_score, weight_regression_loss_by_score=weight_regression_loss_by_score,
use_expected_classification_loss_under_sampling= use_expected_classification_loss_under_sampling=
use_expected_classification_loss_under_sampling, use_expected_classification_loss_under_sampling,
minimum_negative_sampling=minimum_negative_sampling, min_num_negative_samples=min_num_negative_samples,
desired_negative_sampling_ratio=desired_negative_sampling_ratio, desired_negative_sampling_ratio=desired_negative_sampling_ratio,
use_keras=use_keras, use_keras=use_keras,
predict_mask=predict_mask, predict_mask=predict_mask,
...@@ -344,11 +344,11 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase, ...@@ -344,11 +344,11 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase,
preprocessed_input = np.random.rand(batch_size, 2, 2, 3).astype(np.float32) preprocessed_input = np.random.rand(batch_size, 2, 2, 3).astype(np.float32)
groundtruth_boxes1 = np.array([[0, 0, .5, .5]], dtype=np.float32) groundtruth_boxes1 = np.array([[0, 0, .5, .5]], dtype=np.float32)
groundtruth_boxes2 = np.array([[0, 0, .5, .5]], dtype=np.float32) groundtruth_boxes2 = np.array([[0, 0, .5, .5]], dtype=np.float32)
groundtruth_classes1 = np.array([[0, 1]], dtype=np.float32) groundtruth_classes1 = np.array([[1]], dtype=np.float32)
groundtruth_classes2 = np.array([[0, 1]], dtype=np.float32) groundtruth_classes2 = np.array([[1]], dtype=np.float32)
expected_localization_loss = 0.0 expected_localization_loss = 0.0
expected_classification_loss = ( expected_classification_loss = (
batch_size * num_anchors * (num_classes + 1) * np.log(2.0)) batch_size * num_anchors * num_classes * np.log(2.0))
(localization_loss, classification_loss) = self.execute( (localization_loss, classification_loss) = self.execute(
graph_fn, [ graph_fn, [
preprocessed_input, groundtruth_boxes1, groundtruth_boxes2, preprocessed_input, groundtruth_boxes1, groundtruth_boxes2,
...@@ -371,7 +371,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase, ...@@ -371,7 +371,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase,
apply_hard_mining=False, apply_hard_mining=False,
add_background_class=True, add_background_class=True,
use_expected_classification_loss_under_sampling=True, use_expected_classification_loss_under_sampling=True,
minimum_negative_sampling=1, min_num_negative_samples=1,
desired_negative_sampling_ratio=desired_negative_sampling_ratio) desired_negative_sampling_ratio=desired_negative_sampling_ratio)
model.provide_groundtruth(groundtruth_boxes_list, model.provide_groundtruth(groundtruth_boxes_list,
groundtruth_classes_list) groundtruth_classes_list)
...@@ -391,8 +391,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase, ...@@ -391,8 +391,7 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase,
expected_localization_loss = 0.0 expected_localization_loss = 0.0
expected_classification_loss = ( expected_classification_loss = (
batch_size * (desired_negative_sampling_ratio * num_anchors + batch_size * (num_anchors + num_classes * num_anchors) * np.log(2.0))
num_classes * num_anchors) * np.log(2.0))
(localization_loss, classification_loss) = self.execute( (localization_loss, classification_loss) = self.execute(
graph_fn, [ graph_fn, [
preprocessed_input, groundtruth_boxes1, groundtruth_boxes2, preprocessed_input, groundtruth_boxes1, groundtruth_boxes2,
...@@ -432,11 +431,11 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase, ...@@ -432,11 +431,11 @@ class SsdMetaArchTest(ssd_meta_arch_test_lib.SSDMetaArchTestBase,
preprocessed_input = np.random.rand(batch_size, 2, 2, 3).astype(np.float32) preprocessed_input = np.random.rand(batch_size, 2, 2, 3).astype(np.float32)
groundtruth_boxes1 = np.array([[0, 0, 1, 1]], dtype=np.float32) groundtruth_boxes1 = np.array([[0, 0, 1, 1]], dtype=np.float32)
groundtruth_boxes2 = np.array([[0, 0, 1, 1]], dtype=np.float32) groundtruth_boxes2 = np.array([[0, 0, 1, 1]], dtype=np.float32)
groundtruth_classes1 = np.array([[0, 1]], dtype=np.float32) groundtruth_classes1 = np.array([[1]], dtype=np.float32)
groundtruth_classes2 = np.array([[1, 0]], dtype=np.float32) groundtruth_classes2 = np.array([[0]], dtype=np.float32)
expected_localization_loss = 0.25 expected_localization_loss = 0.25
expected_classification_loss = ( expected_classification_loss = (
batch_size * num_anchors * (num_classes + 1) * np.log(2.0)) batch_size * num_anchors * num_classes * np.log(2.0))
(localization_loss, classification_loss) = self.execute( (localization_loss, classification_loss) = self.execute(
graph_fn, [ graph_fn, [
preprocessed_input, groundtruth_boxes1, groundtruth_boxes2, preprocessed_input, groundtruth_boxes1, groundtruth_boxes2,
......
...@@ -119,7 +119,7 @@ class SSDMetaArchTestBase(test_case.TestCase): ...@@ -119,7 +119,7 @@ class SSDMetaArchTestBase(test_case.TestCase):
random_example_sampling=False, random_example_sampling=False,
weight_regression_loss_by_score=False, weight_regression_loss_by_score=False,
use_expected_classification_loss_under_sampling=False, use_expected_classification_loss_under_sampling=False,
minimum_negative_sampling=1, min_num_negative_samples=1,
desired_negative_sampling_ratio=3, desired_negative_sampling_ratio=3,
use_keras=False, use_keras=False,
predict_mask=False, predict_mask=False,
...@@ -130,10 +130,12 @@ class SSDMetaArchTestBase(test_case.TestCase): ...@@ -130,10 +130,12 @@ class SSDMetaArchTestBase(test_case.TestCase):
mock_anchor_generator = MockAnchorGenerator2x2() mock_anchor_generator = MockAnchorGenerator2x2()
if use_keras: if use_keras:
mock_box_predictor = test_utils.MockKerasBoxPredictor( mock_box_predictor = test_utils.MockKerasBoxPredictor(
is_training, num_classes, predict_mask=predict_mask) is_training, num_classes, add_background_class=add_background_class,
predict_mask=predict_mask)
else: else:
mock_box_predictor = test_utils.MockBoxPredictor( mock_box_predictor = test_utils.MockBoxPredictor(
is_training, num_classes, predict_mask=predict_mask) is_training, num_classes, add_background_class=add_background_class,
predict_mask=predict_mask)
mock_box_coder = test_utils.MockBoxCoder() mock_box_coder = test_utils.MockBoxCoder()
if use_keras: if use_keras:
fake_feature_extractor = FakeSSDKerasFeatureExtractor() fake_feature_extractor = FakeSSDKerasFeatureExtractor()
...@@ -182,7 +184,7 @@ class SSDMetaArchTestBase(test_case.TestCase): ...@@ -182,7 +184,7 @@ class SSDMetaArchTestBase(test_case.TestCase):
if use_expected_classification_loss_under_sampling: if use_expected_classification_loss_under_sampling:
expected_classification_loss_under_sampling = functools.partial( expected_classification_loss_under_sampling = functools.partial(
ops.expected_classification_loss_under_sampling, ops.expected_classification_loss_under_sampling,
minimum_negative_sampling=minimum_negative_sampling, min_num_negative_samples=min_num_negative_samples,
desired_negative_sampling_ratio=desired_negative_sampling_ratio) desired_negative_sampling_ratio=desired_negative_sampling_ratio)
code_size = 4 code_size = 4
......
...@@ -25,6 +25,7 @@ import os ...@@ -25,6 +25,7 @@ import os
import tensorflow as tf import tensorflow as tf
from object_detection import eval_util from object_detection import eval_util
from object_detection import exporter as exporter_lib
from object_detection import inputs from object_detection import inputs
from object_detection.builders import graph_rewriter_builder from object_detection.builders import graph_rewriter_builder
from object_detection.builders import model_builder from object_detection.builders import model_builder
...@@ -306,8 +307,7 @@ def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False): ...@@ -306,8 +307,7 @@ def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False):
prediction_dict, features[fields.InputDataFields.true_image_shape]) prediction_dict, features[fields.InputDataFields.true_image_shape])
losses = [loss_tensor for loss_tensor in losses_dict.values()] losses = [loss_tensor for loss_tensor in losses_dict.values()]
if train_config.add_regularization_loss: if train_config.add_regularization_loss:
regularization_losses = tf.get_collection( regularization_losses = detection_model.regularization_losses()
tf.GraphKeys.REGULARIZATION_LOSSES)
if regularization_losses: if regularization_losses:
regularization_loss = tf.add_n( regularization_loss = tf.add_n(
regularization_losses, name='regularization_loss') regularization_losses, name='regularization_loss')
...@@ -353,20 +353,24 @@ def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False): ...@@ -353,20 +353,24 @@ def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False):
for var in optimizer_summary_vars: for var in optimizer_summary_vars:
tf.summary.scalar(var.op.name, var) tf.summary.scalar(var.op.name, var)
summaries = [] if use_tpu else None summaries = [] if use_tpu else None
if train_config.summarize_gradients:
summaries = ['gradients', 'gradient_norm', 'global_gradient_norm']
train_op = tf.contrib.layers.optimize_loss( train_op = tf.contrib.layers.optimize_loss(
loss=total_loss, loss=total_loss,
global_step=global_step, global_step=global_step,
learning_rate=None, learning_rate=None,
clip_gradients=clip_gradients_value, clip_gradients=clip_gradients_value,
optimizer=training_optimizer, optimizer=training_optimizer,
update_ops=detection_model.updates(),
variables=trainable_variables, variables=trainable_variables,
summaries=summaries, summaries=summaries,
name='') # Preventing scope prefix on all variables. name='') # Preventing scope prefix on all variables.
if mode == tf.estimator.ModeKeys.PREDICT: if mode == tf.estimator.ModeKeys.PREDICT:
exported_output = exporter_lib.add_output_tensor_nodes(detections)
export_outputs = { export_outputs = {
tf.saved_model.signature_constants.PREDICT_METHOD_NAME: tf.saved_model.signature_constants.PREDICT_METHOD_NAME:
tf.estimator.export.PredictOutput(detections) tf.estimator.export.PredictOutput(exported_output)
} }
eval_metric_ops = None eval_metric_ops = None
...@@ -456,6 +460,7 @@ def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False): ...@@ -456,6 +460,7 @@ def create_model_fn(detection_model_fn, configs, hparams, use_tpu=False):
def create_estimator_and_inputs(run_config, def create_estimator_and_inputs(run_config,
hparams, hparams,
pipeline_config_path, pipeline_config_path,
config_override=None,
train_steps=None, train_steps=None,
sample_1_of_n_eval_examples=1, sample_1_of_n_eval_examples=1,
sample_1_of_n_eval_on_train_examples=1, sample_1_of_n_eval_on_train_examples=1,
...@@ -465,6 +470,7 @@ def create_estimator_and_inputs(run_config, ...@@ -465,6 +470,7 @@ def create_estimator_and_inputs(run_config,
num_shards=1, num_shards=1,
params=None, params=None,
override_eval_num_epochs=True, override_eval_num_epochs=True,
save_final_config=False,
**kwargs): **kwargs):
"""Creates `Estimator`, input functions, and steps. """Creates `Estimator`, input functions, and steps.
...@@ -472,6 +478,8 @@ def create_estimator_and_inputs(run_config, ...@@ -472,6 +478,8 @@ def create_estimator_and_inputs(run_config,
run_config: A `RunConfig`. run_config: A `RunConfig`.
hparams: A `HParams`. hparams: A `HParams`.
pipeline_config_path: A path to a pipeline config file. 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 train_steps: Number of training steps. If None, the number of training steps
is set from the `TrainConfig` proto. is set from the `TrainConfig` proto.
sample_1_of_n_eval_examples: Integer representing how often an eval example sample_1_of_n_eval_examples: Integer representing how often an eval example
...@@ -499,6 +507,8 @@ def create_estimator_and_inputs(run_config, ...@@ -499,6 +507,8 @@ def create_estimator_and_inputs(run_config,
`use_tpu_estimator` is True. `use_tpu_estimator` is True.
override_eval_num_epochs: Whether to overwrite the number of epochs to override_eval_num_epochs: Whether to overwrite the number of epochs to
1 for eval_input. 1 for eval_input.
save_final_config: Whether to save final config (obtained after applying
overrides) to `estimator.model_dir`.
**kwargs: Additional keyword arguments for configuration override. **kwargs: Additional keyword arguments for configuration override.
Returns: Returns:
...@@ -522,7 +532,8 @@ def create_estimator_and_inputs(run_config, ...@@ -522,7 +532,8 @@ def create_estimator_and_inputs(run_config,
create_eval_input_fn = MODEL_BUILD_UTIL_MAP['create_eval_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'] create_predict_input_fn = MODEL_BUILD_UTIL_MAP['create_predict_input_fn']
configs = get_configs_from_pipeline_file(pipeline_config_path) configs = get_configs_from_pipeline_file(pipeline_config_path,
config_override=config_override)
kwargs.update({ kwargs.update({
'train_steps': train_steps, 'train_steps': train_steps,
'sample_1_of_n_eval_examples': sample_1_of_n_eval_examples 'sample_1_of_n_eval_examples': sample_1_of_n_eval_examples
...@@ -595,7 +606,7 @@ def create_estimator_and_inputs(run_config, ...@@ -595,7 +606,7 @@ def create_estimator_and_inputs(run_config,
estimator = tf.estimator.Estimator(model_fn=model_fn, config=run_config) estimator = tf.estimator.Estimator(model_fn=model_fn, config=run_config)
# Write the as-run pipeline config to disk. # Write the as-run pipeline config to disk.
if run_config.is_chief: if run_config.is_chief and save_final_config:
pipeline_config_final = create_pipeline_proto_from_configs(configs) pipeline_config_final = create_pipeline_proto_from_configs(configs)
config_util.save_pipeline_config(pipeline_config_final, estimator.model_dir) config_util.save_pipeline_config(pipeline_config_final, estimator.model_dir)
...@@ -641,11 +652,17 @@ def create_train_and_eval_specs(train_input_fn, ...@@ -641,11 +652,17 @@ def create_train_and_eval_specs(train_input_fn,
input_fn=train_input_fn, max_steps=train_steps) input_fn=train_input_fn, max_steps=train_steps)
if eval_spec_names is None: if eval_spec_names is None:
eval_spec_names = [ str(i) for i in range(len(eval_input_fns)) ] eval_spec_names = [str(i) for i in range(len(eval_input_fns))]
eval_specs = [] eval_specs = []
for eval_spec_name, eval_input_fn in zip(eval_spec_names, eval_input_fns): for index, (eval_spec_name, eval_input_fn) in enumerate(
exporter_name = '{}_{}'.format(final_exporter_name, eval_spec_name) 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( exporter = tf.estimator.FinalExporter(
name=exporter_name, serving_input_receiver_fn=predict_input_fn) name=exporter_name, serving_input_receiver_fn=predict_input_fn)
eval_specs.append( eval_specs.append(
...@@ -747,6 +764,7 @@ def populate_experiment(run_config, ...@@ -747,6 +764,7 @@ def populate_experiment(run_config,
train_steps=train_steps, train_steps=train_steps,
eval_steps=eval_steps, eval_steps=eval_steps,
model_fn_creator=model_fn_creator, model_fn_creator=model_fn_creator,
save_final_config=True,
**kwargs) **kwargs)
estimator = train_and_eval_dict['estimator'] estimator = train_and_eval_dict['estimator']
train_input_fn = train_and_eval_dict['train_input_fn'] train_input_fn = train_and_eval_dict['train_input_fn']
......
...@@ -310,7 +310,7 @@ class ModelLibTest(tf.test.TestCase): ...@@ -310,7 +310,7 @@ class ModelLibTest(tf.test.TestCase):
self.assertEqual(2, len(eval_specs)) self.assertEqual(2, len(eval_specs))
self.assertEqual(None, eval_specs[0].steps) self.assertEqual(None, eval_specs[0].steps)
self.assertEqual('holdout', eval_specs[0].name) self.assertEqual('holdout', eval_specs[0].name)
self.assertEqual('exporter_holdout', eval_specs[0].exporters[0].name) self.assertEqual('exporter', eval_specs[0].exporters[0].name)
self.assertEqual(None, eval_specs[1].steps) self.assertEqual(None, eval_specs[1].steps)
self.assertEqual('eval_on_train', eval_specs[1].name) self.assertEqual('eval_on_train', eval_specs[1].name)
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment