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)
|
||||
|
||||
# f should not be converted, causing len to error out.
|
||||
with self.assertRaisesRegexp(Exception,
|
||||
'object of type \'Tensor\' has no len()'):
|
||||
with self.assertRaisesRegexp(Exception, 'len is not well defined'):
|
||||
api.converted_call(f, opts, (constant_op.constant([0]),), {})
|
||||
|
||||
# len on the other hand should work fine.
|
||||
|
@ -728,6 +728,15 @@ class Tensor(_TensorLike):
|
||||
# with ndarrays.
|
||||
__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
|
||||
def _override_operator(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\]."):
|
||||
_ = 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):
|
||||
|
||||
|
@ -333,6 +333,11 @@ def _AssertCompatible(values, dtype):
|
||||
|
||||
def _is_array_like(obj): # pylint: disable=invalid-name
|
||||
"""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.
|
||||
if (callable(getattr(obj, "__array__", None)) or
|
||||
isinstance(getattr(obj, "__array_interface__", None), dict)):
|
||||
|
@ -475,9 +475,14 @@ def standardize_input_data(data,
|
||||
Raises:
|
||||
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 (data is not None and hasattr(data, '__len__') and len(data) and
|
||||
not isinstance(data, dict)):
|
||||
if data_len and not isinstance(data, dict):
|
||||
raise ValueError(
|
||||
'Error when checking model ' + exception_prefix + ': '
|
||||
'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",
|
||||
[input, filter, output_shape]) as name:
|
||||
if isinstance(output_shape, collections.Sized):
|
||||
n = len(output_shape) - 2
|
||||
elif isinstance(output_shape, ops.Tensor):
|
||||
if tensor_util.is_tensor(output_shape):
|
||||
n = output_shape.shape[0] - 2
|
||||
elif isinstance(output_shape, collections.Sized):
|
||||
n = len(output_shape) - 2
|
||||
else:
|
||||
raise ValueError("output_shape must be a tensor or sized collection.")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user