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"""Tests for fractional average pool operation.""" 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 24from tensorflow.python.framework import test_util 25from tensorflow.python.ops import array_ops 26from tensorflow.python.ops import gen_nn_ops 27from tensorflow.python.ops import gradient_checker 28from tensorflow.python.ops import nn_ops 29import tensorflow.python.ops.nn_grad # pylint: disable=unused-import 30from tensorflow.python.platform import test 31 32 33class FractionalAvgTest(test.TestCase): 34 35 # Random number generate with seed. 36 _PRNG = np.random.RandomState(341261000) 37 _SEED = 341261001 38 39 def _AvgPoolAlongRows(self, input_matrix, row_seq, overlapping): 40 """Perform average pool along row of a 2-D matrix based on row_seq. 41 42 Args: 43 input_matrix: A 2-D matrix. 44 row_seq: Cumulative pooling sequence along row. 45 overlapping: Whether or not use overlapping when pooling. 46 47 Returns: 48 A 2-D matrix, with 49 * num_rows = len(row_seq)-1 50 * num_cols = input_matrix.num_cols. 51 """ 52 output_image = np.zeros(input_matrix.shape[1]) 53 row_max = row_seq[-1] 54 for i in range(row_seq.shape[0] - 1): 55 row_start = row_seq[i] 56 row_end = row_seq[i + 1] + 1 if overlapping else row_seq[i + 1] 57 row_end = min(row_end, row_max) 58 output_image = np.vstack((output_image, np.mean( 59 input_matrix[row_start:row_end, :], axis=0))) # axis 0 is along row 60 # remove the sentinel row 61 return output_image[1:, :] 62 63 def _AvgPoolAlongCols(self, input_matrix, col_seq, overlapping): 64 """Perform average pool along column of a 2-D matrix based on col_seq. 65 66 Args: 67 input_matrix: A 2-D matrix. 68 col_seq: Cumulative pooling sequence along column. 69 overlapping: Whether or not use overlapping when pooling. 70 71 Returns: 72 A 2-D matrix, with 73 * num_rows = input_matrix.num_rows 74 * num_cols = len(col_seq)-1. 75 """ 76 input_matrix = input_matrix.transpose() 77 output_matrix = self._AvgPoolAlongRows(input_matrix, col_seq, overlapping) 78 return output_matrix.transpose() 79 80 def _GetExpectedFractionalAvgPoolResult(self, input_tensor, row_seq, col_seq, 81 overlapping): 82 """Get expected fractional average pooling result. 83 84 row_seq and col_seq together defines the fractional pooling region. 85 86 Args: 87 input_tensor: Original input tensor, assuming it is a 4-D tensor, with 88 dimension as [batch, height/row, width/column, channels/depth]. 89 row_seq: Cumulative pooling sequence along row. 90 col_seq: Cumulative pooling sequence along column. 91 overlapping: Use overlapping when doing pooling. 92 93 Returns: 94 A 4-D tensor that is the result of average pooling on input_tensor based 95 on pooling region defined by row_seq and col_seq, conditioned on whether 96 or not overlapping is used. 97 """ 98 input_shape = input_tensor.shape 99 output_shape = (input_shape[0], len(row_seq) - 1, len(col_seq) - 1, 100 input_shape[3]) 101 output_tensor = np.zeros(shape=output_shape, dtype=input_tensor.dtype) 102 for batch in range(input_shape[0]): 103 for channel in range(input_shape[3]): 104 two_dim_slice = input_tensor[batch, :, :, channel] 105 tmp = self._AvgPoolAlongRows(two_dim_slice, row_seq, overlapping) 106 output_tensor[batch, :, :, channel] = self._AvgPoolAlongCols( 107 tmp, col_seq, overlapping) 108 109 return output_tensor 110 111 def _ValidateFractionalAvgPoolResult(self, input_tensor, pooling_ratio, 112 pseudo_random, overlapping): 113 """Validate FractionalAvgPool's result against expected. 114 115 Expected result is computed given input_tensor, and pooling region defined 116 by row_seq and col_seq. 117 118 Args: 119 input_tensor: A tensor or numpy ndarray. 120 pooling_ratio: A list or tuple of length 4, first and last element be 1. 121 pseudo_random: Use pseudo random method to generate pooling sequence. 122 overlapping: Use overlapping when pooling. 123 124 Returns: 125 None 126 """ 127 with self.cached_session() as sess: 128 p, r, c = nn_ops.fractional_avg_pool_v2( 129 input_tensor, 130 pooling_ratio, 131 pseudo_random, 132 overlapping, 133 seed=self._SEED) 134 actual, row_seq, col_seq = self.evaluate([p, r, c]) 135 expected = self._GetExpectedFractionalAvgPoolResult(input_tensor, row_seq, 136 col_seq, overlapping) 137 self.assertShapeEqual(expected, p) 138 self.assertAllClose(expected, actual) 139 140 def _testVisually(self): 141 """Manual test by printing out intermediate result of a small random tensor. 142 143 Since _GetExpectedFractionalAvgPoolResult is 'automated', it feels safer to 144 have a test case that you can see what's happening. 145 This test will generate a small, random, int 2D matrix, and feed it to 146 FractionalAvgPool and _GetExpectedFractionalAvgPoolResult. 147 """ 148 num_rows = 6 149 num_cols = 6 150 tensor_shape = (1, num_rows, num_cols, 1) 151 pseudo_random = False 152 for overlapping in True, False: 153 print("-" * 70) 154 print("Testing FractionalAvgPool with overlapping = {}".format( 155 overlapping)) 156 rand_mat = self._PRNG.randint(10, size=tensor_shape) 157 pooling_ratio = [1, math.sqrt(2), math.sqrt(2), 1] 158 with self.cached_session() as sess: 159 p, r, c = nn_ops.fractional_avg_pool_v2( 160 rand_mat.astype(np.float32), 161 pooling_ratio, 162 pseudo_random, 163 overlapping, 164 seed=self._SEED) 165 tensor_output, row_seq, col_seq = self.evaluate([p, r, c]) 166 expected_result = self._GetExpectedFractionalAvgPoolResult( 167 rand_mat.astype(np.float32), row_seq, col_seq, overlapping) 168 print("row sequence:") 169 print(row_seq) 170 print("column sequence:") 171 print(col_seq) 172 173 print("Input:") 174 # Print input with pooling region marked. 175 for i in range(num_rows): 176 row_to_print = [] 177 for j in range(num_cols): 178 if j in col_seq: 179 row_to_print.append("|") 180 row_to_print.append(str(rand_mat[0, i, j, 0])) 181 row_to_print.append("|") 182 if i in row_seq: 183 print("-" * 2 * len(row_to_print)) 184 print(" ".join(row_to_print)) 185 print("-" * 2 * len(row_to_print)) 186 187 print("Output from FractionalAvgPool:") 188 print(tensor_output[0, :, :, 0]) 189 print("Expected result:") 190 print(expected_result[0, :, :, 0]) 191 192 def testAllInputOptions(self): 193 """Try all possible input options for fractional_avg_pool. 194 """ 195 num_batches = 5 196 num_channels = 3 197 num_rows = 20 198 num_cols = 30 199 for pseudo_random in True, False: 200 for overlapping in True, False: 201 tensor_shape = (num_batches, num_rows, num_cols, num_channels) 202 # random tensor with value in [-500.0, 500.0) 203 rand_mat = self._PRNG.random_sample(tensor_shape) * 1000 - 500 204 self._ValidateFractionalAvgPoolResult( 205 rand_mat, [1, math.sqrt(3), math.sqrt(2), 1], pseudo_random, 206 overlapping) 207 208 def testIntegerTensorInput(self): 209 """Test FractionalAvgPool works fine when input tensor is integer type. 210 """ 211 pseudo_random = True 212 overlapping = True 213 tensor_shape = (1, 6, 6, 1) 214 # pyformat: disable 215 mat = np.array([ 216 [2, 6, 4, 1, 3, 6], 217 [8, 9, 1, 6, 6, 8], 218 [3, 9, 8, 2, 5, 6], 219 [2, 7, 9, 5, 4, 5], 220 [8, 5, 0, 5, 7, 4], 221 [4, 4, 5, 9, 7, 2] 222 ]) 223 # pyformat: enable 224 self._ValidateFractionalAvgPoolResult(mat.reshape(tensor_shape), 225 [1, math.sqrt(3), math.sqrt(2), 1], 226 pseudo_random, overlapping) 227 228 def testDifferentTensorShapes(self): 229 """Test different shapes of input tensor. 230 231 Mainly test different combinations of num_rows and num_cols. 232 """ 233 pseudo_random = True 234 overlapping = True 235 for num_batches in [1, 3]: 236 for num_channels in [1, 3]: 237 for num_rows in [10, 20, 50]: 238 for num_cols in [10, 20, 50]: 239 tensor_shape = (num_batches, num_rows, num_cols, num_channels) 240 # random tensor with value in [-500.0, 500.0) 241 rand_mat = self._PRNG.random_sample(tensor_shape) * 1000 - 500 242 self._ValidateFractionalAvgPoolResult( 243 rand_mat, [1, math.sqrt(3), math.sqrt(2), 1], pseudo_random, 244 overlapping) 245 246 def testLargePoolingRatio(self): 247 """Test when pooling ratio is not within [1, 2). 248 """ 249 pseudo_random = True 250 overlapping = True 251 num_batches = 3 252 num_channels = 3 253 num_rows = 30 254 num_cols = 50 255 tensor_shape = (num_batches, num_rows, num_cols, num_channels) 256 for row_ratio in [math.sqrt(11), math.sqrt(37)]: 257 for col_ratio in [math.sqrt(11), math.sqrt(27)]: 258 # random tensor with value in [-500.0, 500.0) 259 rand_mat = self._PRNG.random_sample(tensor_shape) * 1000 - 500 260 self._ValidateFractionalAvgPoolResult(rand_mat, 261 [1, row_ratio, col_ratio, 1], 262 pseudo_random, overlapping) 263 264 def testDivisiblePoolingRatio(self): 265 """Test when num of rows/cols can evenly divide pooling ratio. 266 267 This is a case regular average pooling can handle. Should be handled by 268 fractional pooling as well. 269 """ 270 pseudo_random = True 271 overlapping = True 272 num_batches = 3 273 num_channels = 3 274 num_rows = 30 275 num_cols = 50 276 tensor_shape = (num_batches, num_rows, num_cols, num_channels) 277 # random tensor with value in [-500.0, 500.0) 278 rand_mat = self._PRNG.random_sample(tensor_shape) * 1000 - 500 279 self._ValidateFractionalAvgPoolResult(rand_mat, [1, 2, 2, 1], pseudo_random, 280 overlapping) 281 282 @test_util.run_deprecated_v1 283 def testDifferentInputTensorShape(self): 284 """Runs the operation in one session with different input tensor shapes.""" 285 with self.cached_session() as sess: 286 input_holder = array_ops.placeholder(dtypes.float32, 287 [None, None, None, 3]) 288 pooling_ratio = [1, 1.5, 1.5, 1] 289 pseudo_random = False 290 overlapping = False 291 p, r, c = nn_ops.fractional_avg_pool_v2( 292 input_holder, 293 pooling_ratio, 294 pseudo_random, 295 overlapping, 296 seed=self._SEED) 297 # First run. 298 input_a = np.zeros([3, 32, 32, 3]) 299 actual, row_seq, col_seq = sess.run([p, r, c], {input_holder: input_a}) 300 expected = self._GetExpectedFractionalAvgPoolResult( 301 input_a, row_seq, col_seq, overlapping) 302 self.assertSequenceEqual(expected.shape, actual.shape) 303 # Second run. 304 input_b = np.zeros([4, 60, 60, 3]) 305 actual, row_seq, col_seq = sess.run([p, r, c], {input_holder: input_b}) 306 expected = self._GetExpectedFractionalAvgPoolResult( 307 input_b, row_seq, col_seq, overlapping) 308 self.assertSequenceEqual(expected.shape, actual.shape) 309 310 def testNegativeSeqValuesForGradOp(self): 311 with self.assertRaisesRegex( 312 errors.InvalidArgumentError, 313 r"Row sequence tensor values must not be negative.*"): 314 y = nn_ops.gen_nn_ops.fractional_avg_pool_grad( 315 orig_input_tensor_shape=[2, 2, 2, 2], 316 out_backprop=[[[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 317 12]]]], 318 row_pooling_sequence=[-10, 1, 2, 3], 319 col_pooling_sequence=[1, 2, 3, 4], 320 overlapping=True) 321 322 self.evaluate(y) 323 with self.assertRaisesRegex( 324 errors.InvalidArgumentError, 325 r"Column sequence tensor values must not be negative.*"): 326 z = nn_ops.gen_nn_ops.fractional_avg_pool_grad( 327 orig_input_tensor_shape=[2, 2, 2, 2], 328 out_backprop=[[[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 329 12]]]], 330 row_pooling_sequence=[10, 1, 2, 3], 331 col_pooling_sequence=[1, 2, -3, 4], 332 overlapping=True) 333 334 self.evaluate(z) 335 336 337class FractionalAvgPoolGradTest(test.TestCase): 338 """Tests for FractionalAvgPoolGrad. 339 340 Two types of tests for FractionalAvgPoolGrad. 341 1) Test fractional_avg_pool_grad() directly. 342 This type of test relies on gen_nn_ops.avg_pool_grad() returns the 343 correct result. For example: 344 * input_tensor_shape = (1, 10, 10, 1) 345 * window_size = (1, 2, 2, 1) 346 * stride_size = (1, 2, 2, 1) 347 * padding: not really important, since 10/2 is divisible 348 avg pooling should generate the same result as fractional avg pooling with: 349 * row_sequence = [0, 2, 4, 6, 8, 10] 350 * col_sequence = [0, 2, 4, 6, 8, 10] 351 * overlapping = False 352 This also means their gradients in such case will be the same. 353 354 Similarly, when 355 * input_tensor_shape = (1, 7, 7, 1) 356 * window_size = (1, 3, 3, 1) 357 * stride_size = (1, 2, 2, 1) 358 * padding: not important 359 avg pooling should generate the same result as fractional avg pooling with: 360 * row_sequence = [0, 2, 4, 7] 361 * col_sequence = [0, 2, 4, 7] 362 * overlapping = True 363 2) Test through compute_gradient_error() 364 """ 365 _PRNG = np.random.RandomState(341261004) 366 _SEED = 341261005 367 368 def _GenerateRandomInputTensor(self, shape): 369 num_elements = 1 370 for dim_size in shape: 371 num_elements *= dim_size 372 x = self._PRNG.rand(num_elements) * 1000 373 return x.reshape(shape) 374 375 def testDirectNotUseOverlapping(self): 376 for num_batches in [1, 3]: 377 for row_window_size in [2, 5]: 378 for col_window_size in [2, 4]: 379 num_rows = row_window_size * 5 380 num_cols = col_window_size * 7 381 for num_channels in [1, 2]: 382 input_shape = (num_batches, num_rows, num_cols, num_channels) 383 with self.cached_session() as _: 384 input_tensor = constant_op.constant( 385 self._GenerateRandomInputTensor(input_shape).astype( 386 np.float32)) 387 window_size = [1, row_window_size, col_window_size, 1] 388 stride_size = [1, row_window_size, col_window_size, 1] 389 padding = "VALID" 390 output_tensor = nn_ops.avg_pool(input_tensor, window_size, 391 stride_size, padding) 392 output_data = self.evaluate(output_tensor) 393 num_elements = 1 394 for dim_size in output_data.shape: 395 num_elements *= dim_size 396 output_backprop = (self._PRNG.rand(num_elements) * 397 1000).reshape(output_data.shape) 398 input_backprop_tensor = gen_nn_ops.avg_pool_grad( 399 input_tensor.get_shape(), output_backprop, window_size, 400 stride_size, padding) 401 input_backprop = self.evaluate(input_backprop_tensor) 402 row_seq = list(range(0, num_rows + 1, row_window_size)) 403 col_seq = list(range(0, num_cols + 1, col_window_size)) 404 fap_input_backprop_tensor = gen_nn_ops.fractional_avg_pool_grad( 405 input_tensor.get_shape(), 406 output_backprop, 407 row_seq, 408 col_seq, 409 overlapping=False) 410 fap_input_backprop = self.evaluate(fap_input_backprop_tensor) 411 self.assertShapeEqual(input_backprop, fap_input_backprop_tensor) 412 self.assertAllClose(input_backprop, fap_input_backprop) 413 414 def testDirectUseOverlapping(self): 415 for num_batches in [1, 3]: 416 for row_window_size in [2, 5]: 417 for col_window_size in [2, 4]: 418 num_rows = (row_window_size - 1) * 5 + 1 419 num_cols = (col_window_size - 1) * 7 + 1 420 for num_channels in [1, 2]: 421 input_shape = (num_batches, num_rows, num_cols, num_channels) 422 with self.cached_session() as _: 423 input_tensor = constant_op.constant( 424 self._GenerateRandomInputTensor(input_shape).astype( 425 np.float32)) 426 window_size = [1, row_window_size, col_window_size, 1] 427 stride_size = [1, row_window_size - 1, col_window_size - 1, 1] 428 padding = "VALID" 429 output_tensor = nn_ops.avg_pool(input_tensor, window_size, 430 stride_size, padding) 431 output_data = self.evaluate(output_tensor) 432 num_elements = 1 433 for dim_size in output_data.shape: 434 num_elements *= dim_size 435 output_backprop = (self._PRNG.rand(num_elements) * 436 1000).reshape(output_data.shape) 437 input_backprop_tensor = gen_nn_ops.avg_pool_grad( 438 input_tensor.get_shape(), output_backprop, window_size, 439 stride_size, padding) 440 input_backprop = self.evaluate(input_backprop_tensor) 441 row_seq = list(range(0, num_rows, row_window_size - 1)) 442 col_seq = list(range(0, num_cols, col_window_size - 1)) 443 row_seq[-1] += 1 444 col_seq[-1] += 1 445 fap_input_backprop_tensor = gen_nn_ops.fractional_avg_pool_grad( 446 input_tensor.get_shape(), 447 output_backprop, 448 row_seq, 449 col_seq, 450 overlapping=True) 451 fap_input_backprop = self.evaluate(fap_input_backprop_tensor) 452 self.assertShapeEqual(input_backprop, fap_input_backprop_tensor) 453 self.assertAllClose(input_backprop, fap_input_backprop) 454 455 @test_util.run_deprecated_v1 456 def testAllInputOptionsThroughGradientError(self): 457 input_shape = (1, 7, 13, 1) 458 input_data = self._GenerateRandomInputTensor(input_shape) 459 pooling_ratio = [1, math.sqrt(2), math.sqrt(3), 1] 460 461 for pseudo_random in True, False: 462 for overlapping in True, False: 463 with self.cached_session() as _: 464 input_tensor = constant_op.constant(input_data, shape=input_shape) 465 output_tensor, unused_a, unused_b = nn_ops.fractional_avg_pool_v2( 466 input_tensor, 467 pooling_ratio, 468 pseudo_random=pseudo_random, 469 overlapping=overlapping, 470 seed=self._SEED) 471 output_data = self.evaluate(output_tensor) 472 output_shape = output_data.shape 473 # error_margin and delta setting is similar to avg_pool_grad. 474 error_margin = 1e-4 475 gradient_error = gradient_checker.compute_gradient_error( 476 input_tensor, 477 input_shape, 478 output_tensor, 479 output_shape, 480 x_init_value=input_data.reshape(input_shape), 481 delta=1e-2) 482 self.assertLess(gradient_error, error_margin) 483 484 @test_util.run_deprecated_v1 485 def testDifferentTensorShapesThroughGradientError(self): 486 pseudo_random = True 487 overlapping = True 488 pooling_ratio = [1, math.sqrt(3), math.sqrt(2), 1] 489 for num_batches in [1, 2]: 490 for num_rows in [5, 13]: 491 for num_cols in [5, 11]: 492 for num_channels in [1, 3]: 493 input_shape = (num_batches, num_rows, num_cols, num_channels) 494 input_data = self._GenerateRandomInputTensor(input_shape) 495 with self.cached_session() as _: 496 input_tensor = constant_op.constant(input_data, shape=input_shape) 497 output_tensor, unused_a, unused_b = nn_ops.fractional_avg_pool_v2( 498 input_tensor, 499 pooling_ratio, 500 pseudo_random=pseudo_random, 501 overlapping=overlapping, 502 seed=self._SEED) 503 output_data = self.evaluate(output_tensor) 504 output_shape = output_data.shape 505 # error_margin and delta setting is similar to avg_pool_grad. 506 error_margin = 1e-4 507 gradient_error = gradient_checker.compute_gradient_error( 508 input_tensor, 509 input_shape, 510 output_tensor, 511 output_shape, 512 x_init_value=input_data.reshape(input_shape), 513 delta=1e-2) 514 self.assertLess(gradient_error, error_margin) 515 516 @test_util.run_deprecated_v1 517 def testLargePoolingRatioThroughGradientError(self): 518 input_shape = (1, 17, 23, 1) 519 input_data = self._GenerateRandomInputTensor(input_shape) 520 pooling_ratio = (1, math.sqrt(13), math.sqrt(7), 1) 521 output_shape = [int(a / b) for a, b in zip(input_shape, pooling_ratio)] 522 overlapping = True 523 pseudo_random = False 524 525 with self.cached_session() as _: 526 input_tensor = constant_op.constant(input_data, shape=input_shape) 527 output_tensor, unused_a, unused_b = nn_ops.fractional_avg_pool_v2( 528 input_tensor, 529 pooling_ratio, 530 pseudo_random=pseudo_random, 531 overlapping=overlapping, 532 seed=self._SEED) 533 # error_margin and delta setting is similar to avg_pool_grad. 534 error_margin = 1e-4 535 gradient_error = gradient_checker.compute_gradient_error( 536 input_tensor, 537 input_shape, 538 output_tensor, 539 output_shape, 540 x_init_value=input_data.reshape(input_shape), 541 delta=1e-2) 542 self.assertLess(gradient_error, error_margin) 543 544 def testInvalidSeqRaiseErrorForFractionalAvgPoolGrad(self): 545 with self.assertRaises((errors.InvalidArgumentError, ValueError)): 546 with self.cached_session() as _: 547 overlapping = True 548 orig_input_tensor_shape = constant_op.constant( 549 -1879048192, shape=[4], dtype=dtypes.int64) 550 out_backprop = constant_op.constant([], 551 shape=[0, 0, 0, 0], 552 dtype=dtypes.float64) 553 row_pooling_sequence = constant_op.constant( 554 1, shape=[4], dtype=dtypes.int64) 555 col_pooling_sequence = constant_op.constant( 556 1, shape=[4], dtype=dtypes.int64) 557 t = gen_nn_ops.fractional_avg_pool_grad( 558 orig_input_tensor_shape=orig_input_tensor_shape, 559 out_backprop=out_backprop, 560 row_pooling_sequence=row_pooling_sequence, 561 col_pooling_sequence=col_pooling_sequence, 562 overlapping=overlapping) 563 self.evaluate(t) 564 565 566if __name__ == "__main__": 567 test.main() 568