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