• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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"""Tests for Keras composite tensor support."""
16
17from absl.testing import parameterized
18
19import numpy as np
20import scipy.sparse
21
22from tensorflow.python import keras
23
24from tensorflow.python.data.ops import dataset_ops
25from tensorflow.python.eager import context
26from tensorflow.python.framework import dtypes
27from tensorflow.python.framework import ops
28from tensorflow.python.framework import sparse_tensor
29from tensorflow.python.framework import test_util
30from tensorflow.python.keras import keras_parameterized
31from tensorflow.python.keras import testing_utils
32from tensorflow.python.keras.engine import input_layer
33from tensorflow.python.keras.layers import core
34from tensorflow.python.keras.layers import Dense
35from tensorflow.python.keras.layers import Embedding
36from tensorflow.python.keras.layers import Layer
37from tensorflow.python.ops import array_ops
38from tensorflow.python.ops import math_ops
39from tensorflow.python.ops import sparse_ops
40from tensorflow.python.ops.ragged import ragged_factory_ops
41from tensorflow.python.ops.ragged import ragged_tensor
42from tensorflow.python.platform import test
43from tensorflow.python.util import nest
44
45
46# Define test-only Layer classes to validate passing Sparse and Ragged tensors
47# between layers.
48class ToDense(Layer):
49  """Create a dense (standard) tensor from the given input tensor."""
50
51  def __init__(self, default_value, **kwargs):
52    super(ToDense, self).__init__(**kwargs)
53    self._default_value = default_value
54
55  def call(self, inputs):
56    if isinstance(inputs, dict):  # Dicts are no longer flattened.
57      # Always a single element in these tests.
58      inputs = nest.flatten(inputs)[0]
59
60    if isinstance(inputs, ragged_tensor.RaggedTensor):
61      output = inputs.to_tensor(default_value=self._default_value)
62    elif isinstance(inputs, sparse_tensor.SparseTensor):
63      output = sparse_ops.sparse_tensor_to_dense(
64          inputs, default_value=self._default_value)
65    elif isinstance(inputs, ops.Tensor):
66      output = inputs
67    else:
68      raise TypeError("Unexpected tensor type %s" % type(inputs).__name__)
69
70    # Return a float so that we can compile models with this as the final layer.
71    return math_ops.cast(output, dtypes.float32)
72
73
74class ToRagged(Layer):
75  """Create a ragged tensor based on a given dense tensor."""
76
77  def __init__(self, padding, ragged_rank=1, **kwargs):
78    super(ToRagged, self).__init__(**kwargs)
79    self._padding = padding
80    self._ragged_rank = ragged_rank
81
82  def call(self, inputs):
83    return ragged_tensor.RaggedTensor.from_tensor(
84        inputs, padding=self._padding, ragged_rank=self._ragged_rank)
85
86
87class ToSparse(Layer):
88  """Create a sparse tensor based on a given dense tensor."""
89
90  def call(self, inputs):
91    indices = array_ops.where_v2(math_ops.not_equal(inputs, 0))
92    values = array_ops.gather_nd(inputs, indices)
93    shape = array_ops.shape(inputs, out_type=dtypes.int64)
94    return sparse_tensor.SparseTensor(indices, values, dense_shape=shape)
95
96
97class _SubclassModel(keras.Model):
98  """A Keras subclass model."""
99
100  def __init__(self, layers, i_layer=None):
101    super(_SubclassModel, self).__init__()
102    # Note that clone and build doesn't support lists of layers in subclassed
103    # models. Adding each layer directly here.
104    for i, layer in enumerate(layers):
105      setattr(self, self._layer_name_for_i(i), layer)
106    self.num_layers = len(layers)
107    if i_layer is not None:
108      self._set_inputs(i_layer)
109
110  def _layer_name_for_i(self, i):
111    return "layer{}".format(i)
112
113  def call(self, inputs, **kwargs):
114    x = inputs
115    for i in range(self.num_layers):
116      layer = getattr(self, self._layer_name_for_i(i))
117      x = layer(x)
118    return x
119
120
121def get_model_from_layers_with_input(layers,
122                                     input_shape=None,
123                                     input_dtype=None,
124                                     model_input=None):
125  """Builds a model from a sequence of layers."""
126  if model_input is not None and input_shape is not None:
127    raise ValueError("Cannot specify a model_input and an input shape.")
128
129  model_type = testing_utils.get_model_type()
130  if model_type == "subclass":
131    return _SubclassModel(layers, model_input)
132
133  if model_type == "sequential":
134    model = keras.models.Sequential()
135    if model_input is not None:
136      model.add(model_input)
137    elif input_shape is not None:
138      model.add(keras.Input(shape=input_shape, dtype=input_dtype))
139    for layer in layers:
140      model.add(layer)
141    return model
142
143  if model_type == "functional":
144    if model_input is not None:
145      inputs = model_input
146    else:
147      if not input_shape:
148        raise ValueError("Cannot create a functional model from layers with no "
149                         "input shape.")
150      inputs = keras.Input(shape=input_shape, dtype=input_dtype)
151    outputs = inputs
152    for layer in layers:
153      outputs = layer(outputs)
154    return keras.Model(inputs, outputs)
155
156  raise ValueError("Unknown model type {}".format(model_type))
157
158
159def get_test_mode_kwargs():
160  run_eagerly = testing_utils.should_run_eagerly()
161  return {
162      "run_eagerly": run_eagerly,
163  }
164
165
166@keras_parameterized.run_with_all_model_types
167@keras_parameterized.run_all_keras_modes
168class CompositeTensorInternalTest(keras_parameterized.TestCase):
169
170  def test_internal_ragged_tensors(self):
171    # Create a model that accepts an input, converts it to Ragged, and
172    # converts the ragged tensor back to a dense tensor.
173    layers = [ToRagged(padding=0), ToDense(default_value=-1)]
174    model = testing_utils.get_model_from_layers(layers, input_shape=(None,))
175
176    # Define some input data with additional padding.
177    input_data = np.array([[1, 0, 0], [2, 3, 0]])
178    expected_output = np.array([[1, -1], [2, 3]])
179    output = model.predict(input_data)
180    self.assertAllEqual(expected_output, output)
181
182  def test_internal_sparse_tensors(self):
183    # Create a model that accepts an input, converts it to Sparse, and
184    # converts the sparse tensor back to a dense tensor.
185    layers = [ToSparse(), ToDense(default_value=-1)]
186    model = testing_utils.get_model_from_layers(layers, input_shape=(None,))
187
188    # Define some input data with additional padding.
189    input_data = np.array([[1, 0, 0], [2, 3, 0]])
190    expected_output = np.array([[1, -1, -1], [2, 3, -1]])
191    output = model.predict(input_data)
192    self.assertAllEqual(expected_output, output)
193
194  def test_training_internal_ragged_tensors(self):
195    # Create a model that implements y=Mx. This is easy to learn and will
196    # demonstrate appropriate gradient passing. (We have to use RaggedTensors
197    # for this test, as ToSparse() doesn't support gradient propagation through
198    # the layer.) TODO(b/124796939): Investigate this.
199    layers = [core.Dense(2), ToRagged(padding=0), ToDense(default_value=-1)]
200    model = testing_utils.get_model_from_layers(layers, input_shape=(1,))
201
202    input_data = np.random.rand(1024, 1)
203    expected_data = np.concatenate((input_data * 3, input_data * .5), axis=-1)
204
205    model.compile(loss="mse", optimizer="adam", **get_test_mode_kwargs())
206    history = model.fit(input_data, expected_data, epochs=10, verbose=0)
207
208    # If the model trained, the loss stored at history[0] should be different
209    # than the one stored at history[-1].
210    self.assertNotEqual(history.history["loss"][-1], history.history["loss"][0])
211
212
213@keras_parameterized.run_with_all_model_types
214@keras_parameterized.run_all_keras_modes
215class CompositeTensorOutputTest(keras_parameterized.TestCase):
216
217  def test_ragged_tensor_outputs(self):
218    # Create a model that accepts an input, converts it to Ragged, and
219    # converts the ragged tensor back to a dense tensor.
220    layers = [ToRagged(padding=0)]
221    model = testing_utils.get_model_from_layers(layers, input_shape=(None,))
222    model._run_eagerly = testing_utils.should_run_eagerly()
223
224    # Define some input data with additional padding.
225    input_data = np.array([[1, 0, 0], [2, 3, 0]])
226    output = model.predict(input_data)
227
228    expected_values = [[1], [2, 3]]
229    self.assertAllEqual(expected_values, output)
230
231  def test_ragged_tensor_rebatched_outputs(self):
232    # Create a model that accepts an input, converts it to Ragged, and
233    # converts the ragged tensor back to a dense tensor.
234    layers = [ToRagged(padding=0)]
235    model = testing_utils.get_model_from_layers(layers, input_shape=(None,))
236    model._run_eagerly = testing_utils.should_run_eagerly()
237
238    # Define some input data with additional padding.
239    input_data = np.array([[1, 0, 0], [2, 3, 0], [4, 0, 0], [5, 6, 0]])
240    output = model.predict(input_data, batch_size=2)
241
242    expected_values = [[1], [2, 3], [4], [5, 6]]
243    self.assertAllEqual(expected_values, output)
244
245  def test_sparse_tensor_outputs(self):
246    # Create a model that accepts an input, converts it to Ragged, and
247    # converts the ragged tensor back to a dense tensor.
248    layers = [ToSparse()]
249    model = testing_utils.get_model_from_layers(layers, input_shape=(None,))
250    model._run_eagerly = testing_utils.should_run_eagerly()
251
252    # Define some input data with additional padding.
253    input_data = np.array([[1, 0, 0], [2, 3, 0]])
254    output = model.predict(input_data)
255
256    expected_indices = np.array([[0, 0], [1, 0], [1, 1]])
257    expected_values = np.array([1, 2, 3])
258    expected_dense_shape = np.array([2, 3])
259
260    self.assertAllEqual(output.indices, expected_indices)
261    self.assertAllEqual(output.values, expected_values)
262    self.assertAllEqual(output.dense_shape, expected_dense_shape)
263
264  def test_sparse_tensor_rebatched_outputs(self):
265    # Create a model that accepts an input, converts it to Ragged, and
266    # converts the ragged tensor back to a dense tensor.
267    layers = [ToSparse()]
268    model = testing_utils.get_model_from_layers(layers, input_shape=(None,))
269    model._run_eagerly = testing_utils.should_run_eagerly()
270
271    # Define some input data with additional padding.
272    input_data = np.array([[1, 0, 0], [2, 3, 0], [4, 0, 0], [5, 6, 0]])
273    output = model.predict(input_data, batch_size=2)
274
275    expected_indices = np.array([[0, 0], [1, 0], [1, 1], [2, 0], [3, 0], [3,
276                                                                          1]])
277    expected_values = np.array([1, 2, 3, 4, 5, 6])
278    expected_dense_shape = np.array([4, 3])
279
280    self.assertAllEqual(output.indices, expected_indices)
281    self.assertAllEqual(output.values, expected_values)
282    self.assertAllEqual(output.dense_shape, expected_dense_shape)
283
284
285def get_input_name(use_dict):
286  # Define the input name.
287  if not use_dict:
288    return None  # This is the same as not setting 'name'.
289  elif testing_utils.get_model_type() == "subclass":
290    return "input_1"  # Subclass models don"t support input names.
291  else:
292    return "test_input_name"
293
294
295def get_kwargs(use_dataset, action="predict"):
296  if use_dataset or not context.executing_eagerly():
297    if action == "fit":
298      return {"steps_per_epoch": 1}
299    return {"steps": 1}
300  else:
301    return {"batch_size": 2}
302
303
304def prepare_inputs(data, use_dict, use_dataset, action, input_name):
305  input_data, expected_output = data
306  batch_size = input_data.shape[0]
307  # Prepare the input data.
308  if use_dict:
309    input_data = {input_name: input_data}
310  if use_dataset:
311    if action == "predict":
312      input_data = dataset_ops.DatasetV2.from_tensor_slices(input_data).batch(
313          batch_size)
314    else:
315      input_data = dataset_ops.DatasetV2.from_tensor_slices(
316          (input_data, expected_output)).batch(batch_size)
317      expected_output = None
318  return (input_data, expected_output)
319
320
321@keras_parameterized.run_with_all_model_types
322@keras_parameterized.run_all_keras_modes
323@parameterized.named_parameters(
324    *testing_utils.generate_combinations_with_testcase_name(
325        use_dict=[True, False],
326        use_dataset=[True, False],
327        action=["predict", "evaluate", "fit"]))
328class SparseTensorInputTest(keras_parameterized.TestCase):
329
330  def test_sparse_tensors(self, use_dict, use_dataset, action):
331    data = [(sparse_tensor.SparseTensor([[0, 0, 0], [1, 0, 0], [1, 0, 1]],
332                                        [1, 2, 3], [2, 1, 3]),
333             np.array([[[1, -1, -1]], [[2, 3, -1]]])),
334            (sparse_tensor.SparseTensor(
335                [[0, 0, 0], [1, 0, 0], [1, 0, 1], [2, 0, 1]], [5, 6, 7, 8],
336                [3, 1, 4]),
337             np.array([[[5, -1, -1, -1]], [[6, 7, -1, -1]], [[-1, 8, -1,
338                                                              -1]]]))]
339    # Prepare the model to test.
340    input_name = get_input_name(use_dict)
341    model_input = input_layer.Input(
342        shape=(1, None), sparse=True, name=input_name, dtype=dtypes.int32)
343    layers = [ToDense(default_value=-1)]
344    model = get_model_from_layers_with_input(layers, model_input=model_input)
345    model.compile(
346        optimizer="sgd",
347        loss="mse",
348        metrics=["accuracy"],
349        **get_test_mode_kwargs())
350    kwargs = get_kwargs(use_dataset, action)
351
352    # Prepare the input data
353    for data_element in data:
354      input_data, expected_output = prepare_inputs(data_element, use_dict,
355                                                   use_dataset, action,
356                                                   input_name)
357      # Perform the action.
358      if action == "predict":
359        result = model.predict(input_data, **kwargs)
360        self.assertAllEqual(expected_output, result)
361      if action == "evaluate":
362        result = model.evaluate(input_data, expected_output, **kwargs)
363        self.assertAllEqual(1.0, result[-1])
364      if action == "fit":
365        # TODO(momernick): What's the best way of validating that fit happened?
366        _ = model.fit(input_data, expected_output, shuffle=False, **kwargs)
367
368
369@keras_parameterized.run_with_all_model_types
370@keras_parameterized.run_all_keras_modes
371class ScipySparseTensorInputTest(keras_parameterized.TestCase,
372                                 test_util.TensorFlowTestCase):
373
374  def test_sparse_scipy_predict_inputs_via_input_layer_args(self):
375    # Create a model that accepts a sparse input and converts the sparse tensor
376    # back to a dense tensor. Scipy sparse matrices are limited to 2D, so use
377    # a one-dimensional shape; note also that scipy's default dtype is int64.
378    model_input = input_layer.Input(shape=(3,), sparse=True, dtype=dtypes.int64)
379    layers = [ToDense(default_value=-1)]
380    model = get_model_from_layers_with_input(layers, model_input=model_input)
381
382    input_data = scipy.sparse.coo_matrix(([1, 2, 3], ([0, 1, 1], [0, 0, 1])),
383                                         shape=[2, 3])
384    expected_output = np.array([[1, -1, -1], [2, 3, -1]])
385    output = model.predict(input_data, steps=1)
386    self.assertAllEqual(expected_output, output)
387
388    input_data_2 = scipy.sparse.coo_matrix(
389        ([5, 6, 7, 8], ([0, 1, 1, 2], [0, 0, 1, 1])), shape=[3, 3])
390    expected_output_2 = np.array([[5, -1, -1], [6, 7, -1], [-1, 8, -1]])
391    output_2 = model.predict(input_data_2, steps=1)
392    self.assertAllEqual(expected_output_2, output_2)
393
394  def test_sparse_scipy_eval_inputs(self):
395    # Create a model that accepts a sparse input and converts the sparse tensor
396    # back to a dense tensor. Scipy sparse matrices are limited to 2D, so use
397    # a one-dimensional shape; note also that scipy's default dtype is int64.
398    model_input = input_layer.Input(shape=(3,), sparse=True, dtype=dtypes.int64)
399    layers = [ToDense(default_value=-1)]
400    model = get_model_from_layers_with_input(layers, model_input=model_input)
401    model.compile(
402        optimizer="sgd",
403        loss="mse",
404        metrics=["accuracy"])
405
406    input_data = scipy.sparse.coo_matrix(([1, 2, 3], ([0, 1, 1], [0, 0, 1])),
407                                         shape=[2, 3])
408    expected_output = np.array([[1, -1, -1], [2, 3, -1]])
409
410    output = model.evaluate(input_data, expected_output, steps=1)
411    self.assertAllEqual(1.0, output[-1])
412
413    input_data_2 = scipy.sparse.coo_matrix(
414        ([5, 6, 7, 8], ([0, 1, 1, 2], [0, 0, 1, 1])), shape=[3, 3])
415    expected_output_2 = np.array([[5, -1, -1], [6, 7, -1], [-1, 8, -1]])
416    output_2 = model.evaluate(input_data_2, expected_output_2, steps=1)
417    self.assertAllEqual(1.0, output_2[-1])
418
419  def test_sparse_scipy_predict_input_dicts_via_input_layer_args(self):
420    # Create a model that accepts a sparse input and converts the sparse tensor
421    # back to a dense tensor. Scipy sparse matrices are limited to 2D, so use
422    # a one-dimensional shape; note also that scipy's default dtype is int64.
423    if testing_utils.get_model_type() == "subclass":
424      input_name = "input_1"  # Subclass models don"t support input names.
425    else:
426      input_name = "test_input_name"
427    model_input = input_layer.Input(
428        shape=(3,), sparse=True, name=input_name, dtype=dtypes.int64)
429    layers = [ToDense(default_value=-1)]
430    model = get_model_from_layers_with_input(layers, model_input=model_input)
431
432    input_data = {
433        input_name:
434            scipy.sparse.coo_matrix(([1, 2, 3], ([0, 1, 1], [0, 0, 1])),
435                                    shape=[2, 3])
436    }
437    expected_output = np.array([[1, -1, -1], [2, 3, -1]])
438    output = model.predict(input_data, steps=1)
439    self.assertAllEqual(expected_output, output)
440
441    input_data_2 = {
442        input_name:
443            scipy.sparse.coo_matrix(
444                ([5, 6, 7, 8], ([0, 1, 1, 2], [0, 0, 1, 1])), shape=[3, 3])
445    }
446    expected_output_2 = np.array([[5, -1, -1], [6, 7, -1], [-1, 8, -1]])
447    output_2 = model.predict(input_data_2, steps=1)
448    self.assertAllEqual(expected_output_2, output_2)
449
450  def test_sparse_scipy_eval_input_dicts(self):
451    # Create a model that accepts a sparse input and converts the sparse tensor
452    # back to a dense tensor. Scipy sparse matrices are limited to 2D, so use
453    # a one-dimensional shape; note also that scipy's default dtype is int64.
454    if testing_utils.get_model_type() == "subclass":
455      input_name = "input_1"  # Subclass models don"t support input names.
456    else:
457      input_name = "test_input_name"
458    model_input = input_layer.Input(
459        shape=(3,), sparse=True, name=input_name, dtype=dtypes.int64)
460    layers = [ToDense(default_value=-1)]
461    model = get_model_from_layers_with_input(layers, model_input=model_input)
462    model.compile(
463        optimizer="sgd",
464        loss="mse",
465        metrics=["accuracy"])
466
467    input_data = {
468        input_name:
469            scipy.sparse.coo_matrix(([1, 2, 3], ([0, 1, 1], [0, 0, 1])),
470                                    shape=[2, 3])
471    }
472    expected_output = np.array([[1, -1, -1], [2, 3, -1]])
473    output = model.evaluate(input_data, expected_output, steps=1)
474    self.assertAllEqual(1.0, output[-1])
475
476    input_data_2 = {
477        input_name:
478            scipy.sparse.coo_matrix(
479                ([5, 6, 7, 8], ([0, 1, 1, 2], [0, 0, 1, 1])), shape=[3, 3])
480    }
481    expected_output_2 = np.array([[5, -1, -1], [6, 7, -1], [-1, 8, -1]])
482    output_2 = model.evaluate(input_data_2, expected_output_2, steps=1)
483    self.assertAllEqual(1.0, output_2[-1])
484
485
486@keras_parameterized.run_with_all_model_types
487@keras_parameterized.run_all_keras_modes
488@parameterized.named_parameters(
489    *testing_utils.generate_combinations_with_testcase_name(
490        use_dict=[True, False],
491        use_dataset=[True, False],
492        action=["predict", "evaluate", "fit"]))
493class RaggedTensorInputTest(keras_parameterized.TestCase,
494                            test_util.TensorFlowTestCase):
495
496  def test_ragged_input(self, use_dict, use_dataset, action):
497    data = [(ragged_factory_ops.constant([[[1]], [[2, 3]]]),
498             np.array([[[1, -1]], [[2, 3]]]))]
499
500    # Prepare the model to test.
501    input_name = get_input_name(use_dict)
502    model_input = input_layer.Input(
503        shape=(None, None), ragged=True, name=input_name, dtype=dtypes.int32,
504        batch_size=2)
505    self.assertIsInstance(model_input._type_spec,
506                          ragged_tensor.RaggedTensorSpec)
507    self.assertEqual(model_input.shape.as_list(), [2, None, None])
508    layers = [ToDense(default_value=-1)]
509    model = get_model_from_layers_with_input(layers, model_input=model_input)
510    model.compile(
511        optimizer="sgd",
512        loss="mse",
513        metrics=["accuracy"],
514        **get_test_mode_kwargs())
515
516    # Prepare the input data
517    for data_element in data:
518      input_data, expected_output = prepare_inputs(data_element, use_dict,
519                                                   use_dataset, action,
520                                                   input_name)
521      # Perform the action.
522      if action == "predict":
523        result = model.predict(input_data)
524        self.assertAllEqual(expected_output, result)
525      if action == "evaluate":
526        result = model.evaluate(input_data, expected_output)
527        self.assertAllEqual(1.0, result[-1])
528      if action == "fit":
529        # TODO(momernick): What's the best way of validating that fit happened?
530        _ = model.fit(input_data, expected_output, shuffle=False)
531
532
533@keras_parameterized.run_with_all_model_types
534@keras_parameterized.run_all_keras_modes
535@parameterized.named_parameters(
536    *testing_utils.generate_combinations_with_testcase_name(
537        use_dict=[True, False], use_dataset=[True, False]))
538class RaggedTensorInputValidationTest(keras_parameterized.TestCase,
539                                      test_util.TensorFlowTestCase):
540
541  def test_ragged_tensor_input_with_one_none_dimension(self, use_dict,
542                                                       use_dataset):
543    # Define some input data.
544    data = [(ragged_factory_ops.constant([[[1, 0]], [[2, 3]]], ragged_rank=1),
545             np.array([[[1, 0]], [[2, 3]]]))]
546
547    # Prepare the model to test.
548    input_shape = (None, 2)  # RaggedTensorInputTest uses (None, None).
549    input_name = get_input_name(use_dict)
550    model_input = input_layer.Input(
551        shape=input_shape, ragged=True, name=input_name, dtype=dtypes.int32)
552    layers = [ToDense(default_value=-1)]
553    model = get_model_from_layers_with_input(layers, model_input=model_input)
554    model.compile(
555        optimizer="sgd",
556        loss="mse",
557        metrics=["accuracy"],
558        **get_test_mode_kwargs())
559
560    for data_element in data:
561      input_data, expected_output = prepare_inputs(
562          data_element,
563          use_dict,
564          use_dataset,
565          action="predict",
566          input_name=input_name)
567      result = model.predict(input_data)
568      self.assertAllEqual(expected_output, result)
569
570  def test_ragged_tensor_input_with_no_none_dimension(self, use_dict,
571                                                      use_dataset):
572    # Define some input data.
573    data = [(ragged_factory_ops.constant([[[1, 0]], [[2, 3]]], ragged_rank=0),
574             np.array([[[1, 0]], [[2, 3]]]))]
575
576    # Prepare the model to test.
577    input_shape = (1, 2)  # RaggedTensorInputTest uses (None, None).
578    input_name = get_input_name(use_dict)
579    model_input = input_layer.Input(
580        shape=input_shape, ragged=True, name=input_name, dtype=dtypes.int32)
581    layers = [ToDense(default_value=-1)]
582    model = get_model_from_layers_with_input(layers, model_input=model_input)
583    model.compile(
584        optimizer="sgd",
585        loss="mse",
586        metrics=["accuracy"],
587        **get_test_mode_kwargs())
588    kwargs = get_kwargs(use_dataset)
589
590    for data_element in data:
591      input_data, expected_output = prepare_inputs(
592          data_element,
593          use_dict,
594          use_dataset,
595          action="predict",
596          input_name=input_name)
597      result = model.predict(input_data, **kwargs)
598      self.assertAllEqual(expected_output, result)
599
600
601@keras_parameterized.run_with_all_model_types()
602@keras_parameterized.run_all_keras_modes(always_skip_v1=True)
603class CompositeTensorModelPredictTest(keras_parameterized.TestCase):
604
605  def _normalize_shape(self, shape):
606    if not isinstance(shape, tuple):
607      shape = tuple(shape.as_list())
608    return shape
609
610  def test_sparse_tensor_model_predict(self):
611    # Create a model that accepts a sparse input and runs a "Dense" layer on it.
612    model_input = input_layer.Input(
613        shape=(3,), sparse=True, dtype=dtypes.float32)
614
615    self.assertEqual([None, 3], model_input.shape.as_list())
616
617    layers = [Dense(2)]
618    model = get_model_from_layers_with_input(layers, model_input=model_input)
619
620    sparse_input = sparse_tensor.SparseTensor(
621        # A two-row matrix
622        indices=[(0, 0), (0, 1), (0, 2), (5, 0), (5, 1), (5, 2)],
623        values=[1., 1., 1., 1., 1., 1.],
624        dense_shape=(6, 3))
625
626    shape = model(sparse_input).shape
627    self.assertEqual((6, 2), self._normalize_shape(shape))
628
629    shape = model.predict(sparse_input, steps=1).shape
630    self.assertEqual((6, 2), self._normalize_shape(shape))
631
632  def test_ragged_tensor_model_predict(self):
633    # Create a model that accepts a sparse input and runs a "Dense" layer on it.
634    model_input = input_layer.Input(shape=(None,), ragged=True)
635    self.assertEqual([None, None], model_input.shape.as_list())
636
637    layers = [Embedding(input_dim=7, output_dim=5)]
638    model = get_model_from_layers_with_input(layers, model_input=model_input)
639
640    ragged_input = ragged_factory_ops.constant([
641        [1, 2, 3, 4, 5],
642        [2, 4],
643    ])
644
645    shape = model(ragged_input).shape
646    self.assertEqual((2, None, 5), self._normalize_shape(shape))
647
648    shape = model.predict(ragged_input, steps=1).shape
649    self.assertEqual((2, None, 5), self._normalize_shape(shape))
650
651
652if __name__ == "__main__":
653  test.main()
654