* Fix issues where int64 crops could not be passed to batch_to_space. This fix tries to address the issue where int64 `crops` could not be passed to `batch_to_space` even though both int32 and int64 are specified as supported in the docs (tf.batch_to_space.__doc__) The reason is that BatchToSpace kernel puts a constraint of int32 to crops data types. This fix removed the constraint so that int64 `crops` could be supported. NOTE: Just removing the constraint should work and it is not necessary to add specification to the kernel class template, as `SubtleMustCopyFlat` called in the class already correctly handled both int32 and int64 cases. Besides, other data types (e.g., float or double) will not be passed to the kernel as they are guarded by the specification in `array_ops.cc`. Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Also remove int64/int32 type constraints for SpaceToBatch kernels Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Add test cases for int64 crops of batch_to_space and space_to_batch Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Fix test failures. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
334 lines
12 KiB
Python
334 lines
12 KiB
Python
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""Functional tests for BatchToSpace op.
|
|
|
|
Additional tests are included in spacetobatch_op_test.py, where the BatchToSpace
|
|
op is tested in tandem with its reverse SpaceToBatch op.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import numpy as np
|
|
|
|
from tensorflow.python.framework import constant_op
|
|
from tensorflow.python.framework import dtypes
|
|
from tensorflow.python.framework import ops
|
|
from tensorflow.python.ops import array_ops
|
|
from tensorflow.python.ops import gen_array_ops
|
|
from tensorflow.python.ops import gradient_checker
|
|
from tensorflow.python.platform import test
|
|
|
|
|
|
class PythonOpImpl(object):
|
|
|
|
@staticmethod
|
|
def batch_to_space(*args, **kwargs):
|
|
return array_ops.batch_to_space(*args, **kwargs)
|
|
|
|
|
|
class CppOpImpl(object):
|
|
|
|
@staticmethod
|
|
def batch_to_space(*args, **kwargs):
|
|
return gen_array_ops._batch_to_space(*args, **kwargs)
|
|
|
|
|
|
class BatchToSpaceDepthToSpace(test.TestCase, PythonOpImpl):
|
|
|
|
# Verifies that: batch_to_space(x) = transpose(depth_to_space(transpose(x)))
|
|
def testDepthToSpaceTranspose(self):
|
|
x = np.arange(20 * 5 * 8 * 7, dtype=np.float32).reshape([20, 5, 8, 7])
|
|
block_size = 2
|
|
for crops_dtype in [dtypes.int64, dtypes.int32]:
|
|
crops = array_ops.zeros((2, 2), dtype=crops_dtype)
|
|
y1 = self.batch_to_space(x, crops, block_size=block_size)
|
|
y2 = array_ops.transpose(
|
|
array_ops.depth_to_space(
|
|
array_ops.transpose(x, [3, 1, 2, 0]), block_size=block_size),
|
|
[3, 1, 2, 0])
|
|
with self.test_session():
|
|
self.assertAllEqual(y1.eval(), y2.eval())
|
|
|
|
|
|
class BatchToSpaceDepthToSpaceCpp(BatchToSpaceDepthToSpace, CppOpImpl):
|
|
pass
|
|
|
|
|
|
class BatchToSpaceErrorHandlingTest(test.TestCase, PythonOpImpl):
|
|
|
|
def testInputWrongDimMissingBatch(self):
|
|
# The input is missing the first dimension ("batch")
|
|
x_np = [[[1], [2]], [[3], [4]]]
|
|
crops = np.zeros((2, 2), dtype=np.int32)
|
|
block_size = 2
|
|
with self.assertRaises(ValueError):
|
|
_ = self.batch_to_space(x_np, crops, block_size)
|
|
|
|
def testBlockSize0(self):
|
|
# The block size is 0.
|
|
x_np = [[[[1], [2]], [[3], [4]]]]
|
|
crops = np.zeros((2, 2), dtype=np.int32)
|
|
block_size = 0
|
|
with self.assertRaises(ValueError):
|
|
out_tf = self.batch_to_space(x_np, crops, block_size)
|
|
out_tf.eval()
|
|
|
|
def testBlockSizeOne(self):
|
|
# The block size is 1. The block size needs to be > 1.
|
|
x_np = [[[[1], [2]], [[3], [4]]]]
|
|
crops = np.zeros((2, 2), dtype=np.int32)
|
|
block_size = 1
|
|
with self.assertRaises(ValueError):
|
|
out_tf = self.batch_to_space(x_np, crops, block_size)
|
|
out_tf.eval()
|
|
|
|
def testBlockSizeLarger(self):
|
|
# The block size is too large for this input.
|
|
x_np = [[[[1], [2]], [[3], [4]]]]
|
|
crops = np.zeros((2, 2), dtype=np.int32)
|
|
block_size = 10
|
|
with self.assertRaises(ValueError):
|
|
out_tf = self.batch_to_space(x_np, crops, block_size)
|
|
out_tf.eval()
|
|
|
|
def testBlockSizeSquaredNotDivisibleBatch(self):
|
|
# The block size squared does not divide the batch.
|
|
x_np = [[[[1], [2], [3]], [[3], [4], [7]]]]
|
|
crops = np.zeros((2, 2), dtype=np.int32)
|
|
block_size = 3
|
|
with self.assertRaises(ValueError):
|
|
_ = self.batch_to_space(x_np, crops, block_size)
|
|
|
|
def testUnknownShape(self):
|
|
t = self.batch_to_space(
|
|
array_ops.placeholder(dtypes.float32),
|
|
array_ops.placeholder(dtypes.int32),
|
|
block_size=4)
|
|
self.assertEqual(4, t.get_shape().ndims)
|
|
|
|
|
|
class BatchToSpaceErrorHandlingCppTest(BatchToSpaceErrorHandlingTest,
|
|
CppOpImpl):
|
|
pass
|
|
|
|
|
|
class BatchToSpaceNDErrorHandlingTest(test.TestCase):
|
|
|
|
def _testStaticShape(self, input_shape, block_shape, paddings, error):
|
|
block_shape = np.array(block_shape)
|
|
paddings = np.array(paddings)
|
|
|
|
# Try with sizes known at graph construction time.
|
|
with self.assertRaises(error):
|
|
_ = array_ops.batch_to_space_nd(
|
|
np.zeros(input_shape, np.float32), block_shape, paddings)
|
|
|
|
def _testDynamicShape(self, input_shape, block_shape, paddings):
|
|
block_shape = np.array(block_shape)
|
|
paddings = np.array(paddings)
|
|
|
|
# Try with sizes unknown at graph construction time.
|
|
input_placeholder = array_ops.placeholder(dtypes.float32)
|
|
block_shape_placeholder = array_ops.placeholder(
|
|
dtypes.int32, shape=block_shape.shape)
|
|
paddings_placeholder = array_ops.placeholder(dtypes.int32)
|
|
t = array_ops.batch_to_space_nd(input_placeholder, block_shape_placeholder,
|
|
paddings_placeholder)
|
|
|
|
with self.assertRaises(ValueError):
|
|
_ = t.eval({
|
|
input_placeholder: np.zeros(input_shape, np.float32),
|
|
block_shape_placeholder: block_shape,
|
|
paddings_placeholder: paddings
|
|
})
|
|
|
|
def _testShape(self, input_shape, block_shape, paddings, error):
|
|
self._testStaticShape(input_shape, block_shape, paddings, error)
|
|
self._testDynamicShape(input_shape, block_shape, paddings)
|
|
|
|
def testInputWrongDimMissingBatch(self):
|
|
self._testShape([2, 2], [2, 2], [[0, 0], [0, 0]], ValueError)
|
|
self._testShape([2, 2, 3], [2, 2, 3], [[0, 0], [0, 0]], ValueError)
|
|
|
|
def testBlockSize0(self):
|
|
# The block size is 0.
|
|
self._testShape([1, 2, 2, 1], [0, 1], [[0, 0], [0, 0]], ValueError)
|
|
|
|
def testBlockSizeNegative(self):
|
|
self._testShape([1, 2, 2, 1], [-1, 1], [[0, 0], [0, 0]], ValueError)
|
|
|
|
def testNegativePadding(self):
|
|
self._testShape([1, 2, 2], [1, 1], [[0, -1], [0, 0]], ValueError)
|
|
|
|
def testCropTooLarge(self):
|
|
# The amount to crop exceeds the padded size.
|
|
self._testShape([1 * 2 * 2, 2, 3, 1], [2, 2], [[3, 2], [0, 0]], ValueError)
|
|
|
|
def testBlockSizeSquaredNotDivisibleBatch(self):
|
|
# The batch dimension is not divisible by the product of the block_shape.
|
|
self._testShape([3, 1, 1, 1], [2, 3], [[0, 0], [0, 0]], ValueError)
|
|
|
|
def testUnknownShape(self):
|
|
# Verify that input shape and paddings shape can be unknown.
|
|
_ = array_ops.batch_to_space_nd(
|
|
array_ops.placeholder(dtypes.float32),
|
|
array_ops.placeholder(
|
|
dtypes.int32, shape=(2,)),
|
|
array_ops.placeholder(dtypes.int32))
|
|
|
|
# Only number of input dimensions is known.
|
|
t = array_ops.batch_to_space_nd(
|
|
array_ops.placeholder(
|
|
dtypes.float32, shape=(None, None, None, None)),
|
|
array_ops.placeholder(
|
|
dtypes.int32, shape=(2,)),
|
|
array_ops.placeholder(dtypes.int32))
|
|
self.assertEqual(4, t.get_shape().ndims)
|
|
|
|
# Dimensions are partially known.
|
|
t = array_ops.batch_to_space_nd(
|
|
array_ops.placeholder(
|
|
dtypes.float32, shape=(None, None, None, 2)),
|
|
array_ops.placeholder(
|
|
dtypes.int32, shape=(2,)),
|
|
array_ops.placeholder(dtypes.int32))
|
|
self.assertEqual([None, None, None, 2], t.get_shape().as_list())
|
|
|
|
# Dimensions are partially known.
|
|
t = array_ops.batch_to_space_nd(
|
|
array_ops.placeholder(
|
|
dtypes.float32, shape=(3 * 2 * 3, None, None, 2)), [2, 3],
|
|
array_ops.placeholder(dtypes.int32))
|
|
self.assertEqual([3, None, None, 2], t.get_shape().as_list())
|
|
|
|
# Dimensions are partially known.
|
|
t = array_ops.batch_to_space_nd(
|
|
array_ops.placeholder(
|
|
dtypes.float32, shape=(3 * 2 * 3, None, 2, 2)), [2, 3],
|
|
[[1, 1], [0, 1]])
|
|
self.assertEqual([3, None, 5, 2], t.get_shape().as_list())
|
|
|
|
# Dimensions are fully known.
|
|
t = array_ops.batch_to_space_nd(
|
|
array_ops.placeholder(
|
|
dtypes.float32, shape=(3 * 2 * 3, 2, 1, 2)), [2, 3],
|
|
[[1, 1], [0, 0]])
|
|
self.assertEqual([3, 2, 3, 2], t.get_shape().as_list())
|
|
|
|
|
|
class BatchToSpaceGradientTest(test.TestCase, PythonOpImpl):
|
|
|
|
# Check the gradients.
|
|
def _checkGrad(self, x, crops, block_size):
|
|
assert 4 == x.ndim
|
|
with self.test_session():
|
|
tf_x = ops.convert_to_tensor(x)
|
|
tf_y = self.batch_to_space(tf_x, crops, block_size)
|
|
epsilon = 1e-5
|
|
((x_jacob_t, x_jacob_n)) = gradient_checker.compute_gradient(
|
|
tf_x,
|
|
x.shape,
|
|
tf_y,
|
|
tf_y.get_shape().as_list(),
|
|
x_init_value=x,
|
|
delta=epsilon)
|
|
|
|
self.assertAllClose(x_jacob_t, x_jacob_n, rtol=1e-2, atol=epsilon)
|
|
|
|
# Tests a gradient for batch_to_space of x which is a four dimensional
|
|
# tensor of shape [b * block_size * block_size, h, w, d].
|
|
def _compare(self, b, h, w, d, block_size, crop_beg, crop_end):
|
|
block_size_sq = block_size * block_size
|
|
x = np.random.normal(0, 1, b * h * w * d *
|
|
block_size_sq).astype(np.float32).reshape(
|
|
[b * block_size * block_size, h, w, d])
|
|
crops = np.array(
|
|
[[crop_beg, crop_end], [crop_beg, crop_end]], dtype=np.int32)
|
|
|
|
self._checkGrad(x, crops, block_size)
|
|
|
|
# Don't use very large numbers as dimensions here as the result is tensor
|
|
# with cartesian product of the dimensions.
|
|
def testSmall(self):
|
|
block_size = 2
|
|
crop_beg = 0
|
|
crop_end = 0
|
|
self._compare(1, 2, 3, 5, block_size, crop_beg, crop_end)
|
|
|
|
def testSmall2(self):
|
|
block_size = 2
|
|
crop_beg = 0
|
|
crop_end = 0
|
|
self._compare(2, 4, 3, 2, block_size, crop_beg, crop_end)
|
|
|
|
def testSmallCrop1x1(self):
|
|
block_size = 2
|
|
crop_beg = 1
|
|
crop_end = 1
|
|
self._compare(1, 2, 3, 5, block_size, crop_beg, crop_end)
|
|
|
|
|
|
class BatchToSpaceGradientCppTest(BatchToSpaceGradientTest, CppOpImpl):
|
|
pass
|
|
|
|
|
|
class BatchToSpaceNDGradientTest(test.TestCase):
|
|
|
|
# Check the gradients.
|
|
def _checkGrad(self, x, block_shape, crops, crops_dtype):
|
|
block_shape = np.array(block_shape)
|
|
crops = constant_op.constant(
|
|
np.array(crops).reshape((len(block_shape), 2)), crops_dtype)
|
|
with self.test_session():
|
|
tf_x = ops.convert_to_tensor(x)
|
|
tf_y = array_ops.batch_to_space_nd(tf_x, block_shape, crops)
|
|
epsilon = 1e-5
|
|
((x_jacob_t, x_jacob_n)) = gradient_checker.compute_gradient(
|
|
tf_x,
|
|
x.shape,
|
|
tf_y,
|
|
tf_y.get_shape().as_list(),
|
|
x_init_value=x,
|
|
delta=epsilon)
|
|
|
|
self.assertAllClose(x_jacob_t, x_jacob_n, rtol=1e-2, atol=epsilon)
|
|
|
|
def _compare(self, input_shape, block_shape, crops, crops_dtype):
|
|
input_shape = list(input_shape)
|
|
input_shape[0] *= np.prod(block_shape)
|
|
x = np.random.normal(
|
|
0, 1, np.prod(input_shape)).astype(np.float32).reshape(input_shape)
|
|
self._checkGrad(x, block_shape, crops, crops_dtype)
|
|
|
|
# Don't use very large numbers as dimensions here as the result is tensor
|
|
# with cartesian product of the dimensions.
|
|
def testSmall(self):
|
|
for dtype in [dtypes.int64, dtypes.int32]:
|
|
self._compare([1, 2, 3, 5], [2, 2], [[0, 0], [0, 0]], dtype)
|
|
|
|
def testSmall2(self):
|
|
for dtype in [dtypes.int64, dtypes.int32]:
|
|
self._compare([2, 4, 3, 2], [2, 2], [[0, 0], [0, 0]], dtype)
|
|
|
|
def testSmallCrop1x1(self):
|
|
for dtype in [dtypes.int64, dtypes.int32]:
|
|
self._compare([1, 2, 3, 5], [2, 2], [[1, 1], [1, 1]], dtype)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test.main()
|