In order to support tests running in eager mode we need to avoid unnecessary use of Sessions in tests. This moves to remove some of the uses of the `run` function in favor of `evaluate`. PiperOrigin-RevId: 223009795
206 lines
7.3 KiB
Python
206 lines
7.3 KiB
Python
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""Functional tests for quantized convolutional operations."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import numpy as np
|
|
|
|
from tensorflow.python.framework import constant_op
|
|
from tensorflow.python.framework import dtypes
|
|
from tensorflow.python.ops import nn_ops
|
|
from tensorflow.python.platform import test
|
|
|
|
|
|
class Conv2DTest(test.TestCase):
|
|
|
|
def __init__(self, method_name="runTest"):
|
|
super(Conv2DTest, self).__init__(method_name)
|
|
|
|
def _VerifyValues(self, tensor_in_sizes, filter_in_sizes, stride, padding,
|
|
expected):
|
|
"""Verifies the output values of the convolution function.
|
|
|
|
Args:
|
|
tensor_in_sizes: Input tensor dimensions in
|
|
[batch, input_rows, input_cols, input_depth].
|
|
filter_in_sizes: Filter tensor dimensions in
|
|
[kernel_rows, kernel_cols, input_depth, output_depth].
|
|
stride: Stride.
|
|
padding: Padding type.
|
|
expected: An array containing the expected operation outputs.
|
|
"""
|
|
total_size_1 = 1
|
|
total_size_2 = 1
|
|
for s in tensor_in_sizes:
|
|
total_size_1 *= s
|
|
for s in filter_in_sizes:
|
|
total_size_2 *= s
|
|
# Initializes the input tensor with array containing incrementing
|
|
# numbers from 1.
|
|
x1 = np.array([f for f in range(1, total_size_1 + 1)])
|
|
x1 = x1.astype(np.uint8).reshape(tensor_in_sizes)
|
|
x1_min = 0.0
|
|
x1_max = 255.0
|
|
x2 = np.array([f for f in range(1, total_size_2 + 1)]).astype(np.uint8)
|
|
x2 = x2.astype(np.uint8).reshape(filter_in_sizes)
|
|
x2_min = 0.0
|
|
x2_max = 255.0
|
|
with self.cached_session(use_gpu=False) as sess:
|
|
t1 = constant_op.constant(x1, shape=tensor_in_sizes, dtype=dtypes.quint8)
|
|
t2 = constant_op.constant(x2, shape=filter_in_sizes, dtype=dtypes.quint8)
|
|
conv = nn_ops.quantized_conv2d(
|
|
t1,
|
|
t2,
|
|
out_type=dtypes.qint32,
|
|
strides=[1, stride, stride, 1],
|
|
padding=padding,
|
|
min_input=x1_min,
|
|
max_input=x1_max,
|
|
min_filter=x2_min,
|
|
max_filter=x2_max)
|
|
value = self.evaluate(conv)
|
|
quantized_output = value[0]
|
|
output_min = value[1]
|
|
output_max = value[2]
|
|
float_output = self._QuantizedOutputToFloat(quantized_output, output_min,
|
|
output_max)
|
|
self.assertArrayNear(expected, float_output.flatten(), 1.0)
|
|
self.assertEqual(value[0].shape, conv[0].get_shape())
|
|
|
|
def _assertQuantizedArrayEquals(self, iarray1, iarray2):
|
|
for i1, i2 in zip(iarray1, iarray2):
|
|
self.assertTrue(i1 == i2)
|
|
|
|
def _QuantizedOutputToFloat(self, quantized, quantized_min, quantized_max):
|
|
number_of_bits = 32
|
|
number_of_steps = 1 << number_of_bits
|
|
range_adjust = (number_of_steps / (number_of_steps - 1.0))
|
|
quantized_range = ((quantized_max - quantized_min) * range_adjust)
|
|
range_scale = (quantized_range / number_of_steps)
|
|
lowest_quantized = -(1 << (number_of_bits - 1))
|
|
result = np.array([(quantized_min +
|
|
((float(x) - lowest_quantized) * range_scale))
|
|
for x in quantized.flatten()])
|
|
return result
|
|
|
|
def testConv2D1x1Filter(self):
|
|
# Our generated input is [batch, rows, cols, depth], and looks like this:
|
|
# (1,2,3) (4,5,6) (7,8,9)
|
|
# (10,11,12) (13,14,15) (16,17,18)
|
|
# The filter data is:
|
|
# (1,4,7) (2,5,8) (3,6,9)
|
|
# That means the calculations are:
|
|
# 1*1+2*4+3*7=30
|
|
# 1*2+2*5+3*8=36
|
|
# 1*3+2*6+3*9=42
|
|
# 4*1+5*4+6*7=66
|
|
# 4*2+5*5+6*8=81
|
|
# 4*3+5*6+6*9=96
|
|
# 7*1+5*8+6*9=102
|
|
# 7*2+8*5+9*8=126
|
|
# 7*3+8*6+9*9=150
|
|
# 10*1+11*4+12*7=138
|
|
# 10*2+11*5+12*8=171
|
|
# 10*3+11*6+12*9=204
|
|
# 13*1+14*4+15*7=174
|
|
# 13*2+14*5+15*8=216
|
|
# 13*3+14*6+15*9=258, clamped to 255
|
|
# 16*1+17*4+18*7=210
|
|
# 16*2+17*5+18*8=261, clamped to 255
|
|
# 16*3+17*6+18*9=312, clamped to 255
|
|
# Because the output shift is zero, we call the non-optimized reference
|
|
# path for the convolution.
|
|
expected_output = [
|
|
30, 36, 42, 66, 81, 96, 102, 126, 150, 138, 171, 204, 174, 216, 258,
|
|
210, 261, 312
|
|
]
|
|
self._VerifyValues(
|
|
tensor_in_sizes=[1, 2, 3, 3],
|
|
filter_in_sizes=[1, 1, 3, 3],
|
|
stride=1,
|
|
padding="VALID",
|
|
expected=expected_output)
|
|
|
|
def testConv2D2x2Filter(self):
|
|
# Our generated input is [batch, rows, cols, depth], and looks like this:
|
|
# (1,2,3) (4,5,6) (7,8,9)
|
|
# (10,11,12) (13,14,15) (16,17,18)
|
|
# The filter data is [filter_height, filter_width, depth, filter_count]:
|
|
# ( 1, 4, 7) (10, 13, 16)
|
|
# (19,22,25) (28, 31, 34)
|
|
# -
|
|
# ( 2, 5, 8) (11, 14, 17)
|
|
# (20,23,26) (29, 32, 35)
|
|
# -
|
|
# ( 3, 6, 9) (12, 15, 18)
|
|
# (21,24,27) (30, 33, 36)
|
|
# The raw accumulated totals are:
|
|
# 1*1+2*4+3*7+4*10+5*13+6*16+10*19+11*22+12*25+13*28+14*31+15*34=2271
|
|
# 1*2+2*5+3*8+4*11+5*14+6*17+10*20+11*23+12*26+13*29+14*32+15*35=2367
|
|
# 1*3+2*6+3*9+4*12+5*15+6*18+10*21+11*24+12*27+13*30+14*33+15*36=2463
|
|
# 4*1+5*4+6*7+7*10+8*13+9*16+13*19+14*22+15*25+16*28+17*31+18*34=2901
|
|
# 4*2+5*5+6*8+7*11+8*14+9*17+13*20+14*23+15*26+16*29+17*32+18*35=3033
|
|
# 4*3+5*6+6*9+7*12+8*15+9*18+13*21+14*24+15*27+16*30+17*33+18*36=3165
|
|
# The expected values are taken from the raw totals and rescaled to fit into
|
|
# eight bits.
|
|
expected_output = [2271.0, 2367.0, 2463.0, 2901.0, 3033.0, 3165.0]
|
|
self._VerifyValues(
|
|
tensor_in_sizes=[1, 2, 3, 3],
|
|
filter_in_sizes=[2, 2, 3, 3],
|
|
stride=1,
|
|
padding="VALID",
|
|
expected=expected_output)
|
|
|
|
def testConv2D1x2Filter(self):
|
|
# The outputs are computed using third_party/py/IPython/notebook.
|
|
# With a shift of 21, we should execute the optimized path here.
|
|
expected_output = [
|
|
231.0, 252.0, 273.0, 384.0, 423.0, 462.0, 690.0, 765.0, 840.0, 843.0,
|
|
936.0, 1029.0
|
|
]
|
|
self._VerifyValues(
|
|
tensor_in_sizes=[1, 2, 3, 3],
|
|
filter_in_sizes=[1, 2, 3, 3],
|
|
stride=1,
|
|
padding="VALID",
|
|
expected=expected_output)
|
|
|
|
def testConv2D2x2FilterStride2(self):
|
|
# With a shift of 21, we should execute the optimized path here.
|
|
expected_output = [2271.0, 2367.0, 2463.0]
|
|
self._VerifyValues(
|
|
tensor_in_sizes=[1, 2, 3, 3],
|
|
filter_in_sizes=[2, 2, 3, 3],
|
|
stride=2,
|
|
padding="VALID",
|
|
expected=expected_output)
|
|
|
|
def testConv2D2x2FilterStride2Same(self):
|
|
# With a shift of 21, we should execute the optimized path here.
|
|
expected_output = [2271.0, 2367.0, 2463.0, 1230.0, 1305.0, 1380.0]
|
|
self._VerifyValues(
|
|
tensor_in_sizes=[1, 2, 3, 3],
|
|
filter_in_sizes=[2, 2, 3, 3],
|
|
stride=2,
|
|
padding="SAME",
|
|
expected=expected_output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test.main()
|