From 391ebea26671f25fcddd1315861d535605f6ff03 Mon Sep 17 00:00:00 2001 From: Tomer Kaftan Date: Fri, 17 Jul 2020 11:42:51 -0700 Subject: [PATCH] Add a meaningful repr to KerasTensors, Update the KerasTensor docstring, and update some of the doctests to not fail w/ the new KerasTensor repr. PiperOrigin-RevId: 321822118 Change-Id: Iad58dddac5362301b6a06c532e58378f56e3b9ac --- tensorflow/python/keras/engine/BUILD | 15 ++++ .../python/keras/engine/keras_tensor.py | 38 +++++++-- .../python/keras/engine/keras_tensor_test.py | 85 +++++++++++++++++++ .../python/keras/layers/einsum_dense.py | 6 +- tensorflow/python/ops/array_ops.py | 2 +- 5 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 tensorflow/python/keras/engine/keras_tensor_test.py diff --git a/tensorflow/python/keras/engine/BUILD b/tensorflow/python/keras/engine/BUILD index c64e38122e9..5f65923e6fe 100644 --- a/tensorflow/python/keras/engine/BUILD +++ b/tensorflow/python/keras/engine/BUILD @@ -322,6 +322,21 @@ tf_py_test( ], ) +tf_py_test( + name = "keras_tensor_test", + size = "small", + srcs = ["keras_tensor_test.py"], + python_version = "PY3", + tags = [ + "nomac", # TODO(mihaimaruseac): b/127695564 + ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python/keras", + "@absl_py//absl/testing:parameterized", + ], +) + tf_py_test( name = "input_spec_test", size = "small", diff --git a/tensorflow/python/keras/engine/keras_tensor.py b/tensorflow/python/keras/engine/keras_tensor.py index 8ab6f674243..6bdd11d8ec3 100644 --- a/tensorflow/python/keras/engine/keras_tensor.py +++ b/tensorflow/python/keras/engine/keras_tensor.py @@ -51,16 +51,15 @@ def keras_tensors_enabled(): class KerasTensor(object): """A representation of a Keras in/output during Functional API construction. - `KerasTensor`s are an alternative representation for Keras `Inputs` - and for intermediate outputs of layers during Functional API construction of - models. They are a lightweight data structure comprised of only the - `tf.TypeSpec` of the Tensor that will be consumed/produced in the - corresponding position of the model. + `KerasTensor`s are tensor-like objects that represent the symbolic inputs + and outputs of Keras layers during Functional model construction. They are + compromised of the `tf.TypeSpec` of the Tensor that will be + consumed/produced in the corresponding position of the model. - They implement just small subset of `tf.Tensor`'s attributes and - methods, and also overload - the same operators as `tf.Tensor` and automatically turn them into - Keras layers in the model. + They implement `tf.Tensor`'s attributes and methods, and also overload + the same operators as `tf.Tensor`. Passing a KerasTensor to a TF API that + supports dispatching will automatically turn that API call into a lambda + layer in the Functional model. `KerasTensor`s are still internal-only and are a work in progress, but they have several advantages over using a graph `tf.Tensor` to represent @@ -150,6 +149,27 @@ class KerasTensor(object): else: self._type_spec._shape = shape # pylint: disable=protected-access + def __repr__(self): + symbolic_description = '' + inferred_value_string = '' + if isinstance(self.type_spec, tensor_spec.TensorSpec): + type_spec_string = 'shape=%s dtype=%s' % (self.shape, self.dtype.name) + else: + type_spec_string = 'type_spec=%s' % self.type_spec + + if hasattr(self, '_keras_history'): + layer = self._keras_history.layer + node_index = self._keras_history.node_index + tensor_index = self._keras_history.tensor_index + symbolic_description = ( + ' (Symbolic value %s from symbolic call %s of layer \'%s\')' % ( + tensor_index, node_index, layer.name)) + if self._inferred_shape_value is not None: + inferred_value_string = ( + ' inferred_value=\'%s\'' % self._inferred_shape_value) + return '' % ( + type_spec_string, inferred_value_string, symbolic_description) + @property def dtype(self): """Returns the `dtype` of elements in the tensor.""" diff --git a/tensorflow/python/keras/engine/keras_tensor_test.py b/tensorflow/python/keras/engine/keras_tensor_test.py new file mode 100644 index 00000000000..63e117effec --- /dev/null +++ b/tensorflow/python/keras/engine/keras_tensor_test.py @@ -0,0 +1,85 @@ +# Copyright 2019 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. +# ============================================================================== +"""InputSpec tests.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import sparse_tensor +from tensorflow.python.framework import tensor_shape +from tensorflow.python.framework import tensor_spec +from tensorflow.python.keras import layers +from tensorflow.python.keras import testing_utils +from tensorflow.python.keras.engine import keras_tensor +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import test + + +class KerasTensorTest(test.TestCase): + + def test_repr(self): + kt = keras_tensor.KerasTensor( + type_spec=tensor_spec.TensorSpec(shape=(1, 2, 3), dtype=dtypes.float32)) + expected_repr = "" + self.assertEqual(expected_repr, str(kt)) + self.assertEqual(expected_repr, repr(kt)) + + kt = keras_tensor.KerasTensor( + type_spec=tensor_spec.TensorSpec(shape=(2,), dtype=dtypes.int32), + inferred_shape_value=[2, 3]) + expected_repr = ( + "") + self.assertEqual(expected_repr, str(kt)) + self.assertEqual(expected_repr, repr(kt)) + + kt = keras_tensor.KerasTensor( + type_spec=sparse_tensor.SparseTensorSpec( + shape=(1, 2, 3), dtype=dtypes.float32)) + expected_repr = ( + "") + self.assertEqual(expected_repr, str(kt)) + self.assertEqual(expected_repr, repr(kt)) + + with testing_utils.use_keras_tensors_scope(True): + inp = layers.Input(shape=(3, 5)) + kt = layers.Dense(10)(inp) + expected_repr = ( + "") + self.assertEqual(expected_repr, str(kt)) + self.assertEqual(expected_repr, repr(kt)) + + kt = array_ops.reshape(kt, shape=(3, 5, 2)) + expected_repr = ("") + self.assertEqual(expected_repr, str(kt)) + self.assertEqual(expected_repr, repr(kt)) + + kts = array_ops.unstack(kt) + for i in range(3): + expected_repr = ("" % i) + self.assertEqual(expected_repr, str(kts[i])) + self.assertEqual(expected_repr, repr(kts[i])) + +if __name__ == "__main__": + ops.enable_eager_execution() + tensor_shape.enable_v2_tensorshape() + test.main() diff --git a/tensorflow/python/keras/layers/einsum_dense.py b/tensorflow/python/keras/layers/einsum_dense.py index 7b5bd085703..f8f2e01058d 100644 --- a/tensorflow/python/keras/layers/einsum_dense.py +++ b/tensorflow/python/keras/layers/einsum_dense.py @@ -73,7 +73,7 @@ class EinsumDense(Layer): >>> input_tensor = tf.keras.Input(shape=[32]) >>> output_tensor = layer(input_tensor) >>> output_tensor - + <... shape=(None, 64) dtype=...> **Applying a dense layer to a sequence** @@ -89,7 +89,7 @@ class EinsumDense(Layer): >>> input_tensor = tf.keras.Input(shape=[32, 128]) >>> output_tensor = layer(input_tensor) >>> output_tensor - + <... shape=(None, 32, 64) dtype=...> **Applying a dense layer to a sequence using ellipses** @@ -106,7 +106,7 @@ class EinsumDense(Layer): >>> input_tensor = tf.keras.Input(shape=[32, 128]) >>> output_tensor = layer(input_tensor) >>> output_tensor - + <... shape=(None, 32, 64) dtype=...> """ def __init__(self, diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index cc41f5f1a3c..b8711a444a8 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -591,7 +591,7 @@ def shape_v2(input, out_type=dtypes.int32, name=None): >>> a = tf.keras.layers.Input((None, 10)) >>> tf.shape(a) - + <... shape=(3,) dtype=int32...> In these cases, using `tf.Tensor.shape` will return more informative results.