PR #30694: Fail when np.array and len are called on Tensors.
Imported from GitHub PR #30694 When np.array is called on a symbolic Tensor the result is a `shape=()` numpy array of objects which is basically never what was intended. Similarly, length is not defined and can lead to rather cryptic error messages. This surfaced through #28619; however the fact is that accidentally passing a Tensor rather than and EagerTensor to a package in the NumPy ecosystem can result in very cryptic error messages. This PR simply makes Tensors fail with clear error messages in such cases. (Similar to the treatment of `__iter__`) Copybara import of the project: - 134b5b3155a839824616626b1f4dc101512cc89f Fail when np.array and len are called on Tensors. by Taylor Robie <taylorrobie@google.com> - 1daa5d714de83cf6f1bd37a6fd83deec5347c51a update autograph test by Taylor Robie <taylorrobie@google.com> - e01a1ec7f0ddaa274b5fbc49ef62fe860eb04268 fix tests and remove try-catch on self.name by Taylor Robie <taylorrobie@google.com> - d32883fe8b7040d7d8f72f00a46e9ee58fef72f4 switch __len__ to an AttributeError by Taylor Robie <taylorrobie@google.com> - c43597adb5e2d873339ca9b9059a1e23092adf51 better test graph isolation by Taylor Robie <taylorrobie@google.com> - 6e69d651061262b0f4ab9a39ae79d4461ea29916 minor adjustments to tensor checking logic by Taylor Robie <taylorrobie@google.com> - 112da8fb8bbaf9a6e5ae1545334236845f3e796c address reviewer comments by Taylor Robie <taylorrobie@google.com> - 3cadc8fa89de65a5f3af796e5e84fd37991772dc Merge 112da8fb8bbaf9a6e5ae1545334236845f3e796c into 699e9... by Taylor Robie <taylorrobie@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/tensorflow/tensorflow/pull/30694 from robieta:fix/raise_array_error 112da8fb8bbaf9a6e5ae1545334236845f3e796c PiperOrigin-RevId: 260994937
This commit is contained in:
parent
2cc96948e4
commit
ebca088d52
@ -510,8 +510,7 @@ class ApiTest(test.TestCase):
|
|||||||
opts = converter.ConversionOptions(internal_convert_user_code=False)
|
opts = converter.ConversionOptions(internal_convert_user_code=False)
|
||||||
|
|
||||||
# f should not be converted, causing len to error out.
|
# f should not be converted, causing len to error out.
|
||||||
with self.assertRaisesRegexp(Exception,
|
with self.assertRaisesRegexp(Exception, 'len is not well defined'):
|
||||||
'object of type \'Tensor\' has no len()'):
|
|
||||||
api.converted_call(f, opts, (constant_op.constant([0]),), {})
|
api.converted_call(f, opts, (constant_op.constant([0]),), {})
|
||||||
|
|
||||||
# len on the other hand should work fine.
|
# len on the other hand should work fine.
|
||||||
|
@ -728,6 +728,15 @@ class Tensor(_TensorLike):
|
|||||||
# with ndarrays.
|
# with ndarrays.
|
||||||
__array_priority__ = 100
|
__array_priority__ = 100
|
||||||
|
|
||||||
|
def __array__(self):
|
||||||
|
raise NotImplementedError("Cannot convert a symbolic Tensor ({}) to a numpy"
|
||||||
|
" array.".format(self.name))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
raise TypeError("len is not well defined for symbolic Tensors. ({}) "
|
||||||
|
"Please call `x.shape` rather than `len(x)` for "
|
||||||
|
"shape information.".format(self.name))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _override_operator(operator, func):
|
def _override_operator(operator, func):
|
||||||
_override_helper(Tensor, operator, func)
|
_override_helper(Tensor, operator, func)
|
||||||
|
@ -188,6 +188,25 @@ class TensorAndShapeTest(test_util.TensorFlowTestCase):
|
|||||||
r"\(op: 'Add(V2)?'\) with input shapes: \[1,2,3\], \[4,5,6\]."):
|
r"\(op: 'Add(V2)?'\) with input shapes: \[1,2,3\], \[4,5,6\]."):
|
||||||
_ = a + b
|
_ = a + b
|
||||||
|
|
||||||
|
def testNumpyArray(self):
|
||||||
|
with ops.Graph().as_default():
|
||||||
|
x = array_ops.ones((3, 4), name="test_ones")
|
||||||
|
|
||||||
|
with self.assertRaisesRegexp(NotImplementedError,
|
||||||
|
r"Cannot convert a symbolic.+test_ones"):
|
||||||
|
np.array(x)
|
||||||
|
|
||||||
|
with self.assertRaisesRegexp(TypeError, "not well defined.+test_ones"):
|
||||||
|
len(x)
|
||||||
|
|
||||||
|
# EagerTensors should still behave as numpy arrays.
|
||||||
|
with context.eager_mode():
|
||||||
|
x = array_ops.ones((3, 4))
|
||||||
|
|
||||||
|
self.assertAllEqual(x, np.ones((3, 4)))
|
||||||
|
self.assertAllEqual(np.array(x), np.ones((3, 4)))
|
||||||
|
self.assertEqual(len(x), 3)
|
||||||
|
|
||||||
|
|
||||||
class IndexedSlicesTest(test_util.TensorFlowTestCase):
|
class IndexedSlicesTest(test_util.TensorFlowTestCase):
|
||||||
|
|
||||||
|
@ -333,6 +333,11 @@ def _AssertCompatible(values, dtype):
|
|||||||
|
|
||||||
def _is_array_like(obj): # pylint: disable=invalid-name
|
def _is_array_like(obj): # pylint: disable=invalid-name
|
||||||
"""Check if a given object is array-like."""
|
"""Check if a given object is array-like."""
|
||||||
|
if isinstance(obj, ops.Tensor) and not isinstance(obj, ops._EagerTensorBase): # pylint: disable=protected-access
|
||||||
|
# Tensor implements __array__ only so it can inform the user that it is not
|
||||||
|
# a valid array.
|
||||||
|
return False
|
||||||
|
|
||||||
# TODO(slebedev): an object could also implement C-level array interface.
|
# TODO(slebedev): an object could also implement C-level array interface.
|
||||||
if (callable(getattr(obj, "__array__", None)) or
|
if (callable(getattr(obj, "__array__", None)) or
|
||||||
isinstance(getattr(obj, "__array_interface__", None), dict)):
|
isinstance(getattr(obj, "__array_interface__", None), dict)):
|
||||||
|
@ -475,9 +475,14 @@ def standardize_input_data(data,
|
|||||||
Raises:
|
Raises:
|
||||||
ValueError: in case of improperly formatted user-provided data.
|
ValueError: in case of improperly formatted user-provided data.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
data_len = len(data)
|
||||||
|
except TypeError:
|
||||||
|
# For instance if data is `None` or a symbolic Tensor.
|
||||||
|
data_len = None
|
||||||
|
|
||||||
if not names:
|
if not names:
|
||||||
if (data is not None and hasattr(data, '__len__') and len(data) and
|
if data_len and not isinstance(data, dict):
|
||||||
not isinstance(data, dict)):
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Error when checking model ' + exception_prefix + ': '
|
'Error when checking model ' + exception_prefix + ': '
|
||||||
'expected no data, but got:', data)
|
'expected no data, but got:', data)
|
||||||
|
@ -2602,10 +2602,10 @@ def conv_transpose(input, # pylint: disable=redefined-builtin
|
|||||||
"""
|
"""
|
||||||
with ops.name_scope(name, "conv_transpose",
|
with ops.name_scope(name, "conv_transpose",
|
||||||
[input, filter, output_shape]) as name:
|
[input, filter, output_shape]) as name:
|
||||||
if isinstance(output_shape, collections.Sized):
|
if tensor_util.is_tensor(output_shape):
|
||||||
n = len(output_shape) - 2
|
|
||||||
elif isinstance(output_shape, ops.Tensor):
|
|
||||||
n = output_shape.shape[0] - 2
|
n = output_shape.shape[0] - 2
|
||||||
|
elif isinstance(output_shape, collections.Sized):
|
||||||
|
n = len(output_shape) - 2
|
||||||
else:
|
else:
|
||||||
raise ValueError("output_shape must be a tensor or sized collection.")
|
raise ValueError("output_shape must be a tensor or sized collection.")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user