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_ = absl::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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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 model.Invoke();
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, {2, 3, 3, 1}, 0, 0, 0, 0, true, {18.0 / 127}, {0}, 0},
475 const_filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128},
476 {TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2, GetTestType(),
477 /* version */ 2);
478 model.SetInput({1, 2, 3, 4});
479 if (GetTestType() == TestType::kDynamic) {
480 model.SetFilter(filter_data);
481 }
482 model.Invoke();
483
484 EXPECT_THAT(
485 model.GetDequantizedOutput(),
486 ElementsAreArray(ArrayFloatNear(
487 {1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18,
488 20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30,
489 61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72},
490 1e-5)));
491
492 // GetOutputShape() should always be same as model.SetOutputShape(...);
493 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
494 }
495
496 class PerChannelQuantizedTransposeConvOpModel16x8
497 : public BaseTransposeConvOpModel<int16_t> {
498 public:
499 using BaseTransposeConvOpModel::BaseTransposeConvOpModel;
500
GetDequantizedOutput()501 std::vector<float> GetDequantizedOutput() {
502 return Dequantize<int16_t>(ExtractVector<int16_t>(output_),
503 GetScale(output_), GetZeroPoint(output_));
504 }
505
SetFilter(const std::initializer_list<float> & data)506 void SetFilter(const std::initializer_list<float>& data) {
507 PerChannelSymmetricQuantizeAndPopulate(filter_, data);
508 }
509 };
510
TEST_P(TransposeConvOpTest,SimpleTestQuantizedPerChannel16x8)511 TEST_P(TransposeConvOpTest, SimpleTestQuantizedPerChannel16x8) {
512 const std::initializer_list<float> filter_data = {
513 // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel]
514 1, 2, // out channel = 0, y = 0, x = 0
515 3, 4, // out channel = 0, y = 0, x = 1
516 3, 4, // out channel = 0, y = 1, x = 0
517 5, 6, // out channel = 0, y = 1, x = 1
518 7, 8, // out channel = 1, y = 0, x = 0
519 5, 6, // out channel = 1, y = 0, x = 1
520 3, 4, // out channel = 1, y = 1, x = 0
521 1, 2, // out channel = 1, y = 1, x = 1
522 };
523 PerChannelQuantizedTransposeConvOpModel16x8 model(
524 GetRegistration(),
525 /*output_shape_data=*/{1, 2, 3, 2},
526 /*filter=*/
527 {TensorType_INT8,
528 /*shape=*/{2, 2, 2, 2},
529 /*min=*/-64, /*max=*/64,
530 /*scale=*/0, /*zero_point=*/0,
531 /*per_channel_quantization=*/true,
532 /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127},
533 /*per_channel_quantization_offsets=*/{0, 0},
534 /*channel_index=*/0},
535 /*filter_data=*/{},
536 /*input=*/
537 {TensorType_INT16,
538 /*shape=*/{1, 2, 3, 2},
539 /*min=*/0, /*max=*/0,
540 /*scale=*/4.0 / 127,
541 /*zero_point=*/0},
542 /*output=*/
543 {TensorType_INT16,
544 /*shape=*/{},
545 /*min=*/0, /*max=*/0,
546 /*scale=*/1.0,
547 /*zero_point=*/0},
548 /*padding=*/Padding_SAME,
549 /*stride_w=*/1, /*stride_h=*/1, GetTestType());
550 model.SetInput({
551 // [1 * 2 * 3 * 2] as [batch, y, x, input_channel]
552 3, 2, // batch = 0, y = 0, x = 0
553 1, -1, // batch = 0, y = 0, x = 1
554 -2, -3, // batch = 0, y = 0, x = 2
555 4, 3, // batch = 0, y = 1, x = 0
556 2, -2, // batch = 0, y = 1, x = 1
557 -3, -4, // batch = 0, y = 1, x = 2
558 });
559 model.SetFilter(filter_data);
560 model.Invoke();
561
562 EXPECT_THAT(model.GetDequantizedOutput(),
563 ElementsAreArray(ArrayFloatNear(
564 {7, 37, 16, 26, -9, -39, 27, 69, 48, 42, -32, -74}, 1e-5)));
565
566 // GetOutputShape() should always be same as model.SetOutputShape(...);
567 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2}));
568 }
569
570 template <typename InputType>
571 class BaseTransposeConvBiasOpModel : public SingleOpModel {
572 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)573 BaseTransposeConvBiasOpModel(TfLiteRegistration* registration,
574 std::initializer_list<int> output_shape_data,
575 const TensorData& filter,
576 std::initializer_list<InputType> filter_data,
577 const TensorData& input,
578 const TensorData& output, Padding padding,
579 int stride_w, int stride_h, TestType test_type,
580 int version = 3) {
581 if (test_type == TestType::kDynamic) {
582 output_shape_ = AddInput({TensorType_INT32, {4}});
583 filter_ = AddInput(filter);
584 } else {
585 output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4});
586 filter_ = AddConstInput(filter, filter_data);
587 }
588 input_ = AddInput(input);
589
590 int bias_size = GetShape(filter_)[0];
591 if (input.type == TensorType_FLOAT32) {
592 bias_ = AddInput({TensorType_FLOAT32, {bias_size}});
593 } else if (input.type == TensorType_INT8) {
594 // per channel quantization.
595 std::vector<float> bias_scale(
596 filter.per_channel_quantization_scales.size());
597 std::vector<int64_t> bias_zero_points(
598 filter.per_channel_quantization_scales.size());
599 for (size_t i = 0; i < filter.per_channel_quantization_scales.size();
600 ++i) {
601 bias_scale[i] = input.scale * filter.per_channel_quantization_scales[i];
602 bias_zero_points[i] = 0;
603 }
604 TensorData bias{TensorType_INT32,
605 {bias_size},
606 /*min=*/0,
607 /*max=*/0,
608 /*scale=*/0,
609 /*zero_point=*/0,
610 true,
611 /*per_channel_quantization_scales=*/bias_scale,
612 /*per_channel_quantization_offsets=*/bias_zero_points,
613 /*channel_index==*/0};
614 bias_ = AddInput(bias);
615 } else {
616 // per tensor quantization.
617 auto bias_scale = GetScale(input_) * GetScale(filter_);
618 TensorData bias{TensorType_INT32, {bias_size}, 0, 0, bias_scale};
619 bias_ = AddInput(bias);
620 }
621
622 output_ = AddOutput(output);
623
624 SetBuiltinOp(
625 BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions,
626 CreateTransposeConvOptions(builder_, padding, stride_w, stride_h)
627 .Union());
628 resolver_ = absl::make_unique<SingleOpResolver>(
629 BuiltinOperator_TRANSPOSE_CONV, registration, version);
630 BuildInterpreter({GetShape(output_shape_), GetShape(filter_),
631 GetShape(input_), GetShape(bias_)});
632
633 if (test_type == TestType::kDynamic) {
634 PopulateTensor<int32_t>(output_shape_, output_shape_data);
635 PopulateTensor<InputType>(filter_, filter_data);
636 }
637 }
638
SetInput(std::initializer_list<float> data)639 void SetInput(std::initializer_list<float> data) {
640 if (std::is_same<InputType, uint8_t>::value) {
641 QuantizeAndPopulate<uint8_t>(input_, data);
642 } else if (std::is_same<InputType, int8_t>::value) {
643 QuantizeAndPopulate<int8_t>(input_, data);
644 } else {
645 PopulateTensor(input_, data);
646 }
647 }
648
SetBias(std::initializer_list<float> bias)649 void SetBias(std::initializer_list<float> bias) {
650 if (std::is_same<InputType, uint8_t>::value) {
651 QuantizeAndPopulate<int32_t>(bias_, bias);
652 } else if (std::is_same<InputType, int8_t>::value) {
653 PerChannelQuantizeBias(bias_, bias);
654 } else {
655 PopulateTensor(bias_, bias);
656 }
657 }
658
GetOutputShape()659 std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
660
661 protected:
662 int output_shape_;
663 int filter_;
664 int input_;
665 int bias_;
666 int output_;
667 };
668
669 class TransposeConvOpBiasModel : public BaseTransposeConvBiasOpModel<float> {
670 public:
671 using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel;
672
GetOutput()673 std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
674 };
675
676 // Test case:
677 // input_data = np.arange(1, 5).reshape(1,2,2,1).astype(np.float32)
678 // filter_data = np.arange(1, 19).reshape(3,3,2,1).astype(np.float32)
679 // bias_data = np.array([3,4])
680 // input = tf.keras.layers.Input(shape=(2, 2, 1))
681 // output = tf.keras.layers.Convolution2DTranspose(filters=2,
682 // kernel_size=[3, 3],
683 // strides=[2, 2],
684 // padding="valid")(input)
685 // model = tf.keras.models.Model(input, output)
686 // model.layers[1].set_weights([filter_data, bias_data])
687 // output = model.predict(input_data)
TEST_P(TransposeConvOpTest,MultiChannelBiasTest)688 TEST_P(TransposeConvOpTest, MultiChannelBiasTest) {
689 TransposeConvOpBiasModel model(
690 GetRegistration(), /*output_shape=*/{1, 5, 5, 2},
691 /*filter=*/{TensorType_FLOAT32, {2, 3, 3, 1}},
692 /*filter_data=*/
693 {1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18},
694 /*input=*/{TensorType_FLOAT32, {1, 2, 2, 1}},
695 /*output=*/{TensorType_FLOAT32, {}}, Padding_VALID,
696 /*stride_w=*/2, /*stride_h=*/2, GetTestType(), /* version */ 3);
697 model.SetInput({1, 2, 3, 4});
698 model.SetBias({3, 4});
699 model.Invoke();
700
701 EXPECT_THAT(
702 model.GetOutput(),
703 ElementsAreArray({4, 6, 6, 8, 10, 14, 9, 12, 13, 16, 10, 12, 12,
704 14, 28, 32, 21, 24, 25, 28, 19, 24, 27, 32, 65, 76,
705 45, 52, 57, 64, 24, 28, 30, 34, 64, 72, 39, 44, 47,
706 52, 42, 46, 48, 52, 106, 114, 63, 68, 71, 76}));
707 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
708 }
709
710 class QuantizedTransposeConvBiasOpModel
711 : public BaseTransposeConvBiasOpModel<uint8_t> {
712 public:
713 using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel;
714
GetDequantizedOutput()715 std::vector<float> GetDequantizedOutput() {
716 return Dequantize<uint8_t>(ExtractVector<uint8_t>(output_),
717 GetScale(output_), GetZeroPoint(output_));
718 }
719 };
720
TEST_P(TransposeConvOpTest,SimpleBiasTestQuantized)721 TEST_P(TransposeConvOpTest, SimpleBiasTestQuantized) {
722 // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9}
723 std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137,
724 139, 141, 143, 145};
725 QuantizedTransposeConvBiasOpModel model(
726 GetRegistration(), {1, 4, 4, 1},
727 {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data,
728 {TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64},
729 {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, GetTestType(),
730 /* version */ 3);
731 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
732 model.SetBias({1});
733 model.Invoke();
734
735 EXPECT_THAT(
736 model.GetDequantizedOutput(),
737 ElementsAreArray(ArrayFloatNear({32, 64, 84, 76, 100, 192, 240, 200, 208,
738 372, 420, 332, 264, 448, 488, 368},
739 1e-5)));
740
741 // GetOutputShape() should always be same as model.SetOutputShape(...);
742 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
743 }
744
745 class PerChannelQuantizedTransposeConvBiasOpModel
746 : public BaseTransposeConvBiasOpModel<int8_t> {
747 public:
748 using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel;
749
GetDequantizedOutput()750 std::vector<float> GetDequantizedOutput() {
751 return Dequantize<int8_t>(ExtractVector<int8_t>(output_), GetScale(output_),
752 GetZeroPoint(output_));
753 }
754
SetInput(const std::initializer_list<float> & data)755 void SetInput(const std::initializer_list<float>& data) {
756 QuantizeAndPopulate<int8_t>(input_, data);
757 }
758
SetFilter(const std::initializer_list<float> & data)759 void SetFilter(const std::initializer_list<float>& data) {
760 PerChannelSymmetricQuantizeAndPopulate(filter_, data);
761 }
762 };
763
TEST_P(TransposeConvOpTest,SimpleBiasTestQuantizedPerChannelSingleChannel)764 TEST_P(TransposeConvOpTest, SimpleBiasTestQuantizedPerChannelSingleChannel) {
765 const std::initializer_list<float> filter_data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
766 PerChannelQuantizedTransposeConvBiasOpModel model(
767 GetRegistration(), {1, 4, 4, 1},
768 {TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0},
769 {}, {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 255, -128},
770 {TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1, GetTestType(),
771 /* version */ 3);
772 model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
773 model.SetFilter(filter_data);
774 model.SetBias({1});
775 model.Invoke();
776
777 EXPECT_THAT(
778 model.GetDequantizedOutput(),
779 ElementsAreArray(ArrayFloatNear({30, 62, 84, 76, 100, 194, 238, 200, 208,
780 372, 418, 330, 264, 446, 486, 366},
781 1e-5)));
782
783 // GetOutputShape() should always be same as model.SetOutputShape(...);
784 EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
785 }
786
787 INSTANTIATE_TEST_SUITE_P(
788 TransposeConvOpTest, TransposeConvOpTest,
789 ::testing::Combine(
790 ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap)),
791 ::testing::Values(TestType::kConst, TestType::kDynamic)));
792
793 } // namespace
794 } // namespace tflite
795