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"""Tests for convolution related functionality in tensorflow.ops.nn."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import numpy as np
22from six.moves import xrange  # pylint: disable=redefined-builtin
23
24from tensorflow.python.framework import constant_op
25from tensorflow.python.framework import dtypes
26from tensorflow.python.ops import array_ops
27from tensorflow.python.ops import gradient_checker
28from tensorflow.python.ops import nn_ops
29from tensorflow.python.ops import random_ops
30from tensorflow.python.ops import variable_scope
31from tensorflow.python.ops import variables
32import tensorflow.python.ops.nn_grad  # pylint: disable=unused-import
33from tensorflow.python.platform import test
34
35
36class Conv2DTransposeTest(test.TestCase):
37
38  def testConv2DTransposeSingleStride(self):
39    with self.test_session():
40      strides = [1, 1, 1, 1]
41
42      # Input, output: [batch, height, width, depth]
43      x_shape = [2, 6, 4, 3]
44      y_shape = [2, 6, 4, 2]
45
46      # Filter: [kernel_height, kernel_width, output_depth, input_depth]
47      f_shape = [3, 3, 2, 3]
48
49      x = constant_op.constant(
50          1.0, shape=x_shape, name="x", dtype=dtypes.float32)
51      f = constant_op.constant(
52          1.0, shape=f_shape, name="filter", dtype=dtypes.float32)
53      output = nn_ops.conv2d_transpose(
54          x, f, y_shape, strides=strides, padding="SAME")
55      value = output.eval()
56
57      # We count the number of cells being added at the locations in the output.
58      # At the center, #cells=kernel_height * kernel_width
59      # At the corners, #cells=ceil(kernel_height/2) * ceil(kernel_width/2)
60      # At the borders, #cells=ceil(kernel_height/2)*kernel_width or
61      #                        kernel_height * ceil(kernel_width/2)
62
63      for n in xrange(x_shape[0]):
64        for k in xrange(f_shape[2]):
65          for w in xrange(y_shape[2]):
66            for h in xrange(y_shape[1]):
67              target = 4 * 3.0
68              h_in = h > 0 and h < y_shape[1] - 1
69              w_in = w > 0 and w < y_shape[2] - 1
70              if h_in and w_in:
71                target += 5 * 3.0
72              elif h_in or w_in:
73                target += 2 * 3.0
74              self.assertAllClose(target, value[n, h, w, k])
75
76  def testConv2DTransposeSame(self):
77    with self.test_session():
78      strides = [1, 2, 2, 1]
79
80      # Input, output: [batch, height, width, depth]
81      x_shape = [2, 6, 4, 3]
82      y_shape = [2, 12, 8, 2]
83
84      # Filter: [kernel_height, kernel_width, output_depth, input_depth]
85      f_shape = [3, 3, 2, 3]
86
87      x = constant_op.constant(
88          1.0, shape=x_shape, name="x", dtype=dtypes.float32)
89      f = constant_op.constant(
90          1.0, shape=f_shape, name="filter", dtype=dtypes.float32)
91      output = nn_ops.conv2d_transpose(
92          x, f, y_shape, strides=strides, padding="SAME")
93      value = output.eval()
94
95      for n in xrange(x_shape[0]):
96        for k in xrange(f_shape[2]):
97          for w in xrange(y_shape[2]):
98            for h in xrange(y_shape[1]):
99              target = 3.0
100              # We add a case for locations divisible by the stride.
101              h_in = h % strides[1] == 0 and h > 0 and h < y_shape[1] - 1
102              w_in = w % strides[2] == 0 and w > 0 and w < y_shape[2] - 1
103              if h_in and w_in:
104                target += 9.0
105              elif h_in or w_in:
106                target += 3.0
107              self.assertAllClose(target, value[n, h, w, k])
108
109  def testConv2DTransposeValid(self):
110    with self.test_session():
111      strides = [1, 2, 2, 1]
112
113      # Input, output: [batch, height, width, depth]
114      x_shape = [2, 6, 4, 3]
115      y_shape = [2, 13, 9, 2]
116
117      # Filter: [kernel_height, kernel_width, output_depth, input_depth]
118      f_shape = [3, 3, 2, 3]
119
120      x = constant_op.constant(
121          1.0, shape=x_shape, name="x", dtype=dtypes.float32)
122      f = constant_op.constant(
123          1.0, shape=f_shape, name="filter", dtype=dtypes.float32)
124      output = nn_ops.conv2d_transpose(
125          x, f, y_shape, strides=strides, padding="VALID")
126      value = output.eval()
127
128      cache_values = np.zeros(y_shape, dtype=np.float32)
129
130      # The amount of padding added
131      pad = 1
132
133      for n in xrange(x_shape[0]):
134        for k in xrange(f_shape[2]):
135          for w in xrange(pad, y_shape[2] - pad):
136            for h in xrange(pad, y_shape[1] - pad):
137              target = 3.0
138              # We add a case for locations divisible by the stride.
139              h_in = h % strides[1] == 0 and h > pad and h < y_shape[
140                  1] - 1 - pad
141              w_in = w % strides[2] == 0 and w > pad and w < y_shape[
142                  2] - 1 - pad
143              if h_in and w_in:
144                target += 9.0
145              elif h_in or w_in:
146                target += 3.0
147              cache_values[n, h, w, k] = target
148
149          # copy values in the border
150          cache_values[n, :, 0, k] = cache_values[n, :, 1, k]
151          cache_values[n, :, -1, k] = cache_values[n, :, -2, k]
152          cache_values[n, 0, :, k] = cache_values[n, 1, :, k]
153          cache_values[n, -1, :, k] = cache_values[n, -2, :, k]
154
155    self.assertAllClose(cache_values, value)
156
157  def testGradient(self):
158    x_shape = [2, 6, 4, 3]
159    f_shape = [3, 3, 2, 3]
160    y_shape = [2, 12, 8, 2]
161    strides = [1, 2, 2, 1]
162    np.random.seed(1)  # Make it reproducible.
163    x_val = np.random.random_sample(x_shape).astype(np.float64)
164    f_val = np.random.random_sample(f_shape).astype(np.float64)
165    with self.test_session():
166      x = constant_op.constant(x_val, name="x", dtype=dtypes.float32)
167      f = constant_op.constant(f_val, name="f", dtype=dtypes.float32)
168      output = nn_ops.conv2d_transpose(
169          x, f, y_shape, strides=strides, padding="SAME")
170      err = gradient_checker.compute_gradient_error([x, f], [x_shape, f_shape],
171                                                    output, y_shape)
172    print("conv2d_transpose gradient err = %g " % err)
173    err_tolerance = 0.0005
174    self.assertLess(err, err_tolerance)
175
176  def testConv2DTransposeSingleStrideNCHW(self):
177    # `NCHW` data format is only supported for CUDA device.
178    if test.is_gpu_available(cuda_only=True):
179      with self.test_session(use_gpu=True):
180        strides = [1, 1, 1, 1]
181
182        # Input, output: [batch, depth, height, width, depth]
183        x_shape = [2, 3, 6, 4]
184        y_shape = [2, 2, 6, 4]
185
186        # Filter: [kernel_height, kernel_width, output_depth, input_depth]
187        f_shape = [3, 3, 2, 3]
188
189        x = constant_op.constant(
190            1.0, shape=x_shape, name="x", dtype=dtypes.float32)
191        f = constant_op.constant(
192            1.0, shape=f_shape, name="filter", dtype=dtypes.float32)
193
194        output = nn_ops.conv2d_transpose(
195            x, f, y_shape, strides=strides, padding="SAME", data_format="NCHW")
196
197        value = output.eval()
198        for n in xrange(x_shape[0]):
199          for k in xrange(f_shape[2]):
200            for w in xrange(y_shape[3]):
201              for h in xrange(y_shape[2]):
202                target = 4 * 3.0
203                h_in = h > 0 and h < y_shape[2] - 1
204                w_in = w > 0 and w < y_shape[3] - 1
205                if h_in and w_in:
206                  target += 5 * 3.0
207                elif h_in or w_in:
208                  target += 2 * 3.0
209                self.assertAllClose(target, value[n, k, h, w])
210
211  def testConv2DTransposeSameNCHW(self):
212    # `NCHW` data format is only supported for CUDA device.
213    if test.is_gpu_available(cuda_only=True):
214      with self.test_session(use_gpu=True):
215        strides = [1, 1, 2, 2]
216
217        # Input, output: [batch, depth, height, width]
218        x_shape = [2, 3, 6, 4]
219        y_shape = [2, 2, 12, 8]
220
221        # Filter: [kernel_height, kernel_width, output_depth, input_depth]
222        f_shape = [3, 3, 2, 3]
223
224        x = constant_op.constant(
225            1.0, shape=x_shape, name="x", dtype=dtypes.float32)
226        f = constant_op.constant(
227            1.0, shape=f_shape, name="filter", dtype=dtypes.float32)
228
229        output = nn_ops.conv2d_transpose(
230            x, f, y_shape, strides=strides, padding="SAME", data_format="NCHW")
231
232        value = output.eval()
233        for n in xrange(x_shape[0]):
234          for k in xrange(f_shape[2]):
235            for w in xrange(y_shape[3]):
236              for h in xrange(y_shape[2]):
237                target = 3.0
238                # We add a case for locations divisible by the stride.
239                h_in = h % strides[2] == 0 and h > 0 and h < y_shape[2] - 1
240                w_in = w % strides[3] == 0 and w > 0 and w < y_shape[3] - 1
241                if h_in and w_in:
242                  target += 9.0
243                elif h_in or w_in:
244                  target += 3.0
245                self.assertAllClose(target, value[n, k, h, w])
246
247  def testConv2DTransposeValidNCHW(self):
248    # `NCHW` data format is only supported for CUDA device.
249    if test.is_gpu_available(cuda_only=True):
250      with self.test_session(use_gpu=True):
251        strides = [1, 1, 2, 2]
252
253        # Input, output: [batch, depth, height, width]
254        x_shape = [2, 3, 6, 4]
255        y_shape = [2, 2, 13, 9]
256
257        # Filter: [kernel_height, kernel_width, output_depth, input_depth]
258        f_shape = [3, 3, 2, 3]
259
260        x = constant_op.constant(
261            1.0, shape=x_shape, name="x", dtype=dtypes.float32)
262        f = constant_op.constant(
263            1.0, shape=f_shape, name="filter", dtype=dtypes.float32)
264        output = nn_ops.conv2d_transpose(
265            x, f, y_shape, strides=strides, padding="VALID", data_format="NCHW")
266
267        value = output.eval()
268        cache_values = np.zeros(y_shape, dtype=np.float32)
269        # The amount of padding added
270        pad = 1
271        for n in xrange(x_shape[0]):
272          for k in xrange(f_shape[2]):
273            for w in xrange(pad, y_shape[3] - pad):
274              for h in xrange(pad, y_shape[2] - pad):
275                target = 3.0
276                # We add a case for locations divisible by the stride.
277                h_in = h % strides[2] == 0 and h > pad and h < y_shape[
278                    2] - 1 - pad
279                w_in = w % strides[3] == 0 and w > pad and w < y_shape[
280                    3] - 1 - pad
281                if h_in and w_in:
282                  target += 9.0
283                elif h_in or w_in:
284                  target += 3.0
285                cache_values[n, k, h, w] = target
286
287            # copy values in the border
288            cache_values[n, k, :, 0] = cache_values[n, k, :, 1]
289            cache_values[n, k, :, -1] = cache_values[n, k, :, -2]
290            cache_values[n, k, 0, :] = cache_values[n, k, 1, :]
291            cache_values[n, k, -1, :] = cache_values[n, k, -2, :]
292
293        self.assertAllClose(cache_values, value)
294
295  def testConv2DTransposeShapeInference(self):
296    # Test case for 8972
297    initializer = random_ops.truncated_normal(
298        [3, 3, 5, 1], mean=0.0, stddev=0.01, dtype=dtypes.float32)
299    x = variables.Variable(random_ops.random_normal([3, 10, 5, 1]))
300    f = variable_scope.get_variable("f", initializer=initializer)
301    f_shape = array_ops.stack([array_ops.shape(x)[0], 10, 5, 5])
302    output = nn_ops.conv2d_transpose(
303        x, f, f_shape, strides=[1, 1, 1, 1], padding="SAME")
304    self.assertEqual(output.get_shape().as_list(), [None, 10, 5, 5])
305
306if __name__ == "__main__":
307  test.main()
308