1# Copyright 2015 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"""Functional tests for Stack and ParallelStack Ops.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21import numpy as np 22 23from tensorflow.python.framework import constant_op 24from tensorflow.python.framework import dtypes 25from tensorflow.python.framework import errors_impl 26from tensorflow.python.framework import ops 27from tensorflow.python.framework import test_util 28from tensorflow.python.ops import array_ops 29from tensorflow.python.ops import gradient_checker 30from tensorflow.python.ops import variables 31from tensorflow.python.platform import test 32 33 34def np_split_squeeze(array, axis): 35 axis_len = array.shape[axis] 36 return [ 37 np.squeeze( 38 arr, axis=(axis,)) for arr in np.split( 39 array, axis_len, axis=axis) 40 ] 41 42 43class StackOpTest(test.TestCase): 44 45 def randn(self, shape, dtype): 46 data = np.random.randn(*shape) 47 if dtype == np.bool: 48 return data < 0 # Naive casting yields True with P(1)! 49 else: 50 return data.astype(dtype) 51 52 @test_util.run_deprecated_v1 53 def testSimple(self): 54 np.random.seed(7) 55 with self.session(): 56 for shape in (2,), (3,), (2, 3), (3, 2), (8, 2, 10): 57 rank = len(shape) 58 for axis in range(-rank, rank): 59 for dtype in [np.bool, np.float32, np.int32, np.int64]: 60 data = self.randn(shape, dtype) 61 xs = np_split_squeeze(data, axis) 62 # Stack back into a single tensorflow tensor 63 with self.subTest(shape=shape, axis=axis, dtype=dtype): 64 c = array_ops.stack(xs, axis=axis) 65 self.assertAllEqual(c, data) 66 67 def testSimpleParallelCPU(self): 68 # tf.parallel_stack is only supported in graph mode. 69 with ops.Graph().as_default(): 70 np.random.seed(7) 71 with test_util.device(use_gpu=False): 72 for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2), (100, 24, 24, 3): 73 with self.subTest(shape=shape): 74 data = self.randn(shape, np.float32) 75 xs = list(map(constant_op.constant, data)) 76 c = array_ops.parallel_stack(xs) 77 self.assertAllEqual(c, data) 78 79 def testSimpleParallelGPU(self): 80 # tf.parallel_stack is only supported in graph mode. 81 with ops.Graph().as_default(): 82 with test_util.device(use_gpu=True): 83 for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2), (100, 24, 24, 3): 84 with self.subTest(shape=shape): 85 data = self.randn(shape, np.float32) 86 xs = list(map(constant_op.constant, data)) 87 c = array_ops.parallel_stack(xs) 88 self.assertAllEqual(c, data) 89 90 @test_util.run_deprecated_v1 91 def testConst(self): 92 np.random.seed(7) 93 with self.session(): 94 # Verify that shape induction works with shapes produced via const stack 95 a = constant_op.constant([1, 2, 3, 4, 5, 6]) 96 b = array_ops.reshape(a, array_ops.stack([2, 3])) 97 self.assertAllEqual(b.get_shape(), [2, 3]) 98 99 # Check on a variety of shapes and types 100 for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2), (8, 2, 10): 101 for dtype in [np.bool, np.float32, np.int16, np.int32, np.int64]: 102 with self.subTest(shape=shape, dtype=dtype): 103 data = self.randn(shape, dtype) 104 # Stack back into a single tensorflow tensor directly using np array 105 c = array_ops.stack(data) 106 # This is implemented via a Const: 107 self.assertEqual(c.op.type, "Const") 108 self.assertAllEqual(c, data) 109 110 # Python lists also work for 1-D case: 111 if len(shape) == 1: 112 data_list = list(data) 113 cl = array_ops.stack(data_list) 114 self.assertEqual(cl.op.type, "Const") 115 self.assertAllEqual(cl, data) 116 117 def testConstParallelCPU(self): 118 # tf.parallel_stack is only supported in graph mode. 119 with ops.Graph().as_default(): 120 np.random.seed(7) 121 with test_util.device(use_gpu=False): 122 for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2), (8, 2, 10): 123 with self.subTest(shape=shape): 124 data = self.randn(shape, np.float32) 125 if len(shape) == 1: 126 data_list = list(data) 127 cl = array_ops.parallel_stack(data_list) 128 self.assertAllEqual(cl, data) 129 130 data = self.randn(shape, np.float32) 131 c = array_ops.parallel_stack(data) 132 self.assertAllEqual(c, data) 133 134 def testConstParallelGPU(self): 135 # tf.parallel_stack is only supported in graph mode. 136 with ops.Graph().as_default(): 137 np.random.seed(7) 138 with test_util.device(use_gpu=True): 139 for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): 140 with self.subTest(shape=shape): 141 data = self.randn(shape, np.float32) 142 if len(shape) == 1: 143 data_list = list(data) 144 cl = array_ops.parallel_stack(data_list) 145 self.assertAllEqual(cl, data) 146 147 data = self.randn(shape, np.float32) 148 c = array_ops.parallel_stack(data) 149 self.assertAllEqual(c, data) 150 151 @test_util.run_deprecated_v1 152 def testGradientsAxis0(self): 153 np.random.seed(7) 154 for shape in (2,), (3,), (2, 3), (3, 2), (8, 2, 10): 155 data = np.random.randn(*shape) 156 shapes = [shape[1:]] * shape[0] 157 with self.subTest(shape=shape): 158 with self.cached_session(): 159 # TODO(irving): Remove list() once we handle maps correctly 160 xs = list(map(constant_op.constant, data)) 161 c = array_ops.stack(xs) 162 err = gradient_checker.compute_gradient_error(xs, shapes, c, shape) 163 self.assertLess(err, 1e-6) 164 165 @test_util.run_deprecated_v1 166 def testGradientsAxis1(self): 167 np.random.seed(7) 168 for shape in (2, 3), (3, 2), (8, 2, 10): 169 data = np.random.randn(*shape) 170 shapes = [shape[1:]] * shape[0] 171 out_shape = list(shape[1:]) 172 out_shape.insert(1, shape[0]) 173 with self.subTest(shape=shape): 174 with self.cached_session(): 175 # TODO(irving): Remove list() once we handle maps correctly 176 xs = list(map(constant_op.constant, data)) 177 c = array_ops.stack(xs, axis=1) 178 err = gradient_checker.compute_gradient_error(xs, shapes, c, 179 out_shape) 180 self.assertLess(err, 1e-6) 181 182 def testZeroSizeCPU(self): 183 # tf.parallel_stack is only supported in graph mode. 184 with ops.Graph().as_default(): 185 # Verify that stack doesn't crash for zero size inputs 186 with test_util.device(use_gpu=False): 187 for shape in (0,), (3, 0), (0, 3): 188 with self.subTest(shape=shape): 189 x = np.zeros((2,) + shape).astype(np.int32) 190 p = self.evaluate(array_ops.stack(list(x))) 191 self.assertAllEqual(p, x) 192 193 p = self.evaluate(array_ops.parallel_stack(list(x))) 194 self.assertAllEqual(p, x) 195 196 def testZeroSizeGPU(self): 197 # tf.parallel_stack is only supported in graph mode. 198 with ops.Graph().as_default(): 199 # Verify that stack doesn't crash for zero size inputs 200 with test_util.device(use_gpu=True): 201 for shape in (0,), (3, 0), (0, 3): 202 with self.subTest(shape=shape): 203 x = np.zeros((2,) + shape).astype(np.int32) 204 p = self.evaluate(array_ops.stack(list(x))) 205 self.assertAllEqual(p, x) 206 207 p = self.evaluate(array_ops.parallel_stack(list(x))) 208 self.assertAllEqual(p, x) 209 210 def testAxis0DefaultCPU(self): 211 # tf.parallel_stack is only supported in graph mode. 212 with ops.Graph().as_default(): 213 with test_util.device(use_gpu=False): 214 t = [constant_op.constant([1, 2, 3]), constant_op.constant([4, 5, 6])] 215 stacked = self.evaluate(array_ops.stack(t)) 216 parallel_stacked = self.evaluate(array_ops.parallel_stack(t)) 217 218 expected = np.array([[1, 2, 3], [4, 5, 6]]) 219 self.assertAllEqual(stacked, expected) 220 self.assertAllEqual(parallel_stacked, expected) 221 222 def testAxis0DefaultGPU(self): 223 # tf.parallel_stack is only supported in graph mode. 224 with ops.Graph().as_default(): 225 with test_util.device(use_gpu=True): 226 t = [constant_op.constant([1, 2, 3]), constant_op.constant([4, 5, 6])] 227 stacked = self.evaluate(array_ops.stack(t)) 228 parallel_stacked = self.evaluate(array_ops.parallel_stack(t)) 229 230 expected = np.array([[1, 2, 3], [4, 5, 6]]) 231 self.assertAllEqual(stacked, expected) 232 self.assertAllEqual(parallel_stacked, expected) 233 234 def testAgainstNumpy(self): 235 # For 1 to 5 dimensions. 236 for shape in (3,), (2, 2, 3), (4, 1, 2, 2), (8, 2, 10): 237 rank = len(shape) 238 expected = self.randn(shape, np.float32) 239 for dtype in [np.bool, np.float32, np.int32, np.int64]: 240 # For all the possible axis to split it, including negative indices. 241 for axis in range(-rank, rank): 242 test_arrays = np_split_squeeze(expected, axis) 243 244 with self.cached_session(): 245 with self.subTest(shape=shape, dtype=dtype, axis=axis): 246 actual_pack = array_ops.stack(test_arrays, axis=axis) 247 self.assertEqual(expected.shape, actual_pack.get_shape()) 248 actual_pack = self.evaluate(actual_pack) 249 250 actual_stack = array_ops.stack(test_arrays, axis=axis) 251 self.assertEqual(expected.shape, actual_stack.get_shape()) 252 actual_stack = self.evaluate(actual_stack) 253 254 self.assertNDArrayNear(expected, actual_stack, 1e-6) 255 256 def testDimOutOfRange(self): 257 t = [constant_op.constant([1, 2, 3]), constant_op.constant([4, 5, 6])] 258 with self.assertRaisesRegex(ValueError, r"axis = 2 not in \[-2, 2\)"): 259 array_ops.stack(t, axis=2) 260 261 def testDimOutOfNegativeRange(self): 262 t = [constant_op.constant([1, 2, 3]), constant_op.constant([4, 5, 6])] 263 with self.assertRaisesRegex(ValueError, r"axis = -3 not in \[-2, 2\)"): 264 array_ops.stack(t, axis=-3) 265 266 def testComplex(self): 267 np.random.seed(7) 268 with self.session(): 269 for shape in (2,), (3,), (2, 3), (3, 2), (8, 2, 10): 270 for dtype in [np.complex64, np.complex128]: 271 with self.subTest(shape=shape, dtype=dtype): 272 data = self.randn(shape, dtype) 273 xs = list(map(constant_op.constant, data)) 274 c = array_ops.stack(xs) 275 self.assertAllEqual(self.evaluate(c), data) 276 277 278class AutomaticStackingTest(test.TestCase): 279 280 @test_util.run_deprecated_v1 281 def testSimple(self): 282 with self.session(): 283 self.assertAllEqual( 284 [1, 0, 2], 285 ops.convert_to_tensor([1, constant_op.constant(0), 2]).eval()) 286 self.assertAllEqual([[0, 0, 0], [0, 1, 0], [0, 0, 0]], 287 ops.convert_to_tensor( 288 [[0, 0, 0], [0, constant_op.constant(1), 0], 289 [0, 0, 0]]).eval()) 290 self.assertAllEqual([[0, 0, 0], [0, 1, 0], [0, 0, 0]], 291 ops.convert_to_tensor( 292 [[0, 0, 0], constant_op.constant([0, 1, 0]), 293 [0, 0, 0]]).eval()) 294 self.assertAllEqual([[0, 0, 0], [0, 1, 0], [0, 0, 0]], 295 ops.convert_to_tensor([ 296 constant_op.constant([0, 0, 0]), 297 constant_op.constant([0, 1, 0]), 298 constant_op.constant([0, 0, 0]) 299 ]).eval()) 300 301 def testWithNDArray(self): 302 with self.session(): 303 result = ops.convert_to_tensor([[[0., 0.], 304 constant_op.constant([1., 1.])], 305 np.array( 306 [[2., 2.], [3., 3.]], 307 dtype=np.float32)]) 308 self.assertAllEqual([[[0., 0.], [1., 1.]], [[2., 2.], [3., 3.]]], 309 self.evaluate(result)) 310 311 @test_util.run_deprecated_v1 312 def testVariable(self): 313 with self.session(): 314 v = variables.Variable(17) 315 result = ops.convert_to_tensor([[0, 0, 0], [0, v, 0], [0, 0, 0]]) 316 self.evaluate(v.initializer) 317 self.assertAllEqual([[0, 0, 0], [0, 17, 0], [0, 0, 0]], 318 self.evaluate(result)) 319 320 v.assign(38).op.run() 321 self.assertAllEqual([[0, 0, 0], [0, 38, 0], [0, 0, 0]], 322 self.evaluate(result)) 323 324 def testDtype(self): 325 t_0 = ops.convert_to_tensor([[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]) 326 self.assertEqual(dtypes.float32, t_0.dtype) 327 328 t_1 = ops.convert_to_tensor([[0., 0., 0.], constant_op.constant( 329 [0., 0., 0.], dtype=dtypes.float64), [0., 0., 0.]]) 330 self.assertEqual(dtypes.float64, t_1.dtype) 331 332 t_2 = ops.convert_to_tensor( 333 [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]], dtype=dtypes.float64) 334 self.assertEqual(dtypes.float64, t_2.dtype) 335 336 t_3 = ops.convert_to_tensor( 337 [[0., 0., 0.], 338 constant_op.constant([0., 0., 0.], dtype=dtypes.float64), [0., 0., 0.] 339 ], 340 dtype=dtypes.float32) 341 self.assertEqual(dtypes.float32, t_3.dtype) 342 343 t_4 = ops.convert_to_tensor( 344 [constant_op.constant([0., 0., 0.], dtype=dtypes.float64)], 345 dtype=dtypes.float32) 346 self.assertEqual(dtypes.float32, t_4.dtype) 347 348 with self.assertRaises(TypeError): 349 ops.convert_to_tensor([ 350 constant_op.constant( 351 [0., 0., 0.], dtype=dtypes.float32), constant_op.constant( 352 [0., 0., 0.], dtype=dtypes.float64), [0., 0., 0.] 353 ]) 354 355 def testDtypeConversionWhenTensorDtypeMismatch(self): 356 t_0 = ops.convert_to_tensor([0., 0., 0.]) 357 self.assertEqual(dtypes.float32, t_0.dtype) 358 359 t_1 = ops.convert_to_tensor([0, 0, 0]) 360 self.assertEqual(dtypes.int32, t_1.dtype) 361 362 t_2 = ops.convert_to_tensor([t_0, t_0, t_1], dtype=dtypes.float64) 363 self.assertEqual(dtypes.float64, t_2.dtype) 364 365 @test_util.run_deprecated_v1 366 def testPlaceholder(self): 367 with self.session(): 368 # Test using placeholder with a defined shape. 369 ph_0 = array_ops.placeholder(dtypes.int32, shape=[]) 370 result_0 = ops.convert_to_tensor([[0, 0, 0], [0, ph_0, 0], [0, 0, 0]]) 371 self.assertAllEqual( 372 [[0, 0, 0], [0, 1, 0], [0, 0, 0]], result_0.eval(feed_dict={ph_0: 1})) 373 self.assertAllEqual( 374 [[0, 0, 0], [0, 2, 0], [0, 0, 0]], result_0.eval(feed_dict={ph_0: 2})) 375 376 # Test using placeholder with an undefined shape. 377 ph_1 = array_ops.placeholder(dtypes.int32) 378 result_1 = ops.convert_to_tensor([[0, 0, 0], [0, ph_1, 0], [0, 0, 0]]) 379 self.assertAllEqual( 380 [[0, 0, 0], [0, 1, 0], [0, 0, 0]], result_1.eval(feed_dict={ph_1: 1})) 381 self.assertAllEqual( 382 [[0, 0, 0], [0, 2, 0], [0, 0, 0]], result_1.eval(feed_dict={ph_1: 2})) 383 384 @test_util.run_deprecated_v1 385 def testShapeErrors(self): 386 # Static shape error. 387 ph_0 = array_ops.placeholder(dtypes.int32, shape=[1]) 388 with self.assertRaises(ValueError): 389 ops.convert_to_tensor([[0, 0, 0], [0, ph_0, 0], [0, 0, 0]]) 390 391 # Dynamic shape error. 392 ph_1 = array_ops.placeholder(dtypes.int32) 393 result_1 = ops.convert_to_tensor([[0, 0, 0], [0, ph_1, 0], [0, 0, 0]]) 394 with self.session(): 395 with self.assertRaises(errors_impl.InvalidArgumentError): 396 result_1.eval(feed_dict={ph_1: [1]}) 397 398 399if __name__ == "__main__": 400 test.main() 401