| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Tests for object detection model library.""" |
|
|
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
|
|
| import functools |
| import os |
|
|
| import numpy as np |
| import tensorflow as tf |
|
|
| from tensorflow.contrib.tpu.python.tpu import tpu_config |
| from tensorflow.contrib.tpu.python.tpu import tpu_estimator |
|
|
| from object_detection import inputs |
| from object_detection import model_hparams |
| from object_detection import model_lib |
| from object_detection.builders import model_builder |
| from object_detection.core import standard_fields as fields |
| from object_detection.utils import config_util |
|
|
|
|
| |
| |
| MODEL_NAME_FOR_TEST = 'ssd_inception_v2_pets' |
|
|
|
|
| def _get_data_path(): |
| """Returns an absolute path to TFRecord file.""" |
| return os.path.join(tf.resource_loader.get_data_files_path(), 'test_data', |
| 'pets_examples.record') |
|
|
|
|
| def get_pipeline_config_path(model_name): |
| """Returns path to the local pipeline config file.""" |
| return os.path.join(tf.resource_loader.get_data_files_path(), 'samples', |
| 'configs', model_name + '.config') |
|
|
|
|
| def _get_labelmap_path(): |
| """Returns an absolute path to label map file.""" |
| return os.path.join(tf.resource_loader.get_data_files_path(), 'data', |
| 'pet_label_map.pbtxt') |
|
|
|
|
| def _get_configs_for_model(model_name): |
| """Returns configurations for model.""" |
| filename = get_pipeline_config_path(model_name) |
| data_path = _get_data_path() |
| label_map_path = _get_labelmap_path() |
| configs = config_util.get_configs_from_pipeline_file(filename) |
| override_dict = { |
| 'train_input_path': data_path, |
| 'eval_input_path': data_path, |
| 'label_map_path': label_map_path |
| } |
| configs = config_util.merge_external_params_with_configs( |
| configs, kwargs_dict=override_dict) |
| return configs |
|
|
|
|
| def _make_initializable_iterator(dataset): |
| """Creates an iterator, and initializes tables. |
| |
| Args: |
| dataset: A `tf.data.Dataset` object. |
| |
| Returns: |
| A `tf.data.Iterator`. |
| """ |
| iterator = dataset.make_initializable_iterator() |
| tf.add_to_collection(tf.GraphKeys.TABLE_INITIALIZERS, iterator.initializer) |
| return iterator |
|
|
|
|
| class ModelLibTest(tf.test.TestCase): |
|
|
| @classmethod |
| def setUpClass(cls): |
| tf.reset_default_graph() |
|
|
| def _assert_model_fn_for_train_eval(self, configs, mode, |
| class_agnostic=False): |
| model_config = configs['model'] |
| train_config = configs['train_config'] |
| with tf.Graph().as_default(): |
| if mode == 'train': |
| features, labels = _make_initializable_iterator( |
| inputs.create_train_input_fn(configs['train_config'], |
| configs['train_input_config'], |
| configs['model'])()).get_next() |
| model_mode = tf.estimator.ModeKeys.TRAIN |
| batch_size = train_config.batch_size |
| elif mode == 'eval': |
| features, labels = _make_initializable_iterator( |
| inputs.create_eval_input_fn(configs['eval_config'], |
| configs['eval_input_config'], |
| configs['model'])()).get_next() |
| model_mode = tf.estimator.ModeKeys.EVAL |
| batch_size = 1 |
| elif mode == 'eval_on_train': |
| features, labels = _make_initializable_iterator( |
| inputs.create_eval_input_fn(configs['eval_config'], |
| configs['train_input_config'], |
| configs['model'])()).get_next() |
| model_mode = tf.estimator.ModeKeys.EVAL |
| batch_size = 1 |
|
|
| detection_model_fn = functools.partial( |
| model_builder.build, model_config=model_config, is_training=True) |
|
|
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
|
|
| model_fn = model_lib.create_model_fn(detection_model_fn, configs, hparams) |
| estimator_spec = model_fn(features, labels, model_mode) |
|
|
| self.assertIsNotNone(estimator_spec.loss) |
| self.assertIsNotNone(estimator_spec.predictions) |
| if mode == 'eval' or mode == 'eval_on_train': |
| if class_agnostic: |
| self.assertNotIn('detection_classes', estimator_spec.predictions) |
| else: |
| detection_classes = estimator_spec.predictions['detection_classes'] |
| self.assertEqual(batch_size, detection_classes.shape.as_list()[0]) |
| self.assertEqual(tf.float32, detection_classes.dtype) |
| detection_boxes = estimator_spec.predictions['detection_boxes'] |
| detection_scores = estimator_spec.predictions['detection_scores'] |
| num_detections = estimator_spec.predictions['num_detections'] |
| self.assertEqual(batch_size, detection_boxes.shape.as_list()[0]) |
| self.assertEqual(tf.float32, detection_boxes.dtype) |
| self.assertEqual(batch_size, detection_scores.shape.as_list()[0]) |
| self.assertEqual(tf.float32, detection_scores.dtype) |
| self.assertEqual(tf.float32, num_detections.dtype) |
| if mode == 'eval': |
| self.assertIn('Detections_Left_Groundtruth_Right/0', |
| estimator_spec.eval_metric_ops) |
| if model_mode == tf.estimator.ModeKeys.TRAIN: |
| self.assertIsNotNone(estimator_spec.train_op) |
| return estimator_spec |
|
|
| def _assert_model_fn_for_predict(self, configs): |
| model_config = configs['model'] |
|
|
| with tf.Graph().as_default(): |
| features, _ = _make_initializable_iterator( |
| inputs.create_eval_input_fn(configs['eval_config'], |
| configs['eval_input_config'], |
| configs['model'])()).get_next() |
| detection_model_fn = functools.partial( |
| model_builder.build, model_config=model_config, is_training=False) |
|
|
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
|
|
| model_fn = model_lib.create_model_fn(detection_model_fn, configs, hparams) |
| estimator_spec = model_fn(features, None, tf.estimator.ModeKeys.PREDICT) |
|
|
| self.assertIsNone(estimator_spec.loss) |
| self.assertIsNone(estimator_spec.train_op) |
| self.assertIsNotNone(estimator_spec.predictions) |
| self.assertIsNotNone(estimator_spec.export_outputs) |
| self.assertIn(tf.saved_model.signature_constants.PREDICT_METHOD_NAME, |
| estimator_spec.export_outputs) |
|
|
| def test_model_fn_in_train_mode(self): |
| """Tests the model function in TRAIN mode.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| self._assert_model_fn_for_train_eval(configs, 'train') |
|
|
| def test_model_fn_in_train_mode_freeze_all_variables(self): |
| """Tests model_fn TRAIN mode with all variables frozen.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| configs['train_config'].freeze_variables.append('.*') |
| with self.assertRaisesRegexp(ValueError, 'No variables to optimize'): |
| self._assert_model_fn_for_train_eval(configs, 'train') |
|
|
| def test_model_fn_in_train_mode_freeze_all_included_variables(self): |
| """Tests model_fn TRAIN mode with all included variables frozen.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| train_config = configs['train_config'] |
| train_config.update_trainable_variables.append('FeatureExtractor') |
| train_config.freeze_variables.append('.*') |
| with self.assertRaisesRegexp(ValueError, 'No variables to optimize'): |
| self._assert_model_fn_for_train_eval(configs, 'train') |
|
|
| def test_model_fn_in_train_mode_freeze_box_predictor(self): |
| """Tests model_fn TRAIN mode with FeatureExtractor variables frozen.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| train_config = configs['train_config'] |
| train_config.update_trainable_variables.append('FeatureExtractor') |
| train_config.update_trainable_variables.append('BoxPredictor') |
| train_config.freeze_variables.append('FeatureExtractor') |
| self._assert_model_fn_for_train_eval(configs, 'train') |
|
|
| def test_model_fn_in_eval_mode(self): |
| """Tests the model function in EVAL mode.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| self._assert_model_fn_for_train_eval(configs, 'eval') |
|
|
| def test_model_fn_in_eval_on_train_mode(self): |
| """Tests the model function in EVAL mode with train data.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| self._assert_model_fn_for_train_eval(configs, 'eval_on_train') |
|
|
| def test_model_fn_in_predict_mode(self): |
| """Tests the model function in PREDICT mode.""" |
| configs = _get_configs_for_model(MODEL_NAME_FOR_TEST) |
| self._assert_model_fn_for_predict(configs) |
|
|
| def test_create_estimator_and_inputs(self): |
| """Tests that Estimator and input function are constructed correctly.""" |
| run_config = tf.estimator.RunConfig() |
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
| pipeline_config_path = get_pipeline_config_path(MODEL_NAME_FOR_TEST) |
| train_steps = 20 |
| train_and_eval_dict = model_lib.create_estimator_and_inputs( |
| run_config, |
| hparams, |
| pipeline_config_path, |
| train_steps=train_steps) |
| estimator = train_and_eval_dict['estimator'] |
| train_steps = train_and_eval_dict['train_steps'] |
| self.assertIsInstance(estimator, tf.estimator.Estimator) |
| self.assertEqual(20, train_steps) |
| self.assertIn('train_input_fn', train_and_eval_dict) |
| self.assertIn('eval_input_fns', train_and_eval_dict) |
| self.assertIn('eval_on_train_input_fn', train_and_eval_dict) |
|
|
| def test_create_estimator_with_default_train_eval_steps(self): |
| """Tests that number of train/eval defaults to config values.""" |
| run_config = tf.estimator.RunConfig() |
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
| pipeline_config_path = get_pipeline_config_path(MODEL_NAME_FOR_TEST) |
| configs = config_util.get_configs_from_pipeline_file(pipeline_config_path) |
| config_train_steps = configs['train_config'].num_steps |
| train_and_eval_dict = model_lib.create_estimator_and_inputs( |
| run_config, hparams, pipeline_config_path) |
| estimator = train_and_eval_dict['estimator'] |
| train_steps = train_and_eval_dict['train_steps'] |
|
|
| self.assertIsInstance(estimator, tf.estimator.Estimator) |
| self.assertEqual(config_train_steps, train_steps) |
|
|
| def test_create_tpu_estimator_and_inputs(self): |
| """Tests that number of train/eval defaults to config values.""" |
|
|
| run_config = tpu_config.RunConfig() |
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
| pipeline_config_path = get_pipeline_config_path(MODEL_NAME_FOR_TEST) |
| train_steps = 20 |
| train_and_eval_dict = model_lib.create_estimator_and_inputs( |
| run_config, |
| hparams, |
| pipeline_config_path, |
| train_steps=train_steps, |
| use_tpu_estimator=True) |
| estimator = train_and_eval_dict['estimator'] |
| train_steps = train_and_eval_dict['train_steps'] |
|
|
| self.assertIsInstance(estimator, tpu_estimator.TPUEstimator) |
| self.assertEqual(20, train_steps) |
|
|
| def test_create_train_and_eval_specs(self): |
| """Tests that `TrainSpec` and `EvalSpec` is created correctly.""" |
| run_config = tf.estimator.RunConfig() |
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
| pipeline_config_path = get_pipeline_config_path(MODEL_NAME_FOR_TEST) |
| train_steps = 20 |
| train_and_eval_dict = model_lib.create_estimator_and_inputs( |
| run_config, |
| hparams, |
| pipeline_config_path, |
| train_steps=train_steps) |
| train_input_fn = train_and_eval_dict['train_input_fn'] |
| eval_input_fns = train_and_eval_dict['eval_input_fns'] |
| eval_on_train_input_fn = train_and_eval_dict['eval_on_train_input_fn'] |
| predict_input_fn = train_and_eval_dict['predict_input_fn'] |
| train_steps = train_and_eval_dict['train_steps'] |
|
|
| train_spec, eval_specs = model_lib.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=True, |
| final_exporter_name='exporter', |
| eval_spec_names=['holdout']) |
| self.assertEqual(train_steps, train_spec.max_steps) |
| self.assertEqual(2, len(eval_specs)) |
| self.assertEqual(None, eval_specs[0].steps) |
| self.assertEqual('holdout', eval_specs[0].name) |
| self.assertEqual('exporter', eval_specs[0].exporters[0].name) |
| self.assertEqual(None, eval_specs[1].steps) |
| self.assertEqual('eval_on_train', eval_specs[1].name) |
|
|
| def test_experiment(self): |
| """Tests that the `Experiment` object is constructed correctly.""" |
| run_config = tf.estimator.RunConfig() |
| hparams = model_hparams.create_hparams( |
| hparams_overrides='load_pretrained=false') |
| pipeline_config_path = get_pipeline_config_path(MODEL_NAME_FOR_TEST) |
| experiment = model_lib.populate_experiment( |
| run_config, |
| hparams, |
| pipeline_config_path, |
| train_steps=10, |
| eval_steps=20) |
| self.assertEqual(10, experiment.train_steps) |
| self.assertEqual(None, experiment.eval_steps) |
|
|
|
|
| class UnbatchTensorsTest(tf.test.TestCase): |
|
|
| def test_unbatch_without_unpadding(self): |
| image_placeholder = tf.placeholder(tf.float32, [2, None, None, None]) |
| groundtruth_boxes_placeholder = tf.placeholder(tf.float32, [2, None, None]) |
| groundtruth_classes_placeholder = tf.placeholder(tf.float32, |
| [2, None, None]) |
| groundtruth_weights_placeholder = tf.placeholder(tf.float32, [2, None]) |
|
|
| tensor_dict = { |
| fields.InputDataFields.image: |
| image_placeholder, |
| fields.InputDataFields.groundtruth_boxes: |
| groundtruth_boxes_placeholder, |
| fields.InputDataFields.groundtruth_classes: |
| groundtruth_classes_placeholder, |
| fields.InputDataFields.groundtruth_weights: |
| groundtruth_weights_placeholder |
| } |
| unbatched_tensor_dict = model_lib.unstack_batch( |
| tensor_dict, unpad_groundtruth_tensors=False) |
|
|
| with self.test_session() as sess: |
| unbatched_tensor_dict_out = sess.run( |
| unbatched_tensor_dict, |
| feed_dict={ |
| image_placeholder: |
| np.random.rand(2, 4, 4, 3).astype(np.float32), |
| groundtruth_boxes_placeholder: |
| np.random.rand(2, 5, 4).astype(np.float32), |
| groundtruth_classes_placeholder: |
| np.random.rand(2, 5, 6).astype(np.float32), |
| groundtruth_weights_placeholder: |
| np.random.rand(2, 5).astype(np.float32) |
| }) |
| for image_out in unbatched_tensor_dict_out[fields.InputDataFields.image]: |
| self.assertAllEqual(image_out.shape, [4, 4, 3]) |
| for groundtruth_boxes_out in unbatched_tensor_dict_out[ |
| fields.InputDataFields.groundtruth_boxes]: |
| self.assertAllEqual(groundtruth_boxes_out.shape, [5, 4]) |
| for groundtruth_classes_out in unbatched_tensor_dict_out[ |
| fields.InputDataFields.groundtruth_classes]: |
| self.assertAllEqual(groundtruth_classes_out.shape, [5, 6]) |
| for groundtruth_weights_out in unbatched_tensor_dict_out[ |
| fields.InputDataFields.groundtruth_weights]: |
| self.assertAllEqual(groundtruth_weights_out.shape, [5]) |
|
|
| def test_unbatch_and_unpad_groundtruth_tensors(self): |
| image_placeholder = tf.placeholder(tf.float32, [2, None, None, None]) |
| groundtruth_boxes_placeholder = tf.placeholder(tf.float32, [2, 5, None]) |
| groundtruth_classes_placeholder = tf.placeholder(tf.float32, [2, 5, None]) |
| groundtruth_weights_placeholder = tf.placeholder(tf.float32, [2, 5]) |
| num_groundtruth_placeholder = tf.placeholder(tf.int32, [2]) |
|
|
| tensor_dict = { |
| fields.InputDataFields.image: |
| image_placeholder, |
| fields.InputDataFields.groundtruth_boxes: |
| groundtruth_boxes_placeholder, |
| fields.InputDataFields.groundtruth_classes: |
| groundtruth_classes_placeholder, |
| fields.InputDataFields.groundtruth_weights: |
| groundtruth_weights_placeholder, |
| fields.InputDataFields.num_groundtruth_boxes: |
| num_groundtruth_placeholder |
| } |
| unbatched_tensor_dict = model_lib.unstack_batch( |
| tensor_dict, unpad_groundtruth_tensors=True) |
| with self.test_session() as sess: |
| unbatched_tensor_dict_out = sess.run( |
| unbatched_tensor_dict, |
| feed_dict={ |
| image_placeholder: |
| np.random.rand(2, 4, 4, 3).astype(np.float32), |
| groundtruth_boxes_placeholder: |
| np.random.rand(2, 5, 4).astype(np.float32), |
| groundtruth_classes_placeholder: |
| np.random.rand(2, 5, 6).astype(np.float32), |
| groundtruth_weights_placeholder: |
| np.random.rand(2, 5).astype(np.float32), |
| num_groundtruth_placeholder: |
| np.array([3, 3], np.int32) |
| }) |
| for image_out in unbatched_tensor_dict_out[fields.InputDataFields.image]: |
| self.assertAllEqual(image_out.shape, [4, 4, 3]) |
| for groundtruth_boxes_out in unbatched_tensor_dict_out[ |
| fields.InputDataFields.groundtruth_boxes]: |
| self.assertAllEqual(groundtruth_boxes_out.shape, [3, 4]) |
| for groundtruth_classes_out in unbatched_tensor_dict_out[ |
| fields.InputDataFields.groundtruth_classes]: |
| self.assertAllEqual(groundtruth_classes_out.shape, [3, 6]) |
| for groundtruth_weights_out in unbatched_tensor_dict_out[ |
| fields.InputDataFields.groundtruth_weights]: |
| self.assertAllEqual(groundtruth_weights_out.shape, [3]) |
|
|
|
|
| if __name__ == '__main__': |
| tf.test.main() |
|
|