• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 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 3d convolutional operations."""
16
17import math
18
19import numpy as np
20
21from tensorflow.python.framework import constant_op
22from tensorflow.python.framework import dtypes
23from tensorflow.python.framework import errors_impl
24from tensorflow.python.framework import tensor_shape
25from tensorflow.python.framework import test_util
26from tensorflow.python.ops import gradient_checker
27from tensorflow.python.ops import gradients_impl
28from tensorflow.python.ops import nn_ops
29import tensorflow.python.ops.nn_grad  # pylint: disable=unused-import
30from tensorflow.python.platform import test
31from tensorflow.python.util.compat import collections_abc
32from tensorflow.python.eager import context
33
34
35def GetTestConfigs():
36  """Get all the valid tests configs to run.
37
38  Returns:
39    all the valid test configs as tuples of data_format and use_gpu.
40  """
41  test_configs = [("NDHWC", False), ("NDHWC", True)]
42  if test.is_gpu_available(cuda_only=True):
43    # "NCDHW" format is only supported on CUDA.
44    test_configs += [("NCDHW", True)]
45  return test_configs
46
47
48@test_util.run_all_without_tensor_float_32(
49    "Tests Conv3d, which in some cases is implemented with a matmul. With "
50    "TensorFloat-32, tests fail in some of those cases (and as of August 13 "
51    "2020, only those cases)")
52class Conv3DTest(test.TestCase):
53
54  def _DtypesToTest(self, use_gpu):
55    # double datatype is currently not supported for convolution ops
56    # on the ROCm platform
57    optional_float64 = [] if test.is_built_with_rocm() else [dtypes.float64]
58    if use_gpu:
59      if not test_util.GpuSupportsHalfMatMulAndConv():
60        return optional_float64 + [dtypes.float32]
61      else:
62        # It is important that float32 comes before float16 here,
63        # as we will be using its gradients as reference for fp16 gradients.
64        return optional_float64 + [dtypes.float32, dtypes.float16]
65    else:
66      return optional_float64 + [dtypes.float32, dtypes.float16]
67
68  def _SetupValuesForDevice(self, tensor_in_sizes, filter_in_sizes, stride,
69                            padding, data_format, dtype, use_gpu):
70    total_size_tensor = np.prod(tensor_in_sizes)
71    total_size_filter = np.prod(filter_in_sizes)
72
73    # Initializes the input tensor with array containing numbers from 0 to 1.
74    # We keep the input tensor values fairly small to avoid overflowing float16
75    # during the conv3d.
76    x1 = [f * 1.0 / total_size_tensor for f in range(1, total_size_tensor + 1)]
77    x2 = [f * 1.0 / total_size_filter for f in range(1, total_size_filter + 1)]
78    with self.cached_session(use_gpu=use_gpu):
79      t1 = constant_op.constant(x1, shape=tensor_in_sizes, dtype=dtype)
80      t2 = constant_op.constant(x2, shape=filter_in_sizes, dtype=dtype)
81
82      if isinstance(stride, collections_abc.Iterable):
83        strides = [1] + list(stride) + [1]
84      else:
85        strides = [1, stride, stride, stride, 1]
86
87      if data_format == "NCDHW":
88        t1 = test_util.NHWCToNCHW(t1)
89        strides = test_util.NHWCToNCHW(strides)
90      conv = nn_ops.conv3d(t1, t2, strides, padding=padding,
91                           data_format=data_format)
92      if data_format == "NCDHW":
93        conv = test_util.NCHWToNHWC(conv)
94
95      return conv
96
97  def _VerifyValues(self, tensor_in_sizes, filter_in_sizes, stride, padding,
98                    expected):
99    results = []
100    for data_format, use_gpu in GetTestConfigs():
101      for dtype in self._DtypesToTest(use_gpu):
102        result = self._SetupValuesForDevice(
103            tensor_in_sizes,
104            filter_in_sizes,
105            stride,
106            padding,
107            data_format,
108            dtype,
109            use_gpu=use_gpu)
110        results.append(result)
111
112      with self.cached_session() as sess:
113        values = self.evaluate(results)
114        for value in values:
115          print("expected = ", expected)
116          print("actual = ", value)
117          tol = 1e-6
118          if value.dtype == np.float16:
119            tol = 1e-3
120
121          self.assertAllClose(expected, value.flatten(), atol=tol, rtol=tol)
122
123  def _ComputeReferenceDilatedConv(self, tensor_in_sizes, filter_in_sizes,
124                                   stride, dilation, padding, data_format,
125                                   use_gpu):
126    total_size_tensor = np.prod(tensor_in_sizes)
127    total_size_filter = np.prod(filter_in_sizes)
128
129    # Initializes the input tensor with array containing incrementing
130    # numbers from 1.
131    x1 = [f * 1.0 for f in range(1, total_size_tensor + 1)]
132    x2 = [f * 1.0 for f in range(1, total_size_filter + 1)]
133    with self.cached_session(use_gpu=use_gpu):
134      t1 = constant_op.constant(x1, shape=tensor_in_sizes)
135      t2 = constant_op.constant(x2, shape=filter_in_sizes)
136      if isinstance(stride, collections_abc.Iterable):
137        strides = list(stride)
138      else:
139        strides = [stride, stride, stride]
140      if data_format == "NCDHW":
141        t1 = test_util.NHWCToNCHW(t1)
142        full_strides = [1, 1] + strides
143        full_dilation = [1, 1] + dilation
144      else:
145        full_strides = [1] + strides + [1]
146        full_dilation = [1] + dilation + [1]
147      expected = nn_ops.convolution(
148          t1,
149          t2,
150          padding=padding,
151          strides=strides,
152          dilation_rate=dilation,
153          data_format=data_format)
154      computed = nn_ops.conv3d(
155          t1,
156          t2,
157          strides=full_strides,
158          dilations=full_dilation,
159          padding=padding,
160          data_format=data_format)
161      if data_format == "NCDHW":
162        expected = test_util.NCHWToNHWC(expected)
163        computed = test_util.NCHWToNHWC(computed)
164    return expected, computed
165
166  def _VerifyDilatedConvValues(self, tensor_in_sizes, filter_in_sizes, stride,
167                               padding, dilations):
168    expected_results = []
169    computed_results = []
170    default_dilations = (
171        dilations[0] == 1 and dilations[1] == 1 and dilations[2] == 1)
172    for data_format, use_gpu in GetTestConfigs():
173      # If any dilation rate is larger than 1, only do test on the GPU
174      # because we currently do not have a CPU implementation for arbitrary
175      # dilation rates.
176      if default_dilations or use_gpu:
177        expected, computed = self._ComputeReferenceDilatedConv(
178            tensor_in_sizes, filter_in_sizes, stride, dilations, padding,
179            data_format, use_gpu)
180        expected_results.append(expected)
181        computed_results.append(computed)
182        tolerance = 1e-2 if use_gpu else 1e-5
183        with self.cached_session() as sess:
184          expected_values = self.evaluate(expected_results)
185          computed_values = self.evaluate(computed_results)
186          for e_value, c_value in zip(expected_values, computed_values):
187            print("expected = ", e_value)
188            print("actual = ", c_value)
189            self.assertAllClose(
190                e_value.flatten(), c_value.flatten(), atol=tolerance, rtol=1e-6)
191
192  def _CreateNumpyTensor(self, sizes):
193    return np.asarray([f * 1.0 for f in range(1,
194                                              np.prod(sizes) + 1)],
195                      dtype=np.float32).reshape(sizes)
196
197  @test_util.run_in_graph_and_eager_modes
198  def testConv3DExpandedBatch(self):
199    tensor_in_sizes_batch = [10, 2, 3, 1, 3]
200    tensor_in_sizes_expanded_batch = [2, 5, 2, 3, 1, 3]
201    filter_in_sizes = [1, 1, 1, 3, 3]
202    filter_in = self._CreateNumpyTensor(filter_in_sizes)
203    x1 = self._CreateNumpyTensor(tensor_in_sizes_batch)
204    x2 = x1.reshape(tensor_in_sizes_expanded_batch)
205    conv1 = nn_ops.conv3d_v2(
206        x1, filter_in, strides=[1, 1, 1, 1, 1], padding="VALID")
207    conv2 = nn_ops.conv3d_v2(
208        x2, filter_in, strides=[1, 1, 1, 1, 1], padding="VALID")
209    self.assertEqual(conv1.shape, tensor_in_sizes_batch)
210    self.assertEqual(conv2.shape, tensor_in_sizes_expanded_batch)
211    self.assertAllClose(conv1, self.evaluate(conv2).reshape(conv1.shape))
212
213  @test_util.run_in_graph_and_eager_modes
214  def testConvolutionClass3DExpandedBatch(self):
215    tensor_in_sizes_batch = [10, 2, 3, 1, 3]
216    tensor_in_sizes_expanded_batch = [2, 5, 2, 3, 1, 3]
217    filter_in_sizes = [1, 1, 1, 3, 3]
218    filter_in = self._CreateNumpyTensor(filter_in_sizes)
219    x1 = self._CreateNumpyTensor(tensor_in_sizes_batch)
220    x2 = x1.reshape(tensor_in_sizes_expanded_batch)
221    convolver1 = nn_ops.Convolution(
222        input_shape=x1.shape,
223        filter_shape=filter_in.shape,
224        strides=[1, 1, 1],
225        padding="VALID")
226    self.assertEqual(convolver1.num_batch_dims, 1)
227    convolver2 = nn_ops.Convolution(
228        input_shape=x2.shape,
229        filter_shape=filter_in.shape,
230        strides=[1, 1, 1],
231        padding="VALID")
232    self.assertEqual(convolver2.num_batch_dims, 2)
233    conv1 = convolver1(x1, filter_in)
234    conv2 = convolver2(x2, filter_in)
235    self.assertEqual(conv1.shape, tensor_in_sizes_batch)
236    self.assertEqual(conv2.shape, tensor_in_sizes_expanded_batch)
237    self.assertAllClose(conv1, self.evaluate(conv2).reshape(conv1.shape))
238
239  @test_util.run_in_graph_and_eager_modes
240  def testConvolutionWith2SpatialDimensionsAndExpandedBatch(self):
241    tensor_in_sizes_batch = [10, 2, 3, 1, 3]
242    tensor_in_sizes_expanded_batch = [2, 5, 2, 3, 1, 3]
243    filter_in_sizes = [1, 1, 1, 3, 3]
244    filter_in = self._CreateNumpyTensor(filter_in_sizes)
245    x1 = self._CreateNumpyTensor(tensor_in_sizes_batch)
246    x2 = x1.reshape(tensor_in_sizes_expanded_batch)
247    conv1 = nn_ops.convolution(
248        x1, filter_in, strides=[1, 1, 1], padding="VALID")
249    conv2 = nn_ops.convolution(
250        x2, filter_in, strides=[1, 1, 1], padding="VALID")
251    self.assertEqual(conv1.shape, tensor_in_sizes_batch)
252    self.assertEqual(conv2.shape, tensor_in_sizes_expanded_batch)
253    self.assertAllClose(conv1, self.evaluate(conv2).reshape(conv1.shape))
254
255  def testConv3D1x1x1Filter(self):
256    expected_output = [
257        0.18518519, 0.22222222, 0.25925926, 0.40740741, 0.5, 0.59259259,
258        0.62962963, 0.77777778, 0.92592593, 0.85185185, 1.05555556, 1.25925926,
259        1.07407407, 1.33333333, 1.59259259, 1.2962963, 1.61111111, 1.92592593
260    ]
261
262    # These are equivalent to the Conv2D1x1 case.
263    self._VerifyValues(
264        tensor_in_sizes=[1, 2, 3, 1, 3],
265        filter_in_sizes=[1, 1, 1, 3, 3],
266        stride=1,
267        padding="VALID",
268        expected=expected_output)
269    self._VerifyValues(
270        tensor_in_sizes=[1, 2, 1, 3, 3],
271        filter_in_sizes=[1, 1, 1, 3, 3],
272        stride=1,
273        padding="VALID",
274        expected=expected_output)
275    self._VerifyValues(
276        tensor_in_sizes=[1, 1, 2, 3, 3],
277        filter_in_sizes=[1, 1, 1, 3, 3],
278        stride=1,
279        padding="VALID",
280        expected=expected_output)
281
282  def testConv3D1x1x1Filter2x1x1Dilation(self):
283    ctx = context.context()
284    is_eager = ctx is not None and ctx.executing_eagerly()
285    if test.is_gpu_available(cuda_only=True) or \
286      (test_util.IsMklEnabled() and is_eager is False):
287      self._VerifyDilatedConvValues(
288          tensor_in_sizes=[1, 3, 6, 1, 1],
289          filter_in_sizes=[1, 1, 1, 1, 1],
290          stride=1,
291          padding="VALID",
292          dilations=[2, 1, 1])
293
294  # Expected values computed using scipy's correlate function.
295  def testConv3D2x2x2Filter(self):
296    expected_output = [
297        3.77199074, 3.85069444, 3.92939815, 4.2650463, 4.35763889, 4.45023148,
298        6.73032407, 6.89236111, 7.05439815, 7.22337963, 7.39930556, 7.57523148,
299        9.68865741, 9.93402778, 10.17939815, 10.18171296, 10.44097222,
300        10.70023148
301    ]
302    # expected_shape = [1, 3, 1, 2, 5]
303    self._VerifyValues(
304        tensor_in_sizes=[1, 4, 2, 3, 3],  # b, z, y, x, fin
305        filter_in_sizes=[2, 2, 2, 3, 3],  # z, y, x, fin, fout
306        stride=1,
307        padding="VALID",
308        expected=expected_output)
309
310  def testConv3D2x2x2Filter1x2x1Dilation(self):
311    ctx = context.context()
312    is_eager = ctx is not None and ctx.executing_eagerly()
313    if test.is_gpu_available(cuda_only=True) or \
314      (test_util.IsMklEnabled() and is_eager is False):
315      self._VerifyDilatedConvValues(
316          tensor_in_sizes=[1, 4, 6, 3, 1],
317          filter_in_sizes=[2, 2, 2, 1, 1],
318          stride=1,
319          padding="VALID",
320          dilations=[1, 2, 1])
321
322  def testConv3DStrides(self):
323    expected_output = [
324        0.06071429, 0.08988095, 0.10238095, 0.11488095, 0.12738095, 0.13988095,
325        0.08452381, 0.26071429, 0.35238095, 0.36488095, 0.37738095, 0.38988095,
326        0.40238095, 0.23452381, 0.46071429, 0.61488095, 0.62738095, 0.63988095,
327        0.65238095, 0.66488095, 0.38452381, 1.12738095, 1.48988095, 1.50238095,
328        1.51488095, 1.52738095, 1.53988095, 0.88452381, 1.32738095, 1.75238095,
329        1.76488095, 1.77738095, 1.78988095, 1.80238095, 1.03452381, 1.52738095,
330        2.01488095, 2.02738095, 2.03988095, 2.05238095, 2.06488095, 1.18452381,
331        2.19404762, 2.88988095, 2.90238095, 2.91488095, 2.92738095, 2.93988095,
332        1.68452381, 2.39404762, 3.15238095, 3.16488095, 3.17738095, 3.18988095,
333        3.20238095, 1.83452381, 2.59404762, 3.41488095, 3.42738095, 3.43988095,
334        3.45238095, 3.46488095, 1.98452381
335    ]
336    self._VerifyValues(
337        tensor_in_sizes=[1, 5, 8, 7, 1],
338        filter_in_sizes=[1, 2, 3, 1, 1],
339        stride=[2, 3, 1],  # different stride for each spatial dimension
340        padding="SAME",
341        expected=expected_output)
342
343  def testConv3D2x2x2FilterStride2(self):
344    expected_output = [
345        3.77199074, 3.85069444, 3.92939815, 9.68865741, 9.93402778, 10.17939815
346    ]
347    self._VerifyValues(
348        tensor_in_sizes=[1, 4, 2, 3, 3],
349        filter_in_sizes=[2, 2, 2, 3, 3],
350        stride=2,
351        padding="VALID",
352        expected=expected_output)
353
354  def testConv3DStride3(self):
355    expected_output = [
356        1.51140873, 1.57167659, 1.63194444, 1.56349206, 1.62673611, 1.68998016,
357        1.6155754, 1.68179563, 1.74801587, 1.9280754, 2.01215278, 2.09623016,
358        1.98015873, 2.0672123, 2.15426587, 2.03224206, 2.12227183, 2.21230159,
359        4.4280754, 4.65500992, 4.88194444, 4.48015873, 4.71006944, 4.93998016,
360        4.53224206, 4.76512897, 4.99801587, 4.84474206, 5.09548611, 5.34623016,
361        4.8968254, 5.15054563, 5.40426587, 4.94890873, 5.20560516, 5.46230159
362    ]
363    self._VerifyValues(
364        tensor_in_sizes=[1, 6, 7, 8, 2],
365        filter_in_sizes=[3, 2, 1, 2, 3],
366        stride=3,
367        padding="VALID",
368        expected=expected_output)
369
370  def testConv3D2x2x2FilterStride2Same(self):
371    expected_output = [
372        3.77199074, 3.85069444, 3.92939815, 2.0162037, 2.06597222, 2.11574074,
373        9.68865741, 9.93402778, 10.17939815, 4.59953704, 4.73263889, 4.86574074
374    ]
375    self._VerifyValues(
376        tensor_in_sizes=[1, 4, 2, 3, 3],
377        filter_in_sizes=[2, 2, 2, 3, 3],
378        stride=2,
379        padding="SAME",
380        expected=expected_output)
381
382  def _TestConv3DEmptyTensorOutputShape(self):
383    """Verifies the output shape of the Conv3D op when output tensor is empty.
384
385    Args: none
386    """
387    input_shape = [0, 112, 112, 112, 32]
388    filter_shape = [3, 3, 3, 32, 64]
389
390    output_shape = [0, 112, 112, 112, 64]
391    input_data = 1
392    filter_data = 1
393    for data_type in self._DtypesToTest(False):
394      input_tensor = constant_op.constant(
395          input_data, shape=input_shape, dtype=data_type, name="input")
396      filter_tensor = constant_op.constant(
397          filter_data, shape=filter_shape, dtype=data_type, name="filter")
398      conv = nn_ops.conv3d(
399          input_tensor,
400          filter_tensor,
401          strides=[1, 1, 1, 1, 1],
402          dilations=[1, 1, 1, 1, 1],
403          padding="SAME",
404          data_format="NDHWC",
405          name="conv")
406      values = self.evaluate(conv)
407      self.assertEqual(values.shape, tensor_shape.TensorShape(output_shape))
408
409  def testKernelSmallerThanStride(self):
410    expected_output = [
411        0.03703704, 0.11111111, 0.25925926, 0.33333333, 0.7037037, 0.77777778,
412        0.92592593, 1.
413    ]
414    self._VerifyValues(
415        tensor_in_sizes=[1, 3, 3, 3, 1],
416        filter_in_sizes=[1, 1, 1, 1, 1],
417        stride=2,
418        padding="SAME",
419        expected=expected_output)
420    self._VerifyValues(
421        tensor_in_sizes=[1, 3, 3, 3, 1],
422        filter_in_sizes=[1, 1, 1, 1, 1],
423        stride=2,
424        padding="VALID",
425        expected=expected_output)
426
427    expected_output = [
428        0.54081633, 0.58017493, 0.28061224, 0.81632653, 0.85568513, 0.40306122,
429        0.41873178, 0.4340379, 0.19642857, 2.46938776, 2.50874636, 1.1377551,
430        2.74489796, 2.78425656, 1.26020408, 1.16873178, 1.1840379, 0.51785714,
431        1.09511662, 1.10604956, 0.44642857, 1.17164723, 1.18258017, 0.47704082,
432        0.3691691, 0.37244898, 0.125
433    ]
434    self._VerifyValues(
435        tensor_in_sizes=[1, 7, 7, 7, 1],
436        filter_in_sizes=[2, 2, 2, 1, 1],
437        stride=3,
438        padding="SAME",
439        expected=expected_output)
440
441    expected_output = [
442        0.540816, 0.580175, 0.816327, 0.855685, 2.469388, 2.508746, 2.744898,
443        2.784257
444    ]
445    self._VerifyValues(
446        tensor_in_sizes=[1, 7, 7, 7, 1],
447        filter_in_sizes=[2, 2, 2, 1, 1],
448        stride=3,
449        padding="VALID",
450        expected=expected_output)
451
452  def testKernelSizeMatchesInputSize(self):
453    self._VerifyValues(
454        tensor_in_sizes=[1, 2, 1, 2, 1],
455        filter_in_sizes=[2, 1, 2, 1, 2],
456        stride=1,
457        padding="VALID",
458        expected=[1.5625, 1.875])
459
460  def testZeroSizedFilterThrowsIllegalArgument(self):
461    tensor_in_sizes = [1, 1, 1, 1, 1]
462    x1 = self._CreateNumpyTensor(tensor_in_sizes)
463    filter_in = np.ones((1, 1, 0, 1, 1), dtype=np.float32)
464    with self.assertRaisesRegex(
465        errors_impl.InvalidArgumentError, "filter must not have zero elements"
466        "|has a non-positive dimension"):
467      self.evaluate(
468          nn_ops.conv3d(x1, filter_in, strides=[1, 1, 1, 1, 1], padding="SAME"))
469
470  def _ConstructAndTestGradientForConfig(
471      self, batch, input_shape, filter_shape, in_depth, out_depth, stride,
472      padding, test_input, data_format, use_gpu):
473
474    input_planes, input_rows, input_cols = input_shape
475    filter_planes, filter_rows, filter_cols = filter_shape
476
477    input_shape = [batch, input_planes, input_rows, input_cols, in_depth]
478    filter_shape = [
479        filter_planes, filter_rows, filter_cols, in_depth, out_depth
480    ]
481
482    if isinstance(stride, collections_abc.Iterable):
483      strides = [1] + list(stride) + [1]
484    else:
485      strides = [1, stride, stride, stride, 1]
486
487    if padding == "VALID":
488      output_planes = int(
489          math.ceil((input_planes - filter_planes + 1.0) / strides[1]))
490      output_rows = int(
491          math.ceil((input_rows - filter_rows + 1.0) / strides[2]))
492      output_cols = int(
493          math.ceil((input_cols - filter_cols + 1.0) / strides[3]))
494    else:
495      output_planes = int(math.ceil(float(input_planes) / strides[1]))
496      output_rows = int(math.ceil(float(input_rows) / strides[2]))
497      output_cols = int(math.ceil(float(input_cols) / strides[3]))
498    output_shape = [batch, output_planes, output_rows, output_cols, out_depth]
499    input_size = 1
500    for x in input_shape:
501      input_size *= x
502    filter_size = 1
503    for x in filter_shape:
504      filter_size *= x
505    input_data = [x * 1.0 / input_size for x in range(0, input_size)]
506    filter_data = [x * 1.0 / filter_size for x in range(0, filter_size)]
507
508    for data_type in self._DtypesToTest(use_gpu=use_gpu):
509      # TODO(mjanusz): Modify gradient_checker to also provide max relative
510      # error and synchronize the tolerance levels between the tests for forward
511      # and backward computations.
512      if data_type == dtypes.float64:
513        tolerance = 1e-8
514      elif data_type == dtypes.float32:
515        tolerance = 5e-3
516      elif data_type == dtypes.float16:
517        tolerance = 5e-3 if test.is_built_with_rocm() else 1e-3
518
519      with self.cached_session(use_gpu=use_gpu):
520        orig_input_tensor = constant_op.constant(
521            input_data, shape=input_shape, dtype=data_type, name="input")
522        filter_tensor = constant_op.constant(
523            filter_data, shape=filter_shape, dtype=data_type, name="filter")
524
525        if data_format == "NCDHW":
526          input_tensor = test_util.NHWCToNCHW(orig_input_tensor)
527          new_strides = test_util.NHWCToNCHW(strides)
528        else:
529          input_tensor = orig_input_tensor
530          new_strides = strides
531
532        conv = nn_ops.conv3d(
533            input_tensor,
534            filter_tensor,
535            new_strides,
536            padding,
537            data_format=data_format,
538            name="conv")
539
540        if data_format == "NCDHW":
541          conv = test_util.NCHWToNHWC(conv)
542
543        self.assertEqual(conv.shape, tensor_shape.TensorShape(output_shape))
544
545        if test_input:
546          jacob_t, jacob_n = gradient_checker.compute_gradient(
547              orig_input_tensor, input_shape, conv, output_shape)
548        else:
549          jacob_t, jacob_n = gradient_checker.compute_gradient(
550              filter_tensor, filter_shape, conv, output_shape)
551
552        if data_type != dtypes.float16:
553          reference_jacob_t = jacob_t
554          err = np.fabs(jacob_t - jacob_n).max()
555        else:
556          # Compare fp16 theoretical gradients to fp32 theoretical gradients,
557          # since fp16 numerical gradients are too imprecise.
558          err = np.fabs(jacob_t - reference_jacob_t).max()
559
560      print("conv3d gradient error = ", err)
561      self.assertLess(err, tolerance)
562
563  def ConstructAndTestGradient(self, **kwargs):
564    for data_format, use_gpu in GetTestConfigs():
565      self._ConstructAndTestGradientForConfig(data_format=data_format,
566                                              use_gpu=use_gpu, **kwargs)
567
568  @test_util.run_deprecated_v1
569  def testInputGradientValidPaddingStrideOne(self):
570    self.ConstructAndTestGradient(
571        batch=2,
572        input_shape=(3, 5, 4),
573        filter_shape=(3, 3, 3),
574        in_depth=2,
575        out_depth=3,
576        stride=1,
577        padding="VALID",
578        test_input=True)
579
580  @test_util.run_deprecated_v1
581  def testFilterGradientValidPaddingStrideOne(self):
582    self.ConstructAndTestGradient(
583        batch=4,
584        input_shape=(4, 6, 5),
585        filter_shape=(2, 2, 2),
586        in_depth=2,
587        out_depth=3,
588        stride=1,
589        padding="VALID",
590        test_input=False)
591
592  @test_util.run_deprecated_v1
593  def testInputGradientValidPaddingStrideTwo(self):
594    self.ConstructAndTestGradient(
595        batch=2,
596        input_shape=(6, 3, 5),
597        filter_shape=(3, 3, 3),
598        in_depth=2,
599        out_depth=3,
600        stride=2,
601        padding="VALID",
602        test_input=True)
603
604  @test_util.run_deprecated_v1
605  def testFilterGradientValidPaddingStrideTwo(self):
606    self.ConstructAndTestGradient(
607        batch=2,
608        input_shape=(7, 6, 5),
609        filter_shape=(2, 2, 2),
610        in_depth=2,
611        out_depth=3,
612        stride=2,
613        padding="VALID",
614        test_input=False)
615
616  @test_util.run_deprecated_v1
617  def testInputGradientValidPaddingStrideThree(self):
618    self.ConstructAndTestGradient(
619        batch=2,
620        input_shape=(3, 7, 6),
621        filter_shape=(3, 3, 3),
622        in_depth=2,
623        out_depth=3,
624        stride=3,
625        padding="VALID",
626        test_input=True)
627
628  @test_util.run_deprecated_v1
629  def testFilterGradientValidPaddingStrideThree(self):
630    self.ConstructAndTestGradient(
631        batch=2,
632        input_shape=(4, 4, 7),
633        filter_shape=(4, 4, 4),
634        in_depth=2,
635        out_depth=3,
636        stride=3,
637        padding="VALID",
638        test_input=False)
639
640  @test_util.run_deprecated_v1
641  def testInputGradientSamePaddingStrideOne(self):
642    self.ConstructAndTestGradient(
643        batch=2,
644        input_shape=(3, 2, 2),
645        filter_shape=(3, 2, 1),
646        in_depth=2,
647        out_depth=1,
648        stride=1,
649        padding="SAME",
650        test_input=True)
651
652  @test_util.run_deprecated_v1
653  def testFilterGradientSamePaddingStrideOne(self):
654    self.ConstructAndTestGradient(
655        batch=2,
656        input_shape=(3, 6, 5),
657        filter_shape=(2, 2, 2),
658        in_depth=2,
659        out_depth=3,
660        stride=1,
661        padding="SAME",
662        test_input=False)
663
664  @test_util.run_deprecated_v1
665  def testInputGradientSamePaddingStrideTwo(self):
666    self.ConstructAndTestGradient(
667        batch=2,
668        input_shape=(6, 3, 4),
669        filter_shape=(3, 3, 3),
670        in_depth=2,
671        out_depth=3,
672        stride=2,
673        padding="SAME",
674        test_input=True)
675
676  @test_util.run_deprecated_v1
677  def testFilterGradientSamePaddingStrideTwo(self):
678    self.ConstructAndTestGradient(
679        batch=4,
680        input_shape=(7, 3, 5),
681        filter_shape=(2, 2, 2),
682        in_depth=2,
683        out_depth=3,
684        stride=2,
685        padding="SAME",
686        test_input=False)
687
688  @test_util.run_deprecated_v1
689  def testInputGradientSamePaddingStrideThree(self):
690    self.ConstructAndTestGradient(
691        batch=2,
692        input_shape=(9, 3, 6),
693        filter_shape=(3, 3, 3),
694        in_depth=2,
695        out_depth=3,
696        stride=3,
697        padding="SAME",
698        test_input=True)
699
700  @test_util.run_deprecated_v1
701  def testFilterGradientSamePaddingStrideThree(self):
702    self.ConstructAndTestGradient(
703        batch=2,
704        input_shape=(9, 4, 7),
705        filter_shape=(4, 4, 4),
706        in_depth=2,
707        out_depth=3,
708        stride=3,
709        padding="SAME",
710        test_input=False)
711
712  @test_util.run_deprecated_v1
713  def testInputGradientSamePaddingDifferentStrides(self):
714    self.ConstructAndTestGradient(
715        batch=1,
716        input_shape=(5, 8, 7),
717        filter_shape=(1, 2, 3),
718        in_depth=2,
719        out_depth=3,
720        stride=[2, 3, 1],
721        padding="SAME",
722        test_input=True)
723
724  @test_util.run_deprecated_v1
725  def testFilterGradientKernelSizeMatchesInputSize(self):
726    self.ConstructAndTestGradient(
727        batch=2,
728        input_shape=(5, 4, 3),
729        filter_shape=(5, 4, 3),
730        in_depth=2,
731        out_depth=3,
732        stride=1,
733        padding="VALID",
734        test_input=False)
735
736  @test_util.run_deprecated_v1
737  def testInputGradientKernelSizeMatchesInputSize(self):
738    self.ConstructAndTestGradient(
739        batch=2,
740        input_shape=(5, 4, 3),
741        filter_shape=(5, 4, 3),
742        in_depth=2,
743        out_depth=3,
744        stride=1,
745        padding="VALID",
746        test_input=True)
747
748  def disabledtestFilterGradientSamePaddingDifferentStrides(self):
749    self.ConstructAndTestGradient(
750        batch=1,
751        input_shape=(5, 8, 7),
752        filter_shape=(1, 2, 3),
753        in_depth=2,
754        out_depth=3,
755        stride=[2, 3, 1],
756        padding="SAME",
757        test_input=False)
758
759  # Test the fast path in gemm_pack_rhs/gemm_pack_colmajor_block, when channel
760  # dimension is a multiple of packet size.
761  @test_util.run_deprecated_v1
762  def testInputGradientValidPaddingStrideOneFastPath(self):
763    self.ConstructAndTestGradient(
764        batch=2,
765        input_shape=(3, 5, 4),
766        filter_shape=(2, 2, 2),
767        in_depth=8,
768        out_depth=2,
769        stride=1,
770        padding="VALID",
771        test_input=True)
772
773  @test_util.run_deprecated_v1
774  def testFilterGradientValidPaddingStrideOneFastPath(self):
775    self.ConstructAndTestGradient(
776        batch=2,
777        input_shape=(4, 6, 5),
778        filter_shape=(2, 2, 2),
779        in_depth=8,
780        out_depth=2,
781        stride=1,
782        padding="VALID",
783        test_input=False)
784
785  # Testing for backprops
786  def _RunAndVerifyBackprop(self, input_sizes, filter_sizes, output_sizes,
787                            strides, dilations, padding, data_format, use_gpu,
788                            err, mode):
789    total_input_size = 1
790    total_filter_size = 1
791    for s in input_sizes:
792      total_input_size *= s
793    for s in filter_sizes:
794      total_filter_size *= s
795    # Initializes the input tensor with array containing incrementing
796    # numbers from 1.
797    x1 = [f * 1.0 for f in range(1, total_input_size + 1)]
798    x2 = [f * 1.0 for f in range(1, total_filter_size + 1)]
799    default_dilations = (
800        dilations[0] == 1 and dilations[1] == 1 and dilations[2] == 1)
801
802    # If any dilation rate is larger than 1, only do test on the GPU
803    # because we currently do not have a CPU implementation for arbitrary
804    # dilation rates.
805    if default_dilations or use_gpu:
806      with self.cached_session(use_gpu=use_gpu) as sess:
807        if data_format == "NCDHW":
808          input_sizes = test_util.NHWCToNCHW(input_sizes)
809        t1 = constant_op.constant(x1, shape=input_sizes)
810        t2 = constant_op.constant(x2, shape=filter_sizes)
811        full_strides = [1] + strides + [1]
812        full_dilations = [1] + dilations + [1]
813        if data_format == "NCDHW":
814          full_strides = test_util.NHWCToNCHW(full_strides)
815          full_dilations = test_util.NHWCToNCHW(full_dilations)
816        actual = nn_ops.conv3d(
817            t1,
818            t2,
819            strides=full_strides,
820            dilations=full_dilations,
821            padding=padding,
822            data_format=data_format)
823        expected = nn_ops.convolution(
824            t1,
825            t2,
826            padding=padding,
827            strides=strides,
828            dilation_rate=dilations,
829            data_format=data_format)
830        if data_format == "NCDHW":
831          actual = test_util.NCHWToNHWC(actual)
832          expected = test_util.NCHWToNHWC(expected)
833        actual_grad = gradients_impl.gradients(actual, t1
834                                               if mode == "input" else t2)[0]
835        expected_grad = gradients_impl.gradients(expected, t1
836                                                 if mode == "input" else t2)[0]
837        # "values" consists of two tensors for two backprops
838        actual_value = self.evaluate(actual_grad)
839        expected_value = self.evaluate(expected_grad)
840        self.assertShapeEqual(actual_value, actual_grad)
841        self.assertShapeEqual(expected_value, expected_grad)
842      print("expected = ", expected_value)
843      print("actual = ", actual_value)
844      self.assertArrayNear(expected_value.flatten(), actual_value.flatten(),
845                           err)
846
847  @test_util.run_deprecated_v1
848  def testConv3D2x2Depth3ValidBackpropFilterStride1x1Dilation2x1(self):
849    if test.is_gpu_available(cuda_only=True):
850      for (data_format, use_gpu) in GetTestConfigs():
851        self._RunAndVerifyBackprop(
852            input_sizes=[1, 3, 6, 1, 1],
853            filter_sizes=[2, 2, 1, 1, 1],
854            output_sizes=[1, 1, 5, 1, 1],
855            strides=[1, 1, 1],
856            dilations=[2, 1, 1],
857            padding="VALID",
858            data_format=data_format,
859            use_gpu=use_gpu,
860            err=1e-5,
861            mode="filter")
862
863  @test_util.run_deprecated_v1
864  def testConv3D2x2Depth3ValidBackpropInputStride1x1Dilation2x1(self):
865    if test.is_gpu_available(cuda_only=True):
866      for (data_format, use_gpu) in GetTestConfigs():
867        self._RunAndVerifyBackprop(
868            input_sizes=[1, 3, 6, 1, 1],
869            filter_sizes=[2, 2, 1, 1, 1],
870            output_sizes=[1, 1, 5, 1, 1],
871            strides=[1, 1, 1],
872            dilations=[2, 1, 1],
873            padding="VALID",
874            data_format=data_format,
875            use_gpu=use_gpu,
876            err=1e-5,
877            mode="input")
878
879
880if __name__ == "__main__":
881  test.main()
882