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