Home | History | Annotate | Download | only in kernel_tests
      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 SpacetoDepth op."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 import numpy as np
     22 
     23 from tensorflow.python.framework import constant_op
     24 from tensorflow.python.framework import dtypes
     25 from tensorflow.python.framework import ops
     26 from tensorflow.python.framework import test_util
     27 from tensorflow.python.ops import array_ops
     28 from tensorflow.python.ops import gen_array_ops
     29 from tensorflow.python.ops import gradient_checker
     30 from tensorflow.python.ops import math_ops
     31 from tensorflow.python.platform import test
     32 from tensorflow.python.platform import tf_logging
     33 
     34 
     35 class SpaceToDepthTest(test.TestCase):
     36 
     37   def _testOne(self, inputs, block_size, outputs):
     38     input_nhwc = math_ops.to_float(inputs)
     39     with self.test_session(use_gpu=False):
     40       # test NHWC (default) on CPU
     41       x_tf = array_ops.space_to_depth(input_nhwc, block_size)
     42       self.assertAllEqual(x_tf.eval(), outputs)
     43     if test.is_gpu_available():
     44       with self.test_session(use_gpu=True):
     45         # test NHWC (default) on GPU
     46         x_tf = array_ops.space_to_depth(input_nhwc, block_size)
     47         self.assertAllEqual(x_tf.eval(), outputs)
     48         # test NCHW on GPU
     49         input_nchw = test_util.NHWCToNCHW(input_nhwc)
     50         output_nchw = array_ops.space_to_depth(
     51             input_nchw, block_size, data_format="NCHW")
     52         output_nhwc = test_util.NCHWToNHWC(output_nchw)
     53         self.assertAllEqual(output_nhwc.eval(), outputs)
     54 
     55   def testBasic(self):
     56     x_np = [[[[1], [2]], [[3], [4]]]]
     57     block_size = 2
     58     x_out = [[[[1, 2, 3, 4]]]]
     59     self._testOne(x_np, block_size, x_out)
     60 
     61   # Tests for larger input dimensions. To make sure elements are
     62   # correctly ordered spatially.
     63   def testLargerInput2x2(self):
     64     x_np = [[[[1], [2], [5], [6]], [[3], [4], [7], [8]],
     65              [[9], [10], [13], [14]], [[11], [12], [15], [16]]]]
     66     block_size = 2
     67     x_out = [[[[1, 2, 3, 4], [5, 6, 7, 8]], [[9, 10, 11, 12],
     68                                              [13, 14, 15, 16]]]]
     69     self._testOne(x_np, block_size, x_out)
     70 
     71   # Tests for larger input dimensions. To make sure elements are
     72   # correctly ordered in depth. Here, larger block size.
     73   def testLargerInput4x4(self):
     74     x_np = [[[[1], [2], [5], [6]], [[3], [4], [7], [8]],
     75              [[9], [10], [13], [14]], [[11], [12], [15], [16]]]]
     76     block_size = 4
     77     x_out = [[[[1, 2, 5, 6, 3, 4, 7, 8, 9, 10, 13, 14, 11, 12, 15, 16]]]]
     78     self._testOne(x_np, block_size, x_out)
     79 
     80   # Tests for larger input depths.
     81   # To make sure elements are properly interleaved in depth.
     82   def testDepthInterleaved(self):
     83     x_np = [[[[1, 10], [2, 20]], [[3, 30], [4, 40]]]]
     84     block_size = 2
     85     x_out = [[[[1, 10, 2, 20, 3, 30, 4, 40]]]]
     86     self._testOne(x_np, block_size, x_out)
     87 
     88   # Tests for larger input depths. Here an odd depth.
     89   # To make sure elements are properly interleaved in depth.
     90   def testDepthInterleavedDepth3(self):
     91     x_np = [[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]]
     92     block_size = 2
     93     x_out = [[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]]]
     94     self._testOne(x_np, block_size, x_out)
     95 
     96   # Tests for larger input dimensions AND for larger input depths.
     97   # To make sure elements are properly interleaved in depth and ordered
     98   # spatially.
     99   def testDepthInterleavedLarge(self):
    100     x_np = [[[[1, 10], [2, 20], [5, 50], [6, 60]],
    101              [[3, 30], [4, 40], [7, 70], [8, 80]],
    102              [[9, 90], [10, 100], [13, 130], [14, 140]],
    103              [[11, 110], [12, 120], [15, 150], [16, 160]]]]
    104     block_size = 2
    105     x_out = [[[[1, 10, 2, 20, 3, 30, 4, 40], [5, 50, 6, 60, 7, 70, 8, 80]],
    106               [[9, 90, 10, 100, 11, 110, 12, 120],
    107                [13, 130, 14, 140, 15, 150, 16, 160]]]]
    108     self._testOne(x_np, block_size, x_out)
    109 
    110   def testBlockSize2Batch10(self):
    111     block_size = 2
    112 
    113     def batch_input_elt(i):
    114       return [[[1 * i], [2 * i], [5 * i], [6 * i]],
    115               [[3 * i], [4 * i], [7 * i], [8 * i]],
    116               [[9 * i], [10 * i], [13 * i], [14 * i]],
    117               [[11 * i], [12 * i], [15 * i], [16 * i]]]
    118 
    119     def batch_output_elt(i):
    120       return [[[1 * i, 2 * i, 3 * i, 4 * i], [5 * i, 6 * i, 7 * i, 8 * i]],
    121               [[9 * i, 10 * i, 11 * i, 12 * i],
    122                [13 * i, 14 * i, 15 * i, 16 * i]]]
    123 
    124     batch_size = 10
    125     x_np = [batch_input_elt(i) for i in range(batch_size)]
    126     x_out = [batch_output_elt(i) for i in range(batch_size)]
    127     self._testOne(x_np, block_size, x_out)
    128 
    129   # Tests for different width and height.
    130   def testNonSquare(self):
    131     x_np = [[[[1, 10], [2, 20]], [[3, 30], [4, 40]], [[5, 50], [6, 60]],
    132              [[7, 70], [8, 80]], [[9, 90], [10, 100]], [[11, 110], [12, 120]]]]
    133     block_size = 2
    134     x_out = [[[[1, 10, 2, 20, 3, 30, 4, 40]], [[5, 50, 6, 60, 7, 70, 8, 80]],
    135               [[9, 90, 10, 100, 11, 110, 12, 120]]]]
    136     self._testOne(x_np, block_size, x_out)
    137 
    138   # Error handling:
    139 
    140   def testInputWrongDimMissingDepth(self):
    141     # The input is missing the last dimension ("depth")
    142     x_np = [[[1, 2], [3, 4]]]
    143     block_size = 2
    144     with self.assertRaises(ValueError):
    145       out_tf = array_ops.space_to_depth(x_np, block_size)
    146       out_tf.eval()
    147 
    148   def testInputWrongDimMissingBatch(self):
    149     # The input is missing the first dimension ("batch")
    150     x_np = [[[1], [2]], [[3], [4]]]
    151     block_size = 2
    152     with self.assertRaises(ValueError):
    153       _ = array_ops.space_to_depth(x_np, block_size)
    154 
    155   def testBlockSize0(self):
    156     # The block size is 0.
    157     x_np = [[[[1], [2]], [[3], [4]]]]
    158     block_size = 0
    159     with self.assertRaises(ValueError):
    160       out_tf = array_ops.space_to_depth(x_np, block_size)
    161       out_tf.eval()
    162 
    163   def testBlockSizeOne(self):
    164     # The block size is 1. The block size needs to be > 1.
    165     x_np = [[[[1], [2]], [[3], [4]]]]
    166     block_size = 1
    167     with self.assertRaises(ValueError):
    168       out_tf = array_ops.space_to_depth(x_np, block_size)
    169       out_tf.eval()
    170 
    171   def testBlockSizeLarger(self):
    172     # The block size is too large for this input.
    173     x_np = [[[[1], [2]], [[3], [4]]]]
    174     block_size = 10
    175     with self.assertRaises(ValueError):
    176       out_tf = array_ops.space_to_depth(x_np, block_size)
    177       out_tf.eval()
    178 
    179   def testBlockSizeNotDivisibleWidth(self):
    180     # The block size divides width but not height.
    181     x_np = [[[[1], [2], [3]], [[3], [4], [7]]]]
    182     block_size = 3
    183     with self.assertRaises(ValueError):
    184       _ = array_ops.space_to_depth(x_np, block_size)
    185 
    186   def testBlockSizeNotDivisibleHeight(self):
    187     # The block size divides height but not width.
    188     x_np = [[[[1], [2]], [[3], [4]], [[5], [6]]]]
    189     block_size = 3
    190     with self.assertRaises(ValueError):
    191       _ = array_ops.space_to_depth(x_np, block_size)
    192 
    193   def testBlockSizeNotDivisibleBoth(self):
    194     # The block size does not divide neither width or height.
    195     x_np = [[[[1], [2]], [[3], [4]]]]
    196     block_size = 3
    197     with self.assertRaises(ValueError):
    198       _ = array_ops.space_to_depth(x_np, block_size)
    199 
    200   def testUnknownShape(self):
    201     t = array_ops.space_to_depth(
    202         array_ops.placeholder(dtypes.float32), block_size=4)
    203     self.assertEqual(4, t.get_shape().ndims)
    204 
    205   def spaceToDepthUsingTranspose(self, tensor, block_size, data_format):
    206     block_size_sq = block_size * block_size
    207     if data_format == "NHWC":
    208       b, ih, iw, ic = tensor.shape.as_list()
    209       assert ih % block_size == 0, (ih, block_size)
    210       assert iw % block_size == 0, (iw, block_size)
    211       ow, oh, oc = iw // block_size, ih // block_size, ic * block_size_sq
    212       tensor = array_ops.reshape(tensor,
    213                                  [b, oh, block_size, ow, block_size, ic])
    214       tensor = array_ops.transpose(tensor, [0, 1, 3, 2, 4, 5])
    215       tensor = array_ops.reshape(tensor, [b, oh, ow, oc])
    216     elif data_format == "NCHW":
    217       b, ic, ih, iw = tensor.shape.as_list()
    218       assert ih % block_size == 0, (ih, block_size)
    219       assert iw % block_size == 0, (iw, block_size)
    220       ow, oh, oc = iw // block_size, ih // block_size, ic * block_size_sq
    221       tensor = array_ops.reshape(tensor,
    222                                  [b, ic, oh, block_size, ow, block_size])
    223       tensor = array_ops.transpose(tensor, [0, 3, 5, 1, 2, 4])
    224       tensor = array_ops.reshape(tensor, [b, oc, oh, ow])
    225     return tensor
    226 
    227   def compareToTranspose(self, batch_size, out_height, out_width, in_channels,
    228                          block_size, data_format, use_gpu):
    229     in_height = out_height * block_size
    230     in_width = out_width * block_size
    231     nhwc_input_shape = [batch_size, in_height, in_width, in_channels]
    232     nchw_input_shape = [batch_size, in_channels, in_height, in_width]
    233     total_size = np.prod(nhwc_input_shape)
    234 
    235     if data_format == "NCHW_VECT_C":
    236       # Initialize the input tensor with qint8 values that circle -127..127.
    237       x = [((f + 128) % 255) - 127 for f in range(total_size)]
    238       t = constant_op.constant(x, shape=nhwc_input_shape, dtype=dtypes.float32)
    239       expected = self.spaceToDepthUsingTranspose(t, block_size, "NHWC")
    240       t = test_util.NHWCToNCHW_VECT_C(t)
    241       t, _, _ = gen_array_ops.quantize_v2(t, -128.0, 127.0, dtypes.qint8)
    242       t = array_ops.space_to_depth(t, block_size, data_format="NCHW_VECT_C")
    243       t = gen_array_ops.dequantize(t, -128, 127)
    244       actual = test_util.NCHW_VECT_CToNHWC(t)
    245     else:
    246       # Initialize the input tensor with ascending whole numbers as floats.
    247       x = [f * 1.0 for f in range(total_size)]
    248       shape = nchw_input_shape if data_format == "NCHW" else nhwc_input_shape
    249       t = constant_op.constant(x, shape=shape, dtype=dtypes.float32)
    250       expected = self.spaceToDepthUsingTranspose(t, block_size, data_format)
    251       actual = array_ops.space_to_depth(t, block_size, data_format=data_format)
    252 
    253     with self.test_session(use_gpu=use_gpu) as sess:
    254       actual_vals, expected_vals = sess.run([actual, expected])
    255       self.assertTrue(np.array_equal(actual_vals, expected_vals))
    256 
    257   def testAgainstTranspose(self):
    258     self.compareToTranspose(3, 2, 3, 1, 2, "NHWC", False)
    259     self.compareToTranspose(1, 2, 3, 2, 2, "NHWC", False)
    260     self.compareToTranspose(1, 2, 3, 2, 3, "NHWC", False)
    261 
    262     if not test.is_gpu_available():
    263       tf_logging.info("skipping gpu tests since gpu not available")
    264       return
    265 
    266     self.compareToTranspose(3, 2, 3, 1, 2, "NHWC", True)
    267     self.compareToTranspose(3, 2, 3, 2, 2, "NHWC", True)
    268     self.compareToTranspose(3, 2, 3, 1, 2, "NCHW", True)
    269     self.compareToTranspose(3, 2, 3, 2, 3, "NCHW", True)
    270     self.compareToTranspose(5, 7, 11, 3, 2, "NCHW", True)
    271 
    272     self.compareToTranspose(3, 2, 3, 4, 2, "NCHW_VECT_C", True)
    273     self.compareToTranspose(3, 2, 3, 8, 3, "NCHW_VECT_C", True)
    274     self.compareToTranspose(5, 7, 11, 12, 2, "NCHW_VECT_C", True)
    275 
    276 
    277 class SpaceToDepthGradientTest(test.TestCase):
    278 
    279   # Check the gradients.
    280   def _checkGrad(self, x, block_size, data_format):
    281     # NCHW is implemented for only GPU.
    282     if data_format == "NCHW" and not test.is_gpu_available():
    283       return
    284 
    285     assert 4 == x.ndim
    286     with self.test_session(use_gpu=True):
    287       tf_x = ops.convert_to_tensor(x)
    288       tf_y = array_ops.space_to_depth(tf_x, block_size, data_format=data_format)
    289       epsilon = 1e-2
    290       ((x_jacob_t, x_jacob_n)) = gradient_checker.compute_gradient(
    291           tf_x,
    292           x.shape,
    293           tf_y,
    294           tf_y.get_shape().as_list(),
    295           x_init_value=x,
    296           delta=epsilon)
    297 
    298     self.assertAllClose(x_jacob_t, x_jacob_n, rtol=1e-2, atol=epsilon)
    299 
    300   # Tests a gradient for space_to_depth of x which is a four dimensional
    301   # tensor of shape [b, h * block_size, w * block_size, d].
    302   def _compare(self, b, h, w, d, block_size, data_format):
    303     block_size_sq = block_size * block_size
    304     data = np.random.normal(0, 1, b * h * w * d * block_size_sq).astype(
    305         np.float32)
    306     if data_format == "NHWC":
    307       x = data.reshape([b, h * block_size, w * block_size, d])
    308     else:
    309       x = data.reshape([b, d, h * block_size, w * block_size])
    310 
    311     self._checkGrad(x, block_size, data_format)
    312 
    313   # Don't use very large numbers as dimensions here as the result is tensor
    314   # with cartesian product of the dimensions.
    315   def testSmall(self):
    316     block_size = 2
    317     self._compare(1, 2, 3, 5, block_size, "NHWC")
    318     self._compare(1, 2, 3, 5, block_size, "NCHW")
    319 
    320   def testSmall2(self):
    321     block_size = 2
    322     self._compare(2, 4, 3, 2, block_size, "NHWC")
    323     self._compare(2, 4, 3, 2, block_size, "NCHW")
    324 
    325 
    326 if __name__ == "__main__":
    327   test.main()
    328