• 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"""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