xref: /aosp_15_r20/external/tensorflow/tensorflow/python/ops/sparse_ops_test.py (revision b6fb3261f9314811a0f4371741dbb8839866f948)
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 sparse ops."""
16
17from absl.testing import parameterized
18import numpy as np
19
20from tensorflow.python.eager import context
21from tensorflow.python.framework import constant_op
22from tensorflow.python.framework import dtypes
23from tensorflow.python.framework import errors
24from tensorflow.python.framework import ops
25from tensorflow.python.framework import sparse_tensor
26from tensorflow.python.framework import test_util
27# Need array_grad to register gradient for Identity.
28from tensorflow.python.ops import array_grad  # pylint: disable=unused-import
29from tensorflow.python.ops import array_ops
30from tensorflow.python.ops import gen_sparse_ops
31from tensorflow.python.ops import gradient_checker_v2 as gradient_checker
32from tensorflow.python.ops import math_ops
33# Need sparse_grad to register gradient for SparseToDense.
34from tensorflow.python.ops import sparse_grad  # pylint: disable=unused-import
35from tensorflow.python.ops import sparse_ops
36from tensorflow.python.platform import googletest
37
38
39@test_util.run_all_in_graph_and_eager_modes
40class SparseOpsTest(test_util.TensorFlowTestCase, parameterized.TestCase):
41
42  def testSparseEye(self):
43    def test_one(n, m, as_tensors):
44      expected = np.eye(n, m)
45      if as_tensors:
46        m = constant_op.constant(m)
47        n = constant_op.constant(n)
48      s = sparse_ops.sparse_eye(n, m)
49      d = sparse_ops.sparse_to_dense(s.indices, s.dense_shape, s.values)
50      self.assertAllEqual(self.evaluate(d), expected)
51
52    for n in range(2, 10, 2):
53      for m in range(2, 10, 2):
54        # Test with n and m as both constants and tensors.
55        test_one(n, m, True)
56        test_one(n, m, False)
57
58  def testDenseFromConstantToSparse(self):
59    expected_constant = np.reshape(np.arange(24, dtype=np.int64), (3, 4, 2))
60    tensor = constant_op.constant(expected_constant)
61    sparse = sparse_ops.from_dense(tensor)
62    dense = sparse_ops.sparse_to_dense(sparse.indices, sparse.dense_shape,
63                                       sparse.values)
64    constant = self.evaluate(dense)
65    self.assertAllEqual(expected_constant, constant)
66
67  def testTransposePreservesShape(self):
68    with ops.Graph().as_default():
69      t = sparse_tensor.SparseTensor(indices=[[0, 0]],
70                                     values=[0.],
71                                     dense_shape=[3, 4])
72      self.assertTrue(t.shape.is_fully_defined)
73      transposed = sparse_ops.sparse_transpose(t)
74      self.assertAllEqual(transposed.shape, [4, 3])
75
76  def testSparseExpandDims(self):
77    for rank in range(1, 4):
78      # Create a dummy input. When rank=3, shape=[2, 4, 6].
79      shape = np.arange(1, rank + 1) * 2
80      before = np.arange(np.prod(shape)).reshape(shape)
81
82      # Make entries sparse.
83      before *= np.random.binomial(1, .2, before.shape)
84      dense_shape = before.shape
85      indices = np.array(np.where(before)).T
86      values = before[before != 0]
87
88      # Try every possible valid value of axis.
89      for axis in range(-rank - 1, rank):
90        expected_after = np.expand_dims(before, axis)
91
92        for axis_as_tensor in [False, True]:
93          dense_shape_t = constant_op.constant(dense_shape, dtype=dtypes.int64)
94          indices_t = constant_op.constant(indices)
95          values_t = constant_op.constant(values)
96          before_t = sparse_tensor.SparseTensor(
97              indices=indices_t, values=values_t, dense_shape=dense_shape_t)
98
99          if axis_as_tensor:
100            axis = constant_op.constant(axis)
101
102          s = sparse_ops.sparse_expand_dims(before_t, axis)
103          d = sparse_ops.sparse_to_dense(s.indices, s.dense_shape, s.values)
104          self.assertAllEqual(self.evaluate(d), expected_after)
105
106  @parameterized.parameters([
107      (math_ops.abs, [1.0, -1.0, 3.0, -4.0], [1.0, 1.0, 3.0, 4.0]),
108      (math_ops.negative, [1.0, -1.0, 3.0, -4.0], [-1.0, 1.0, -3.0, 4.0]),
109      (math_ops.sign, [3.0, -2.0, 0.0, -4.0], [1.0, -1.0, 0.0, -1.0]),
110      (math_ops.square, [1.0, -1.0, 3.0, -4.0], [1.0, 1.0, 9.0, 16.0]),
111  ])
112  def testUnarySparseDispatch(self, op, values, expected):
113    st = sparse_tensor.SparseTensor(
114        indices=[[0, 0], [0, 1], [2, 0], [2, 4]],
115        values=values,
116        dense_shape=[3, 6])
117    result = op(st)
118    result_value = self.evaluate(result)
119    self.assertAllEqual(result_value.indices, st.indices)
120    self.assertAllEqual(result_value.values, expected)
121    self.assertAllEqual(result_value.dense_shape, st.dense_shape)
122
123  def testSparseToDenseGradient(self):
124
125    def f(sparse_values, default_value):
126      st = sparse_tensor.SparseTensor(
127          indices=[[0, 3, 6], [1, 4, 7], [2, 5, 8]],
128          values=sparse_values,
129          dense_shape=[3, 6, 9])
130      return sparse_ops.sparse_tensor_to_dense(st, default_value)
131
132    grads = gradient_checker.compute_gradient(
133        f, [constant_op.constant([1.0, 2.0, 3.0]),
134            constant_op.constant(0.0)])
135    epsilon = 1e-4
136    self.assertLess(gradient_checker.max_error(*grads), epsilon)
137
138  def testSparseTensorToDenseString(self):
139    sp = sparse_tensor.SparseTensor(
140        indices=[[0, 0], [1, 2]], values=['a', 'b'], dense_shape=[2, 3])
141    dense = sparse_ops.sparse_tensor_to_dense(sp)
142    expected_dense = [[b'a', b'', b''], [b'', b'', b'b']]
143    result_dense = self.evaluate(dense)
144    self.assertAllEqual(expected_dense, result_dense)
145
146  def testDenseSparseTensorMatMul(self):
147
148    np.random.seed(42)
149    dense_numpy_array = np.random.rand(3, 3)
150    independent_dense_tf = constant_op.constant(
151        dense_numpy_array, dtype='float32')
152
153    sp = sparse_tensor.SparseTensor(
154        indices=[[0, 0], [1, 2]], values=[4., 8.], dense_shape=[3, 3])
155    dense_of_sparse = sparse_ops.sparse_to_dense(sp.indices, sp.shape,
156                                                 sp.values)
157
158    result = sparse_ops.sparse_tensor_dense_matmul(
159        independent_dense_tf, sp, adjoint_a=False, adjoint_b=False)
160    expected = math_ops.matmul(independent_dense_tf, dense_of_sparse)
161    self.assertAllEqual(expected, result)
162
163    result = sparse_ops.sparse_tensor_dense_matmul(
164        independent_dense_tf, sp, adjoint_a=False, adjoint_b=True)
165    expected = math_ops.matmul(independent_dense_tf,
166                               array_ops.transpose(dense_of_sparse))
167    self.assertAllEqual(expected, result)
168
169    result = sparse_ops.sparse_tensor_dense_matmul(
170        independent_dense_tf, sp, adjoint_a=True, adjoint_b=False)
171    expected = math_ops.matmul(
172        array_ops.transpose(independent_dense_tf), dense_of_sparse)
173    self.assertAllEqual(expected, result)
174
175    result = sparse_ops.sparse_tensor_dense_matmul(
176        independent_dense_tf, sp, adjoint_a=True, adjoint_b=True)
177    expected = math_ops.matmul(
178        array_ops.transpose(independent_dense_tf),
179        array_ops.transpose(dense_of_sparse))
180    self.assertAllEqual(expected, result)
181
182  def testMapValues(self):
183    # supplying no sparse tensor should result in ValueError
184    with self.assertRaises(ValueError):
185      sparse_ops.map_values(math_ops.abs, 0.0)
186
187    sp = sparse_ops.from_dense([[0.0, 1.0, 0.0], [-2.0, 1.0, 0.0]])
188
189    # helper function to check equality of sparse tensor
190    def assert_sparse_equal(expected, result):
191      self.assertAllEqual(expected.values, result.values, msg='Values differ')
192      self.assertAllEqual(
193          expected.indices, result.indices, msg='Indices differ')
194      self.assertAllEqual(
195          expected.dense_shape, result.dense_shape, msg='Shapes differ')
196
197    # check for a single sparse argument
198    expected = sparse_ops.from_dense([[0.0, 1.0, 0.0], [2.0, 1.0, 0.0]])
199    result = sparse_ops.map_values(math_ops.abs, sp)
200    assert_sparse_equal(expected, result)
201
202    # check correct passing of keyword argument, and handling of two sparse
203    # arguments at the same time
204    def mapping(arg1, arg2, kwarg):
205      self.assertEqual(kwarg, 'kwarg')
206      return arg1 + arg2
207
208    result = sparse_ops.map_values(mapping, sp, sp, kwarg='kwarg')
209    expected = sparse_ops.from_dense([[0.0, 2.0, 0.0], [-4.0, 2.0, 0.0]])
210    assert_sparse_equal(expected, result)
211
212    # check that index mismatches are correctly detected even if the `value`s
213    # have compatible shape
214    sp_incomp = sparse_ops.from_dense([[0.0, 1.0, 0.0], [-2.0, 0.0, 1.0]])
215    with self.assertRaises((errors.InvalidArgumentError, ValueError)):
216      result = sparse_ops.map_values(mapping, sp, sp_incomp, kwarg='kwarg')
217      self.evaluate(result)
218
219    # check that shape mismatches are correctly detected
220    sp_incomp = sparse_tensor.SparseTensor(sp.indices, sp.values, (25, 25))
221    with self.assertRaises((errors.InvalidArgumentError, ValueError)):
222      result = sparse_ops.map_values(mapping, sp, sp_incomp, kwarg='kwarg')
223      self.evaluate(result)
224
225  def testConstantStringToSparse(self):
226    # Test case for GitHub issue 40633.
227    tensor = constant_op.constant(list('ababa'))
228    sparse = sparse_ops.from_dense(tensor)
229    result = self.evaluate(sparse)
230    self.assertAllEqual([[0], [1], [2], [3], [4]], result.indices)
231    self.assertAllEqual([b'a', b'b', b'a', b'b', b'a'], result.values)
232    self.assertAllEqual([5], result.dense_shape)
233
234
235@test_util.run_all_in_graph_and_eager_modes
236class RawOpsTest(test_util.TensorFlowTestCase, parameterized.TestCase):
237
238  def testSparseFillEmptyRowsGrad(self):
239    reverse_index_map = [2, 1]
240    grad_values = [0, 1, 2, 3]
241    d_values, d_default_value = self.evaluate(
242        gen_sparse_ops.SparseFillEmptyRowsGrad(
243            reverse_index_map=reverse_index_map, grad_values=grad_values))
244    self.assertAllEqual([2, 1], d_values)
245    self.assertEqual(3, d_default_value)
246
247  def testSparseFillEmptyRowsGradNegativeIndexMapValue(self):
248    reverse_index_map = [2, -1]
249    grad_values = [0, 1, 2, 3]
250    with self.assertRaisesRegex(
251        errors.InvalidArgumentError,
252        r'Elements in reverse index must be in \[0, 4\)'):
253      self.evaluate(
254          gen_sparse_ops.SparseFillEmptyRowsGrad(
255              reverse_index_map=reverse_index_map, grad_values=grad_values))
256
257  def testSparseFillEmptyRowsGradLargeIndexMapValue(self):
258    reverse_index_map = [2, 10]
259    grad_values = [0, 1, 2, 3]
260    with self.assertRaisesRegex(
261        errors.InvalidArgumentError,
262        r'Elements in reverse index must be in \[0, 4\)'):
263      self.evaluate(
264          gen_sparse_ops.SparseFillEmptyRowsGrad(
265              reverse_index_map=reverse_index_map, grad_values=grad_values))
266
267  def testSparseFillEmptyRowsGradMatrix(self):
268    reverse_index_map = [0, 1]
269    grad_values = [[0, 1], [2, 3]]
270    # Note: Eager mode and graph mode throw different errors here. Graph mode
271    # will fail with a ValueError from the shape checking logic, while Eager
272    # will fail with an InvalidArgumentError from the kernel itself.
273    if context.executing_eagerly():
274      with self.assertRaisesRegex(errors.InvalidArgumentError,
275                                  r'grad_values must be a vector'):
276        self.evaluate(
277            gen_sparse_ops.SparseFillEmptyRowsGrad(
278                reverse_index_map=reverse_index_map, grad_values=grad_values))
279    else:
280      with self.assertRaisesRegex(ValueError,
281                                  r'Shape must be rank 1 but is rank 2'):
282        self.evaluate(
283            gen_sparse_ops.SparseFillEmptyRowsGrad(
284                reverse_index_map=reverse_index_map, grad_values=grad_values))
285
286  def testSparseConcatStaticShape(self):
287    if context.executing_eagerly():
288      self.skipTest('sparse_spaceholder is only available in graph context.')
289    input_a = array_ops.sparse_placeholder(dtypes.float32, shape=(2, 1))
290    input_b = array_ops.sparse_placeholder(dtypes.float32, shape=(2, 2))
291
292    result = sparse_ops.sparse_concat_v2(axis=1, sp_inputs=[input_a, input_b])
293    self.assertEqual(result.shape, [2, 3])
294
295
296if __name__ == '__main__':
297  googletest.main()
298