Home | History | Annotate | Download | only in kernels
      1 /* Copyright 2015 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 
     16 #include "tensorflow/core/kernels/eigen_backward_spatial_convolutions.h"
     17 #include "tensorflow/core/framework/types.h"
     18 #include "tensorflow/core/kernels/eigen_backward_cuboid_convolutions.h"
     19 #include "tensorflow/core/platform/test.h"
     20 
     21 namespace Eigen {
     22 
     23 namespace {
     24 void EigenApprox(float a, float b) {
     25   ASSERT_TRUE(std::abs(a - b) <= std::min(std::abs(a), std::abs(b)) * 1e-3);
     26 }
     27 static int ceil_div(int a, int b) { return (a + b - 1) / b; }
     28 }  // namespace
     29 
     30 TEST(EigenBackwardSpatialConvolutionsTest,
     31      test_simple_spatial_convolution_backward_input_valid) {
     32   const int input_depth = 2;
     33   const int input_rows = 3;
     34   const int input_cols = 4;
     35   const int output_depth = 5;
     36   const int patch_rows = 2;
     37   const int patch_cols = 2;
     38   const int output_rows = input_rows - patch_rows + 1;
     39   const int output_cols = input_cols - patch_cols + 1;
     40 
     41   Tensor<float, 3> input_backward(input_depth, input_rows, input_cols);
     42   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
     43   Tensor<float, 3> output_backward(output_depth, output_rows, output_cols);
     44 
     45   output_backward = output_backward.constant(11.0f) + output_backward.random();
     46   kernel = kernel.constant(2.0f) + kernel.random();
     47   input_backward.setRandom();
     48 
     49   input_backward = SpatialConvolutionBackwardInput(kernel, output_backward,
     50                                                    input_rows, input_cols, 1);
     51 
     52   EXPECT_EQ(input_backward.dimension(0), input_depth);
     53   EXPECT_EQ(input_backward.dimension(1), input_rows);
     54   EXPECT_EQ(input_backward.dimension(2), input_cols);
     55 
     56   for (int id = 0; id < input_depth; ++id) {
     57     for (int i = 0; i < input_rows; ++i) {
     58       for (int j = 0; j < input_cols; ++j) {
     59         float expected = 0.0f;
     60         for (int c = 0; c < patch_cols; ++c) {
     61           for (int r = 0; r < patch_rows; ++r) {
     62             for (int od = 0; od < output_depth; ++od) {
     63               int output_i = i - r;
     64               int output_j = j - c;
     65               if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
     66                   output_j < output_cols) {
     67                 expected += output_backward(od, output_i, output_j) *
     68                             kernel(od, id, r, c);
     69               }
     70             }
     71           }
     72         }
     73         EigenApprox(input_backward(id, i, j), expected);
     74       }
     75     }
     76   }
     77 }
     78 
     79 TEST(EigenBackwardSpatialConvolutionsTest,
     80      test_simple_spatial_convolution_backward_input_valid_row_major) {
     81   const int input_depth = 2;
     82   const int input_rows = 3;
     83   const int input_cols = 4;
     84   const int output_depth = 5;
     85   const int patch_rows = 2;
     86   const int patch_cols = 2;
     87   const int output_rows = input_rows - patch_rows + 1;
     88   const int output_cols = input_cols - patch_cols + 1;
     89 
     90   Tensor<float, 3, RowMajor> input_backward(input_cols, input_rows,
     91                                             input_depth);
     92   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
     93                                     output_depth);
     94   Tensor<float, 3, RowMajor> output_backward(output_cols, output_rows,
     95                                              output_depth);
     96 
     97   output_backward = output_backward.constant(11.0f) + output_backward.random();
     98   kernel = kernel.constant(2.0f) + kernel.random();
     99   input_backward.setRandom();
    100 
    101   input_backward = SpatialConvolutionBackwardInput(kernel, output_backward,
    102                                                    input_rows, input_cols, 1);
    103 
    104   EXPECT_EQ(input_backward.dimension(0), input_cols);
    105   EXPECT_EQ(input_backward.dimension(1), input_rows);
    106   EXPECT_EQ(input_backward.dimension(2), input_depth);
    107 
    108   for (int id = 0; id < input_depth; ++id) {
    109     for (int i = 0; i < input_rows; ++i) {
    110       for (int j = 0; j < input_cols; ++j) {
    111         float expected = 0.0f;
    112         for (int c = 0; c < patch_cols; ++c) {
    113           for (int r = 0; r < patch_rows; ++r) {
    114             for (int od = 0; od < output_depth; ++od) {
    115               int output_i = i - r;
    116               int output_j = j - c;
    117               if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    118                   output_j < output_cols) {
    119                 expected += output_backward(output_j, output_i, od) *
    120                             kernel(c, r, id, od);
    121               }
    122             }
    123           }
    124         }
    125         EigenApprox(input_backward(j, i, id), expected);
    126       }
    127     }
    128   }
    129 }
    130 
    131 TEST(EigenBackwardSpatialConvolutionsTest,
    132      test_simple_cuboid_convolution_backward_input_valid) {
    133   const int input_depth = 2;
    134   const int input_planes = 5;
    135   const int input_rows = 3;
    136   const int input_cols = 4;
    137   const int patch_rows = 2;
    138   const int patch_cols = 2;
    139   const int patch_planes = 2;
    140   const int output_rows = input_rows - patch_rows + 1;
    141   const int output_cols = input_cols - patch_cols + 1;
    142   const int output_planes = input_planes - patch_planes + 1;
    143   const int output_depth = 5;
    144 
    145   Tensor<float, 4> input_backward(input_depth, input_planes, input_rows,
    146                                   input_cols);
    147   Tensor<float, 5> kernel(output_depth, input_depth, patch_planes, patch_rows,
    148                           patch_cols);
    149   Tensor<float, 4> output_backward(output_depth, output_planes, output_rows,
    150                                    output_cols);
    151 
    152   output_backward = output_backward.constant(11.0f) + output_backward.random();
    153   kernel = kernel.constant(2.0f) + kernel.random();
    154   input_backward.setRandom();
    155 
    156   input_backward = CuboidConvolutionBackwardInput(
    157       kernel, output_backward, input_planes, input_rows, input_cols);
    158 
    159   EXPECT_EQ(input_backward.dimension(3), input_cols);
    160   EXPECT_EQ(input_backward.dimension(2), input_rows);
    161   EXPECT_EQ(input_backward.dimension(1), input_planes);
    162   EXPECT_EQ(input_backward.dimension(0), input_depth);
    163 
    164   for (int id = 0; id < input_depth; ++id) {
    165     for (int i = 0; i < input_planes; ++i) {
    166       for (int j = 0; j < input_rows; ++j) {
    167         for (int k = 0; k < input_cols; ++k) {
    168           float expected = 0.0f;
    169           for (int c = 0; c < patch_cols; ++c) {
    170             for (int r = 0; r < patch_rows; ++r) {
    171               for (int p = 0; p < patch_planes; ++p) {
    172                 for (int od = 0; od < output_depth; ++od) {
    173                   int output_j = j - r;
    174                   int output_k = k - c;
    175                   int output_i = i - p;
    176                   if (output_i >= 0 && output_i < output_planes &&
    177                       output_j >= 0 && output_j < output_rows &&
    178                       output_k >= 0 && output_k < output_cols) {
    179                     expected +=
    180                         output_backward(od, output_i, output_j, output_k) *
    181                         kernel(od, id, p, r, c);
    182                   }
    183                 }
    184               }
    185             }
    186           }
    187           EigenApprox(input_backward(id, i, j, k), expected);
    188         }
    189       }
    190     }
    191   }
    192 }
    193 
    194 TEST(EigenBackwardSpatialConvolutionsTest,
    195      test_simple_cuboid_convolution_backward_input_valid_row_major) {
    196   const int input_depth = 2;
    197   const int input_planes = 5;
    198   const int input_rows = 3;
    199   const int input_cols = 4;
    200   const int patch_rows = 2;
    201   const int patch_cols = 2;
    202   const int patch_planes = 2;
    203   const int output_rows = input_rows - patch_rows + 1;
    204   const int output_cols = input_cols - patch_cols + 1;
    205   const int output_planes = input_planes - patch_planes + 1;
    206   const int output_depth = 5;
    207 
    208   Tensor<float, 4, RowMajor> input_backward(input_cols, input_rows,
    209                                             input_planes, input_depth);
    210   Tensor<float, 5, RowMajor> kernel(patch_cols, patch_rows, patch_planes,
    211                                     input_depth, output_depth);
    212   Tensor<float, 4, RowMajor> output_backward(output_cols, output_rows,
    213                                              output_planes, output_depth);
    214 
    215   output_backward = output_backward.constant(11.0f) + output_backward.random();
    216   kernel = kernel.constant(2.0f) + kernel.random();
    217   input_backward.setRandom();
    218 
    219   input_backward = CuboidConvolutionBackwardInput(
    220       kernel, output_backward, input_planes, input_rows, input_cols);
    221 
    222   EXPECT_EQ(input_backward.dimension(0), input_cols);
    223   EXPECT_EQ(input_backward.dimension(1), input_rows);
    224   EXPECT_EQ(input_backward.dimension(2), input_planes);
    225   EXPECT_EQ(input_backward.dimension(3), input_depth);
    226 
    227   for (int id = 0; id < input_depth; ++id) {
    228     for (int i = 0; i < input_planes; ++i) {
    229       for (int j = 0; j < input_rows; ++j) {
    230         for (int k = 0; k < input_cols; ++k) {
    231           float expected = 0.0f;
    232           for (int c = 0; c < patch_cols; ++c) {
    233             for (int r = 0; r < patch_rows; ++r) {
    234               for (int p = 0; p < patch_planes; ++p) {
    235                 for (int od = 0; od < output_depth; ++od) {
    236                   int output_j = j - r;
    237                   int output_k = k - c;
    238                   int output_i = i - p;
    239                   if (output_i >= 0 && output_i < output_planes &&
    240                       output_j >= 0 && output_j < output_rows &&
    241                       output_k >= 0 && output_k < output_cols) {
    242                     expected +=
    243                         output_backward(output_k, output_j, output_i, od) *
    244                         kernel(c, r, p, id, od);
    245                   }
    246                 }
    247               }
    248             }
    249           }
    250           EigenApprox(input_backward(k, j, i, id), expected);
    251         }
    252       }
    253     }
    254   }
    255 }
    256 
    257 TEST(EigenBackwardSpatialConvolutionsTest,
    258      test_simple_spatial_convolution_backward_input_same) {
    259   const int input_depth = 2;
    260   const int input_rows = 7;
    261   const int input_cols = 9;
    262   const int output_depth = 3;
    263   const int patch_rows = 4;
    264   const int patch_cols = 4;
    265   const int output_rows = input_rows;
    266   const int output_cols = input_cols;
    267 
    268   Tensor<float, 3> input_backward(input_depth, input_rows, input_cols);
    269   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    270   Tensor<float, 3> output_backward(output_depth, output_rows, output_cols);
    271 
    272   output_backward = output_backward.constant(11.0f) + output_backward.random();
    273   kernel = kernel.constant(2.0f) + kernel.random();
    274 
    275   input_backward = SpatialConvolutionBackwardInput(kernel, output_backward,
    276                                                    input_rows, input_cols, 1);
    277 
    278   EXPECT_EQ(input_backward.dimension(0), input_depth);
    279   EXPECT_EQ(input_backward.dimension(1), input_rows);
    280   EXPECT_EQ(input_backward.dimension(2), input_cols);
    281 
    282   for (int id = 0; id < input_depth; ++id) {
    283     for (int i = 0; i < input_rows; ++i) {
    284       for (int j = 0; j < input_cols; ++j) {
    285         float expected = 0.0f;
    286         for (int c = 0; c < patch_cols; ++c) {
    287           for (int r = 0; r < patch_rows; ++r) {
    288             for (int od = 0; od < output_depth; ++od) {
    289               int output_i = i - r + (patch_rows - 1) / 2;
    290               int output_j = j - c + (patch_cols - 1) / 2;
    291               if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    292                   output_j < output_cols) {
    293                 expected += output_backward(od, output_i, output_j) *
    294                             kernel(od, id, r, c);
    295               }
    296             }
    297           }
    298         }
    299         EigenApprox(input_backward(id, i, j), expected);
    300       }
    301     }
    302   }
    303 }
    304 
    305 TEST(EigenBackwardSpatialConvolutionsTest,
    306      test_simple_spatial_convolution_backward_input_same_row_major) {
    307   const int input_depth = 2;
    308   const int input_rows = 7;
    309   const int input_cols = 9;
    310   const int output_depth = 3;
    311   const int patch_rows = 4;
    312   const int patch_cols = 4;
    313   const int output_rows = input_rows;
    314   const int output_cols = input_cols;
    315 
    316   Tensor<float, 3, RowMajor> input_backward(input_cols, input_rows,
    317                                             input_depth);
    318   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    319                                     output_depth);
    320   Tensor<float, 3, RowMajor> output_backward(output_cols, output_rows,
    321                                              output_depth);
    322 
    323   output_backward = output_backward.constant(11.0f) + output_backward.random();
    324   kernel = kernel.constant(2.0f) + kernel.random();
    325 
    326   input_backward = SpatialConvolutionBackwardInput(kernel, output_backward,
    327                                                    input_rows, input_cols, 1);
    328 
    329   EXPECT_EQ(input_backward.dimension(0), input_cols);
    330   EXPECT_EQ(input_backward.dimension(1), input_rows);
    331   EXPECT_EQ(input_backward.dimension(2), input_depth);
    332 
    333   for (int id = 0; id < input_depth; ++id) {
    334     for (int i = 0; i < input_rows; ++i) {
    335       for (int j = 0; j < input_cols; ++j) {
    336         float expected = 0.0f;
    337         for (int c = 0; c < patch_cols; ++c) {
    338           for (int r = 0; r < patch_rows; ++r) {
    339             for (int od = 0; od < output_depth; ++od) {
    340               int output_i = i - r + (patch_rows - 1) / 2;
    341               int output_j = j - c + (patch_cols - 1) / 2;
    342               if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    343                   output_j < output_cols) {
    344                 expected += output_backward(output_j, output_i, od) *
    345                             kernel(c, r, id, od);
    346               }
    347             }
    348           }
    349         }
    350         EigenApprox(input_backward(j, i, id), expected);
    351       }
    352     }
    353   }
    354 }
    355 
    356 TEST(EigenBackwardSpatialConvolutionsTest,
    357      test_simple_cuboid_convolution_backward_input_same) {
    358   const int input_depth = 2;
    359   const int input_planes = 5;
    360   const int input_rows = 3;
    361   const int input_cols = 4;
    362   const int patch_rows = 3;
    363   const int patch_cols = 2;
    364   const int patch_planes = 4;
    365   const int output_rows = input_rows;
    366   const int output_cols = input_cols;
    367   const int output_planes = input_planes;
    368   const int output_depth = 5;
    369 
    370   Tensor<float, 4> input_backward(input_depth, input_planes, input_rows,
    371                                   input_cols);
    372   Tensor<float, 5> kernel(output_depth, input_depth, patch_planes, patch_rows,
    373                           patch_cols);
    374   Tensor<float, 4> output_backward(output_depth, output_planes, output_rows,
    375                                    output_cols);
    376 
    377   output_backward = output_backward.constant(11.0f) + output_backward.random();
    378   kernel = kernel.constant(2.0f) + kernel.random();
    379   input_backward.setRandom();
    380 
    381   input_backward = CuboidConvolutionBackwardInput(
    382       kernel, output_backward, input_planes, input_rows, input_cols);
    383 
    384   EXPECT_EQ(input_backward.dimension(3), input_cols);
    385   EXPECT_EQ(input_backward.dimension(2), input_rows);
    386   EXPECT_EQ(input_backward.dimension(1), input_planes);
    387   EXPECT_EQ(input_backward.dimension(0), input_depth);
    388 
    389   const int dz = patch_planes - 1;
    390   const int dy = patch_rows - 1;
    391   const int dx = patch_cols - 1;
    392 
    393   const int forward_pad_x = dx / 2;
    394   const int forward_pad_y = dy / 2;
    395   const int forward_pad_z = dz / 2;
    396 
    397   for (int id = 0; id < input_depth; ++id) {
    398     for (int i = 0; i < input_planes; ++i) {
    399       for (int j = 0; j < input_rows; ++j) {
    400         for (int k = 0; k < input_cols; ++k) {
    401           float expected = 0.0f;
    402           for (int c = 0; c < patch_cols; ++c) {
    403             for (int r = 0; r < patch_rows; ++r) {
    404               for (int p = 0; p < patch_planes; ++p) {
    405                 for (int od = 0; od < output_depth; ++od) {
    406                   int output_i = i - p + forward_pad_z;
    407                   int output_j = j - r + forward_pad_y;
    408                   int output_k = k - c + forward_pad_x;
    409                   if (output_i >= 0 && output_i < output_planes &&
    410                       output_j >= 0 && output_j < output_rows &&
    411                       output_k >= 0 && output_k < output_cols) {
    412                     expected +=
    413                         output_backward(od, output_i, output_j, output_k) *
    414                         kernel(od, id, p, r, c);
    415                   }
    416                 }
    417               }
    418             }
    419           }
    420           EigenApprox(input_backward(id, i, j, k), expected);
    421         }
    422       }
    423     }
    424   }
    425 }
    426 
    427 TEST(EigenBackwardSpatialConvolutionsTest,
    428      test_simple_cuboid_convolution_backward_input_same_row_major) {
    429   const int input_depth = 2;
    430   const int input_planes = 5;
    431   const int input_rows = 3;
    432   const int input_cols = 4;
    433   const int patch_rows = 2;
    434   const int patch_cols = 3;
    435   const int patch_planes = 4;
    436   const int output_rows = input_rows;
    437   const int output_cols = input_cols;
    438   const int output_planes = input_planes;
    439   const int output_depth = 5;
    440 
    441   Tensor<float, 4, RowMajor> input_backward(input_cols, input_rows,
    442                                             input_planes, input_depth);
    443   Tensor<float, 5, RowMajor> kernel(patch_cols, patch_rows, patch_planes,
    444                                     input_depth, output_depth);
    445   Tensor<float, 4, RowMajor> output_backward(output_cols, output_rows,
    446                                              output_planes, output_depth);
    447 
    448   output_backward = output_backward.constant(11.0f) + output_backward.random();
    449   kernel = kernel.constant(2.0f) + kernel.random();
    450   input_backward.setRandom();
    451 
    452   input_backward = CuboidConvolutionBackwardInput(
    453       kernel, output_backward, input_planes, input_rows, input_cols);
    454 
    455   EXPECT_EQ(input_backward.dimension(0), input_cols);
    456   EXPECT_EQ(input_backward.dimension(1), input_rows);
    457   EXPECT_EQ(input_backward.dimension(2), input_planes);
    458   EXPECT_EQ(input_backward.dimension(3), input_depth);
    459 
    460   const int dz = patch_planes - 1;
    461   const int dy = patch_rows - 1;
    462   const int dx = patch_cols - 1;
    463 
    464   const int forward_pad_x = dx / 2;
    465   const int forward_pad_y = dy / 2;
    466   const int forward_pad_z = dz / 2;
    467 
    468   for (int id = 0; id < input_depth; ++id) {
    469     for (int i = 0; i < input_planes; ++i) {
    470       for (int j = 0; j < input_rows; ++j) {
    471         for (int k = 0; k < input_cols; ++k) {
    472           float expected = 0.0f;
    473           for (int c = 0; c < patch_cols; ++c) {
    474             for (int r = 0; r < patch_rows; ++r) {
    475               for (int p = 0; p < patch_planes; ++p) {
    476                 for (int od = 0; od < output_depth; ++od) {
    477                   int output_i = i - p + forward_pad_z;
    478                   int output_j = j - r + forward_pad_y;
    479                   int output_k = k - c + forward_pad_x;
    480                   if (output_i >= 0 && output_i < output_planes &&
    481                       output_j >= 0 && output_j < output_rows &&
    482                       output_k >= 0 && output_k < output_cols) {
    483                     expected +=
    484                         output_backward(output_k, output_j, output_i, od) *
    485                         kernel(c, r, p, id, od);
    486                   }
    487                 }
    488               }
    489             }
    490           }
    491           EigenApprox(input_backward(k, j, i, id), expected);
    492         }
    493       }
    494     }
    495   }
    496 }
    497 
    498 TEST(EigenBackwardSpatialConvolutionsTest,
    499      test_batched_spatial_convolution_backward_input_valid) {
    500   const int num_batches = 13;
    501   const int input_depth = 2;
    502   const int input_rows = 7;
    503   const int input_cols = 9;
    504   const int output_depth = 3;
    505   const int patch_rows = 5;
    506   const int patch_cols = 5;
    507   const int output_rows = input_rows - patch_rows + 1;
    508   const int output_cols = input_cols - patch_cols + 1;
    509 
    510   Tensor<float, 4> input_backward(input_depth, input_rows, input_cols,
    511                                   num_batches);
    512   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    513   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
    514                                    num_batches);
    515 
    516   output_backward = output_backward.constant(11.0f) + output_backward.random();
    517   kernel = kernel.constant(2.0f) + kernel.random();
    518   input_backward.setRandom();
    519 
    520   input_backward = SpatialConvolutionBackwardInput(kernel, output_backward,
    521                                                    input_rows, input_cols, 1);
    522 
    523   EXPECT_EQ(input_backward.dimension(0), input_depth);
    524   EXPECT_EQ(input_backward.dimension(1), input_rows);
    525   EXPECT_EQ(input_backward.dimension(2), input_cols);
    526   EXPECT_EQ(input_backward.dimension(3), num_batches);
    527 
    528   for (int b = 0; b < num_batches; ++b) {
    529     for (int id = 0; id < input_depth; ++id) {
    530       for (int i = 0; i < input_rows; ++i) {
    531         for (int j = 0; j < input_cols; ++j) {
    532           float expected = 0.0f;
    533           for (int c = 0; c < patch_cols; ++c) {
    534             for (int r = 0; r < patch_rows; ++r) {
    535               for (int od = 0; od < output_depth; ++od) {
    536                 int output_i = i - r;
    537                 int output_j = j - c;
    538                 if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    539                     output_j < output_cols) {
    540                   expected += output_backward(od, output_i, output_j, b) *
    541                               kernel(od, id, r, c);
    542                 }
    543               }
    544             }
    545           }
    546           EigenApprox(input_backward(id, i, j, b), expected);
    547         }
    548       }
    549     }
    550   }
    551 }
    552 
    553 TEST(EigenBackwardSpatialConvolutionsTest,
    554      test_batched_spatial_convolution_backward_input_valid_row_major) {
    555   const int num_batches = 13;
    556   const int input_depth = 2;
    557   const int input_rows = 7;
    558   const int input_cols = 9;
    559   const int output_depth = 3;
    560   const int patch_rows = 5;
    561   const int patch_cols = 5;
    562   const int output_rows = input_rows - patch_rows + 1;
    563   const int output_cols = input_cols - patch_cols + 1;
    564 
    565   Tensor<float, 4, RowMajor> input_backward(num_batches, input_cols, input_rows,
    566                                             input_depth);
    567   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    568                                     output_depth);
    569   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
    570                                              output_rows, output_depth);
    571 
    572   output_backward = output_backward.constant(11.0f) + output_backward.random();
    573   kernel = kernel.constant(2.0f) + kernel.random();
    574   input_backward.setRandom();
    575 
    576   input_backward = SpatialConvolutionBackwardInput(kernel, output_backward,
    577                                                    input_rows, input_cols, 1);
    578 
    579   EXPECT_EQ(input_backward.dimension(0), num_batches);
    580   EXPECT_EQ(input_backward.dimension(1), input_cols);
    581   EXPECT_EQ(input_backward.dimension(2), input_rows);
    582   EXPECT_EQ(input_backward.dimension(3), input_depth);
    583 
    584   for (int b = 0; b < num_batches; ++b) {
    585     for (int id = 0; id < input_depth; ++id) {
    586       for (int i = 0; i < input_rows; ++i) {
    587         for (int j = 0; j < input_cols; ++j) {
    588           float expected = 0.0f;
    589           for (int c = 0; c < patch_cols; ++c) {
    590             for (int r = 0; r < patch_rows; ++r) {
    591               for (int od = 0; od < output_depth; ++od) {
    592                 int output_i = i - r;
    593                 int output_j = j - c;
    594                 if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    595                     output_j < output_cols) {
    596                   expected += output_backward(b, output_j, output_i, od) *
    597                               kernel(c, r, id, od);
    598                 }
    599               }
    600             }
    601           }
    602           EigenApprox(input_backward(b, j, i, id), expected);
    603         }
    604       }
    605     }
    606   }
    607 }
    608 
    609 TEST(EigenBackwardSpatialConvolutionsTest,
    610      test_batched_cuboid_convolution_backward_input_valid) {
    611   const int num_batches = 13;
    612   const int input_depth = 2;
    613   const int input_planes = 5;
    614   const int input_rows = 3;
    615   const int input_cols = 4;
    616   const int patch_rows = 2;
    617   const int patch_cols = 2;
    618   const int patch_planes = 2;
    619   const int output_rows = input_rows - patch_rows + 1;
    620   const int output_cols = input_cols - patch_cols + 1;
    621   const int output_planes = input_planes - patch_planes + 1;
    622   const int output_depth = 5;
    623 
    624   Tensor<float, 5> input_backward(input_depth, input_planes, input_rows,
    625                                   input_cols, num_batches);
    626   Tensor<float, 5> kernel(output_depth, input_depth, patch_planes, patch_rows,
    627                           patch_cols);
    628   Tensor<float, 5> output_backward(output_depth, output_planes, output_rows,
    629                                    output_cols, num_batches);
    630 
    631   output_backward = output_backward.constant(11.0f) + output_backward.random();
    632   kernel = kernel.constant(2.0f) + kernel.random();
    633   input_backward.setRandom();
    634 
    635   input_backward = CuboidConvolutionBackwardInput(
    636       kernel, output_backward, input_planes, input_rows, input_cols);
    637 
    638   EXPECT_EQ(input_backward.dimension(4), num_batches);
    639   EXPECT_EQ(input_backward.dimension(3), input_cols);
    640   EXPECT_EQ(input_backward.dimension(2), input_rows);
    641   EXPECT_EQ(input_backward.dimension(1), input_planes);
    642   EXPECT_EQ(input_backward.dimension(0), input_depth);
    643 
    644   for (int b = 0; b < num_batches; ++b) {
    645     for (int id = 0; id < input_depth; ++id) {
    646       for (int i = 0; i < input_planes; ++i) {
    647         for (int j = 0; j < input_rows; ++j) {
    648           for (int k = 0; k < input_cols; ++k) {
    649             float expected = 0.0f;
    650             for (int c = 0; c < patch_cols; ++c) {
    651               for (int r = 0; r < patch_rows; ++r) {
    652                 for (int p = 0; p < patch_planes; ++p) {
    653                   for (int od = 0; od < output_depth; ++od) {
    654                     int output_i = i - p;
    655                     int output_j = j - r;
    656                     int output_k = k - c;
    657                     if (output_i >= 0 && output_i < output_planes &&
    658                         output_j >= 0 && output_j < output_rows &&
    659                         output_k >= 0 && output_k < output_cols) {
    660                       expected +=
    661                           output_backward(od, output_i, output_j, output_k, b) *
    662                           kernel(od, id, p, r, c);
    663                     }
    664                   }
    665                 }
    666               }
    667             }
    668             EigenApprox(input_backward(id, i, j, k, b), expected);
    669           }
    670         }
    671       }
    672     }
    673   }
    674 }
    675 
    676 TEST(EigenBackwardSpatialConvolutionsTest,
    677      test_batched_cuboid_convolution_backward_input_valid_row_major) {
    678   const int num_batches = 13;
    679   const int input_depth = 2;
    680   const int input_planes = 5;
    681   const int input_rows = 3;
    682   const int input_cols = 4;
    683   const int patch_rows = 2;
    684   const int patch_cols = 2;
    685   const int patch_planes = 2;
    686   const int output_rows = input_rows - patch_rows + 1;
    687   const int output_cols = input_cols - patch_cols + 1;
    688   const int output_planes = input_planes - patch_planes + 1;
    689   const int output_depth = 5;
    690 
    691   Tensor<float, 5, RowMajor> input_backward(num_batches, input_cols, input_rows,
    692                                             input_planes, input_depth);
    693   Tensor<float, 5, RowMajor> kernel(patch_cols, patch_rows, patch_planes,
    694                                     input_depth, output_depth);
    695   Tensor<float, 5, RowMajor> output_backward(
    696       num_batches, output_cols, output_rows, output_planes, output_depth);
    697 
    698   output_backward = output_backward.constant(11.0f) + output_backward.random();
    699   kernel = kernel.constant(2.0f) + kernel.random();
    700   input_backward.setRandom();
    701 
    702   input_backward = CuboidConvolutionBackwardInput(
    703       kernel, output_backward, input_planes, input_rows, input_cols);
    704 
    705   EXPECT_EQ(input_backward.dimension(0), num_batches);
    706   EXPECT_EQ(input_backward.dimension(1), input_cols);
    707   EXPECT_EQ(input_backward.dimension(2), input_rows);
    708   EXPECT_EQ(input_backward.dimension(3), input_planes);
    709   EXPECT_EQ(input_backward.dimension(4), input_depth);
    710 
    711   for (int b = 0; b < num_batches; ++b) {
    712     for (int id = 0; id < input_depth; ++id) {
    713       for (int i = 0; i < input_planes; ++i) {
    714         for (int j = 0; j < input_rows; ++j) {
    715           for (int k = 0; k < input_cols; ++k) {
    716             float expected = 0.0f;
    717             for (int c = 0; c < patch_cols; ++c) {
    718               for (int r = 0; r < patch_rows; ++r) {
    719                 for (int p = 0; p < patch_planes; ++p) {
    720                   for (int od = 0; od < output_depth; ++od) {
    721                     int output_i = i - p;
    722                     int output_j = j - r;
    723                     int output_k = k - c;
    724                     if (output_i >= 0 && output_i < output_planes &&
    725                         output_j >= 0 && output_j < output_rows &&
    726                         output_k >= 0 && output_k < output_cols) {
    727                       expected +=
    728                           output_backward(b, output_k, output_j, output_i, od) *
    729                           kernel(c, r, p, id, od);
    730                     }
    731                   }
    732                 }
    733               }
    734             }
    735             EigenApprox(input_backward(b, k, j, i, id), expected);
    736           }
    737         }
    738       }
    739     }
    740   }
    741 }
    742 
    743 static void test_batched_strided_spatial_convolution_backward_input_valid(
    744     const int num_batches, const int input_depth, const int input_rows,
    745     const int input_cols, const int output_depth) {
    746   const int patch_rows = 3;
    747   const int patch_cols = 3;
    748 
    749   const int stride = 3;
    750 
    751   const int output_rows = divup(input_rows - patch_rows + 1, stride);
    752   const int output_cols = divup(input_cols - patch_cols + 1, stride);
    753 
    754   Tensor<float, 4> input_backward(input_depth, input_rows, input_cols,
    755                                   num_batches);
    756   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    757   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
    758                                    num_batches);
    759 
    760   output_backward = output_backward.constant(11.0f) + output_backward.random();
    761   kernel = kernel.constant(2.0f) + kernel.random();
    762   input_backward.setRandom();
    763 
    764   input_backward = SpatialConvolutionBackwardInput(
    765       kernel, output_backward, input_rows, input_cols, stride, stride);
    766 
    767   EXPECT_EQ(input_backward.dimension(0), input_depth);
    768   EXPECT_EQ(input_backward.dimension(1), input_rows);
    769   EXPECT_EQ(input_backward.dimension(2), input_cols);
    770   EXPECT_EQ(input_backward.dimension(3), num_batches);
    771 
    772   for (int b = 0; b < num_batches; ++b) {
    773     for (int id = 0; id < input_depth; ++id) {
    774       for (int i = 0; i < input_rows; ++i) {
    775         for (int j = 0; j < input_cols; ++j) {
    776           float expected = 0.0f;
    777           for (int c = 0; c < patch_cols; ++c) {
    778             for (int r = 0; r < patch_rows; ++r) {
    779               for (int od = 0; od < output_depth; ++od) {
    780                 int output_i = i - r;
    781                 int output_j = j - c;
    782                 if (output_i >= 0 && output_i / stride < output_rows &&
    783                     output_j >= 0 && output_j / stride < output_cols &&
    784                     output_i % stride == 0 && output_j % stride == 0) {
    785                   expected += output_backward(od, output_i / stride,
    786                                               output_j / stride, b) *
    787                               kernel(od, id, r, c);
    788                 }
    789               }
    790             }
    791           }
    792           EigenApprox(input_backward(id, i, j, b), expected);
    793         }
    794       }
    795     }
    796   }
    797 }
    798 
    799 TEST(EigenBackwardSpatialConvolutionsTest,
    800      test_batched_strided_spatial_convolution_backward_input_valid) {
    801   int num_batches = 1;
    802   int input_depth = 1;
    803   int input_rows = 3;
    804   int input_cols = 5;
    805   int output_depth = 1;
    806   test_batched_strided_spatial_convolution_backward_input_valid(
    807       num_batches, input_depth, input_rows, input_cols, output_depth);
    808 
    809   num_batches = 11;
    810   input_depth = 2;
    811   input_rows = 9;
    812   input_cols = 13;
    813   output_depth = 5;
    814   test_batched_strided_spatial_convolution_backward_input_valid(
    815       num_batches, input_depth, input_rows, input_cols, output_depth);
    816 }
    817 
    818 static void
    819 test_batched_strided_spatial_convolution_backward_input_valid_row_major(
    820     const int num_batches, const int input_depth, const int input_rows,
    821     const int input_cols, const int output_depth) {
    822   const int patch_rows = 3;
    823   const int patch_cols = 3;
    824 
    825   const int stride = 3;
    826 
    827   const int output_rows = divup(input_rows - patch_rows + 1, stride);
    828   const int output_cols = divup(input_cols - patch_cols + 1, stride);
    829 
    830   Tensor<float, 4, RowMajor> input_backward(num_batches, input_cols, input_rows,
    831                                             input_depth);
    832   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    833                                     output_depth);
    834   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
    835                                              output_rows, output_depth);
    836 
    837   output_backward = output_backward.constant(11.0f) + output_backward.random();
    838   kernel = kernel.constant(2.0f) + kernel.random();
    839   input_backward.setRandom();
    840 
    841   input_backward = SpatialConvolutionBackwardInput(
    842       kernel, output_backward, input_rows, input_cols, stride, stride);
    843 
    844   EXPECT_EQ(input_backward.dimension(0), num_batches);
    845   EXPECT_EQ(input_backward.dimension(1), input_cols);
    846   EXPECT_EQ(input_backward.dimension(2), input_rows);
    847   EXPECT_EQ(input_backward.dimension(3), input_depth);
    848 
    849   for (int b = 0; b < num_batches; ++b) {
    850     for (int id = 0; id < input_depth; ++id) {
    851       for (int i = 0; i < input_rows; ++i) {
    852         for (int j = 0; j < input_cols; ++j) {
    853           float expected = 0.0f;
    854           for (int c = 0; c < patch_cols; ++c) {
    855             for (int r = 0; r < patch_rows; ++r) {
    856               for (int od = 0; od < output_depth; ++od) {
    857                 int output_i = i - r;
    858                 int output_j = j - c;
    859                 if (output_i >= 0 && output_i / stride < output_rows &&
    860                     output_j >= 0 && output_j / stride < output_cols &&
    861                     output_i % stride == 0 && output_j % stride == 0) {
    862                   expected += output_backward(b, output_j / stride,
    863                                               output_i / stride, od) *
    864                               kernel(c, r, id, od);
    865                 }
    866               }
    867             }
    868           }
    869           EigenApprox(input_backward(b, j, i, id), expected);
    870         }
    871       }
    872     }
    873   }
    874 }
    875 
    876 TEST(EigenBackwardSpatialConvolutionsTest,
    877      test_batched_strided_spatial_convolution_backward_input_valid_row_major) {
    878   int num_batches = 1;
    879   int input_depth = 1;
    880   int input_rows = 3;
    881   int input_cols = 5;
    882   int output_depth = 1;
    883   test_batched_strided_spatial_convolution_backward_input_valid_row_major(
    884       num_batches, input_depth, input_rows, input_cols, output_depth);
    885 
    886   num_batches = 11;
    887   input_depth = 2;
    888   input_rows = 9;
    889   input_cols = 13;
    890   output_depth = 5;
    891   test_batched_strided_spatial_convolution_backward_input_valid_row_major(
    892       num_batches, input_depth, input_rows, input_cols, output_depth);
    893 }
    894 
    895 TEST(EigenBackwardSpatialConvolutionsTest,
    896      test_simple_spatial_convolution_backward_kernel_valid) {
    897   const int input_depth = 2;
    898   const int input_rows = 3;
    899   const int input_cols = 4;
    900   const int output_depth = 5;
    901   const int patch_rows = 2;
    902   const int patch_cols = 2;
    903   const int output_rows = input_rows - patch_rows + 1;
    904   const int output_cols = input_cols - patch_cols + 1;
    905 
    906   Tensor<float, 3> input(input_depth, input_rows, input_cols);
    907   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    908   Tensor<float, 3> output_backward(output_depth, output_rows, output_cols);
    909 
    910   output_backward = output_backward.constant(11.0f) + output_backward.random();
    911   input = input.constant(2.0f) + input.random();
    912   kernel.setRandom();
    913 
    914   kernel = SpatialConvolutionBackwardKernel(input, output_backward, patch_rows,
    915                                             patch_cols, 1, 1);
    916 
    917   EXPECT_EQ(kernel.dimension(0), output_depth);
    918   EXPECT_EQ(kernel.dimension(1), input_depth);
    919   EXPECT_EQ(kernel.dimension(2), patch_rows);
    920   EXPECT_EQ(kernel.dimension(3), patch_cols);
    921 
    922   for (int od = 0; od < output_depth; ++od) {
    923     for (int id = 0; id < input_depth; ++id) {
    924       for (int r = 0; r < patch_rows; ++r) {
    925         for (int c = 0; c < patch_cols; ++c) {
    926           float expected = 0.0f;
    927           for (int i = 0; i < input_rows; ++i) {
    928             for (int j = 0; j < input_cols; ++j) {
    929               int output_i = i - r;
    930               int output_j = j - c;
    931               if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    932                   output_j < output_cols) {
    933                 expected +=
    934                     input(id, i, j) * output_backward(od, output_i, output_j);
    935               }
    936             }
    937           }
    938           EigenApprox(kernel(od, id, r, c), expected);
    939         }
    940       }
    941     }
    942   }
    943 }
    944 
    945 TEST(EigenBackwardSpatialConvolutionsTest,
    946      test_simple_spatial_convolution_backward_kernel_valid_row_major) {
    947   const int input_depth = 2;
    948   const int input_rows = 3;
    949   const int input_cols = 4;
    950   const int output_depth = 5;
    951   const int patch_rows = 2;
    952   const int patch_cols = 2;
    953   const int output_rows = input_rows - patch_rows + 1;
    954   const int output_cols = input_cols - patch_cols + 1;
    955 
    956   Tensor<float, 3, RowMajor> input(input_cols, input_rows, input_depth);
    957   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    958                                     output_depth);
    959   Tensor<float, 3, RowMajor> output_backward(output_cols, output_rows,
    960                                              output_depth);
    961 
    962   output_backward = output_backward.constant(11.0f) + output_backward.random();
    963   input = input.constant(2.0f) + input.random();
    964   kernel.setRandom();
    965 
    966   kernel = SpatialConvolutionBackwardKernel(input, output_backward, patch_rows,
    967                                             patch_cols, 1, 1);
    968 
    969   EXPECT_EQ(kernel.dimension(0), patch_cols);
    970   EXPECT_EQ(kernel.dimension(1), patch_rows);
    971   EXPECT_EQ(kernel.dimension(2), input_depth);
    972   EXPECT_EQ(kernel.dimension(3), output_depth);
    973 
    974   for (int od = 0; od < output_depth; ++od) {
    975     for (int id = 0; id < input_depth; ++id) {
    976       for (int r = 0; r < patch_rows; ++r) {
    977         for (int c = 0; c < patch_cols; ++c) {
    978           float expected = 0.0f;
    979           for (int i = 0; i < input_rows; ++i) {
    980             for (int j = 0; j < input_cols; ++j) {
    981               int output_i = i - r;
    982               int output_j = j - c;
    983               if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
    984                   output_j < output_cols) {
    985                 expected +=
    986                     input(j, i, id) * output_backward(output_j, output_i, od);
    987               }
    988             }
    989           }
    990           EigenApprox(kernel(c, r, id, od), expected);
    991         }
    992       }
    993     }
    994   }
    995 }
    996 
    997 TEST(EigenBackwardSpatialConvolutionsTest,
    998      test_batched_atrous_spatial_convolution_backward_input_valid) {
    999   const int num_batches = 11;
   1000   const int patch_rows = 3;
   1001   const int patch_cols = 3;
   1002 
   1003   const int input_depth = 2;
   1004   const int input_rows = 9;
   1005   const int input_cols = 13;
   1006 
   1007   const int in_stride = 3;
   1008   const int patch_rows_eff = patch_rows + (patch_rows - 1) * (in_stride - 1);
   1009   const int patch_cols_eff = patch_cols + (patch_cols - 1) * (in_stride - 1);
   1010 
   1011   const int output_depth = 5;
   1012   const int output_rows = input_rows - patch_rows_eff + 1;
   1013   const int output_cols = input_cols - patch_cols_eff + 1;
   1014 
   1015   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
   1016                                    num_batches);
   1017   output_backward.setRandom();
   1018   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
   1019   kernel.setRandom();
   1020 
   1021   const array<DenseIndex, 4> kernel_strides({1, 1, in_stride, in_stride});
   1022   const Tensor<float, 4> kernel_eff = kernel.inflate(kernel_strides);
   1023 
   1024   const Tensor<float, 4> input_backward =
   1025       SpatialConvolutionBackwardInput(kernel, output_backward, input_rows,
   1026                                       input_cols, 1, 1, in_stride, in_stride);
   1027   const Tensor<float, 4> expected_input_backward =
   1028       SpatialConvolutionBackwardInput(kernel_eff, output_backward, input_rows,
   1029                                       input_cols);
   1030 
   1031   EXPECT_EQ(input_backward.dimension(0), input_depth);
   1032   EXPECT_EQ(input_backward.dimension(1), input_rows);
   1033   EXPECT_EQ(input_backward.dimension(2), input_cols);
   1034   EXPECT_EQ(input_backward.dimension(3), num_batches);
   1035 
   1036   eigen_assert(dimensions_match(input_backward.dimensions(),
   1037                                 expected_input_backward.dimensions()));
   1038   for (ptrdiff_t i = 0; i < input_backward.dimensions().TotalSize(); ++i) {
   1039     EigenApprox(input_backward.data()[i], expected_input_backward.data()[i]);
   1040   }
   1041 }
   1042 
   1043 TEST(
   1044     EigenBackwardSpatialConvolutionsTest,
   1045     test_batched_atrous_spatial_convolution_backward_input_valid_unequal_strides) {
   1046   const int num_batches = 11;
   1047   const int patch_rows = 3;
   1048   const int patch_cols = 3;
   1049 
   1050   const int input_depth = 2;
   1051   const int input_rows = 9;
   1052   const int input_cols = 13;
   1053 
   1054   const int row_in_stride = 3;
   1055   const int col_in_stride = 1;
   1056   const int patch_rows_eff =
   1057       patch_rows + (patch_rows - 1) * (row_in_stride - 1);
   1058   const int patch_cols_eff =
   1059       patch_cols + (patch_cols - 1) * (col_in_stride - 1);
   1060 
   1061   const int output_depth = 5;
   1062   const int output_rows = input_rows - patch_rows_eff + 1;
   1063   const int output_cols = input_cols - patch_cols_eff + 1;
   1064 
   1065   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
   1066                                    num_batches);
   1067   output_backward.setRandom();
   1068   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
   1069   kernel.setRandom();
   1070 
   1071   const array<DenseIndex, 4> kernel_strides(
   1072       {1, 1, row_in_stride, col_in_stride});
   1073   const Tensor<float, 4> kernel_eff = kernel.inflate(kernel_strides);
   1074 
   1075   const Tensor<float, 4> input_backward = SpatialConvolutionBackwardInput(
   1076       kernel, output_backward, input_rows, input_cols, 1, 1, row_in_stride,
   1077       col_in_stride);
   1078   const Tensor<float, 4> expected_input_backward =
   1079       SpatialConvolutionBackwardInput(kernel_eff, output_backward, input_rows,
   1080                                       input_cols);
   1081 
   1082   EXPECT_EQ(input_backward.dimension(0), input_depth);
   1083   EXPECT_EQ(input_backward.dimension(1), input_rows);
   1084   EXPECT_EQ(input_backward.dimension(2), input_cols);
   1085   EXPECT_EQ(input_backward.dimension(3), num_batches);
   1086 
   1087   eigen_assert(dimensions_match(input_backward.dimensions(),
   1088                                 expected_input_backward.dimensions()));
   1089   for (ptrdiff_t i = 0; i < input_backward.dimensions().TotalSize(); ++i) {
   1090     EigenApprox(input_backward.data()[i], expected_input_backward.data()[i]);
   1091   }
   1092 }
   1093 
   1094 TEST(EigenBackwardSpatialConvolutionsTest,
   1095      test_batched_atrous_spatial_convolution_backward_input_valid_row_major) {
   1096   const int num_batches = 11;
   1097   const int patch_rows = 3;
   1098   const int patch_cols = 3;
   1099 
   1100   const int input_depth = 2;
   1101   const int input_rows = 9;
   1102   const int input_cols = 13;
   1103 
   1104   const int in_stride = 3;
   1105   const int patch_rows_eff = patch_rows + (patch_rows - 1) * (in_stride - 1);
   1106   const int patch_cols_eff = patch_cols + (patch_cols - 1) * (in_stride - 1);
   1107 
   1108   const int output_depth = 5;
   1109   const int output_rows = input_rows - patch_rows_eff + 1;
   1110   const int output_cols = input_cols - patch_cols_eff + 1;
   1111 
   1112   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
   1113                                              output_rows, output_depth);
   1114   output_backward.setRandom();
   1115 
   1116   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
   1117                                     output_depth);
   1118   kernel.setRandom();
   1119 
   1120   const array<DenseIndex, 4> kernel_strides({in_stride, in_stride, 1, 1});
   1121   const Tensor<float, 4, RowMajor> kernel_eff = kernel.inflate(kernel_strides);
   1122 
   1123   const Tensor<float, 4, RowMajor> input_backward =
   1124       SpatialConvolutionBackwardInput(kernel, output_backward, input_rows,
   1125                                       input_cols, 1, 1, in_stride, in_stride);
   1126   const Tensor<float, 4, RowMajor> expected_input_backward =
   1127       SpatialConvolutionBackwardInput(kernel_eff, output_backward, input_rows,
   1128                                       input_cols);
   1129 
   1130   EXPECT_EQ(input_backward.dimension(0), num_batches);
   1131   EXPECT_EQ(input_backward.dimension(1), input_cols);
   1132   EXPECT_EQ(input_backward.dimension(2), input_rows);
   1133   EXPECT_EQ(input_backward.dimension(3), input_depth);
   1134 
   1135   eigen_assert(dimensions_match(input_backward.dimensions(),
   1136                                 expected_input_backward.dimensions()));
   1137   for (ptrdiff_t i = 0; i < input_backward.dimensions().TotalSize(); ++i) {
   1138     EigenApprox(input_backward.data()[i], expected_input_backward.data()[i]);
   1139   }
   1140 }
   1141 
   1142 TEST(EigenBackwardSpatialConvolutionsTest,
   1143      test_batched_atrous_spatial_convolution_backward_kernel_valid) {
   1144   const int num_batches = 11;
   1145   const int patch_rows = 3;
   1146   const int patch_cols = 3;
   1147 
   1148   const int input_depth = 2;
   1149   const int input_rows = 9;
   1150   const int input_cols = 13;
   1151 
   1152   const int in_stride = 3;
   1153   const int patch_rows_eff = patch_rows + (patch_rows - 1) * (in_stride - 1);
   1154   const int patch_cols_eff = patch_cols + (patch_cols - 1) * (in_stride - 1);
   1155 
   1156   const int output_depth = 5;
   1157   const int output_rows = input_rows - patch_rows_eff + 1;
   1158   const int output_cols = input_cols - patch_cols_eff + 1;
   1159 
   1160   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
   1161                                    num_batches);
   1162   output_backward.setRandom();
   1163 
   1164   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
   1165   input.setRandom();
   1166 
   1167   const array<DenseIndex, 4> kernel_strides({1, 1, in_stride, in_stride});
   1168 
   1169   const Tensor<float, 4> kernel_backward =
   1170       SpatialConvolutionBackwardKernel(input, output_backward, patch_rows,
   1171                                        patch_cols, 1, 1, in_stride, in_stride);
   1172   const Tensor<float, 4> expected_kernel_backward =
   1173       SpatialConvolutionBackwardKernel(input, output_backward, patch_rows_eff,
   1174                                        patch_cols_eff)
   1175           .stride(kernel_strides);
   1176 
   1177   EXPECT_EQ(kernel_backward.dimension(0), output_depth);
   1178   EXPECT_EQ(kernel_backward.dimension(1), input_depth);
   1179   EXPECT_EQ(kernel_backward.dimension(2), patch_rows);
   1180   EXPECT_EQ(kernel_backward.dimension(3), patch_cols);
   1181 
   1182   eigen_assert(dimensions_match(kernel_backward.dimensions(),
   1183                                 expected_kernel_backward.dimensions()));
   1184   for (ptrdiff_t i = 0; i < kernel_backward.dimensions().TotalSize(); ++i) {
   1185     EigenApprox(kernel_backward.data()[i], expected_kernel_backward.data()[i]);
   1186   }
   1187 }
   1188 
   1189 TEST(EigenBackwardSpatialConvolutionsTest,
   1190      test_batched_atrous_spatial_convolution_backward_kernel_valid_row_major) {
   1191   const int num_batches = 11;
   1192   const int patch_rows = 3;
   1193   const int patch_cols = 3;
   1194 
   1195   const int input_depth = 2;
   1196   const int input_rows = 9;
   1197   const int input_cols = 13;
   1198 
   1199   const int in_stride = 3;
   1200   const int patch_rows_eff = patch_rows + (patch_rows - 1) * (in_stride - 1);
   1201   const int patch_cols_eff = patch_cols + (patch_cols - 1) * (in_stride - 1);
   1202 
   1203   const int output_depth = 5;
   1204   const int output_rows = input_rows - patch_rows_eff + 1;
   1205   const int output_cols = input_cols - patch_cols_eff + 1;
   1206 
   1207   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
   1208                                              output_rows, output_depth);
   1209   output_backward.setRandom();
   1210 
   1211   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
   1212                                    input_depth);
   1213   input.setRandom();
   1214 
   1215   const array<DenseIndex, 4> kernel_strides({in_stride, in_stride, 1, 1});
   1216 
   1217   const Tensor<float, 4, RowMajor> kernel_backward =
   1218       SpatialConvolutionBackwardKernel(input, output_backward, patch_rows,
   1219                                        patch_cols, 1, 1, in_stride, in_stride);
   1220   const Tensor<float, 4, RowMajor> expected_kernel_backward =
   1221       SpatialConvolutionBackwardKernel(input, output_backward, patch_rows_eff,
   1222                                        patch_cols_eff)
   1223           .stride(kernel_strides);
   1224 
   1225   EXPECT_EQ(kernel_backward.dimension(0), patch_cols);
   1226   EXPECT_EQ(kernel_backward.dimension(1), patch_rows);
   1227   EXPECT_EQ(kernel_backward.dimension(2), input_depth);
   1228   EXPECT_EQ(kernel_backward.dimension(3), output_depth);
   1229 
   1230   eigen_assert(dimensions_match(kernel_backward.dimensions(),
   1231                                 expected_kernel_backward.dimensions()));
   1232   for (ptrdiff_t i = 0; i < kernel_backward.dimensions().TotalSize(); ++i) {
   1233     EigenApprox(kernel_backward.data()[i], expected_kernel_backward.data()[i]);
   1234   }
   1235 }
   1236 
   1237 TEST(EigenBackwardSpatialConvolutionsTest,
   1238      test_simple_cuboid_convolution_backward_kernel_valid) {
   1239   const int input_depth = 2;
   1240   const int input_planes = 5;
   1241   const int input_rows = 3;
   1242   const int input_cols = 4;
   1243   const int output_depth = 5;
   1244   const int patch_rows = 2;
   1245   const int patch_cols = 2;
   1246   const int patch_planes = 3;
   1247   const int output_rows = input_rows - patch_rows + 1;
   1248   const int output_cols = input_cols - patch_cols + 1;
   1249   const int output_planes = input_planes - patch_planes + 1;
   1250 
   1251   Tensor<float, 4> input(input_depth, input_planes, input_rows, input_cols);
   1252   Tensor<float, 5> kernel(output_depth, input_depth, patch_planes, patch_rows,
   1253                           patch_cols);
   1254   Tensor<float, 4> output_backward(output_depth, output_planes, output_rows,
   1255                                    output_cols);
   1256 
   1257   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1258   input = input.constant(2.0f) + input.random();
   1259   kernel.setRandom();
   1260 
   1261   kernel = CuboidConvolutionBackwardKernel(input, output_backward, patch_planes,
   1262                                            patch_rows, patch_cols, 1, 1, 1);
   1263 
   1264   EXPECT_EQ(kernel.dimension(0), output_depth);
   1265   EXPECT_EQ(kernel.dimension(1), input_depth);
   1266   EXPECT_EQ(kernel.dimension(2), patch_planes);
   1267   EXPECT_EQ(kernel.dimension(3), patch_rows);
   1268   EXPECT_EQ(kernel.dimension(4), patch_cols);
   1269 
   1270   for (int od = 0; od < output_depth; ++od) {
   1271     for (int id = 0; id < input_depth; ++id) {
   1272       for (int p = 0; p < patch_planes; ++p) {
   1273         for (int r = 0; r < patch_rows; ++r) {
   1274           for (int c = 0; c < patch_cols; ++c) {
   1275             float expected = 0.0f;
   1276             for (int i = 0; i < input_planes; ++i) {
   1277               for (int j = 0; j < input_rows; ++j) {
   1278                 for (int k = 0; k < input_cols; ++k) {
   1279                   int output_j = j - r;
   1280                   int output_k = k - c;
   1281                   int output_i = i - p;
   1282                   if (output_i >= 0 && output_i < output_planes &&
   1283                       output_j >= 0 && output_j < output_rows &&
   1284                       output_k >= 0 && output_k < output_cols) {
   1285                     expected +=
   1286                         input(id, i, j, k) *
   1287                         output_backward(od, output_i, output_j, output_k);
   1288                   }
   1289                 }
   1290               }
   1291             }
   1292             EigenApprox(kernel(od, id, p, r, c), expected);
   1293           }
   1294         }
   1295       }
   1296     }
   1297   }
   1298 }
   1299 
   1300 TEST(EigenBackwardSpatialConvolutionsTest,
   1301      test_simple_cuboid_convolution_backward_kernel_valid_row_major) {
   1302   const int input_depth = 2;
   1303   const int input_planes = 5;
   1304   const int input_rows = 3;
   1305   const int input_cols = 4;
   1306   const int output_depth = 5;
   1307   const int patch_rows = 2;
   1308   const int patch_cols = 2;
   1309   const int patch_planes = 3;
   1310   const int output_rows = input_rows - patch_rows + 1;
   1311   const int output_cols = input_cols - patch_cols + 1;
   1312   const int output_planes = input_planes - patch_planes + 1;
   1313 
   1314   Tensor<float, 4, RowMajor> input(input_cols, input_rows, input_planes,
   1315                                    input_depth);
   1316   Tensor<float, 5, RowMajor> kernel(patch_cols, patch_rows, patch_planes,
   1317                                     input_depth, output_depth);
   1318   Tensor<float, 4, RowMajor> output_backward(output_cols, output_rows,
   1319                                              output_planes, output_depth);
   1320 
   1321   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1322   input = input.constant(2.0f) + input.random();
   1323   kernel.setRandom();
   1324 
   1325   kernel = CuboidConvolutionBackwardKernel(input, output_backward, patch_planes,
   1326                                            patch_rows, patch_cols, 1, 1, 1);
   1327 
   1328   EXPECT_EQ(kernel.dimension(4), output_depth);
   1329   EXPECT_EQ(kernel.dimension(3), input_depth);
   1330   EXPECT_EQ(kernel.dimension(2), patch_planes);
   1331   EXPECT_EQ(kernel.dimension(1), patch_rows);
   1332   EXPECT_EQ(kernel.dimension(0), patch_cols);
   1333 
   1334   for (int od = 0; od < output_depth; ++od) {
   1335     for (int id = 0; id < input_depth; ++id) {
   1336       for (int p = 0; p < patch_planes; ++p) {
   1337         for (int r = 0; r < patch_rows; ++r) {
   1338           for (int c = 0; c < patch_cols; ++c) {
   1339             float expected = 0.0f;
   1340             for (int i = 0; i < input_planes; ++i) {
   1341               for (int j = 0; j < input_rows; ++j) {
   1342                 for (int k = 0; k < input_cols; ++k) {
   1343                   int output_j = j - r;
   1344                   int output_k = k - c;
   1345                   int output_i = i - p;
   1346                   if (output_i >= 0 && output_i < output_planes &&
   1347                       output_j >= 0 && output_j < output_rows &&
   1348                       output_k >= 0 && output_k < output_cols) {
   1349                     expected +=
   1350                         input(k, j, i, id) *
   1351                         output_backward(output_k, output_j, output_i, od);
   1352                   }
   1353                 }
   1354               }
   1355             }
   1356             EigenApprox(kernel(c, r, p, id, od), expected);
   1357           }
   1358         }
   1359       }
   1360     }
   1361   }
   1362 }
   1363 
   1364 TEST(EigenBackwardSpatialConvolutionsTest,
   1365      test_batched_spatial_convolution_backward_kernel_valid) {
   1366   const int num_batches = 13;
   1367   const int input_depth = 2;
   1368   const int input_rows = 7;
   1369   const int input_cols = 9;
   1370   const int output_depth = 3;
   1371   const int patch_rows = 5;
   1372   const int patch_cols = 5;
   1373   const int output_rows = input_rows - patch_rows + 1;
   1374   const int output_cols = input_cols - patch_cols + 1;
   1375 
   1376   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
   1377   Tensor<float, 4> kernel_backward(output_depth, input_depth, patch_rows,
   1378                                    patch_cols);
   1379   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
   1380                                    num_batches);
   1381 
   1382   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1383   input = input.constant(2.0f) + input.random();
   1384   kernel_backward.setRandom();
   1385 
   1386   kernel_backward = SpatialConvolutionBackwardKernel(
   1387       input, output_backward, patch_rows, patch_cols, 1, 1);
   1388 
   1389   EXPECT_EQ(kernel_backward.dimension(0), output_depth);
   1390   EXPECT_EQ(kernel_backward.dimension(1), input_depth);
   1391   EXPECT_EQ(kernel_backward.dimension(2), patch_rows);
   1392   EXPECT_EQ(kernel_backward.dimension(3), patch_cols);
   1393 
   1394   for (int od = 0; od < output_depth; ++od) {
   1395     for (int id = 0; id < input_depth; ++id) {
   1396       for (int c = 0; c < patch_cols; ++c) {
   1397         for (int r = 0; r < patch_rows; ++r) {
   1398           float expected = 0.0f;
   1399           for (int b = 0; b < num_batches; ++b) {
   1400             for (int i = 0; i < input_rows; ++i) {
   1401               for (int j = 0; j < input_cols; ++j) {
   1402                 int output_i = i - r;
   1403                 int output_j = j - c;
   1404                 if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
   1405                     output_j < output_cols) {
   1406                   expected += input(id, i, j, b) *
   1407                               output_backward(od, output_i, output_j, b);
   1408                 }
   1409               }
   1410             }
   1411           }
   1412           EigenApprox(kernel_backward(od, id, r, c), expected);
   1413         }
   1414       }
   1415     }
   1416   }
   1417 }
   1418 
   1419 TEST(EigenBackwardSpatialConvolutionsTest,
   1420      test_batched_spatial_convolution_backward_kernel_valid_row_major) {
   1421   const int num_batches = 13;
   1422   const int input_depth = 2;
   1423   const int input_rows = 7;
   1424   const int input_cols = 9;
   1425   const int output_depth = 3;
   1426   const int patch_rows = 4;
   1427   const int patch_cols = 4;
   1428   const int output_rows = input_rows - patch_rows + 1;
   1429   const int output_cols = input_cols - patch_cols + 1;
   1430 
   1431   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
   1432                                    input_depth);
   1433   Tensor<float, 4, RowMajor> kernel_backward(patch_cols, patch_rows,
   1434                                              input_depth, output_depth);
   1435   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
   1436                                              output_rows, output_depth);
   1437 
   1438   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1439   input = input.constant(2.0f) + input.random();
   1440   kernel_backward.setRandom();
   1441 
   1442   kernel_backward = SpatialConvolutionBackwardKernel(
   1443       input, output_backward, patch_rows, patch_cols, 1, 1);
   1444 
   1445   EXPECT_EQ(kernel_backward.dimension(0), patch_cols);
   1446   EXPECT_EQ(kernel_backward.dimension(1), patch_rows);
   1447   EXPECT_EQ(kernel_backward.dimension(2), input_depth);
   1448   EXPECT_EQ(kernel_backward.dimension(3), output_depth);
   1449 
   1450   for (int od = 0; od < output_depth; ++od) {
   1451     for (int id = 0; id < input_depth; ++id) {
   1452       for (int c = 0; c < patch_cols; ++c) {
   1453         for (int r = 0; r < patch_rows; ++r) {
   1454           float expected = 0.0f;
   1455           for (int b = 0; b < num_batches; ++b) {
   1456             for (int i = 0; i < input_rows; ++i) {
   1457               for (int j = 0; j < input_cols; ++j) {
   1458                 int output_i = i - r;
   1459                 int output_j = j - c;
   1460                 if (output_i >= 0 && output_i < output_rows && output_j >= 0 &&
   1461                     output_j < output_cols) {
   1462                   expected += input(b, j, i, id) *
   1463                               output_backward(b, output_j, output_i, od);
   1464                 }
   1465               }
   1466             }
   1467           }
   1468           EigenApprox(kernel_backward(c, r, id, od), expected);
   1469         }
   1470       }
   1471     }
   1472   }
   1473 }
   1474 
   1475 TEST(EigenBackwardSpatialConvolutionsTest,
   1476      test_batched_spatial_convolution_backward_kernel_valid_row_major_unequal) {
   1477   const int num_batches = 13;
   1478   const int input_depth = 2;
   1479   const int input_rows = 7;
   1480   const int input_cols = 9;
   1481   const int output_depth = 3;
   1482   const int patch_rows = 4;
   1483   const int patch_cols = 4;
   1484   const int r_stride = 2;
   1485   const int c_stride = 1;
   1486   const int output_rows =
   1487       (input_rows - patch_rows + 1 + r_stride - 1) / r_stride;
   1488   const int output_cols =
   1489       (input_cols - patch_cols + 1 + c_stride - 1) / c_stride;
   1490 
   1491   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
   1492                                    input_depth);
   1493   Tensor<float, 4, RowMajor> kernel_backward(patch_cols, patch_rows,
   1494                                              input_depth, output_depth);
   1495   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
   1496                                              output_rows, output_depth);
   1497 
   1498   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1499   input = input.constant(2.0f) + input.random();
   1500   kernel_backward.setRandom();
   1501 
   1502   kernel_backward = SpatialConvolutionBackwardKernel(
   1503       input, output_backward, patch_rows, patch_cols, r_stride, c_stride);
   1504 
   1505   EXPECT_EQ(kernel_backward.dimension(0), patch_cols);
   1506   EXPECT_EQ(kernel_backward.dimension(1), patch_rows);
   1507   EXPECT_EQ(kernel_backward.dimension(2), input_depth);
   1508   EXPECT_EQ(kernel_backward.dimension(3), output_depth);
   1509 
   1510   for (int od = 0; od < output_depth; ++od) {
   1511     for (int id = 0; id < input_depth; ++id) {
   1512       for (int c = 0; c < patch_cols; ++c) {
   1513         for (int r = 0; r < patch_rows; ++r) {
   1514           float expected = 0.0f;
   1515           for (int b = 0; b < num_batches; ++b) {
   1516             for (int i = 0; i < input_rows; ++i) {
   1517               for (int j = 0; j < input_cols; ++j) {
   1518                 int output_i = i - r;
   1519                 int output_j = j - c;
   1520                 if (output_i >= 0 && output_i / r_stride < output_rows &&
   1521                     output_i % r_stride == 0 && output_j >= 0 &&
   1522                     output_j / c_stride < output_cols &&
   1523                     output_j % c_stride == 0) {
   1524                   expected += input(b, j, i, id) *
   1525                               output_backward(b, output_j / c_stride,
   1526                                               output_i / r_stride, od);
   1527                 }
   1528               }
   1529             }
   1530           }
   1531           EigenApprox(kernel_backward(c, r, id, od), expected);
   1532         }
   1533       }
   1534     }
   1535   }
   1536 }
   1537 
   1538 TEST(EigenBackwardSpatialConvolutionsTest,
   1539      test_batched_cuboid_convolution_backward_kernel_valid) {
   1540   const int num_batches = 13;
   1541   const int input_depth = 2;
   1542   const int input_planes = 5;
   1543   const int input_rows = 7;
   1544   const int input_cols = 9;
   1545   const int output_depth = 3;
   1546   const int patch_rows = 5;
   1547   const int patch_cols = 5;
   1548   const int patch_planes = 3;
   1549   const int output_rows = input_rows - patch_rows + 1;
   1550   const int output_cols = input_cols - patch_cols + 1;
   1551   const int output_planes = input_planes - patch_planes + 1;
   1552 
   1553   Tensor<float, 5> input(input_depth, input_planes, input_rows, input_cols,
   1554                          num_batches);
   1555   Tensor<float, 5> kernel_backward(output_depth, input_depth, patch_planes,
   1556                                    patch_rows, patch_cols);
   1557   Tensor<float, 5> output_backward(output_depth, output_planes, output_rows,
   1558                                    output_cols, num_batches);
   1559 
   1560   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1561   input = input.constant(2.0f) + input.random();
   1562   kernel_backward.setRandom();
   1563 
   1564   kernel_backward = CuboidConvolutionBackwardKernel(
   1565       input, output_backward, patch_planes, patch_rows, patch_cols, 1, 1, 1);
   1566 
   1567   EXPECT_EQ(kernel_backward.dimension(0), output_depth);
   1568   EXPECT_EQ(kernel_backward.dimension(1), input_depth);
   1569   EXPECT_EQ(kernel_backward.dimension(2), patch_planes);
   1570   EXPECT_EQ(kernel_backward.dimension(3), patch_rows);
   1571   EXPECT_EQ(kernel_backward.dimension(4), patch_cols);
   1572 
   1573   for (int od = 0; od < output_depth; ++od) {
   1574     for (int id = 0; id < input_depth; ++id) {
   1575       for (int p = 0; p < patch_planes; ++p) {
   1576         for (int c = 0; c < patch_cols; ++c) {
   1577           for (int r = 0; r < patch_rows; ++r) {
   1578             float expected = 0.0f;
   1579             for (int b = 0; b < num_batches; ++b) {
   1580               for (int i = 0; i < input_planes; ++i) {
   1581                 for (int j = 0; j < input_rows; ++j) {
   1582                   for (int k = 0; k < input_cols; ++k) {
   1583                     int output_j = j - r;
   1584                     int output_k = k - c;
   1585                     int output_i = i - p;
   1586                     if (output_i >= 0 && output_i < output_planes &&
   1587                         output_j >= 0 && output_j < output_rows &&
   1588                         output_k >= 0 && output_k < output_cols) {
   1589                       expected +=
   1590                           input(id, i, j, k, b) *
   1591                           output_backward(od, output_i, output_j, output_k, b);
   1592                     }
   1593                   }
   1594                 }
   1595               }
   1596             }
   1597             EigenApprox(kernel_backward(od, id, p, r, c), expected);
   1598           }
   1599         }
   1600       }
   1601     }
   1602   }
   1603 }
   1604 
   1605 TEST(EigenBackwardSpatialConvolutionsTest,
   1606      test_batched_cuboid_convolution_backward_kernel_valid_row_major) {
   1607   const int num_batches = 13;
   1608   const int input_depth = 2;
   1609   const int input_planes = 5;
   1610   const int input_rows = 7;
   1611   const int input_cols = 9;
   1612   const int output_depth = 3;
   1613   const int patch_rows = 5;
   1614   const int patch_cols = 5;
   1615   const int patch_planes = 3;
   1616   const int output_rows = input_rows - patch_rows + 1;
   1617   const int output_cols = input_cols - patch_cols + 1;
   1618   const int output_planes = input_planes - patch_planes + 1;
   1619 
   1620   Tensor<float, 5, RowMajor> input(num_batches, input_cols, input_rows,
   1621                                    input_planes, input_depth);
   1622   Tensor<float, 5, RowMajor> kernel_backward(
   1623       patch_cols, patch_rows, patch_planes, input_depth, output_depth);
   1624   Tensor<float, 5, RowMajor> output_backward(
   1625       num_batches, output_cols, output_rows, output_planes, output_depth);
   1626 
   1627   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1628   input = input.constant(2.0f) + input.random();
   1629   kernel_backward.setRandom();
   1630 
   1631   kernel_backward = CuboidConvolutionBackwardKernel(
   1632       input, output_backward, patch_planes, patch_rows, patch_cols, 1, 1, 1);
   1633 
   1634   EXPECT_EQ(kernel_backward.dimension(4), output_depth);
   1635   EXPECT_EQ(kernel_backward.dimension(3), input_depth);
   1636   EXPECT_EQ(kernel_backward.dimension(2), patch_planes);
   1637   EXPECT_EQ(kernel_backward.dimension(1), patch_rows);
   1638   EXPECT_EQ(kernel_backward.dimension(0), patch_cols);
   1639 
   1640   for (int od = 0; od < output_depth; ++od) {
   1641     for (int id = 0; id < input_depth; ++id) {
   1642       for (int p = 0; p < patch_planes; ++p) {
   1643         for (int c = 0; c < patch_cols; ++c) {
   1644           for (int r = 0; r < patch_rows; ++r) {
   1645             float expected = 0.0f;
   1646             for (int b = 0; b < num_batches; ++b) {
   1647               for (int i = 0; i < input_planes; ++i) {
   1648                 for (int j = 0; j < input_rows; ++j) {
   1649                   for (int k = 0; k < input_cols; ++k) {
   1650                     int output_j = j - r;
   1651                     int output_k = k - c;
   1652                     int output_i = i - p;
   1653                     if (output_i >= 0 && output_i < output_planes &&
   1654                         output_j >= 0 && output_j < output_rows &&
   1655                         output_k >= 0 && output_k < output_cols) {
   1656                       expected +=
   1657                           input(b, k, j, i, id) *
   1658                           output_backward(b, output_k, output_j, output_i, od);
   1659                     }
   1660                   }
   1661                 }
   1662               }
   1663             }
   1664             EigenApprox(kernel_backward(c, r, p, id, od), expected);
   1665           }
   1666         }
   1667       }
   1668     }
   1669   }
   1670 }
   1671 
   1672 TEST(EigenBackwardSpatialConvolutionsTest,
   1673      test_batched_strided_spatial_convolution_backward_kernel_valid) {
   1674   const int num_batches = 13;
   1675   const int input_depth = 2;
   1676   const int input_rows = 7;
   1677   const int input_cols = 9;
   1678   const int output_depth = 3;
   1679   const int patch_rows = 5;
   1680   const int patch_cols = 5;
   1681 
   1682   const int stride = 2;
   1683 
   1684   const int output_rows = (input_rows - patch_rows + 1 + stride - 1) / stride;
   1685   const int output_cols = (input_cols - patch_cols + 1 + stride - 1) / stride;
   1686 
   1687   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
   1688   Tensor<float, 4> kernel_backward(output_depth, input_depth, patch_rows,
   1689                                    patch_cols);
   1690   Tensor<float, 4> output_backward(output_depth, output_rows, output_cols,
   1691                                    num_batches);
   1692 
   1693   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1694   input = input.constant(2.0f) + input.random();
   1695   kernel_backward.setRandom();
   1696 
   1697   kernel_backward = SpatialConvolutionBackwardKernel(
   1698       input, output_backward, patch_rows, patch_cols, stride, stride);
   1699 
   1700   EXPECT_EQ(kernel_backward.dimension(0), output_depth);
   1701   EXPECT_EQ(kernel_backward.dimension(1), input_depth);
   1702   EXPECT_EQ(kernel_backward.dimension(2), patch_rows);
   1703   EXPECT_EQ(kernel_backward.dimension(3), patch_cols);
   1704 
   1705   for (int od = 0; od < output_depth; ++od) {
   1706     for (int id = 0; id < input_depth; ++id) {
   1707       for (int c = 0; c < patch_cols; ++c) {
   1708         for (int r = 0; r < patch_rows; ++r) {
   1709           float expected = 0.0f;
   1710           for (int b = 0; b < num_batches; ++b) {
   1711             for (int i = 0; i < input_rows; ++i) {
   1712               for (int j = 0; j < input_cols; ++j) {
   1713                 int output_i = i - r;
   1714                 int output_j = j - c;
   1715                 if (output_i >= 0 && output_i / stride < output_rows &&
   1716                     output_j >= 0 && output_j / stride < output_cols &&
   1717                     output_i % stride == 0 && output_j % stride == 0) {
   1718                   expected += input(id, i, j, b) *
   1719                               output_backward(od, output_i / stride,
   1720                                               output_j / stride, b);
   1721                 }
   1722               }
   1723             }
   1724           }
   1725           EigenApprox(kernel_backward(od, id, r, c), expected);
   1726         }
   1727       }
   1728     }
   1729   }
   1730 }
   1731 
   1732 TEST(EigenBackwardSpatialConvolutionsTest,
   1733      test_batched_strided_spatial_convolution_backward_kernel_valid_row_major) {
   1734   const int num_batches = 13;
   1735   const int input_depth = 2;
   1736   const int input_rows = 7;
   1737   const int input_cols = 9;
   1738   const int output_depth = 3;
   1739   const int patch_rows = 4;
   1740   const int patch_cols = 4;
   1741 
   1742   const int stride = 2;
   1743 
   1744   const int output_rows = (input_rows - patch_rows + 1 + stride - 1) / stride;
   1745   const int output_cols = (input_cols - patch_cols + 1 + stride - 1) / stride;
   1746 
   1747   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
   1748                                    input_depth);
   1749   Tensor<float, 4, RowMajor> kernel_backward(patch_cols, patch_rows,
   1750                                              input_depth, output_depth);
   1751   Tensor<float, 4, RowMajor> output_backward(num_batches, output_cols,
   1752                                              output_rows, output_depth);
   1753 
   1754   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1755   input = input.constant(2.0f) + input.random();
   1756   kernel_backward.setRandom();
   1757 
   1758   kernel_backward = SpatialConvolutionBackwardKernel(
   1759       input, output_backward, patch_rows, patch_cols, stride, stride);
   1760 
   1761   EXPECT_EQ(kernel_backward.dimension(0), patch_cols);
   1762   EXPECT_EQ(kernel_backward.dimension(1), patch_rows);
   1763   EXPECT_EQ(kernel_backward.dimension(2), input_depth);
   1764   EXPECT_EQ(kernel_backward.dimension(3), output_depth);
   1765 
   1766   for (int od = 0; od < output_depth; ++od) {
   1767     for (int id = 0; id < input_depth; ++id) {
   1768       for (int c = 0; c < patch_cols; ++c) {
   1769         for (int r = 0; r < patch_rows; ++r) {
   1770           float expected = 0.0f;
   1771           for (int b = 0; b < num_batches; ++b) {
   1772             for (int i = 0; i < input_rows; ++i) {
   1773               for (int j = 0; j < input_cols; ++j) {
   1774                 int output_i = i - r;
   1775                 int output_j = j - c;
   1776                 if (output_i >= 0 && output_i / stride < output_rows &&
   1777                     output_j >= 0 && output_j / stride < output_cols &&
   1778                     output_i % stride == 0 && output_j % stride == 0) {
   1779                   expected += input(b, j, i, id) *
   1780                               output_backward(b, output_j / stride,
   1781                                               output_i / stride, od);
   1782                 }
   1783               }
   1784             }
   1785           }
   1786           EigenApprox(kernel_backward(c, r, id, od), expected);
   1787         }
   1788       }
   1789     }
   1790   }
   1791 }
   1792 
   1793 TEST(EigenBackwardSpatialConvolutionsTest,
   1794      test_batched_strided_cuboid_convolution_backward_kernel_valid) {
   1795   const int num_batches = 13;
   1796   const int input_depth = 2;
   1797   const int input_planes = 8;
   1798   const int input_rows = 7;
   1799   const int input_cols = 9;
   1800   const int output_depth = 3;
   1801   const int patch_planes = 3;
   1802   const int patch_rows = 3;
   1803   const int patch_cols = 2;
   1804 
   1805   const int stride_planes = 2;
   1806   const int stride_cols = 3;
   1807   const int stride_rows = 1;
   1808 
   1809   const int output_rows = ceil_div(input_rows - patch_rows + 1, stride_rows);
   1810   const int output_cols = ceil_div(input_cols - patch_cols + 1, stride_cols);
   1811   const int output_planes =
   1812       ceil_div(input_planes - patch_planes + 1, stride_planes);
   1813 
   1814   Tensor<float, 5> input(input_depth, input_planes, input_rows, input_cols,
   1815                          num_batches);
   1816   Tensor<float, 5> kernel_backward(output_depth, input_depth, patch_planes,
   1817                                    patch_rows, patch_cols);
   1818   Tensor<float, 5> output_backward(output_depth, output_planes, output_rows,
   1819                                    output_cols, num_batches);
   1820 
   1821   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1822   input = input.constant(2.0f) + input.random();
   1823   kernel_backward.setRandom();
   1824 
   1825   kernel_backward = CuboidConvolutionBackwardKernel(
   1826       input, output_backward, patch_planes, patch_rows, patch_cols,
   1827       stride_planes, stride_rows, stride_cols);
   1828 
   1829   EXPECT_EQ(kernel_backward.dimension(0), output_depth);
   1830   EXPECT_EQ(kernel_backward.dimension(1), input_depth);
   1831   EXPECT_EQ(kernel_backward.dimension(2), patch_planes);
   1832   EXPECT_EQ(kernel_backward.dimension(3), patch_rows);
   1833   EXPECT_EQ(kernel_backward.dimension(4), patch_cols);
   1834 
   1835   for (int od = 0; od < output_depth; ++od) {
   1836     for (int id = 0; id < input_depth; ++id) {
   1837       for (int p = 0; p < patch_planes; ++p) {
   1838         for (int c = 0; c < patch_cols; ++c) {
   1839           for (int r = 0; r < patch_rows; ++r) {
   1840             float expected = 0.0f;
   1841             for (int b = 0; b < num_batches; ++b) {
   1842               for (int i = 0; i < input_planes; ++i) {
   1843                 for (int j = 0; j < input_rows; ++j) {
   1844                   for (int k = 0; k < input_cols; ++k) {
   1845                     int output_j = j - r;
   1846                     int output_k = k - c;
   1847                     int output_i = i - p;
   1848                     if (output_i >= 0 &&
   1849                         output_i / stride_planes < output_planes &&
   1850                         output_j >= 0 && output_j / stride_rows < output_rows &&
   1851                         output_k >= 0 && output_k / stride_cols < output_cols &&
   1852                         output_i % stride_planes == 0 &&
   1853                         output_j % stride_rows == 0 &&
   1854                         output_k % stride_cols == 0) {
   1855                       expected += input(id, i, j, k, b) *
   1856                                   output_backward(od, output_i / stride_planes,
   1857                                                   output_j / stride_rows,
   1858                                                   output_k / stride_cols, b);
   1859                     }
   1860                   }
   1861                 }
   1862               }
   1863             }
   1864             EigenApprox(kernel_backward(od, id, p, r, c), expected);
   1865           }
   1866         }
   1867       }
   1868     }
   1869   }
   1870 }
   1871 
   1872 TEST(EigenBackwardSpatialConvolutionsTest,
   1873      test_batched_strided_cuboid_convolution_backward_kernel_valid_row_major) {
   1874   const int num_batches = 13;
   1875   const int input_depth = 2;
   1876   const int input_planes = 8;
   1877   const int input_rows = 7;
   1878   const int input_cols = 9;
   1879   const int output_depth = 3;
   1880   const int patch_planes = 3;
   1881   const int patch_rows = 3;
   1882   const int patch_cols = 2;
   1883 
   1884   const int stride_planes = 2;
   1885   const int stride_cols = 3;
   1886   const int stride_rows = 1;
   1887 
   1888   const int output_rows = ceil_div(input_rows - patch_rows + 1, stride_rows);
   1889   const int output_cols = ceil_div(input_cols - patch_cols + 1, stride_cols);
   1890   const int output_planes =
   1891       ceil_div(input_planes - patch_planes + 1, stride_planes);
   1892 
   1893   Tensor<float, 5, RowMajor> input(num_batches, input_cols, input_rows,
   1894                                    input_planes, input_depth);
   1895   Tensor<float, 5, RowMajor> kernel_backward(
   1896       patch_cols, patch_rows, patch_planes, input_depth, output_depth);
   1897   Tensor<float, 5, RowMajor> output_backward(
   1898       num_batches, output_cols, output_rows, output_planes, output_depth);
   1899 
   1900   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1901   input = input.constant(2.0f) + input.random();
   1902   kernel_backward.setRandom();
   1903 
   1904   kernel_backward = CuboidConvolutionBackwardKernel(
   1905       input, output_backward, patch_planes, patch_rows, patch_cols,
   1906       stride_planes, stride_rows, stride_cols);
   1907 
   1908   EXPECT_EQ(kernel_backward.dimension(4), output_depth);
   1909   EXPECT_EQ(kernel_backward.dimension(3), input_depth);
   1910   EXPECT_EQ(kernel_backward.dimension(2), patch_planes);
   1911   EXPECT_EQ(kernel_backward.dimension(1), patch_rows);
   1912   EXPECT_EQ(kernel_backward.dimension(0), patch_cols);
   1913 
   1914   for (int od = 0; od < output_depth; ++od) {
   1915     for (int id = 0; id < input_depth; ++id) {
   1916       for (int p = 0; p < patch_planes; ++p) {
   1917         for (int c = 0; c < patch_cols; ++c) {
   1918           for (int r = 0; r < patch_rows; ++r) {
   1919             float expected = 0.0f;
   1920             for (int b = 0; b < num_batches; ++b) {
   1921               for (int i = 0; i < input_planes; ++i) {
   1922                 for (int j = 0; j < input_rows; ++j) {
   1923                   for (int k = 0; k < input_cols; ++k) {
   1924                     int output_j = j - r;
   1925                     int output_k = k - c;
   1926                     int output_i = i - p;
   1927                     if (output_i >= 0 &&
   1928                         output_i / stride_planes < output_planes &&
   1929                         output_j >= 0 && output_j / stride_rows < output_rows &&
   1930                         output_k >= 0 && output_k / stride_cols < output_cols &&
   1931                         output_i % stride_planes == 0 &&
   1932                         output_j % stride_rows == 0 &&
   1933                         output_k % stride_cols == 0) {
   1934                       expected += input(b, k, j, i, id) *
   1935                                   output_backward(b, output_k / stride_cols,
   1936                                                   output_j / stride_rows,
   1937                                                   output_i / stride_planes, od);
   1938                     }
   1939                   }
   1940                 }
   1941               }
   1942             }
   1943             EigenApprox(kernel_backward(c, r, p, id, od), expected);
   1944           }
   1945         }
   1946       }
   1947     }
   1948   }
   1949 }
   1950 
   1951 TEST(EigenBackwardSpatialConvolutionsTest,
   1952      test_batched_strided_cuboid_convolution_backward_input_valid) {
   1953   const int num_batches = 13;
   1954   const int input_depth = 2;
   1955   const int input_planes = 14;
   1956   const int input_rows = 13;
   1957   const int input_cols = 15;
   1958   const int patch_rows = 3;
   1959   const int patch_cols = 2;
   1960   const int patch_planes = 4;
   1961   const int stride_rows = 3;
   1962   const int stride_cols = 2;
   1963   const int stride_planes = 3;
   1964   const int output_rows = ceil_div(input_rows - patch_rows + 1, stride_rows);
   1965   const int output_cols = ceil_div(input_cols - patch_cols + 1, stride_cols);
   1966   const int output_planes =
   1967       ceil_div(input_planes - patch_planes + 1, stride_planes);
   1968   const int output_depth = 5;
   1969 
   1970   Tensor<float, 5> input_backward(input_depth, input_planes, input_rows,
   1971                                   input_cols, num_batches);
   1972   Tensor<float, 5> kernel(output_depth, input_depth, patch_planes, patch_rows,
   1973                           patch_cols);
   1974   Tensor<float, 5> output_backward(output_depth, output_planes, output_rows,
   1975                                    output_cols, num_batches);
   1976 
   1977   output_backward = output_backward.constant(11.0f) + output_backward.random();
   1978   kernel = kernel.constant(2.0f) + kernel.random();
   1979   input_backward.setRandom();
   1980 
   1981   input_backward = CuboidConvolutionBackwardInput(
   1982       kernel, output_backward, input_planes, input_rows, input_cols,
   1983       stride_planes, stride_rows, stride_cols);
   1984 
   1985   EXPECT_EQ(input_backward.dimension(4), num_batches);
   1986   EXPECT_EQ(input_backward.dimension(3), input_cols);
   1987   EXPECT_EQ(input_backward.dimension(2), input_rows);
   1988   EXPECT_EQ(input_backward.dimension(1), input_planes);
   1989   EXPECT_EQ(input_backward.dimension(0), input_depth);
   1990 
   1991   for (int b = 0; b < num_batches; ++b) {
   1992     for (int id = 0; id < input_depth; ++id) {
   1993       for (int i = 0; i < input_planes; ++i) {
   1994         for (int j = 0; j < input_rows; ++j) {
   1995           for (int k = 0; k < input_cols; ++k) {
   1996             float expected = 0.0f;
   1997             for (int c = 0; c < patch_cols; ++c) {
   1998               for (int r = 0; r < patch_rows; ++r) {
   1999                 for (int p = 0; p < patch_planes; ++p) {
   2000                   for (int od = 0; od < output_depth; ++od) {
   2001                     int output_j = j - r;
   2002                     int output_k = k - c;
   2003                     int output_i = i - p;
   2004                     if (output_i >= 0 &&
   2005                         output_i / stride_planes < output_planes &&
   2006                         output_j >= 0 && output_j / stride_rows < output_rows &&
   2007                         output_k >= 0 && output_k / stride_cols < output_cols &&
   2008                         output_i % stride_planes == 0 &&
   2009                         output_j % stride_rows == 0 &&
   2010                         output_k % stride_cols == 0) {
   2011                       expected += output_backward(od, output_i / stride_planes,
   2012                                                   output_j / stride_rows,
   2013                                                   output_k / stride_cols, b) *
   2014                                   kernel(od, id, p, r, c);
   2015                     }
   2016                   }
   2017                 }
   2018               }
   2019             }
   2020             EigenApprox(input_backward(id, i, j, k, b), expected);
   2021           }
   2022         }
   2023       }
   2024     }
   2025   }
   2026 }
   2027 
   2028 TEST(EigenBackwardSpatialConvolutionsTest,
   2029      test_batched_strided_cuboid_convolution_backward_input_valid_row_major) {
   2030   const int num_batches = 13;
   2031   const int input_depth = 2;
   2032   const int input_planes = 14;
   2033   const int input_rows = 13;
   2034   const int input_cols = 15;
   2035   const int patch_rows = 3;
   2036   const int patch_cols = 2;
   2037   const int patch_planes = 4;
   2038   const int stride_rows = 3;
   2039   const int stride_cols = 2;
   2040   const int stride_planes = 3;
   2041   const int output_rows = ceil_div(input_rows - patch_rows + 1, stride_rows);
   2042   const int output_cols = ceil_div(input_cols - patch_cols + 1, stride_cols);
   2043   const int output_planes =
   2044       ceil_div(input_planes - patch_planes + 1, stride_planes);
   2045   const int output_depth = 5;
   2046 
   2047   Tensor<float, 5, RowMajor> input_backward(num_batches, input_cols, input_rows,
   2048                                             input_planes, input_depth);
   2049   Tensor<float, 5, RowMajor> kernel(patch_cols, patch_rows, patch_planes,
   2050                                     input_depth, output_depth);
   2051   Tensor<float, 5, RowMajor> output_backward(
   2052       num_batches, output_cols, output_rows, output_planes, output_depth);
   2053 
   2054   output_backward = output_backward.constant(11.0f) + output_backward.random();
   2055   kernel = kernel.constant(2.0f) + kernel.random();
   2056   input_backward.setRandom();
   2057 
   2058   input_backward = CuboidConvolutionBackwardInput(
   2059       kernel, output_backward, input_planes, input_rows, input_cols,
   2060       stride_planes, stride_rows, stride_cols);
   2061 
   2062   EXPECT_EQ(input_backward.dimension(0), num_batches);
   2063   EXPECT_EQ(input_backward.dimension(1), input_cols);
   2064   EXPECT_EQ(input_backward.dimension(2), input_rows);
   2065   EXPECT_EQ(input_backward.dimension(3), input_planes);
   2066   EXPECT_EQ(input_backward.dimension(4), input_depth);
   2067 
   2068   for (int b = 0; b < num_batches; ++b) {
   2069     for (int id = 0; id < input_depth; ++id) {
   2070       for (int i = 0; i < input_planes; ++i) {
   2071         for (int j = 0; j < input_rows; ++j) {
   2072           for (int k = 0; k < input_cols; ++k) {
   2073             float expected = 0.0f;
   2074             for (int c = 0; c < patch_cols; ++c) {
   2075               for (int r = 0; r < patch_rows; ++r) {
   2076                 for (int p = 0; p < patch_planes; ++p) {
   2077                   for (int od = 0; od < output_depth; ++od) {
   2078                     int output_j = j - r;
   2079                     int output_k = k - c;
   2080                     int output_i = i - p;
   2081                     if (output_i >= 0 &&
   2082                         output_i / stride_planes < output_planes &&
   2083                         output_j >= 0 && output_j / stride_rows < output_rows &&
   2084                         output_k >= 0 && output_k / stride_cols < output_cols &&
   2085                         output_i % stride_planes == 0 &&
   2086                         output_j % stride_rows == 0 &&
   2087                         output_k % stride_cols == 0) {
   2088                       expected +=
   2089                           output_backward(b, output_k / stride_cols,
   2090                                           output_j / stride_rows,
   2091                                           output_i / stride_planes, od) *
   2092                           kernel(c, r, p, id, od);
   2093                     }
   2094                   }
   2095                 }
   2096               }
   2097             }
   2098             EigenApprox(input_backward(b, k, j, i, id), expected);
   2099           }
   2100         }
   2101       }
   2102     }
   2103   }
   2104 }
   2105 
   2106 }  // namespace Eigen
   2107