[locomotiv] Conv2D support padding (#3814)
author채성우/On-Device Lab(SR)/Engineer/삼성전자 <sw4670.chae@samsung.com>
Mon, 24 Jun 2019 04:57:11 +0000 (13:57 +0900)
committer박종현/On-Device Lab(SR)/Staff Engineer/삼성전자 <jh1302.park@samsung.com>
Mon, 24 Jun 2019 04:57:11 +0000 (13:57 +0900)
* [locomotiv]Conv2D support padding

This commit adds a test for padding.

Signed-off-by: seongwoo <sw4670.chae@samsung.com>
* This commit add some example for padding

* This commit modify some indentation.

* This commit moidfy indentation.

* This commit is adding comment in test script to help understand.

* This commit add tensorflow code.

* This commit modify comment for test

* This commit apply comments.

contrib/locomotiv/src/Node/Conv2D.cpp
contrib/locomotiv/src/Node/Conv2D.test.cpp

index dbfe52d..39e2b19 100644 (file)
 
 namespace
 {
-
+// image size includes padding.
 inline uint32_t compute_out_size(uint32_t image_size, uint32_t filter_size, uint32_t stride)
 {
+  assert((image_size + stride - filter_size) % stride == 0);
   return (image_size + stride - filter_size) / stride;
 }
 
@@ -54,10 +55,6 @@ template <typename RET_T, typename IFM_T, typename FIL_T>
 Buffer<RET_T> calc_conv2D(const loco::Conv2D *conv2d, const Buffer<IFM_T> *input_buf,
                           const Buffer<FIL_T> *filter_buf)
 {
-  // TODO Consider complex padding. This version works only when padding = {0,0,0,0}
-  auto pad = conv2d->pad();
-  assert(pad->bottom() == 0 && pad->top() == 0 && pad->right() == 0 && pad->left() == 0);
-
   auto input_shape = input_buf->shape();
   auto filter_shape = filter_buf->shape();
 
@@ -74,20 +71,20 @@ Buffer<RET_T> calc_conv2D(const loco::Conv2D *conv2d, const Buffer<IFM_T> *input
   const uint32_t stride_width = conv2d->stride()->horizontal();
   const uint32_t stride_height = conv2d->stride()->vertical();
 
-  // loco invariant
-  assert((input_height - filter_height) % stride_height == 0);
-  assert((input_width - filter_width) % stride_width == 0);
-
   // TODO Enable dilations. Let's set these to 1 for now.
   const uint32_t dilation_width_factor = 1;
   const uint32_t dilation_height_factor = 1;
 
-  // TODO enable padding.
-  const uint32_t pad_width = 0;  // with padding, pad_width = conv2d->pad()->left();
-  const uint32_t pad_height = 0; // with padding, pad_height = conv2d->pad()->top();
+  const uint32_t pad_top = conv2d->pad()->top();
+  const uint32_t pad_bottom = conv2d->pad()->bottom();
+
+  const uint32_t pad_left = conv2d->pad()->left();
+  const uint32_t pad_right = conv2d->pad()->right();
 
-  const uint32_t output_height = compute_out_size(input_height, filter_height, stride_height);
-  const uint32_t output_width = compute_out_size(input_width, filter_width, stride_width);
+  const uint32_t output_height =
+      compute_out_size(input_height + pad_top + pad_bottom, filter_height, stride_height);
+  const uint32_t output_width =
+      compute_out_size(input_width + pad_left + pad_right, filter_width, stride_width);
 
   const uint32_t batches = input_shape.dim(0);
   const uint32_t input_depth = input_shape.dim(3);
@@ -104,8 +101,8 @@ Buffer<RET_T> calc_conv2D(const loco::Conv2D *conv2d, const Buffer<IFM_T> *input
       {
         for (uint32_t out_channel = 0; out_channel < output_depth; ++out_channel)
         {
-          const int in_x_origin = (out_x * stride_width) - pad_width;
-          const int in_y_origin = (out_y * stride_height) - pad_height;
+          const int in_x_origin = (out_x * stride_width) - pad_left;
+          const int in_y_origin = (out_y * stride_height) - pad_top;
 
           RET_T total = static_cast<RET_T>(0);
 
index 9f50ae7..83d7fc2 100644 (file)
@@ -35,10 +35,10 @@ using nncc::core::ADT::tensor::LexicalLayout;
 using nncc::core::ADT::tensor::make_buffer;
 using nncc::core::ADT::tensor::make_overlay;
 
-// TODO Add test for padding
 void run_test(const float *ifm, const float *ker, const float *expected_ofm, const Shape &ifm_shape,
               const Shape ker_shape, const Shape ofm_shape, const uint32_t stride_v,
-              const uint32_t stride_h)
+              const uint32_t stride_h, const uint32_t pad_top = 0, const uint32_t pad_bottom = 0,
+              const uint32_t pad_left = 0, const uint32_t pad_right = 0)
 {
   auto g = loco::make_graph();
 
@@ -80,6 +80,10 @@ void run_test(const float *ifm, const float *ker, const float *expected_ofm, con
   conv2d->ker(ker_enc);
   conv2d->stride()->vertical(stride_v);
   conv2d->stride()->horizontal(stride_h);
+  conv2d->pad()->top(pad_top);
+  conv2d->pad()->bottom(pad_bottom);
+  conv2d->pad()->left(pad_left);
+  conv2d->pad()->right(pad_right);
 
   // run interpreter
   locomotiv::NodeExecution::get().run(conv2d);
@@ -115,7 +119,6 @@ out = tf.nn.conv2d(ifm, ker, strides = [1, 2, 2, 1], padding= 'VALID')
 with tf.Session() as sess:
     print(sess.run(out))
 */
-
 TEST(NodeExecution_Conv2D, f32_1x5x5x1_calculation)
 {
   using nncc::core::ADT::tensor::Shape;
@@ -167,4 +170,62 @@ TEST(NodeExecution_Conv2D, f32_multiple_channel)
            1, 1  // stride
   );
 }
+
+/* ifm and ofm are from the code below:
+tensorflow version : 1.12.0
+
+import tensorflow as tf
+
+ifm = tf.constant([-1.3653529,  0.4160791,  0.5059157,  0.7649683,  0.39364856,
+        -1.0164733,  1.506766,  -1.1413091,  1.2766701, -0.9253511,
+        1.3570246,  0.32089928,  -0.9898171,  1.983792,  -0.3423274,
+        -1.1901658,  1.2288222,  -0.47401968,  -0.01369802,  0.4136331,
+        0.06960588,  -0.16537654,  -0.65015996,  -0.555224,  0.7140603
+], shape=[1, 5, 5, 1])
+
+ker = tf.constant([2.3490515,  -0.4572366,  0.05790535,
+        0.3672005,  0.52679914,  0.74607974,
+        -1.7211207,  1.1174419,  -0.59663385
+], shape=[3, 3, 1, 1])
+
+ofm = tf.nn.conv2d(ifm, ker, strides=[1, 1, 1, 1], padding='SAME')
+
+with tf.Session() as sess:
+    print(sess.run(ofm))
+*/
+TEST(NodeExecution_Conv2D, with_padding)
+{
+  using nncc::core::ADT::tensor::Shape;
+
+  const float ifm[] =
+  {
+    -1.3653529,  0.4160791,  0.5059157,  0.7649683,  0.39364856,
+    -1.0164733,  1.506766,  -1.1413091,  1.2766701, -0.9253511,
+    1.3570246,  0.32089928,  -0.9898171,  1.983792,  -0.3423274,
+    -1.1901658,  1.2288222,  -0.47401968,  -0.01369802,  0.4136331,
+    0.06960588,  -0.16537654,  -0.65015996,  -0.555224,  0.7140603 
+  };
+
+  const float ker[] =
+  {
+    2.3490515,  -0.4572366,  0.05790535,
+    0.3672005,  0.52679914,  0.74607974,
+    -1.7211207,  1.1174419,  -0.59663385
+  };
+
+  const float ofm[] =
+  {
+    -2.443676,  4.2094254,  -3.6403496,  4.8254814,  -2.743059,
+    2.5620093,  -5.185688,  -1.1470609,  4.54913,  -2.1985974,
+    -0.5567835,  0.49045527,  2.5752437,  -2.3383713,  4.455967,
+    -0.13562866,  2.9236434,  1.4019353,  -3.0521483,  6.782954,
+    0.5286269,  -3.9317036,  2.285041,  -1.0817666,  -0.04901773
+  };
+
+  run_test(ifm, ker, ofm,
+           Shape{1, 5, 5, 1}, Shape{1, 3, 3, 1}, Shape{1, 5, 5, 1}, // shapes of input, ker, output
+           1, 1,  // stride
+           1, 1, 1, 1  // padding
+  );
+}
 // clang-format on