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_spatial_convolutions.h" 17 #include "tensorflow/core/framework/types.h" 18 #include "tensorflow/core/kernels/eigen_cuboid_convolution.h" 19 #include "tensorflow/core/platform/test.h" 20 21 namespace Eigen { 22 23 #define EigenApprox(a, b) \ 24 { ASSERT_TRUE(std::abs(a - b) <= std::min(std::abs(a), std::abs(b)) * 1e-3); } 25 static int ceil_div(int a, int b) { return (a + b - 1) / b; } 26 27 TEST(EigenSpatialConvolutionsTest, Simple) { 28 const int input_depth = 7; 29 const int input_rows = 4; 30 const int input_cols = 5; 31 const int output_depth = 10; 32 const int patch_rows = 3; 33 const int patch_cols = 4; 34 const int output_rows = input_rows; 35 const int output_cols = input_cols; 36 37 Tensor<float, 3> input(input_depth, input_rows, input_cols); 38 Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols); 39 Tensor<float, 3> result(output_depth, output_rows, output_cols); 40 41 input = input.constant(11.0f) + input.random(); 42 kernel = kernel.constant(2.0f) + kernel.random(); 43 result.setRandom(); 44 45 result = SpatialConvolution(input, kernel); 46 47 EXPECT_EQ(result.dimension(0), output_depth); 48 EXPECT_EQ(result.dimension(1), output_rows); 49 EXPECT_EQ(result.dimension(2), output_cols); 50 51 for (int od = 0; od < output_depth; ++od) { 52 for (int i = 0; i < output_rows; ++i) { 53 for (int j = 0; j < output_cols; ++j) { 54 float expected = 0.0f; 55 for (int c = 0; c < patch_cols; ++c) { 56 for (int r = 0; r < patch_rows; ++r) { 57 for (int id = 0; id < input_depth; ++id) { 58 if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < output_rows && 59 c - 1 + j < output_cols) { 60 expected += 61 input(id, r - 1 + i, c - 1 + j) * kernel(od, id, r, c); 62 } 63 } 64 } 65 } 66 EigenApprox(result(od, i, j), expected); 67 } 68 } 69 } 70 } 71 72 TEST(EigenSpatialConvolutionsTest, SimpleRowMajor) { 73 const int input_depth = 7; 74 const int input_rows = 4; 75 const int input_cols = 5; 76 const int output_depth = 10; 77 const int patch_rows = 3; 78 const int patch_cols = 4; 79 const int output_rows = input_rows; 80 const int output_cols = input_cols; 81 82 Tensor<float, 3, RowMajor> input(input_cols, input_rows, input_depth); 83 Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth, 84 output_depth); 85 Tensor<float, 3, RowMajor> result(output_cols, output_rows, output_depth); 86 input = input.constant(11.0f) + input.random(); 87 kernel = kernel.constant(2.0f) + kernel.random(); 88 result.setRandom(); 89 90 result = SpatialConvolution(input, kernel); 91 92 EXPECT_EQ(result.dimension(0), output_cols); 93 EXPECT_EQ(result.dimension(1), output_rows); 94 EXPECT_EQ(result.dimension(2), output_depth); 95 96 for (int od = 0; od < output_depth; ++od) { 97 for (int i = 0; i < output_rows; ++i) { 98 for (int j = 0; j < output_cols; ++j) { 99 float expected = 0.0f; 100 for (int c = 0; c < patch_cols; ++c) { 101 for (int r = 0; r < patch_rows; ++r) { 102 for (int id = 0; id < input_depth; ++id) { 103 if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < output_rows && 104 c - 1 + j < output_cols) { 105 expected += 106 input(c - 1 + j, r - 1 + i, id) * kernel(c, r, id, od); 107 } 108 } 109 } 110 } 111 EigenApprox(result(j, i, od), expected); 112 } 113 } 114 } 115 } 116 117 TEST(EigenSpatialConvolutionsTest, BatchedSpatialConvolution) { 118 Tensor<float, 4> input(10, 5, 5, 13); 119 Tensor<float, 4> kernel(7, 10, 3, 3); 120 Tensor<float, 4> result(7, 5, 5, 13); 121 input = input.constant(11.0f) + input.random(); 122 kernel = kernel.constant(2.0f) + kernel.random(); 123 result.setRandom(); 124 125 result = SpatialConvolution(input, kernel); 126 127 EXPECT_EQ(result.dimension(0), 7); 128 EXPECT_EQ(result.dimension(1), 5); 129 EXPECT_EQ(result.dimension(2), 5); 130 131 for (int b = 0; b < 13; ++b) { 132 for (int od = 0; od < 7; ++od) { 133 for (int i = 0; i < 5; ++i) { 134 for (int j = 0; j < 5; ++j) { 135 float expected = 0.0f; 136 for (int c = 0; c < 3; ++c) { 137 for (int r = 0; r < 3; ++r) { 138 for (int id = 0; id < 10; ++id) { 139 if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < 5 && 140 c - 1 + j < 5) { 141 expected += 142 input(id, r - 1 + i, c - 1 + j, b) * kernel(od, id, r, c); 143 } 144 } 145 } 146 } 147 EigenApprox(result(od, i, j, b), expected); 148 } 149 } 150 } 151 } 152 } 153 154 TEST(EigenSpatialConvolutionsTest, BatchedSpatialConvolutionRowMajor) { 155 Tensor<float, 4, RowMajor> input(13, 5, 5, 10); 156 Tensor<float, 4, RowMajor> kernel(3, 3, 10, 7); 157 Tensor<float, 4, RowMajor> result(13, 5, 5, 7); 158 input = input.constant(11.0f) + input.random(); 159 kernel = kernel.constant(2.0f) + kernel.random(); 160 result.setRandom(); 161 162 result = SpatialConvolution(input, kernel); 163 164 EXPECT_EQ(result.dimension(1), 5); 165 EXPECT_EQ(result.dimension(2), 5); 166 EXPECT_EQ(result.dimension(3), 7); 167 168 for (int b = 0; b < 13; ++b) { 169 for (int od = 0; od < 7; ++od) { 170 for (int i = 0; i < 5; ++i) { 171 for (int j = 0; j < 5; ++j) { 172 float expected = 0.0f; 173 for (int c = 0; c < 3; ++c) { 174 for (int r = 0; r < 3; ++r) { 175 for (int id = 0; id < 10; ++id) { 176 if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < 5 && 177 c - 1 + j < 5) { 178 expected += 179 input(b, c - 1 + j, r - 1 + i, id) * kernel(c, r, id, od); 180 } 181 } 182 } 183 } 184 EigenApprox(result(b, j, i, od), expected); 185 } 186 } 187 } 188 } 189 } 190 191 TEST(EigenSpatialConvolutionsTest, ValidSpatialConvolution) { 192 const int input_depth = 10; 193 const int input_rows = 5; 194 const int input_cols = 5; 195 const int num_batches = 13; 196 const int output_depth = 7; 197 const int patch_rows = 4; 198 const int patch_cols = 4; 199 const int output_rows = input_rows - patch_rows + 1; 200 const int output_cols = input_cols - patch_cols + 1; 201 202 Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches); 203 Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols); 204 Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches); 205 input = input.constant(11.0f) + input.random(); 206 kernel = kernel.constant(2.0f) + kernel.random(); 207 result.setRandom(); 208 209 // Apply a spatial convolution using a 4x4 kernel, valid padding, and a stride 210 // of 1. 211 const int stride = 1; 212 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID); 213 214 EXPECT_EQ(result.dimension(0), output_depth); 215 EXPECT_EQ(result.dimension(1), output_rows); 216 EXPECT_EQ(result.dimension(2), output_cols); 217 EXPECT_EQ(result.dimension(3), num_batches); 218 219 for (int b = 0; b < num_batches; ++b) { 220 for (int od = 0; od < output_depth; ++od) { 221 for (int i = 0; i < output_rows; ++i) { 222 for (int j = 0; j < output_cols; ++j) { 223 float expected = 0.0f; 224 for (int c = 0; c < patch_cols; ++c) { 225 for (int r = 0; r < patch_rows; ++r) { 226 for (int id = 0; id < input_depth; ++id) { 227 expected += input(id, r + i, c + j, b) * kernel(od, id, r, c); 228 } 229 } 230 } 231 if (result(od, i, j, b) != expected) { 232 std::cout << "at od=" << od << " b=" << b << " i=" << i 233 << " j=" << j << " " << result(od, i, j, b) << " vs " 234 << expected << std::endl; 235 } 236 EigenApprox(result(od, i, j, b), expected); 237 } 238 } 239 } 240 } 241 } 242 243 TEST(EigenSpatialConvolutionsTest, ValidSpatialConvolutionUnequalStrides) { 244 const int input_depth = 10; 245 const int input_rows = 5; 246 const int input_cols = 5; 247 const int num_batches = 13; 248 const int output_depth = 7; 249 const int patch_rows = 4; 250 const int patch_cols = 4; 251 252 const int row_stride = 1; 253 const int col_stride = 2; 254 const int output_rows = 2; 255 const int output_cols = 1; 256 257 Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches); 258 Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols); 259 Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches); 260 input = input.constant(11.0f) + input.random(); 261 kernel = kernel.constant(2.0f) + kernel.random(); 262 result.setRandom(); 263 264 // Apply a spatial convolution using a 4x4 kernel, valid padding, and a stride 265 // of 1. 266 result = 267 SpatialConvolution(input, kernel, row_stride, col_stride, PADDING_VALID); 268 269 EXPECT_EQ(result.dimension(0), output_depth); 270 EXPECT_EQ(result.dimension(1), output_rows); 271 EXPECT_EQ(result.dimension(2), output_cols); 272 EXPECT_EQ(result.dimension(3), num_batches); 273 if (true) return; 274 275 for (int b = 0; b < num_batches; ++b) { 276 for (int od = 0; od < output_depth; ++od) { 277 for (int i = 0; i < output_rows; ++i) { 278 for (int j = 0; j < output_cols; ++j) { 279 float expected = 0.0f; 280 for (int c = 0; c < patch_cols; ++c) { 281 for (int r = 0; r < patch_rows; ++r) { 282 for (int id = 0; id < input_depth; ++id) { 283 expected += 284 input(id, r + row_stride * i, c + col_stride * j, b) * 285 kernel(od, id, r, c); 286 } 287 } 288 } 289 if (result(od, i, j, b) != expected) { 290 std::cout << "at od=" << od << " b=" << b << " i=" << i 291 << " j=" << j << " " << result(od, i, j, b) << " vs " 292 << expected << std::endl; 293 } 294 EigenApprox(result(od, i, j, b), expected); 295 } 296 } 297 } 298 } 299 } 300 301 TEST(EigenSpatialConvolutionsTest, ValidSpatialConvolutionRowMajor) { 302 const int input_depth = 10; 303 const int input_rows = 5; 304 const int input_cols = 5; 305 const int num_batches = 13; 306 const int output_depth = 7; 307 const int patch_rows = 4; 308 const int patch_cols = 4; 309 const int output_rows = input_rows - patch_rows + 1; 310 const int output_cols = input_cols - patch_cols + 1; 311 312 Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows, 313 input_depth); 314 Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth, 315 output_depth); 316 Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows, 317 output_depth); 318 319 input = input.constant(11.0f) + input.random(); 320 kernel = kernel.constant(2.0f) + kernel.random(); 321 result.setRandom(); 322 323 // Apply a spatial convolution using a 4x4 kernel, valid padding, and a stride 324 // of 1. 325 const int stride = 1; 326 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID); 327 328 EXPECT_EQ(result.dimension(0), num_batches); 329 EXPECT_EQ(result.dimension(1), output_cols); 330 EXPECT_EQ(result.dimension(2), output_rows); 331 EXPECT_EQ(result.dimension(3), output_depth); 332 333 for (int b = 0; b < num_batches; ++b) { 334 for (int od = 0; od < output_depth; ++od) { 335 for (int i = 0; i < output_rows; ++i) { 336 for (int j = 0; j < output_cols; ++j) { 337 float expected = 0.0f; 338 for (int c = 0; c < patch_rows; ++c) { 339 for (int r = 0; r < patch_cols; ++r) { 340 for (int id = 0; id < input_depth; ++id) { 341 expected += input(b, c + j, r + i, id) * kernel(c, r, id, od); 342 } 343 } 344 } 345 if (result(b, j, i, od) != expected) { 346 std::cout << "at od=" << od << " b=" << b << " i=" << i 347 << " j=" << j << " " << result(b, j, i, od) << " vs " 348 << expected << std::endl; 349 } 350 EigenApprox(result(b, j, i, od), expected); 351 } 352 } 353 } 354 } 355 } 356 357 TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolution) { 358 const int input_depth = 10; 359 const int input_rows = 5; 360 const int input_cols = 5; 361 const int num_batches = 13; 362 const int output_depth = 7; 363 const int patch_rows = 3; 364 const int patch_cols = 3; 365 const int output_rows = 2; 366 const int output_cols = 2; 367 368 Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches); 369 Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols); 370 Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches); 371 input = input.constant(11.0f) + input.random(); 372 kernel = kernel.constant(2.0f) + kernel.random(); 373 result.setRandom(); 374 375 // Apply a spatial convolution using a 3x3 kernel, valid padding, and a stride 376 // of 2. 377 int stride = 2; 378 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID); 379 380 EXPECT_EQ(result.dimension(0), output_depth); 381 EXPECT_EQ(result.dimension(1), output_rows); 382 EXPECT_EQ(result.dimension(2), output_cols); 383 EXPECT_EQ(result.dimension(3), num_batches); 384 385 for (int b = 0; b < num_batches; ++b) { 386 for (int od = 0; od < output_depth; ++od) { 387 for (int i = 0; i < output_rows; ++i) { 388 for (int j = 0; j < output_cols; ++j) { 389 float expected = 0.0f; 390 for (int c = 0; c < patch_cols; ++c) { 391 for (int r = 0; r < patch_rows; ++r) { 392 for (int id = 0; id < input_depth; ++id) { 393 expected += input(id, r + stride * i, c + stride * j, b) * 394 kernel(od, id, r, c); 395 } 396 } 397 } 398 EigenApprox(result(od, i, j, b), expected); 399 } 400 } 401 } 402 } 403 } 404 405 TEST(EigenSpatialConvolutionsTest, KernelSmallerThanStride) { 406 const int input_depth = 2; 407 const int input_rows = 3; 408 const int input_cols = 3; 409 const int num_batches = 5; 410 const int output_depth = 6; 411 const int patch_rows = 1; 412 const int patch_cols = 1; 413 const int output_rows = 2; 414 const int output_cols = 2; 415 416 Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches); 417 Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols); 418 Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches); 419 input = input.constant(11.0f) + input.random(); 420 kernel = kernel.constant(2.0f) + kernel.random(); 421 result.setRandom(); 422 423 // Apply a spatial convolution using a 1x1 kernel, valid padding, and a stride 424 // of 2. 425 int stride = 2; 426 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID); 427 428 EXPECT_EQ(result.dimension(0), output_depth); 429 EXPECT_EQ(result.dimension(1), output_rows); 430 EXPECT_EQ(result.dimension(2), output_cols); 431 EXPECT_EQ(result.dimension(3), num_batches); 432 433 for (int b = 0; b < num_batches; ++b) { 434 for (int od = 0; od < output_depth; ++od) { 435 for (int i = 0; i < output_rows; ++i) { 436 for (int j = 0; j < output_cols; ++j) { 437 float expected = 0.0f; 438 for (int c = 0; c < patch_cols; ++c) { 439 for (int r = 0; r < patch_rows; ++r) { 440 for (int id = 0; id < input_depth; ++id) { 441 expected += input(id, r + stride * i, c + stride * j, b) * 442 kernel(od, id, r, c); 443 } 444 } 445 } 446 EigenApprox(result(od, i, j, b), expected); 447 } 448 } 449 } 450 } 451 } 452 453 TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolutionRowMajor) { 454 const int input_depth = 10; 455 const int input_rows = 5; 456 const int input_cols = 5; 457 const int num_batches = 13; 458 const int output_depth = 7; 459 const int patch_rows = 3; 460 const int patch_cols = 3; 461 const int output_rows = 2; 462 const int output_cols = 2; 463 464 Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows, 465 input_depth); 466 Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth, 467 output_depth); 468 Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows, 469 output_depth); 470 input = input.constant(11.0f) + input.random(); 471 kernel = kernel.constant(2.0f) + kernel.random(); 472 result.setRandom(); 473 474 // Apply a spatial convolution using a 3x3 kernel, valid padding, and a stride 475 // of 2. 476 int stride = 2; 477 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID); 478 479 EXPECT_EQ(result.dimension(0), num_batches); 480 EXPECT_EQ(result.dimension(1), output_cols); 481 EXPECT_EQ(result.dimension(2), output_rows); 482 EXPECT_EQ(result.dimension(3), output_depth); 483 484 for (int b = 0; b < num_batches; ++b) { 485 for (int od = 0; od < output_depth; ++od) { 486 for (int i = 0; i < output_rows; ++i) { 487 for (int j = 0; j < output_cols; ++j) { 488 float expected = 0.0f; 489 for (int c = 0; c < patch_cols; ++c) { 490 for (int r = 0; r < patch_rows; ++r) { 491 for (int id = 0; id < input_depth; ++id) { 492 expected += input(b, c + stride * j, r + stride * i, id) * 493 kernel(c, r, id, od); 494 } 495 } 496 } 497 EigenApprox(result(b, j, i, od), expected); 498 } 499 } 500 } 501 } 502 } 503 504 TEST(EigenSpatialConvolutionsTest, AtrousSpatial) { 505 const int input_depth = 10; 506 const int input_rows = 7; 507 const int input_cols = 7; 508 const int num_batches = 13; 509 const int output_depth = 7; 510 const int patch_rows = 3; 511 const int patch_cols = 3; 512 const int output_rows = 3; 513 const int output_cols = 3; 514 515 Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches); 516 Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols); 517 Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches); 518 input = input.constant(11.0f) + input.random(); 519 kernel = kernel.constant(2.0f) + kernel.random(); 520 result.setRandom(); 521 522 // Apply a spatial convolution using a 3x3 kernel, valid padding 523 // output (standard) stride 1, and input (atrous) stride of 2. 524 int stride = 1; 525 int in_stride = 2; 526 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID, 527 in_stride, in_stride); 528 529 EXPECT_EQ(result.dimension(0), output_depth); 530 EXPECT_EQ(result.dimension(1), output_rows); 531 EXPECT_EQ(result.dimension(2), output_cols); 532 EXPECT_EQ(result.dimension(3), num_batches); 533 534 for (int b = 0; b < num_batches; ++b) { 535 for (int od = 0; od < output_depth; ++od) { 536 for (int i = 0; i < output_rows; ++i) { 537 for (int j = 0; j < output_cols; ++j) { 538 float expected = 0.0f; 539 for (int c = 0; c < patch_cols; ++c) { 540 for (int r = 0; r < patch_rows; ++r) { 541 for (int id = 0; id < input_depth; ++id) { 542 expected += input(id, in_stride * r + stride * i, 543 in_stride * c + stride * j, b) * 544 kernel(od, id, r, c); 545 } 546 } 547 } 548 EigenApprox(result(od, i, j, b), expected); 549 } 550 } 551 } 552 } 553 } 554 555 TEST(EigenSpatialConvolutionsTest, AtrousSpatialRowMajor) { 556 const int input_depth = 10; 557 const int input_rows = 7; 558 const int input_cols = 7; 559 const int num_batches = 13; 560 const int output_depth = 7; 561 const int patch_rows = 3; 562 const int patch_cols = 3; 563 const int output_rows = 3; 564 const int output_cols = 3; 565 566 Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows, 567 input_depth); 568 Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth, 569 output_depth); 570 Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows, 571 output_depth); 572 input = input.constant(11.0f) + input.random(); 573 kernel = kernel.constant(2.0f) + kernel.random(); 574 result.setRandom(); 575 576 // Apply a spatial convolution using a 3x3 kernel, valid padding 577 // output (standard) stride 1, and input (atrous) stride of 2. 578 int stride = 1; 579 int in_stride = 2; 580 result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID, 581 in_stride, in_stride); 582 583 EXPECT_EQ(result.dimension(0), num_batches); 584 EXPECT_EQ(result.dimension(1), output_cols); 585 EXPECT_EQ(result.dimension(2), output_rows); 586 EXPECT_EQ(result.dimension(3), output_depth); 587 588 for (int b = 0; b < num_batches; ++b) { 589 for (int od = 0; od < output_depth; ++od) { 590 for (int i = 0; i < output_rows; ++i) { 591 for (int j = 0; j < output_cols; ++j) { 592 float expected = 0.0f; 593 for (int c = 0; c < patch_cols; ++c) { 594 for (int r = 0; r < patch_rows; ++r) { 595 for (int id = 0; id < input_depth; ++id) { 596 expected += input(b, in_stride * c + stride * j, 597 in_stride * r + stride * i, id) * 598 kernel(c, r, id, od); 599 } 600 } 601 } 602 EigenApprox(result(b, j, i, od), expected); 603 } 604 } 605 } 606 } 607 } 608 609 TEST(EigenSpatialConvolutionsTest, AtrousSpatialRowMajorUnequalStrides) { 610 const int input_depth = 10; 611 const int input_rows = 7; 612 const int input_cols = 7; 613 const int num_batches = 13; 614 const int output_depth = 7; 615 const int patch_rows = 3; 616 const int patch_cols = 3; 617 const int output_rows = 1; 618 const int output_cols = 3; 619 620 Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows, 621 input_depth); 622 Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth, 623 output_depth); 624 Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows, 625 output_depth); 626 input = input.constant(11.0f) + input.random(); 627 kernel = kernel.constant(2.0f) + kernel.random(); 628 result.setRandom(); 629 630 // Apply a spatial convolution using a 3x3 kernel, valid padding 631 // output (standard) stride 1, and input (atrous) stride of 2. 632 int row_stride = 1; 633 int col_stride = 2; 634 int row_in_stride = 3; 635 int col_in_stride = 1; 636 result = SpatialConvolution(input, kernel, row_stride, col_stride, 637 PADDING_VALID, row_in_stride, col_in_stride); 638 639 EXPECT_EQ(result.dimension(0), num_batches); 640 EXPECT_EQ(result.dimension(1), output_cols); 641 EXPECT_EQ(result.dimension(2), output_rows); 642 EXPECT_EQ(result.dimension(3), output_depth); 643 644 for (int b = 0; b < num_batches; ++b) { 645 for (int od = 0; od < output_depth; ++od) { 646 for (int i = 0; i < output_rows; ++i) { 647 for (int j = 0; j < output_cols; ++j) { 648 float expected = 0.0f; 649 for (int c = 0; c < patch_cols; ++c) { 650 for (int r = 0; r < patch_rows; ++r) { 651 for (int id = 0; id < input_depth; ++id) { 652 expected += input(b, col_in_stride * c + col_stride * j, 653 row_in_stride * r + row_stride * i, id) * 654 kernel(c, r, id, od); 655 } 656 } 657 } 658 EigenApprox(result(b, j, i, od), expected); 659 } 660 } 661 } 662 } 663 } 664 665 TEST(EigenSpatialConvolutionsTest, Cuboid) { 666 const int in_channels = 10; 667 const int in_depth = 5; 668 const int in_rows = 8; 669 const int in_cols = 7; 670 671 const int kern_filters = 7; 672 const int kern_depth = 3; 673 const int kern_width = 4; 674 const int kern_height = 4; 675 676 const int out_depth = in_depth; 677 const int out_height = in_rows; 678 const int out_width = in_cols; 679 680 Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols); 681 Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height, 682 kern_width); 683 Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width); 684 input = input.constant(11.0f) + input.random(); 685 kernel = kernel.constant(2.0f) + kernel.random(); 686 result.setRandom(); 687 688 result = CuboidConvolution(input, kernel); 689 690 EXPECT_EQ(result.dimension(0), kern_filters); 691 EXPECT_EQ(result.dimension(1), out_depth); 692 EXPECT_EQ(result.dimension(2), out_height); 693 EXPECT_EQ(result.dimension(3), out_width); 694 695 const int off_p = (kern_depth - 1) / 2; 696 const int off_r = (kern_height - 1) / 2; 697 const int off_c = (kern_width - 1) / 2; 698 699 for (int od = 0; od < kern_filters; ++od) { 700 for (int i = 0; i < out_depth; ++i) { 701 for (int j = 0; j < out_height; ++j) { 702 for (int k = 0; k < out_width; ++k) { 703 float expected = 0.0f; 704 for (int c = 0; c < kern_width; ++c) { 705 for (int r = 0; r < kern_height; ++r) { 706 for (int p = 0; p < kern_depth; ++p) { 707 for (int id = 0; id < in_channels; ++id) { 708 if (p - off_p + i >= 0 && r - off_r + j >= 0 && 709 c - off_c + k >= 0 && p - off_p + i < in_depth && 710 r - off_r + j < in_rows && c - off_c + k < in_cols) { 711 expected += 712 input(id, p - off_p + i, r - off_r + j, c - off_c + k) * 713 kernel(od, id, p, r, c); 714 } 715 } 716 } 717 } 718 } 719 EigenApprox(result(od, i, j, k), expected); 720 } 721 } 722 } 723 } 724 } 725 726 TEST(EigenSpatialConvolutionsTest, CuboidRowMajor) { 727 const int in_channels = 10; 728 const int in_depth = 5; 729 const int in_rows = 8; 730 const int in_cols = 7; 731 732 const int kern_filters = 7; 733 const int kern_depth = 3; 734 const int kern_width = 4; 735 const int kern_height = 4; 736 737 const int out_depth = in_depth; 738 const int out_height = in_rows; 739 const int out_width = in_cols; 740 741 Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels); 742 Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth, 743 in_channels, kern_filters); 744 Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth, 745 kern_filters); 746 input = input.constant(11.0f) + input.random(); 747 kernel = kernel.constant(2.0f) + kernel.random(); 748 result.setRandom(); 749 750 result = CuboidConvolution(input, kernel); 751 752 EXPECT_EQ(result.dimension(3), kern_filters); 753 EXPECT_EQ(result.dimension(2), out_depth); 754 EXPECT_EQ(result.dimension(1), out_height); 755 EXPECT_EQ(result.dimension(0), out_width); 756 757 const int off_p = (kern_depth - 1) / 2; 758 const int off_r = (kern_height - 1) / 2; 759 const int off_c = (kern_width - 1) / 2; 760 761 for (int od = 0; od < kern_filters; ++od) { 762 for (int i = 0; i < out_depth; ++i) { 763 for (int j = 0; j < out_height; ++j) { 764 for (int k = 0; k < out_width; ++k) { 765 float expected = 0.0f; 766 for (int c = 0; c < kern_width; ++c) { 767 for (int r = 0; r < kern_height; ++r) { 768 for (int p = 0; p < kern_depth; ++p) { 769 for (int id = 0; id < in_channels; ++id) { 770 if (p - off_p + i >= 0 && r - off_r + j >= 0 && 771 c - off_c + k >= 0 && p - off_p + i < in_depth && 772 r - off_r + j < in_rows && c - off_c + k < in_cols) { 773 expected += 774 input(c - off_c + k, r - off_r + j, p - off_p + i, id) * 775 kernel(c, r, p, id, od); 776 } 777 } 778 } 779 } 780 } 781 EigenApprox(result(k, j, i, od), expected); 782 } 783 } 784 } 785 } 786 } 787 788 TEST(EigenSpatialConvolutionsTest, ValidCuboid) { 789 const int in_channels = 10; 790 const int in_depth = 5; 791 const int in_rows = 5; 792 const int in_cols = 5; 793 794 const int kern_filters = 7; 795 const int kern_depth = 3; 796 const int kern_width = 3; 797 const int kern_height = 3; 798 799 const int out_depth = 3; 800 const int out_height = 3; 801 const int out_width = 3; 802 803 Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols); 804 Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height, 805 kern_width); 806 Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width); 807 input = input.constant(11.0f) + input.random(); 808 kernel = kernel.constant(2.0f) + kernel.random(); 809 result.setRandom(); 810 811 result = CuboidConvolution(input, kernel, 1, 1, 1, PADDING_VALID); 812 813 EXPECT_EQ(result.dimension(0), kern_filters); 814 EXPECT_EQ(result.dimension(1), out_depth); 815 EXPECT_EQ(result.dimension(2), out_height); 816 EXPECT_EQ(result.dimension(3), out_width); 817 818 for (int od = 0; od < kern_filters; ++od) { 819 for (int i = 0; i < out_depth; ++i) { 820 for (int j = 0; j < out_height; ++j) { 821 for (int k = 0; k < out_width; ++k) { 822 float expected = 0.0f; 823 for (int c = 0; c < kern_width; ++c) { 824 for (int r = 0; r < kern_height; ++r) { 825 for (int p = 0; p < kern_depth; ++p) { 826 for (int id = 0; id < in_channels; ++id) { 827 expected += 828 input(id, p + i, r + j, c + k) * kernel(od, id, p, r, c); 829 } 830 } 831 } 832 } 833 EigenApprox(result(od, i, j, k), expected); 834 } 835 } 836 } 837 } 838 } 839 840 TEST(EigenSpatialConvolutionsTest, ValidCuboidRowMajor) { 841 const int in_channels = 10; 842 const int in_depth = 5; 843 const int in_rows = 5; 844 const int in_cols = 5; 845 846 const int kern_filters = 7; 847 const int kern_depth = 3; 848 const int kern_width = 3; 849 const int kern_height = 3; 850 851 const int out_depth = 3; 852 const int out_height = 3; 853 const int out_width = 3; 854 855 Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels); 856 Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth, 857 in_channels, kern_filters); 858 Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth, 859 kern_filters); 860 input = input.constant(11.0f) + input.random(); 861 kernel = kernel.constant(2.0f) + kernel.random(); 862 result.setRandom(); 863 864 result = CuboidConvolution(input, kernel, 1, 1, 1, PADDING_VALID); 865 866 EXPECT_EQ(result.dimension(3), kern_filters); 867 EXPECT_EQ(result.dimension(2), out_depth); 868 EXPECT_EQ(result.dimension(1), out_height); 869 EXPECT_EQ(result.dimension(0), out_width); 870 871 for (int od = 0; od < kern_filters; ++od) { 872 for (int i = 0; i < out_depth; ++i) { 873 for (int j = 0; j < out_height; ++j) { 874 for (int k = 0; k < out_width; ++k) { 875 float expected = 0.0f; 876 for (int c = 0; c < kern_width; ++c) { 877 for (int r = 0; r < kern_height; ++r) { 878 for (int p = 0; p < kern_depth; ++p) { 879 for (int id = 0; id < in_channels; ++id) { 880 expected += 881 input(c + k, r + j, p + i, id) * kernel(c, r, p, id, od); 882 } 883 } 884 } 885 } 886 EigenApprox(result(k, j, i, od), expected); 887 } 888 } 889 } 890 } 891 } 892 893 TEST(EigenSpatialConvolutionsTest, BatchedCuboid) { 894 const int batches = 2; 895 const int in_channels = 10; 896 const int in_depth = 5; 897 const int in_rows = 8; 898 const int in_cols = 7; 899 900 const int kern_filters = 7; 901 const int kern_depth = 3; 902 const int kern_width = 4; 903 const int kern_height = 4; 904 905 const int out_depth = in_depth; 906 const int out_height = in_rows; 907 const int out_width = in_cols; 908 909 Tensor<float, 5> input(in_channels, in_depth, in_rows, in_cols, batches); 910 Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height, 911 kern_width); 912 Tensor<float, 5> result(kern_filters, out_depth, out_height, out_width, 913 batches); 914 input = input.constant(11.0f) + input.random(); 915 kernel = kernel.constant(2.0f) + kernel.random(); 916 result.setRandom(); 917 918 result = CuboidConvolution(input, kernel); 919 920 EXPECT_EQ(result.dimension(0), kern_filters); 921 EXPECT_EQ(result.dimension(1), out_depth); 922 EXPECT_EQ(result.dimension(2), out_height); 923 EXPECT_EQ(result.dimension(3), out_width); 924 EXPECT_EQ(result.dimension(4), batches); 925 926 const int off_p = (kern_depth - 1) / 2; 927 const int off_r = (kern_height - 1) / 2; 928 const int off_c = (kern_width - 1) / 2; 929 930 for (int b = 0; b < batches; b++) { 931 for (int od = 0; od < kern_filters; ++od) { 932 for (int i = 0; i < out_depth; ++i) { 933 for (int j = 0; j < out_height; ++j) { 934 for (int k = 0; k < out_width; ++k) { 935 float expected = 0.0f; 936 for (int c = 0; c < kern_width; ++c) { 937 for (int r = 0; r < kern_height; ++r) { 938 for (int p = 0; p < kern_depth; ++p) { 939 for (int id = 0; id < in_channels; ++id) { 940 if (p - off_p + i >= 0 && r - off_r + j >= 0 && 941 c - off_c + k >= 0 && p - off_p + i < in_depth && 942 r - off_r + j < in_rows && c - off_c + k < in_cols) { 943 expected += input(id, p - off_p + i, r - off_r + j, 944 c - off_c + k, b) * 945 kernel(od, id, p, r, c); 946 } 947 } 948 } 949 } 950 } 951 EigenApprox(result(od, i, j, k, b), expected); 952 } 953 } 954 } 955 } 956 } 957 } 958 959 TEST(EigenSpatialConvolutionsTest, BatchedCuboidRowMajor) { 960 const int batches = 2; 961 const int in_channels = 10; 962 const int in_depth = 5; 963 const int in_rows = 8; 964 const int in_cols = 7; 965 966 const int kern_filters = 7; 967 const int kern_depth = 3; 968 const int kern_width = 4; 969 const int kern_height = 4; 970 971 const int out_depth = in_depth; 972 const int out_height = in_rows; 973 const int out_width = in_cols; 974 975 Tensor<float, 5, RowMajor> input(batches, in_cols, in_rows, in_depth, 976 in_channels); 977 Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth, 978 in_channels, kern_filters); 979 Tensor<float, 5, RowMajor> result(batches, out_width, out_height, out_depth, 980 kern_filters); 981 input = input.constant(11.0f) + input.random(); 982 kernel = kernel.constant(2.0f) + kernel.random(); 983 result.setRandom(); 984 985 result = CuboidConvolution(input, kernel); 986 987 EXPECT_EQ(result.dimension(4), kern_filters); 988 EXPECT_EQ(result.dimension(3), out_depth); 989 EXPECT_EQ(result.dimension(2), out_height); 990 EXPECT_EQ(result.dimension(1), out_width); 991 EXPECT_EQ(result.dimension(0), batches); 992 993 const int off_p = (kern_depth - 1) / 2; 994 const int off_r = (kern_height - 1) / 2; 995 const int off_c = (kern_width - 1) / 2; 996 997 for (int b = 0; b < batches; b++) { 998 for (int od = 0; od < kern_filters; ++od) { 999 for (int i = 0; i < out_depth; ++i) { 1000 for (int j = 0; j < out_height; ++j) { 1001 for (int k = 0; k < out_width; ++k) { 1002 float expected = 0.0f; 1003 for (int c = 0; c < kern_width; ++c) { 1004 for (int r = 0; r < kern_height; ++r) { 1005 for (int p = 0; p < kern_depth; ++p) { 1006 for (int id = 0; id < in_channels; ++id) { 1007 if (p - off_p + i >= 0 && r - off_r + j >= 0 && 1008 c - off_c + k >= 0 && p - off_p + i < in_depth && 1009 r - off_r + j < in_rows && c - off_c + k < in_cols) { 1010 expected += input(b, c - off_c + k, r - off_r + j, 1011 p - off_p + i, id) * 1012 kernel(c, r, p, id, od); 1013 } 1014 } 1015 } 1016 } 1017 } 1018 EigenApprox(result(b, k, j, i, od), expected); 1019 } 1020 } 1021 } 1022 } 1023 } 1024 } 1025 1026 TEST(EigenSpatialConvolutionsTest, StridedValidCuboid) { 1027 const int in_channels = 10; 1028 const int in_depth = 8; 1029 const int in_rows = 7; 1030 const int in_cols = 5; 1031 1032 const int kern_filters = 7; 1033 const int kern_depth = 3; 1034 const int kern_width = 3; 1035 const int kern_height = 3; 1036 1037 const int out_depth = 3; 1038 const int out_height = 3; 1039 const int out_width = 2; 1040 1041 Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols); 1042 Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height, 1043 kern_width); 1044 Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width); 1045 input = input.constant(11.0f) + input.random(); 1046 kernel = kernel.constant(2.0f) + kernel.random(); 1047 result.setRandom(); 1048 1049 const int stride = 2; 1050 result = 1051 CuboidConvolution(input, kernel, stride, stride, stride, PADDING_VALID); 1052 1053 EXPECT_EQ(result.dimension(0), kern_filters); 1054 EXPECT_EQ(result.dimension(1), out_depth); 1055 EXPECT_EQ(result.dimension(2), out_height); 1056 EXPECT_EQ(result.dimension(3), out_width); 1057 1058 for (int od = 0; od < kern_filters; ++od) { 1059 for (int i = 0; i < out_depth; ++i) { 1060 for (int j = 0; j < out_height; ++j) { 1061 for (int k = 0; k < out_width; ++k) { 1062 float expected = 0.0f; 1063 for (int c = 0; c < kern_width; ++c) { 1064 for (int r = 0; r < kern_height; ++r) { 1065 for (int p = 0; p < kern_depth; ++p) { 1066 for (int id = 0; id < in_channels; ++id) { 1067 expected += input(id, p + stride * i, r + stride * j, 1068 c + stride * k) * 1069 kernel(od, id, p, r, c); 1070 } 1071 } 1072 } 1073 } 1074 EigenApprox(result(od, i, j, k), expected); 1075 } 1076 } 1077 } 1078 } 1079 } 1080 1081 TEST(EigenSpatialConvolutionsTest, StridedValidCuboidRowMajor) { 1082 const int in_channels = 10; 1083 const int in_depth = 8; 1084 const int in_rows = 7; 1085 const int in_cols = 5; 1086 1087 const int kern_filters = 7; 1088 const int kern_depth = 3; 1089 const int kern_width = 3; 1090 const int kern_height = 3; 1091 1092 const int out_depth = 3; 1093 const int out_height = 3; 1094 const int out_width = 2; 1095 1096 Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels); 1097 Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth, 1098 in_channels, kern_filters); 1099 Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth, 1100 kern_filters); 1101 input = input.constant(11.0f) + input.random(); 1102 kernel = kernel.constant(2.0f) + kernel.random(); 1103 result.setRandom(); 1104 1105 const int stride = 2; 1106 result = 1107 CuboidConvolution(input, kernel, stride, stride, stride, PADDING_VALID); 1108 1109 EXPECT_EQ(result.dimension(3), kern_filters); 1110 EXPECT_EQ(result.dimension(2), out_depth); 1111 EXPECT_EQ(result.dimension(1), out_height); 1112 EXPECT_EQ(result.dimension(0), out_width); 1113 1114 for (int od = 0; od < kern_filters; ++od) { 1115 for (int i = 0; i < out_depth; ++i) { 1116 for (int j = 0; j < out_height; ++j) { 1117 for (int k = 0; k < out_width; ++k) { 1118 float expected = 0.0f; 1119 for (int c = 0; c < kern_width; ++c) { 1120 for (int r = 0; r < kern_height; ++r) { 1121 for (int p = 0; p < kern_depth; ++p) { 1122 for (int id = 0; id < in_channels; ++id) { 1123 expected += input(c + stride * k, r + stride * j, 1124 p + stride * i, id) * 1125 kernel(c, r, p, id, od); 1126 } 1127 } 1128 } 1129 } 1130 EigenApprox(result(k, j, i, od), expected); 1131 } 1132 } 1133 } 1134 } 1135 } 1136 1137 TEST(EigenSpatialConvolutionsTest, StridedSameCuboid) { 1138 const int in_channels = 10; 1139 const int in_depth = 8; 1140 const int in_rows = 7; 1141 const int in_cols = 5; 1142 1143 const int kern_filters = 7; 1144 const int kern_depth = 3; 1145 const int kern_width = 3; 1146 const int kern_height = 3; 1147 1148 const int stride = 2; 1149 const int out_depth = ceil_div(in_depth, stride); 1150 const int out_height = ceil_div(in_rows, stride); 1151 const int out_width = ceil_div(in_cols, stride); 1152 1153 Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols); 1154 Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height, 1155 kern_width); 1156 Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width); 1157 input = input.constant(11.0f) + input.random(); 1158 kernel = kernel.constant(2.0f) + kernel.random(); 1159 result.setRandom(); 1160 1161 result = 1162 CuboidConvolution(input, kernel, stride, stride, stride, PADDING_SAME); 1163 1164 EXPECT_EQ(result.dimension(0), kern_filters); 1165 EXPECT_EQ(result.dimension(1), out_depth); 1166 EXPECT_EQ(result.dimension(2), out_height); 1167 EXPECT_EQ(result.dimension(3), out_width); 1168 1169 const int pad_p = (out_depth - 1) * stride - in_depth + kern_depth; 1170 const int pad_r = (out_height - 1) * stride - in_rows + kern_height; 1171 const int pad_c = (out_width - 1) * stride - in_cols + kern_width; 1172 1173 // Number of pixels the input is extended with at the lower end in every 1174 // dimension. 1175 const int dp = pad_p / 2; 1176 const int dr = pad_r / 2; 1177 const int dc = pad_c / 2; 1178 1179 for (int od = 0; od < kern_filters; ++od) { 1180 for (int i = 0; i < out_depth; ++i) { 1181 for (int j = 0; j < out_height; ++j) { 1182 for (int k = 0; k < out_width; ++k) { 1183 float expected = 0.0f; 1184 for (int c = 0; c < kern_width; ++c) { 1185 for (int r = 0; r < kern_height; ++r) { 1186 for (int p = 0; p < kern_depth; ++p) { 1187 for (int id = 0; id < in_channels; ++id) { 1188 const int in_p = p - dp + i * stride; 1189 const int in_r = r - dr + j * stride; 1190 const int in_c = c - dc + k * stride; 1191 if (in_p >= 0 && in_r >= 0 && in_c >= 0 && in_p < in_depth && 1192 in_r < in_rows && in_c < in_cols) { 1193 expected += 1194 input(id, in_p, in_r, in_c) * kernel(od, id, p, r, c); 1195 } 1196 } 1197 } 1198 } 1199 } 1200 EigenApprox(result(od, i, j, k), expected); 1201 } 1202 } 1203 } 1204 } 1205 } 1206 1207 TEST(EigenSpatialConvolutionsTest, StridedSameCuboidRowMajor) { 1208 const int in_channels = 10; 1209 const int in_depth = 8; 1210 const int in_rows = 7; 1211 const int in_cols = 5; 1212 1213 const int kern_filters = 7; 1214 const int kern_depth = 3; 1215 const int kern_width = 3; 1216 const int kern_height = 3; 1217 1218 const int stride = 2; 1219 const int out_depth = ceil_div(in_depth, stride); 1220 const int out_height = ceil_div(in_rows, stride); 1221 const int out_width = ceil_div(in_cols, stride); 1222 1223 Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels); 1224 Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth, 1225 in_channels, kern_filters); 1226 Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth, 1227 kern_filters); 1228 input = input.constant(11.0f) + input.random(); 1229 kernel = kernel.constant(2.0f) + kernel.random(); 1230 result.setRandom(); 1231 1232 result = 1233 CuboidConvolution(input, kernel, stride, stride, stride, PADDING_SAME); 1234 1235 EXPECT_EQ(result.dimension(3), kern_filters); 1236 EXPECT_EQ(result.dimension(2), out_depth); 1237 EXPECT_EQ(result.dimension(1), out_height); 1238 EXPECT_EQ(result.dimension(0), out_width); 1239 1240 const int pad_p = (out_depth - 1) * stride - in_depth + kern_depth; 1241 const int pad_r = (out_height - 1) * stride - in_rows + kern_height; 1242 const int pad_c = (out_width - 1) * stride - in_cols + kern_width; 1243 1244 // Number of pixels the input is extended with at the lower end in every 1245 // dimension. 1246 const int dp = pad_p / 2; 1247 const int dr = pad_r / 2; 1248 const int dc = pad_c / 2; 1249 1250 for (int od = 0; od < kern_filters; ++od) { 1251 for (int i = 0; i < out_depth; ++i) { 1252 for (int j = 0; j < out_height; ++j) { 1253 for (int k = 0; k < out_width; ++k) { 1254 float expected = 0.0f; 1255 for (int c = 0; c < kern_width; ++c) { 1256 for (int r = 0; r < kern_height; ++r) { 1257 for (int p = 0; p < kern_depth; ++p) { 1258 for (int id = 0; id < in_channels; ++id) { 1259 const int in_p = p - dp + i * stride; 1260 const int in_r = r - dr + j * stride; 1261 const int in_c = c - dc + k * stride; 1262 if (in_p >= 0 && in_r >= 0 && in_c >= 0 && in_p < in_depth && 1263 in_r < in_rows && in_c < in_cols) { 1264 expected += 1265 input(in_c, in_r, in_p, id) * kernel(c, r, p, id, od); 1266 } 1267 } 1268 } 1269 } 1270 } 1271 EigenApprox(result(k, j, i, od), expected); 1272 } 1273 } 1274 } 1275 } 1276 } 1277 1278 // A test case discovered when testing backward spatial convolution where the 1279 // special tensor contraction mapper for spatial convolution contains a bug. 1280 TEST(EigenSpatialConvolutionsTest, SpatialConvContractionMapper) { 1281 // We have a 3x4 input image with 2x2 patch and stride of 2. 1282 // The output has size 1x2. 1283 typedef Tensor<float, 1>::DimensionPair DimPair; 1284 Tensor<float, 4> out(1, 1, 2, 1); 1285 Tensor<float, 4> kern(1, 1, 2, 2); 1286 for (int i = 0; i < kern.size(); ++i) { 1287 kern.coeffRef(i) = static_cast<float>(i) + 1; 1288 } 1289 for (int i = 0; i < out.size(); ++i) { 1290 out.coeffRef(i) = static_cast<float>(i) + 1; 1291 } 1292 1293 DSizes<ptrdiff_t, 4> strides; 1294 strides[0] = 1; 1295 strides[1] = 2; 1296 strides[2] = 2; 1297 strides[3] = 1; 1298 1299 array<std::pair<ptrdiff_t, ptrdiff_t>, 4> paddings; 1300 paddings[0] = std::make_pair(0, 0); 1301 paddings[1] = std::make_pair(1, 2); 1302 paddings[2] = std::make_pair(1, 1); 1303 paddings[3] = std::make_pair(0, 0); 1304 1305 DSizes<ptrdiff_t, 3> out_dim; 1306 out_dim[0] = 1; 1307 out_dim[1] = 4; 1308 out_dim[2] = 12; 1309 1310 array<bool, 4> kernel_reverse; 1311 kernel_reverse[0] = false; 1312 kernel_reverse[1] = false; 1313 kernel_reverse[2] = true; 1314 kernel_reverse[3] = true; 1315 1316 DSizes<ptrdiff_t, 3> k_dims; 1317 k_dims[0] = 1; 1318 k_dims[1] = 1; 1319 k_dims[2] = 4; 1320 1321 array<DimPair, 2> contract_dims; 1322 contract_dims[0] = DimPair(0, 0); 1323 contract_dims[1] = DimPair(2, 1); 1324 1325 DSizes<ptrdiff_t, 4> in_dim; 1326 in_dim[0] = 1; 1327 in_dim[1] = 3; 1328 in_dim[2] = 4; 1329 in_dim[3] = 1; 1330 1331 DSizes<ptrdiff_t, 2> in_dbg_dim; 1332 in_dbg_dim[0] = 3; 1333 in_dbg_dim[1] = 4; 1334 1335 DSizes<ptrdiff_t, 2> out_dbg_dim; 1336 out_dbg_dim[0] = 4; 1337 out_dbg_dim[1] = 12; 1338 1339 // This is the formula for computing the backward prop for input with a 1340 // spatial convolution. 1341 Tensor<float, 4> direct = 1342 kern.reverse(kernel_reverse) 1343 .reshape(k_dims) 1344 .contract( 1345 out.extract_image_patches(2, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1, 0) 1346 .reshape(out_dim), 1347 contract_dims) 1348 .reshape(in_dim); 1349 1350 Tensor<float, 4> indirect = 1351 kern.reverse(kernel_reverse) 1352 .reshape(k_dims) 1353 .contract( 1354 out.inflate(strides) 1355 .pad(paddings) 1356 .extract_image_patches(2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0) 1357 .reshape(out_dim), 1358 contract_dims) 1359 .reshape(in_dim); 1360 1361 eigen_assert(dimensions_match(direct.dimensions(), indirect.dimensions())); 1362 for (size_t i = 0; i < direct.dimensions().TotalSize(); ++i) { 1363 EigenApprox(direct.data()[i], indirect.data()[i]); 1364 } 1365 EigenApprox(1.0f, direct(0, 0, 0, 0)); 1366 EigenApprox(3.0f, direct(0, 0, 1, 0)); 1367 EigenApprox(2.0f, direct(0, 0, 2, 0)); 1368 EigenApprox(6.0f, direct(0, 0, 3, 0)); 1369 1370 EigenApprox(2.0f, direct(0, 1, 0, 0)); 1371 EigenApprox(4.0f, direct(0, 1, 1, 0)); 1372 EigenApprox(4.0f, direct(0, 1, 2, 0)); 1373 EigenApprox(8.0f, direct(0, 1, 3, 0)); 1374 } 1375 1376 } // namespace Eigen 1377