xref: /aosp_15_r20/external/tensorflow/tensorflow/lite/kernels/transpose_conv_test.cc (revision b6fb3261f9314811a0f4371741dbb8839866f948)
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