1# Copyright 2019 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""End-to-end tests for a variety of small models.""" 16 17import collections 18import itertools 19 20from absl.testing import parameterized 21import numpy as np 22 23from tensorflow.python import keras 24from tensorflow.python.data.ops import dataset_ops 25from tensorflow.python.eager import context 26from tensorflow.python.framework import ops 27from tensorflow.python.keras import keras_parameterized 28from tensorflow.python.keras import testing_utils 29from tensorflow.python.ops import math_ops 30from tensorflow.python.platform import test 31 32 33def _conv2d_filter(**kwargs): 34 """Convolution with non-default strides and dilation rate is not supported.""" 35 return kwargs['strides'] <= 1 or kwargs['dilation_rate'] <= 1 36 37 38# Scheme: (layer_class, data_shape, fuzz_dims, constructor_args, filter_fn) 39# layer_class: 40# A keras Layer class to be tested. 41# data_shape: 42# The shape of the input data. (not including batch dim) 43# fuzz_dims: 44# Dimensions which can be unspecified during model construction. For 45# instance, if data_shape is (2, 5) and fuzz_dims is (False, True), a pass 46# with model input shape of (2, None) will also be performed. 47# constructor_args: 48# An OrderedDict (to ensure consistent test names) with a key and a list 49# of values to test. Test cases will be generated for the Cartesian product 50# of all constructor args, so adding more fields can cause the drastically 51# increase the testing load. 52# filter_fn: 53# If not None, this function will be called on each set of generated 54# constructor args, and prevents generation of contradictory combinations. 55# A True return value indicates a valid test. 56_LAYERS_TO_TEST = [ 57 (keras.layers.Dense, (1,), (False,), collections.OrderedDict([ 58 ('units', [1])]), None), 59 (keras.layers.Activation, (2, 2), (True, True), collections.OrderedDict([ 60 ('activation', ['relu'])]), None), 61 (keras.layers.Dropout, (16,), (False,), collections.OrderedDict([ 62 ('rate', [0.25])]), None), 63 (keras.layers.BatchNormalization, (8, 8, 3), (True, True, False), 64 collections.OrderedDict([ 65 ('axis', [3]), 66 ('center', [True, False]), 67 ('scale', [True, False]) 68 ]), None), 69 (keras.layers.Conv1D, (8, 8), (False, False), collections.OrderedDict([ 70 ('filters', [1]), 71 ('kernel_size', [1, 3]), 72 ('strides', [1, 2]), 73 ('padding', ['valid', 'same']), 74 ('use_bias', [True]), 75 ('kernel_regularizer', ['l2']), 76 ('data_format', ['channels_last']) 77 ]), None), 78 (keras.layers.Conv2D, (8, 8, 3), (True, True, False), 79 collections.OrderedDict([ 80 ('filters', [1]), 81 ('kernel_size', [1, 3]), 82 ('strides', [1, 2]), 83 ('padding', ['valid', 'same']), 84 ('use_bias', [True, False]), 85 ('kernel_regularizer', ['l2']), 86 ('dilation_rate', [1, 2]), 87 ('data_format', ['channels_last']) 88 ]), _conv2d_filter), 89 (keras.layers.LSTM, (4, 4), (False, False), collections.OrderedDict([ 90 ('units', [1]), 91 ('kernel_regularizer', ['l2']), 92 ('dropout', [0, 0.5]), 93 ('stateful', [True, False]), 94 ('unroll', [True, False]), 95 ('return_sequences', [True, False]) 96 ]), None), 97] 98 99 100def _gather_test_cases(): 101 cases = [] 102 for layer_type, inp_shape, fuzz_dims, arg_dict, filter_fn in _LAYERS_TO_TEST: 103 arg_combinations = [[(k, i) for i in v] for k, v in arg_dict.items()] # pylint: disable=g-complex-comprehension 104 for arguments in itertools.product(*arg_combinations): 105 layer_kwargs = {k: v for k, v in arguments} 106 if filter_fn is not None and not filter_fn(**layer_kwargs): 107 continue 108 109 name = '_{}_{}'.format(layer_type.__name__, 110 '_'.join('{}_{}'.format(*i) for i in arguments)) 111 cases.append((name, layer_type, inp_shape, fuzz_dims, layer_kwargs)) 112 return cases 113 114 115OUTPUT_TEST_CASES = _gather_test_cases() 116 117 118class CoreLayerIntegrationTest(keras_parameterized.TestCase): 119 """Test that layers and models produce the correct tensor types.""" 120 121 # In v1 graph there are only symbolic tensors. 122 @keras_parameterized.run_all_keras_modes(always_skip_v1=True) 123 @parameterized.named_parameters(*OUTPUT_TEST_CASES) 124 def test_layer_output_type(self, layer_to_test, input_shape, _, layer_kwargs): 125 layer = layer_to_test(**layer_kwargs) 126 127 input_data = np.ones(shape=(2,) + input_shape, dtype=np.float32) 128 layer_result = layer(input_data) 129 130 inp = keras.layers.Input(shape=input_shape, batch_size=2) 131 model = keras.models.Model(inp, layer_to_test(**layer_kwargs)(inp)) 132 model_result = model(input_data) 133 134 for x in [layer_result, model_result]: 135 if not isinstance(x, ops.Tensor): 136 raise ValueError('Tensor or EagerTensor expected, got type {}' 137 .format(type(x))) 138 139 if isinstance(x, ops.EagerTensor) != context.executing_eagerly(): 140 expected_type = (ops.EagerTensor if context.executing_eagerly() 141 else ops.Tensor) 142 raise ValueError('Expected type {}, got type {}' 143 .format(expected_type, type(x))) 144 145 def _run_fit_eval_predict(self, layer_to_test, input_shape, data_shape, 146 layer_kwargs): 147 batch_size = 2 148 run_eagerly = testing_utils.should_run_eagerly() 149 150 def map_fn(_): 151 x = keras.backend.random_uniform(shape=data_shape) 152 y = keras.backend.random_uniform(shape=(1,)) 153 return x, y 154 155 dataset = dataset_ops.DatasetV2.range(4).map(map_fn).batch(batch_size) 156 157 inp = keras.layers.Input(shape=input_shape, batch_size=batch_size) 158 layer = layer_to_test(**layer_kwargs)(inp) 159 160 # Condense the output down to a single scalar. 161 layer = keras.layers.Flatten()(layer) 162 layer = keras.layers.Lambda( 163 lambda x: math_ops.reduce_mean(x, keepdims=True))(layer) 164 layer = keras.layers.Dense(1, activation=None)(layer) 165 model = keras.models.Model(inp, layer) 166 167 model.compile(loss='mse', optimizer='sgd', run_eagerly=run_eagerly) 168 model.fit(dataset, verbose=2, epochs=2) 169 170 model.compile(loss='mse', optimizer='sgd', run_eagerly=run_eagerly) 171 model.fit(dataset.repeat(2), verbose=2, epochs=2, steps_per_epoch=2) 172 173 eval_dataset = dataset_ops.DatasetV2.range(4).map(map_fn).batch(batch_size) 174 model.evaluate(eval_dataset, verbose=2) 175 176 def pred_map_fn(_): 177 return keras.backend.random_uniform(shape=data_shape) 178 179 pred_dataset = dataset_ops.DatasetV2.range(4) 180 pred_dataset = pred_dataset.map(pred_map_fn).batch(batch_size) 181 model.predict(pred_dataset, verbose=2) 182 183 @keras_parameterized.run_all_keras_modes(always_skip_v1=False) 184 @parameterized.named_parameters(*OUTPUT_TEST_CASES) 185 def test_model_loops(self, layer_to_test, input_shape, fuzz_dims, 186 layer_kwargs): 187 self._run_fit_eval_predict(layer_to_test, input_shape, 188 input_shape, layer_kwargs) 189 190 if any(fuzz_dims): 191 fuzzed_shape = [] 192 for dim, should_fuzz in zip(input_shape, fuzz_dims): 193 fuzzed_shape.append(None if should_fuzz else dim) 194 195 self._run_fit_eval_predict(layer_to_test, fuzzed_shape, 196 input_shape, layer_kwargs) 197 198 199if __name__ == '__main__': 200 test.main() 201