1 /* Copyright 2020 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 #include <stddef.h>
16 #include <stdint.h>
17
18 #include <initializer_list>
19 #include <map>
20 #include <memory>
21 #include <string>
22 #include <tuple>
23 #include <type_traits>
24 #include <vector>
25
26 #include <gmock/gmock.h>
27 #include <gtest/gtest.h>
28 #include "absl/memory/memory.h"
29 #include "tensorflow/lite/interpreter.h"
30 #include "tensorflow/lite/kernels/test_util.h"
31 #include "tensorflow/lite/schema/schema_generated.h"
32 #include "tensorflow/lite/string_type.h"
33
34 namespace tflite {
35
36 namespace ops {
37 namespace builtin {
38
39 TfLiteRegistration* Register_TRANSPOSECONV_REF();
40 TfLiteRegistration* Register_TRANSPOSECONV_GENERIC_OPT();
41
42 } // namespace builtin
43 } // namespace ops
44
45 namespace {
46
47 using ::testing::ElementsAreArray;
48
49 enum class TestType {
50 kConst = 0,
51 kDynamic = 1,
52 };
53
54 template <typename InputType>
55 class BaseTransposeConvOpModel : public SingleOpModel {
56 public:
BaseTransposeConvOpModel(TfLiteRegistration * registration,std::initializer_list<int> output_shape_data,const TensorData & filter,std::initializer_list<InputType> filter_data,const TensorData & input,const TensorData & output,Padding padding,int stride_w,int stride_h,TestType test_type,int version=1)57 BaseTransposeConvOpModel(TfLiteRegistration* registration,
58 std::initializer_list<int> output_shape_data,
59 const TensorData& filter,
60 std::initializer_list<InputType> filter_data,
61 const TensorData& input, const TensorData& output,
62 Padding padding, int stride_w, int stride_h,
63 TestType test_type, int version = 1) {
64 // Just to be confusing, transpose_conv has an _input_ named "output_shape"
65 // that sets the shape of the output tensor of the op :). It must always be
66 // an int32 1D four element tensor.
67 if (test_type == TestType::kDynamic) {
68 output_shape_ = AddInput({TensorType_INT32, {4}});
69 filter_ = AddInput(filter);
70 } else {
71 output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4});
72 filter_ = AddConstInput(filter, filter_data);
73 }
74 input_ = AddInput(input);
75
76 output_ = AddOutput(output);
77
78 SetBuiltinOp(
79 BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions,
80 CreateTransposeConvOptions(builder_, padding, stride_w, stride_h)
81 .Union());
82 resolver_ = std::make_unique<SingleOpResolver>(
83 BuiltinOperator_TRANSPOSE_CONV, registration, version);
84 BuildInterpreter(
85 {GetShape(output_shape_), GetShape(filter_), GetShape(input_)});
86
87 if (test_type == TestType::kDynamic) {
88 PopulateTensor<int32_t>(output_shape_, output_shape_data);
89 if (!std::is_same<InputType, int16_t>::value &&
90 !std::is_same<InputType, int8_t>::value) {
91 PopulateTensor<InputType>(filter_, filter_data);
92 }
93 }
94 }
95
SetInput(std::initializer_list<float> data)96 void SetInput(std::initializer_list<float> data) {
97 if (std::is_same<InputType, uint8_t>::value) {
98 QuantizeAndPopulate<uint8_t>(input_, data);
99 } else if (std::is_same<InputType, int8_t>::value) {
100 QuantizeAndPopulate<int8_t>(input_, data);
101 } else if (std::is_same<InputType, int16_t>::value) {
102 QuantizeAndPopulate<int16_t>(input_, data);
103 } else {
104 PopulateTensor(input_, data);
105 }
106 }
107
GetOutputShape()108 std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
109
110 protected:
111 int output_shape_;
112 int filter_;
113 int input_;
114 int output_;
115 };
116
117 class TransposeConvOpModel : public BaseTransposeConvOpModel<float> {
118 public:
119 using BaseTransposeConvOpModel::BaseTransposeConvOpModel;
120
GetOutput()121 std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
122 };
123
124 const auto kKernelMap = new std::map<string, TfLiteRegistration*>({
125 {"Reference", ops::builtin::Register_TRANSPOSECONV_REF()},
126 {"GenericOptimized", ops::builtin::Register_TRANSPOSECONV_GENERIC_OPT()},
127 });
128
129 class TransposeConvOpTest
130 : public ::testing::TestWithParam<std::tuple<string, TestType>> {
131 public:
GetRegistration()132 TfLiteRegistration* GetRegistration() {
133 return kKernelMap->at(std::get<0>(GetParam()));
134 }
GetTestType()135 TestType GetTestType() { return std::get<1>(GetParam()); }
136 };
137
138 // Test case:
139 // output = tf.nn.conv2d_backprop_input(
140 // tf.constant([ 1, 4, 4, 1 ]),
141 // tf.constant(np.arange(1, 10), shape=[ 3, 3, 1, 1 ], dtype=tf.float32),
142 // tf.constant(np.arange(1, 17), shape=[ 1, 4, 4, 1 ], dtype=tf.float32),
143 // [1, 1, 1, 1 ],
144 // "SAME")
TEST_P(TransposeConvOpTest,SimpleTest)145 TEST_P(TransposeConvOpTest, SimpleTest) {
146 TransposeConvOpModel model(
147 GetRegistration(), {1, 4, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}},
148 {1, 2, 3, 4, 5, 6, 7, 8, 9}, {TensorType_FLOAT32, {1, 4, 4, 1}},
149 {TensorType_FLOAT32, {}}, Padding_SAME, 1, 1, GetTestType());
150 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
151 ASSERT_EQ(model.Invoke(), kTfLiteOk);
152
153 EXPECT_THAT(model.GetOutput(),
154 ElementsAreArray({29, 62, 83, 75, 99, 192, 237, 198, 207, 372,
155 417, 330, 263, 446, 485, 365}));
156 // GetOutputShape() should always be same as model.SetOutputShape(...);
157 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
158 }
159
160 // Test case:
161 // filter = tf.constant(np.arange(1, 19),
162 // shape=[ 3, 3, 1, 2 ],
163 // dtype=tf.float32)
164 // output = tf.nn.conv2d_backprop_input(
165 // tf.constant([ 1, 4, 4, 1 ]),
166 // filter,
167 // tf.constant(np.arange(1, 33), shape=[ 1, 4, 4, 2 ], dtype=tf.float32),
168 // [1, 1, 1, 1 ],
169 // "SAME")
170 // And filter value is derived by:
171 // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[18, 1])
TEST_P(TransposeConvOpTest,TwoFiltersTest)172 TEST_P(TransposeConvOpTest, TwoFiltersTest) {
173 TransposeConvOpModel model(
174 GetRegistration(), {1, 4, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 2}},
175 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18},
176 {TensorType_FLOAT32, {1, 4, 4, 2}}, {TensorType_FLOAT32, {}},
177 Padding_SAME, 1, 1, GetTestType());
178 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
179 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
180 23, 24, 25, 26, 27, 28, 29, 30, 31, 32});
181 ASSERT_EQ(model.Invoke(), kTfLiteOk);
182
183 EXPECT_THAT(model.GetOutput(),
184 ElementsAreArray({184, 412, 568, 528, 678, 1347, 1689, 1434, 1494,
185 2715, 3057, 2442, 1968, 3352, 3652, 2760}));
186 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
187 }
188
189 // Test case:
190 // filter = tf.constant(np.arange(1, 19),
191 // shape=[ 3, 3, 1, 2 ],
192 // dtype=tf.float32)
193 // output = tf.nn.conv2d_backprop_input(
194 // tf.constant([ 1, 6, 6, 1 ]),
195 // filter,
196 // tf.constant(np.arange(1, 33), shape=[ 1, 4, 4, 2 ], dtype=tf.float32),
197 // [1, 1, 1, 1 ],
198 // "VALID")
199 // And filter value is derived by:
200 // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[1, 18])
TEST_P(TransposeConvOpTest,PaddingValidTest)201 TEST_P(TransposeConvOpTest, PaddingValidTest) {
202 TransposeConvOpModel model(
203 GetRegistration(), {1, 6, 6, 1}, {TensorType_FLOAT32, {1, 3, 3, 2}},
204 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18},
205 {TensorType_FLOAT32, {1, 4, 4, 2}}, {TensorType_FLOAT32, {}},
206 Padding_VALID, 1, 1, GetTestType());
207 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
208 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
209 23, 24, 25, 26, 27, 28, 29, 30, 31, 32});
210 ASSERT_EQ(model.Invoke(), kTfLiteOk);
211
212 EXPECT_THAT(
213 model.GetOutput(),
214 ElementsAreArray({5, 22, 59, 101, 114, 83, 52, 184, 412,
215 568, 528, 344, 237, 678, 1347, 1689, 1434, 879,
216 597, 1494, 2715, 3057, 2442, 1431, 856, 1968, 3352,
217 3652, 2760, 1548, 689, 1534, 2543, 2729, 2010, 1103}));
218 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 6, 6, 1}));
219 }
220
221 // Test case:
222 // filter = tf.constant(np.arange(1, 10),
223 // shape=[ 3, 3, 1, 1 ],
224 // dtype=tf.float32)
225 // output = tf.nn.conv2d_backprop_input(
226 // tf.constant([ 1, 5, 5, 1 ]),
227 // filter,
228 // tf.constant(np.arange(1, 5), shape=[ 1, 2, 2, 1 ], dtype=tf.float32),
229 // [1, 2, 2, 1 ],
230 // "VALID")
TEST_P(TransposeConvOpTest,StrideValidTest)231 TEST_P(TransposeConvOpTest, StrideValidTest) {
232 TransposeConvOpModel model(
233 GetRegistration(), {1, 5, 5, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}},
234 {1, 2, 3, 4, 5, 6, 7, 8, 9}, {TensorType_FLOAT32, {1, 2, 2, 1}},
235 {TensorType_FLOAT32, {}}, Padding_VALID, 2, 2, GetTestType());
236 model.SetInput({1, 2, 3, 4});
237 ASSERT_EQ(model.Invoke(), kTfLiteOk);
238
239 EXPECT_THAT(
240 model.GetOutput(),
241 ElementsAreArray({1, 2, 5, 4, 6, 4, 5, 14, 10, 12, 10, 14, 36,
242 24, 30, 12, 15, 34, 20, 24, 21, 24, 55, 32, 36}));
243 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 1}));
244 }
245
246 // Test case:
247 // filter = tf.constant(np.arange(1, 19),
248 // shape=[ 3, 3, 2, 1 ],
249 // dtype=tf.float32)
250 // output = tf.nn.conv2d_backprop_input(
251 // tf.constant([ 1, 5, 5, 2 ]),
252 // filter,
253 // tf.constant(np.arange(1, 5), shape=[ 1, 2, 2, 1 ], dtype=tf.float32),
254 // [1, 2, 2, 1 ],
255 // "VALID")
TEST_P(TransposeConvOpTest,MultiChannelTest)256 TEST_P(TransposeConvOpTest, MultiChannelTest) {
257 TransposeConvOpModel model(
258 GetRegistration(), {1, 5, 5, 2}, {TensorType_FLOAT32, {2, 3, 3, 1}},
259 {1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18},
260 {TensorType_FLOAT32, {1, 2, 2, 1}}, {TensorType_FLOAT32, {}},
261 Padding_VALID, 2, 2, GetTestType());
262 model.SetInput({1, 2, 3, 4});
263 ASSERT_EQ(model.Invoke(), kTfLiteOk);
264
265 EXPECT_THAT(
266 model.GetOutput(),
267 ElementsAreArray({1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9,
268 10, 25, 28, 18, 20, 22, 24, 16, 20, 24, 28, 62, 72,
269 42, 48, 54, 60, 21, 24, 27, 30, 61, 68, 36, 40, 44,
270 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72}));
271 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
272 }
273
274 // Test case:
275 // filter = tf.constant(np.random.randint(1, 10, size=9),
276 // shape=[ 3, 3, 1, 1 ],
277 // dtype=tf.float32)
278 // output = tf.nn.conv2d_backprop_input(
279 // tf.constant([ 1, 3, 4, 1 ]),
280 // filter,
281 // tf.constant([323, 521], shape=[ 1, 1, 2, 1], dtype=tf.float32),
282 // [1, 3, 3, 1 ],
283 // "SAME")
284 // And filter value is derived by:
285 // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[-1])
TEST_P(TransposeConvOpTest,AccuracyTest)286 TEST_P(TransposeConvOpTest, AccuracyTest) {
287 TransposeConvOpModel model(
288 GetRegistration(), {1, 3, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}},
289 {9, 5, 6, 9, 8, 5, 3, 1, 4}, {TensorType_FLOAT32, {1, 1, 2, 1}},
290 {TensorType_FLOAT32, {}}, Padding_SAME, 3, 3, GetTestType());
291 model.SetInput({323, 521});
292 ASSERT_EQ(model.Invoke(), kTfLiteOk);
293
294 EXPECT_THAT(model.GetOutput(),
295 ElementsAreArray(
296 ArrayFloatNear({1615., 1938., 4689., 2605., 2584., 1615.,
297 4689., 4168., 323., 1292., 1563., 521.})));
298 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3, 4, 1}));
299 }
300
301 class QuantizedTransposeConvOpModel : public BaseTransposeConvOpModel<uint8_t> {
302 public:
303 using BaseTransposeConvOpModel::BaseTransposeConvOpModel;
304
GetDequantizedOutput()305 std::vector<float> GetDequantizedOutput() {
306 return Dequantize<uint8_t>(ExtractVector<uint8_t>(output_),
307 GetScale(output_), GetZeroPoint(output_));
308 }
309 };
310
TEST_P(TransposeConvOpTest,SimpleTestQuantized)311 TEST_P(TransposeConvOpTest, SimpleTestQuantized) {
312 // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9}
313 std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137,
314 139, 141, 143, 145};
315 QuantizedTransposeConvOpModel model(
316 GetRegistration(), {1, 4, 4, 1},
317 {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data,
318 {TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64},
319 {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, GetTestType());
320 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
321 ASSERT_EQ(model.Invoke(), kTfLiteOk);
322
323 EXPECT_THAT(
324 model.GetDequantizedOutput(),
325 ElementsAreArray(ArrayFloatNear({28, 64, 84, 76, 100, 192, 236, 200, 208,
326 372, 416, 332, 264, 448, 484, 364},
327 1e-5)));
328
329 // GetOutputShape() should always be same as model.SetOutputShape(...);
330 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
331 }
332
TEST_P(TransposeConvOpTest,TwoFiltersTestQuantized)333 TEST_P(TransposeConvOpTest, TwoFiltersTestQuantized) {
334 // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
335 // 18}
336 std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137, 139,
337 141, 143, 145, 147, 149, 151,
338 153, 155, 157, 159, 161, 163};
339 QuantizedTransposeConvOpModel model(
340 GetRegistration(), {1, 4, 4, 1},
341 {TensorType_UINT8, {1, 3, 3, 2}, -63.5, 64}, filter_data,
342 {TensorType_UINT8, {1, 4, 4, 2}, -63.5, 64},
343 {TensorType_UINT8, {}, -4064, 4096}, Padding_SAME, 1, 1, GetTestType());
344 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
345 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
346 23, 24, 25, 26, 27, 28, 29, 30, 31, 32});
347 ASSERT_EQ(model.Invoke(), kTfLiteOk);
348
349 EXPECT_THAT(model.GetDequantizedOutput(),
350 ElementsAreArray(ArrayFloatNear(
351 {192, 416, 576, 544, 672, 1344, 1696, 1440, 1504, 2720, 3072,
352 2432, 1984, 3360, 3648, 2752},
353 1e-5)));
354 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
355 }
356
TEST_P(TransposeConvOpTest,PaddingValidTestQuantized)357 TEST_P(TransposeConvOpTest, PaddingValidTestQuantized) {
358 // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
359 // 18}
360 std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137, 139,
361 141, 143, 145, 147, 149, 151,
362 153, 155, 157, 159, 161, 163};
363 QuantizedTransposeConvOpModel model(
364 GetRegistration(), {1, 6, 6, 1},
365 {TensorType_UINT8, {1, 3, 3, 2}, -63.5, 64}, filter_data,
366 {TensorType_UINT8, {1, 4, 4, 2}, -63.5, 64},
367 {TensorType_UINT8, {}, -4064, 4096}, Padding_VALID, 1, 1, GetTestType());
368 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
369 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
370 23, 24, 25, 26, 27, 28, 29, 30, 31, 32});
371 ASSERT_EQ(model.Invoke(), kTfLiteOk);
372
373 EXPECT_THAT(model.GetDequantizedOutput(),
374 ElementsAreArray(ArrayFloatNear(
375 {0, 32, 64, 96, 128, 96, 64, 192, 416,
376 576, 544, 352, 224, 672, 1344, 1696, 1440, 864,
377 608, 1504, 2720, 3072, 2432, 1440, 864, 1984, 3360,
378 3648, 2752, 1536, 704, 1536, 2528, 2720, 2016, 1088},
379 1e-5)));
380 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 6, 6, 1}));
381 }
382
383 class PerChannelQuantizedTransposeConvOpModel
384 : public BaseTransposeConvOpModel<int8_t> {
385 public:
386 using BaseTransposeConvOpModel::BaseTransposeConvOpModel;
387
GetDequantizedOutput()388 std::vector<float> GetDequantizedOutput() {
389 return Dequantize<int8_t>(ExtractVector<int8_t>(output_), GetScale(output_),
390 GetZeroPoint(output_));
391 }
392
SetFilter(const std::initializer_list<float> & data)393 void SetFilter(const std::initializer_list<float>& data) {
394 PerChannelSymmetricQuantizeAndPopulate(filter_, data);
395 }
396 };
397
TEST_P(TransposeConvOpTest,SimpleTestQuantizedPerChannelSingleChannel)398 TEST_P(TransposeConvOpTest, SimpleTestQuantizedPerChannelSingleChannel) {
399 const std::initializer_list<float> filter_data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
400 const std::initializer_list<int8_t> const_filter_data = {14, 28, 42, 56, 71,
401 85, 99, 113, 127};
402 PerChannelQuantizedTransposeConvOpModel model(
403 GetRegistration(), {1, 4, 4, 1},
404 {TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0},
405 const_filter_data,
406 {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 255, -128},
407 {TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1, GetTestType(),
408 /* version */ 2);
409 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
410 if (GetTestType() == TestType::kDynamic) {
411 model.SetFilter(filter_data);
412 }
413 ASSERT_EQ(model.Invoke(), kTfLiteOk);
414
415 EXPECT_THAT(
416 model.GetDequantizedOutput(),
417 ElementsAreArray(ArrayFloatNear({28, 62, 82, 76, 98, 192, 238, 198, 206,
418 372, 416, 330, 262, 446, 486, 366},
419 1e-5)));
420
421 // GetOutputShape() should always be same as model.SetOutputShape(...);
422 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
423 }
424
425 // Test data copied from the float multi-channel test above.
TEST_P(TransposeConvOpTest,TestQuantizedPerChannelMultiChannel)426 TEST_P(TransposeConvOpTest, TestQuantizedPerChannelMultiChannel) {
427 const std::initializer_list<float> filter_data = {
428 1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18};
429 const std::initializer_list<int8_t> const_filter_data = {
430 7, 22, 37, 52, 67, 82, 97, 112, 127,
431 14, 28, 42, 56, 71, 85, 99, 113, 127};
432 PerChannelQuantizedTransposeConvOpModel model(
433 GetRegistration(), {1, 5, 5, 2},
434 {TensorType_INT8,
435 {2, 3, 3, 1},
436 0,
437 0,
438 0,
439 0,
440 true,
441 {17.0 / 127, 18.0 / 127},
442 {0, 0},
443 0},
444 const_filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128},
445 {TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2, GetTestType(),
446 /* version */ 2);
447 model.SetInput({1, 2, 3, 4});
448 if (GetTestType() == TestType::kDynamic) {
449 model.SetFilter(filter_data);
450 }
451 ASSERT_EQ(model.Invoke(), kTfLiteOk);
452
453 EXPECT_THAT(
454 model.GetDequantizedOutput(),
455 ElementsAreArray(ArrayFloatNear(
456 {1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18,
457 20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30,
458 61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72},
459 1e-5)));
460
461 // GetOutputShape() should always be same as model.SetOutputShape(...);
462 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
463 }
464
465 // Test data copied from the float multi-channel test above.
TEST_P(TransposeConvOpTest,TestQuantizedPerTensorMultiChannel)466 TEST_P(TransposeConvOpTest, TestQuantizedPerTensorMultiChannel) {
467 const std::initializer_list<float> filter_data = {
468 1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18};
469 const std::initializer_list<int8_t> const_filter_data = {
470 7, 21, 35, 49, 64, 78, 92, 106, 120,
471 14, 28, 42, 56, 71, 85, 99, 113, 127};
472 PerChannelQuantizedTransposeConvOpModel model(
473 GetRegistration(), {1, 5, 5, 2},
474 {TensorType_INT8,
475 {2, 3, 3, 1},
476 0,
477 0,
478 0,
479 0,
480 true,
481 {18.0 / 127, 18.0 / 127},
482 {0, 0},
483 0},
484 const_filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128},
485 {TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2, GetTestType(),
486 /* version */ 2);
487 model.SetInput({1, 2, 3, 4});
488 if (GetTestType() == TestType::kDynamic) {
489 model.SetFilter(filter_data);
490 }
491 ASSERT_EQ(model.Invoke(), kTfLiteOk);
492
493 EXPECT_THAT(
494 model.GetDequantizedOutput(),
495 ElementsAreArray(ArrayFloatNear(
496 {1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18,
497 20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30,
498 61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72},
499 1e-5)));
500
501 // GetOutputShape() should always be same as model.SetOutputShape(...);
502 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
503 }
504
505 class PerChannelQuantizedTransposeConvOpModel16x8
506 : public BaseTransposeConvOpModel<int16_t> {
507 public:
508 using BaseTransposeConvOpModel::BaseTransposeConvOpModel;
509
GetDequantizedOutput()510 std::vector<float> GetDequantizedOutput() {
511 return Dequantize<int16_t>(ExtractVector<int16_t>(output_),
512 GetScale(output_), GetZeroPoint(output_));
513 }
514
SetFilter(const std::initializer_list<float> & data)515 void SetFilter(const std::initializer_list<float>& data) {
516 PerChannelSymmetricQuantizeAndPopulate(filter_, data);
517 }
518 };
519
TEST_P(TransposeConvOpTest,SimpleTestQuantizedPerChannel16x8)520 TEST_P(TransposeConvOpTest, SimpleTestQuantizedPerChannel16x8) {
521 const std::initializer_list<float> filter_data = {
522 // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel]
523 1, 2, // out channel = 0, y = 0, x = 0
524 3, 4, // out channel = 0, y = 0, x = 1
525 3, 4, // out channel = 0, y = 1, x = 0
526 5, 6, // out channel = 0, y = 1, x = 1
527 7, 8, // out channel = 1, y = 0, x = 0
528 5, 6, // out channel = 1, y = 0, x = 1
529 3, 4, // out channel = 1, y = 1, x = 0
530 1, 2, // out channel = 1, y = 1, x = 1
531 };
532 PerChannelQuantizedTransposeConvOpModel16x8 model(
533 GetRegistration(),
534 /*output_shape_data=*/{1, 2, 3, 2},
535 /*filter=*/
536 {TensorType_INT8,
537 /*shape=*/{2, 2, 2, 2},
538 /*min=*/-64, /*max=*/64,
539 /*scale=*/0, /*zero_point=*/0,
540 /*per_channel_quantization=*/true,
541 /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127},
542 /*per_channel_quantization_offsets=*/{0, 0},
543 /*channel_index=*/0},
544 /*filter_data=*/{},
545 /*input=*/
546 {TensorType_INT16,
547 /*shape=*/{1, 2, 3, 2},
548 /*min=*/0, /*max=*/0,
549 /*scale=*/4.0 / 127,
550 /*zero_point=*/0},
551 /*output=*/
552 {TensorType_INT16,
553 /*shape=*/{},
554 /*min=*/0, /*max=*/0,
555 /*scale=*/1.0,
556 /*zero_point=*/0},
557 /*padding=*/Padding_SAME,
558 /*stride_w=*/1, /*stride_h=*/1, GetTestType());
559 model.SetInput({
560 // [1 * 2 * 3 * 2] as [batch, y, x, input_channel]
561 3, 2, // batch = 0, y = 0, x = 0
562 1, -1, // batch = 0, y = 0, x = 1
563 -2, -3, // batch = 0, y = 0, x = 2
564 4, 3, // batch = 0, y = 1, x = 0
565 2, -2, // batch = 0, y = 1, x = 1
566 -3, -4, // batch = 0, y = 1, x = 2
567 });
568 model.SetFilter(filter_data);
569 ASSERT_EQ(model.Invoke(), kTfLiteOk);
570
571 EXPECT_THAT(model.GetDequantizedOutput(),
572 ElementsAreArray(ArrayFloatNear(
573 {7, 37, 16, 26, -9, -39, 27, 69, 48, 42, -32, -74}, 1e-5)));
574
575 // GetOutputShape() should always be same as model.SetOutputShape(...);
576 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2}));
577 }
578
579 template <typename InputType>
580 class BaseTransposeConvBiasOpModel : public SingleOpModel {
581 public:
BaseTransposeConvBiasOpModel(TfLiteRegistration * registration,std::initializer_list<int> output_shape_data,const TensorData & filter,std::initializer_list<InputType> filter_data,const TensorData & input,const TensorData & output,Padding padding,int stride_w,int stride_h,TestType test_type,int version=3)582 BaseTransposeConvBiasOpModel(TfLiteRegistration* registration,
583 std::initializer_list<int> output_shape_data,
584 const TensorData& filter,
585 std::initializer_list<InputType> filter_data,
586 const TensorData& input,
587 const TensorData& output, Padding padding,
588 int stride_w, int stride_h, TestType test_type,
589 int version = 3) {
590 if (test_type == TestType::kDynamic) {
591 output_shape_ = AddInput({TensorType_INT32, {4}});
592 filter_ = AddInput(filter);
593 } else {
594 output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4});
595 filter_ = AddConstInput(filter, filter_data);
596 }
597 input_ = AddInput(input);
598
599 int bias_size = GetShape(filter_)[0];
600 if (input.type == TensorType_FLOAT32) {
601 bias_ = AddInput({TensorType_FLOAT32, {bias_size}});
602 } else if (input.type == TensorType_INT8) {
603 // per channel quantization.
604 std::vector<float> bias_scale(
605 filter.per_channel_quantization_scales.size());
606 std::vector<int64_t> bias_zero_points(
607 filter.per_channel_quantization_scales.size());
608 for (size_t i = 0; i < filter.per_channel_quantization_scales.size();
609 ++i) {
610 bias_scale[i] = input.scale * filter.per_channel_quantization_scales[i];
611 bias_zero_points[i] = 0;
612 }
613 TensorData bias{TensorType_INT32,
614 {bias_size},
615 /*min=*/0,
616 /*max=*/0,
617 /*scale=*/0,
618 /*zero_point=*/0,
619 true,
620 /*per_channel_quantization_scales=*/bias_scale,
621 /*per_channel_quantization_offsets=*/bias_zero_points,
622 /*channel_index==*/0};
623 bias_ = AddInput(bias);
624 } else {
625 // per tensor quantization.
626 auto bias_scale = GetScale(input_) * GetScale(filter_);
627 TensorData bias{TensorType_INT32, {bias_size}, 0, 0, bias_scale};
628 bias_ = AddInput(bias);
629 }
630
631 output_ = AddOutput(output);
632
633 SetBuiltinOp(
634 BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions,
635 CreateTransposeConvOptions(builder_, padding, stride_w, stride_h)
636 .Union());
637 resolver_ = std::make_unique<SingleOpResolver>(
638 BuiltinOperator_TRANSPOSE_CONV, registration, version);
639 BuildInterpreter({GetShape(output_shape_), GetShape(filter_),
640 GetShape(input_), GetShape(bias_)});
641
642 if (test_type == TestType::kDynamic) {
643 PopulateTensor<int32_t>(output_shape_, output_shape_data);
644 PopulateTensor<InputType>(filter_, filter_data);
645 }
646 }
647
SetInput(std::initializer_list<float> data)648 void SetInput(std::initializer_list<float> data) {
649 if (std::is_same<InputType, uint8_t>::value) {
650 QuantizeAndPopulate<uint8_t>(input_, data);
651 } else if (std::is_same<InputType, int8_t>::value) {
652 QuantizeAndPopulate<int8_t>(input_, data);
653 } else {
654 PopulateTensor(input_, data);
655 }
656 }
657
SetBias(std::initializer_list<float> bias)658 void SetBias(std::initializer_list<float> bias) {
659 if (std::is_same<InputType, uint8_t>::value) {
660 QuantizeAndPopulate<int32_t>(bias_, bias);
661 } else if (std::is_same<InputType, int8_t>::value) {
662 PerChannelQuantizeBias(bias_, bias);
663 } else {
664 PopulateTensor(bias_, bias);
665 }
666 }
667
GetOutputShape()668 std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
669
670 protected:
671 int output_shape_;
672 int filter_;
673 int input_;
674 int bias_;
675 int output_;
676 };
677
678 class TransposeConvOpBiasModel : public BaseTransposeConvBiasOpModel<float> {
679 public:
680 using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel;
681
GetOutput()682 std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
683 };
684
685 // Test case:
686 // input_data = np.arange(1, 5).reshape(1,2,2,1).astype(np.float32)
687 // filter_data = np.arange(1, 19).reshape(3,3,2,1).astype(np.float32)
688 // bias_data = np.array([3,4])
689 // input = tf.keras.layers.Input(shape=(2, 2, 1))
690 // output = tf.keras.layers.Convolution2DTranspose(filters=2,
691 // kernel_size=[3, 3],
692 // strides=[2, 2],
693 // padding="valid")(input)
694 // model = tf.keras.models.Model(input, output)
695 // model.layers[1].set_weights([filter_data, bias_data])
696 // output = model.predict(input_data)
TEST_P(TransposeConvOpTest,MultiChannelBiasTest)697 TEST_P(TransposeConvOpTest, MultiChannelBiasTest) {
698 TransposeConvOpBiasModel model(
699 GetRegistration(), /*output_shape=*/{1, 5, 5, 2},
700 /*filter=*/{TensorType_FLOAT32, {2, 3, 3, 1}},
701 /*filter_data=*/
702 {1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18},
703 /*input=*/{TensorType_FLOAT32, {1, 2, 2, 1}},
704 /*output=*/{TensorType_FLOAT32, {}}, Padding_VALID,
705 /*stride_w=*/2, /*stride_h=*/2, GetTestType(), /* version */ 3);
706 model.SetInput({1, 2, 3, 4});
707 model.SetBias({3, 4});
708 ASSERT_EQ(model.Invoke(), kTfLiteOk);
709
710 EXPECT_THAT(
711 model.GetOutput(),
712 ElementsAreArray({4, 6, 6, 8, 10, 14, 9, 12, 13, 16, 10, 12, 12,
713 14, 28, 32, 21, 24, 25, 28, 19, 24, 27, 32, 65, 76,
714 45, 52, 57, 64, 24, 28, 30, 34, 64, 72, 39, 44, 47,
715 52, 42, 46, 48, 52, 106, 114, 63, 68, 71, 76}));
716 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
717 }
718
719 class QuantizedTransposeConvBiasOpModel
720 : public BaseTransposeConvBiasOpModel<uint8_t> {
721 public:
722 using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel;
723
GetDequantizedOutput()724 std::vector<float> GetDequantizedOutput() {
725 return Dequantize<uint8_t>(ExtractVector<uint8_t>(output_),
726 GetScale(output_), GetZeroPoint(output_));
727 }
728 };
729
TEST_P(TransposeConvOpTest,SimpleBiasTestQuantized)730 TEST_P(TransposeConvOpTest, SimpleBiasTestQuantized) {
731 // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9}
732 std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137,
733 139, 141, 143, 145};
734 QuantizedTransposeConvBiasOpModel model(
735 GetRegistration(), {1, 4, 4, 1},
736 {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data,
737 {TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64},
738 {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, GetTestType(),
739 /* version */ 3);
740 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
741 model.SetBias({1});
742 ASSERT_EQ(model.Invoke(), kTfLiteOk);
743
744 EXPECT_THAT(
745 model.GetDequantizedOutput(),
746 ElementsAreArray(ArrayFloatNear({32, 64, 84, 76, 100, 192, 240, 200, 208,
747 372, 420, 332, 264, 448, 488, 368},
748 1e-5)));
749
750 // GetOutputShape() should always be same as model.SetOutputShape(...);
751 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
752 }
753
754 class PerChannelQuantizedTransposeConvBiasOpModel
755 : public BaseTransposeConvBiasOpModel<int8_t> {
756 public:
757 using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel;
758
GetDequantizedOutput()759 std::vector<float> GetDequantizedOutput() {
760 return Dequantize<int8_t>(ExtractVector<int8_t>(output_), GetScale(output_),
761 GetZeroPoint(output_));
762 }
763
SetInput(const std::initializer_list<float> & data)764 void SetInput(const std::initializer_list<float>& data) {
765 QuantizeAndPopulate<int8_t>(input_, data);
766 }
767
SetFilter(const std::initializer_list<float> & data)768 void SetFilter(const std::initializer_list<float>& data) {
769 PerChannelSymmetricQuantizeAndPopulate(filter_, data);
770 }
771 };
772
TEST_P(TransposeConvOpTest,SimpleBiasTestQuantizedPerChannelSingleChannel)773 TEST_P(TransposeConvOpTest, SimpleBiasTestQuantizedPerChannelSingleChannel) {
774 const std::initializer_list<float> filter_data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
775 const std::initializer_list<int8_t> const_filter_data = {14, 28, 42, 56, 71,
776 85, 99, 113, 127};
777 PerChannelQuantizedTransposeConvBiasOpModel model(
778 GetRegistration(), {1, 4, 4, 1},
779 {TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0},
780 const_filter_data,
781 {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 255, -128},
782 {TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1, GetTestType(),
783 /* version */ 3);
784 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
785 if (GetTestType() == TestType::kDynamic) {
786 model.SetFilter(filter_data);
787 }
788 model.SetBias({1});
789 ASSERT_EQ(model.Invoke(), kTfLiteOk);
790
791 EXPECT_THAT(
792 model.GetDequantizedOutput(),
793 ElementsAreArray(ArrayFloatNear({30, 62, 84, 76, 100, 194, 238, 200, 208,
794 372, 418, 330, 264, 446, 486, 366},
795 1e-5)));
796
797 // GetOutputShape() should always be same as model.SetOutputShape(...);
798 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
799 }
800
801 INSTANTIATE_TEST_SUITE_P(
802 TransposeConvOpTest, TransposeConvOpTest,
803 ::testing::Combine(
804 ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap)),
805 ::testing::Values(TestType::kConst, TestType::kDynamic)));
806
807 } // namespace
808 } // namespace tflite
809