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