ONE - On-device Neural Engine
Loading...
Searching...
No Matches
eigen_backward_spatial_convolutions.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved
3 * Copyright 2015 The TensorFlow Authors. All Rights Reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#ifndef __NNFW_CKER_EGIEN_EIGEN_BACKWARD_SPATIAL_CONVOLUTIONS_H__
19#define __NNFW_CKER_EGIEN_EIGEN_BACKWARD_SPATIAL_CONVOLUTIONS_H__
20
21#include "unsupported/Eigen/CXX11/Tensor"
23
24namespace Eigen
25{
26
51typedef IndexList<type2index<0>, type2index<0>, type2index<1>, type2index<1>> ReverseColMajor;
52typedef IndexList<type2index<1>, type2index<1>, type2index<0>, type2index<0>> ReverseRowMajor;
53
54template <typename OutputBackward, typename Kernel>
55EIGEN_ALWAYS_INLINE static const std::conditional_t<
56 internal::traits<OutputBackward>::Layout == ColMajor,
57 TensorReshapingOp<
58 const DSizes<typename internal::traits<OutputBackward>::Index,
59 internal::traits<OutputBackward>::NumDimensions>,
60 const TensorContractionOp<
61 const array<IndexPair<typename internal::traits<OutputBackward>::Index>, 1>,
62 const TensorReshapingOp<const DSizes<typename internal::traits<OutputBackward>::Index, 2>,
63 const Eigen::TensorForcedEvalOp<const TensorShufflingOp<
64 const array<typename internal::traits<OutputBackward>::Index, 4>,
65 const Eigen::TensorForcedEvalOp<
66 const TensorReverseOp<const ReverseColMajor, const Kernel>>>>>,
67 const TensorReshapingOp<const DSizes<typename internal::traits<OutputBackward>::Index, 2>,
68 const TensorImagePatchOp<Dynamic, Dynamic, const OutputBackward>>>>,
69 TensorReshapingOp<
70
71 const DSizes<typename internal::traits<OutputBackward>::Index,
72 internal::traits<OutputBackward>::NumDimensions>,
73 const TensorContractionOp<
74 const array<IndexPair<typename internal::traits<OutputBackward>::Index>, 1>,
75 const TensorReshapingOp<const DSizes<typename internal::traits<OutputBackward>::Index, 2>,
76 const TensorImagePatchOp<Dynamic, Dynamic, const OutputBackward>>,
77 const TensorReshapingOp<const DSizes<typename internal::traits<OutputBackward>::Index, 2>,
78 const Eigen::TensorForcedEvalOp<const TensorShufflingOp<
79 const array<typename internal::traits<OutputBackward>::Index, 4>,
80 const Eigen::TensorForcedEvalOp<
81 const TensorReverseOp<const ReverseRowMajor, const Kernel>>>>>>>>
82SpatialConvolutionBackwardInput(const Kernel &kernel, const OutputBackward &output_backward,
83 typename internal::traits<OutputBackward>::Index inputRows,
84 typename internal::traits<OutputBackward>::Index inputCols,
85 const DenseIndex row_stride = 1, const DenseIndex col_stride = 1,
86 const DenseIndex row_in_stride = 1,
87 const DenseIndex col_in_stride = 1)
88{
89 typedef typename internal::traits<OutputBackward>::Index TensorIndex;
90 typedef typename internal::traits<OutputBackward>::Scalar OutScalar;
91 TensorRef<
92 Tensor<typename internal::traits<Kernel>::Scalar, internal::traits<Kernel>::NumDimensions,
93 internal::traits<Kernel>::Layout, TensorIndex>>
94 kern(kernel);
95 TensorRef<Tensor<OutScalar, internal::traits<OutputBackward>::NumDimensions,
96 internal::traits<OutputBackward>::Layout, TensorIndex>>
97 out(output_backward);
98
99 EIGEN_STATIC_ASSERT(internal::traits<Kernel>::Layout == internal::traits<OutputBackward>::Layout,
100 YOU_MADE_A_PROGRAMMING_MISTAKE);
101
102 static const bool isColMajor = (internal::traits<OutputBackward>::Layout == ColMajor);
103
104 static const int NumDims = internal::traits<OutputBackward>::NumDimensions;
105
106 // Number of filters to apply. This is the same as the output depth of the
107 // result
108 const TensorIndex kernelFilters = isColMajor ? kern.dimensions()[0] : kern.dimensions()[3];
109 // Number of channels. This is the same as the input depth.
110 const TensorIndex kernelChannels = isColMajor ? kern.dimensions()[1] : kern.dimensions()[2];
111 const TensorIndex kernelRows = isColMajor ? kern.dimensions()[2] : kern.dimensions()[1];
112 const TensorIndex kernelCols = isColMajor ? kern.dimensions()[3] : kern.dimensions()[0];
113
114 // This is the effective kernel size, taking into account the (*_in_stride -
115 // 1) zero-values
116 // inserted between consecutive kernel elements in atrous convolution
117 const TensorIndex kernelRowsEff = kernelRows + (kernelRows - 1) * (row_in_stride - 1);
118 const TensorIndex kernelColsEff = kernelCols + (kernelCols - 1) * (col_in_stride - 1);
119
120 const TensorIndex outputRows =
121 isColMajor ? output_backward.dimension(1) : output_backward.dimension(NumDims - 2);
122 const TensorIndex outputCols =
123 isColMajor ? output_backward.dimension(2) : output_backward.dimension(NumDims - 3);
124
125 // Computing the forward padding
126 const TensorIndex forward_pad_top =
127 numext::maxi<Index>(0, ((outputRows - 1) * row_stride + kernelRowsEff - inputRows) / 2);
128 const TensorIndex forward_pad_left =
129 numext::maxi<Index>(0, ((outputCols - 1) * col_stride + kernelColsEff - inputCols) / 2);
130 const TensorIndex padding_top = kernelRowsEff - 1 - forward_pad_top;
131 const TensorIndex padding_left = kernelColsEff - 1 - forward_pad_left;
132
133 const TensorIndex padding_bottom =
134 inputRows - (outputRows - 1) * row_stride - 2 - padding_top + kernelRowsEff;
135 const TensorIndex padding_right =
136 inputCols - (outputCols - 1) * col_stride - 2 - padding_left + kernelColsEff;
137
138 eigen_assert(padding_top >= 0);
139 eigen_assert(padding_left >= 0);
140 eigen_assert(padding_bottom >= 0);
141 eigen_assert(padding_right >= 0);
142
143 // The kernel has dimensions filters X channels X patch_rows X patch_cols
144 // We need to reverse the kernel along dimensions corresponding to rows and
145 // cols.
146 // TODO(yangke): we can make things slightly faster by collapsing the
147 // dimensions
148 // where we don't reverse. Try that once we have a faster compiler.
149 typedef std::conditional_t<isColMajor, ReverseColMajor, ReverseRowMajor> Reverse;
150 Reverse kernel_reverse;
151 // Reorder the dimensions to:
152 // filters x patch_rows x patch_cols x channels
153 array<TensorIndex, 4> kernel_shuffle;
154 if (isColMajor)
155 {
156 // From: filters x channels x rows x cols
157 // To: filters x rows x cols x channels
158 kernel_shuffle[0] = 0;
159 kernel_shuffle[1] = 2;
160 kernel_shuffle[2] = 3;
161 kernel_shuffle[3] = 1;
162 }
163 else
164 {
165 // From: cols x rows x channels x filters
166 // To: channels x cols x rows x filters
167 kernel_shuffle[0] = 2;
168 kernel_shuffle[1] = 0;
169 kernel_shuffle[2] = 1;
170 kernel_shuffle[3] = 3;
171 }
172
173 // Collapse the dims
174 DSizes<TensorIndex, 2> kernel_dims;
175 if (isColMajor)
176 {
177 kernel_dims[0] = kernelFilters * kernelRows * kernelCols;
178 kernel_dims[1] = kernelChannels;
179 }
180 else
181 {
182 kernel_dims[1] = kernelFilters * kernelRows * kernelCols;
183 kernel_dims[0] = kernelChannels;
184 }
185
186 // The output_backward has dimensions out_depth X out_rows X out_cols X OTHERS
187 // When we extract the image patches from output_backward, it will have
188 // dimensions
189 // out_depth X (patch_rows * patch_cols) X (input_rows * input_cols *
190 // OTHERS)
191 DSizes<TensorIndex, 2> pre_contract_dims;
192 if (isColMajor)
193 {
194 pre_contract_dims[0] = kernelFilters * kernelRows * kernelCols;
195 pre_contract_dims[1] = inputRows * inputCols;
196 for (int i = 3; i < NumDims; ++i)
197 {
198 pre_contract_dims[1] *= out.dimension(i);
199 }
200 }
201 else
202 {
203 pre_contract_dims[1] = kernelFilters * kernelRows * kernelCols;
204 pre_contract_dims[0] = inputRows * inputCols;
205 for (int i = 0; i < NumDims - 3; ++i)
206 {
207 pre_contract_dims[0] *= out.dimension(i);
208 }
209 }
210
211 // We will contract along the collapsed dimension that contains the
212 // kernelFilters, the kernelRows and the kernelCols.
213 array<IndexPair<TensorIndex>, 1> contract_dims;
214 if (isColMajor)
215 {
216 // col-major: kernel.contract(output.patches)
217 contract_dims[0] = IndexPair<TensorIndex>(0, 0);
218 }
219 else
220 {
221 // row-major: output.patches.contract(kernel)
222 contract_dims[0] = IndexPair<TensorIndex>(1, 1);
223 }
224
225 // Post contraction, the dimensions of the input_backprop is
226 // channels X input_rows X input_cols X OTHERS
227 DSizes<TensorIndex, NumDims> post_contract_dims;
228 if (isColMajor)
229 {
230 post_contract_dims[0] = kernelChannels;
231 post_contract_dims[1] = inputRows;
232 post_contract_dims[2] = inputCols;
233 for (int i = 3; i < NumDims; ++i)
234 {
235 post_contract_dims[i] = out.dimension(i);
236 }
237 }
238 else
239 {
240 post_contract_dims[NumDims - 1] = kernelChannels;
241 post_contract_dims[NumDims - 2] = inputRows;
242 post_contract_dims[NumDims - 3] = inputCols;
243 for (int i = 0; i < NumDims - 3; ++i)
244 {
245 post_contract_dims[i] = out.dimension(i);
246 }
247 }
248
249 // NOTE(ezhulenev): We do eval after reverse and shuffle, because tiled
250 // evaluation of these ops does not compose. Doing explicit eval is ~8x
251 // faster in micro benchmarks.
252
253 return choose(
254 Cond<internal::traits<OutputBackward>::Layout == ColMajor>(),
255 kernel.reverse(kernel_reverse)
256 .eval()
257 .shuffle(kernel_shuffle)
258 .eval()
259 .reshape(kernel_dims)
260 .contract(output_backward
261 .extract_image_patches(kernelRows, kernelCols, 1, 1, row_in_stride, col_in_stride,
262 row_stride, col_stride, padding_top, padding_bottom,
263 padding_left, padding_right, OutScalar(0))
264 .reshape(pre_contract_dims),
265 contract_dims)
266 .reshape(post_contract_dims),
267 output_backward
268 .extract_image_patches(kernelRows, kernelCols, 1, 1, row_in_stride, col_in_stride, row_stride,
269 col_stride, padding_top, padding_bottom, padding_left, padding_right,
270 OutScalar(0))
271 .reshape(pre_contract_dims)
272 .contract(
273 kernel.reverse(kernel_reverse).eval().shuffle(kernel_shuffle).eval().reshape(kernel_dims),
274 contract_dims)
275 .reshape(post_contract_dims));
276}
277
303template <typename OutputBackward, typename Input>
304EIGEN_ALWAYS_INLINE static const std::conditional_t<
305 internal::traits<Input>::Layout == ColMajor,
306 const TensorReverseOp<
307 const Eigen::array<typename internal::traits<Input>::Index,
308 internal::traits<Input>::NumDimensions>,
309 const Eigen::TensorForcedEvalOp<const Eigen::TensorShufflingOp<
310 const Eigen::array<typename internal::traits<Input>::Index,
311 internal::traits<Input>::NumDimensions>,
312 const Eigen::TensorReshapingOp<
313 const Eigen::DSizes<typename internal::traits<Input>::Index,
314 internal::traits<Input>::NumDimensions>,
315 const TensorContractionOp<
316 const array<IndexPair<typename internal::traits<Input>::Index>, 1>,
317 const TensorReshapingOp<const DSizes<typename internal::traits<Input>::Index, 2>,
318 const Eigen::TensorForcedEvalOp<const Eigen::TensorShufflingOp<
319 const Eigen::array<typename internal::traits<Input>::Index,
320 internal::traits<Input>::NumDimensions>,
321 const Input>>>,
322 const TensorReshapingOp<
323 const DSizes<typename internal::traits<Input>::Index, 2>,
324 const TensorImagePatchOp<Dynamic, Dynamic,
325 const Eigen::TensorForcedEvalOp<const Eigen::TensorShufflingOp<
326 const Eigen::array<typename internal::traits<Input>::Index,
327 internal::traits<Input>::NumDimensions>,
328 const OutputBackward>>>>>>>>>,
329 const TensorReverseOp<
330 const Eigen::array<typename internal::traits<Input>::Index,
331 internal::traits<Input>::NumDimensions>,
332 const Eigen::TensorForcedEvalOp<const Eigen::TensorShufflingOp<
333 const Eigen::array<typename internal::traits<Input>::Index,
334 internal::traits<Input>::NumDimensions>,
335 const Eigen::TensorReshapingOp<
336 const Eigen::DSizes<typename internal::traits<Input>::Index,
337 internal::traits<Input>::NumDimensions>,
338 const TensorContractionOp<
339 const array<IndexPair<typename internal::traits<Input>::Index>, 1>,
340 const TensorReshapingOp<
341 const DSizes<typename internal::traits<Input>::Index, 2>,
342 const TensorImagePatchOp<Dynamic, Dynamic,
343 const Eigen::TensorForcedEvalOp<const Eigen::TensorShufflingOp<
344 const Eigen::array<typename internal::traits<Input>::Index,
345 internal::traits<Input>::NumDimensions>,
346 const OutputBackward>>>>,
347 const TensorReshapingOp<const DSizes<typename internal::traits<Input>::Index, 2>,
348 const Eigen::TensorForcedEvalOp<const Eigen::TensorShufflingOp<
349 const Eigen::array<typename internal::traits<Input>::Index,
350 internal::traits<Input>::NumDimensions>,
351 const Input>>>>>>>>>
352SpatialConvolutionBackwardKernel(const Input &input, const OutputBackward &output_backward,
353 typename internal::traits<Input>::Index kernelRows,
354 typename internal::traits<Input>::Index kernelCols,
355 const DenseIndex row_stride = 1, const DenseIndex col_stride = 1,
356 const DenseIndex row_in_stride = 1,
357 const DenseIndex col_in_stride = 1)
358{
359 typedef typename internal::traits<Input>::Index TensorIndex;
360 typedef typename internal::traits<OutputBackward>::Scalar OutScalar;
361 TensorRef<Tensor<typename internal::traits<Input>::Scalar, internal::traits<Input>::NumDimensions,
362 internal::traits<Input>::Layout, TensorIndex>>
363 in(input);
364 TensorRef<Tensor<OutScalar, internal::traits<OutputBackward>::NumDimensions,
365 internal::traits<OutputBackward>::Layout, TensorIndex>>
366 out(output_backward);
367
368 EIGEN_STATIC_ASSERT(internal::traits<Input>::Layout == internal::traits<OutputBackward>::Layout,
369 YOU_MADE_A_PROGRAMMING_MISTAKE);
370
371 // stride and in_stride cannot both be larger than 1
372 eigen_assert(!(row_stride > 1 && row_in_stride > 1));
373 eigen_assert(!(col_stride > 1 && col_in_stride > 1));
374
375 static const bool isColMajor = (internal::traits<Input>::Layout == ColMajor);
376
377 static const int NumDims = internal::traits<Input>::NumDimensions;
378 EIGEN_STATIC_ASSERT(internal::traits<Input>::NumDimensions ==
379 internal::traits<OutputBackward>::NumDimensions,
380 YOU_MADE_A_PROGRAMMING_MISTAKE);
381 EIGEN_STATIC_ASSERT(NumDims == 4, YOU_MADE_A_PROGRAMMING_MISTAKE);
382
383 const TensorIndex inputRows = isColMajor ? in.dimension(1) : in.dimension(NumDims - 2);
384 const TensorIndex inputCols = isColMajor ? in.dimension(2) : in.dimension(NumDims - 3);
385
386 const TensorIndex outputRows =
387 isColMajor ? output_backward.dimension(1) : output_backward.dimension(NumDims - 2);
388 const TensorIndex outputCols =
389 isColMajor ? output_backward.dimension(2) : output_backward.dimension(NumDims - 3);
390
391 // Number of filters to apply. This is the same as the output depth of the
392 // result
393 const TensorIndex kernelFilters =
394 isColMajor ? out.dimensions()[0] : out.dimensions()[NumDims - 1];
395
396 // Number of channels. This is the same as the input depth.
397 const TensorIndex kernelChannels = isColMajor ? in.dimensions()[0] : in.dimensions()[NumDims - 1];
398
399 // This is the effective kernel size, taking into account the
400 // (*_in_stride - 1) zero-values inserted between consecutive kernel
401 // elements in atrous convolution
402 const TensorIndex kernelRowsEff = kernelRows + (kernelRows - 1) * (row_in_stride - 1);
403 const TensorIndex kernelColsEff = kernelCols + (kernelCols - 1) * (col_in_stride - 1);
404
405 // Number of batches (and other dimensions) in the input tensor.
406 TensorIndex batch = 1;
407 for (int d = 3; d < NumDims; ++d)
408 {
409 batch *= isColMajor ? in.dimension(d) : in.dimension(NumDims - d - 1);
410 }
411
412 // Computing the forward padding
413 const TensorIndex padRows =
414 numext::maxi<Index>(0, (outputRows - 1) * row_stride + kernelRowsEff - inputRows);
415 const TensorIndex padCols =
416 numext::maxi<Index>(0, (outputCols - 1) * col_stride + kernelColsEff - inputCols);
417
418 TensorIndex padding_top = padRows / 2;
419 TensorIndex padding_left = padCols / 2;
420
421 // Compute paddings for output_backward before extracting patches.
422 const TensorIndex expanded_out_rows = (outputRows - 1) * row_stride + 1;
423 const TensorIndex expanded_out_cols = (outputCols - 1) * col_stride + 1;
424
425 const TensorIndex padded_out_rows = inputRows + kernelRowsEff - 1;
426 const TensorIndex padded_out_cols = inputCols + kernelColsEff - 1;
427
428 const TensorIndex top_pad_rows = kernelRowsEff - 1 - padding_top;
429 const TensorIndex left_pad_cols = kernelColsEff - 1 - padding_left;
430
431 const TensorIndex bottom_pad_rows = padded_out_rows - expanded_out_rows - top_pad_rows;
432 const TensorIndex right_pad_cols = padded_out_cols - expanded_out_cols - left_pad_cols;
433
434 // Reorder output_backward dimensions.
435 array<TensorIndex, 4> output_backward_shuffle;
436 if (isColMajor)
437 {
438 // From: [out_depth, out_rows, out_cols, batch]
439 // To: [batch, out_rows, out_cols, out_depth]
440 output_backward_shuffle = {3, 1, 2, 0};
441 }
442 else
443 {
444 // From: [batch, out_cols, out_rows, out_depth]
445 // To: [out_depth, out_cols, out_rows, batch]
446 output_backward_shuffle = {3, 1, 2, 0};
447 }
448
449 // Reorder input dimensions.
450 array<TensorIndex, 4> input_shuffle;
451 if (isColMajor)
452 {
453 // From: [in_depth, in_rows, in_cols, batch]
454 // To: [in_depth, batch, in_rows, in_cols]
455 input_shuffle = {0, 3, 1, 2};
456 }
457 else
458 {
459 // From: [batch, in_cols, in_rows, in_depth]
460 // To: [in_cols, in_rows, batch, in_depth]
461 input_shuffle = {1, 2, 0, 3};
462 }
463
464 // Input is playing the role of a "kernel" in this convolution.
465 DSizes<TensorIndex, 2> input_dims;
466 if (isColMajor)
467 {
468 input_dims[0] = kernelChannels;
469 input_dims[1] = batch * inputRows * inputCols;
470 }
471 else
472 {
473 input_dims[1] = kernelChannels;
474 input_dims[0] = inputCols * inputRows * batch;
475 }
476
477 // Molds the output of the patch extraction result into a 2D tensor:
478 // - the first dimension (dims[0]): the patch values to be multiplied with the
479 // kernels
480 // - the second dimension (dims[1]): everything else
481 DSizes<TensorIndex, 2> pre_contract_dims;
482 if (isColMajor)
483 {
484 pre_contract_dims[0] = batch * inputRows * inputCols;
485 pre_contract_dims[1] = kernelRows * kernelCols * kernelFilters;
486 }
487 else
488 {
489 pre_contract_dims[1] = inputCols * inputRows * batch;
490 pre_contract_dims[0] = kernelFilters * kernelCols * kernelRows;
491 }
492
493 // We will contract along the collapsed dimension that contains the
494 // batch, inputRows and inputCols.
495 array<IndexPair<TensorIndex>, 1> contract_dims;
496 contract_dims[0] = IndexPair<TensorIndex>(1, 0);
497
498 // Dimensions after contraction.
499 DSizes<TensorIndex, NumDims> post_contract_dims;
500 if (isColMajor)
501 {
502 post_contract_dims[0] = kernelChannels;
503 post_contract_dims[1] = kernelRows;
504 post_contract_dims[2] = kernelCols;
505 post_contract_dims[3] = kernelFilters;
506 }
507 else
508 {
509 post_contract_dims[0] = kernelFilters;
510 post_contract_dims[1] = kernelCols;
511 post_contract_dims[2] = kernelRows;
512 post_contract_dims[3] = kernelChannels;
513 }
514
515 // Reorder output of contraction to a valid filter shape.
516 array<TensorIndex, 4> kernel_shuffle;
517 if (isColMajor)
518 {
519 // From: [in_depth, kernel_rows, kernel_cols, out_depth]
520 // To: [out_depth, in_depth, kernel_rows, kernel_cols]
521 kernel_shuffle = {3, 0, 1, 2};
522 }
523 else
524 {
525 // From: [out_depth, kernel_cols, kernel_rows, in_depth]
526 // To: [kernel_cols, kernel_rows, in_depth, out_depth]
527 kernel_shuffle = {1, 2, 3, 0};
528 }
529
530 // Reverse kernel backprop dimensions.
531 array<TensorIndex, 4> kernel_reverse;
532 if (isColMajor)
533 {
534 kernel_reverse = {false, false, true, true};
535 }
536 else
537 {
538 kernel_reverse = {true, true, false, false};
539 }
540
541 // Create convolution input (aka source of patches) from output backward
542 // tensor by shuffling dimensions.
543 const auto output_backward_shuffled = output_backward.shuffle(output_backward_shuffle).eval();
544
545 // Create convolution kernel (aka filter) from input by shuffling and
546 // reshaping.
547 const auto input_shuffled = input.shuffle(input_shuffle).eval().reshape(input_dims);
548
549 return choose(Cond<internal::traits<OutputBackward>::Layout == ColMajor>(),
550 input_shuffled.contract(
551 output_backward_shuffled
552 .extract_image_patches(inputRows, inputCols, row_in_stride, col_in_stride, 1, 1,
553 row_stride, col_stride, top_pad_rows, bottom_pad_rows,
554 left_pad_cols, right_pad_cols, OutScalar(0))
555 .reshape(pre_contract_dims),
556 contract_dims),
557 output_backward_shuffled
558 .extract_image_patches(inputRows, inputCols, row_in_stride, col_in_stride, 1, 1,
559 row_stride, col_stride, top_pad_rows, bottom_pad_rows,
560 left_pad_cols, right_pad_cols, OutScalar(0))
561 .reshape(pre_contract_dims)
562 .contract(input_shuffled, contract_dims))
563 .reshape(post_contract_dims)
564 .shuffle(kernel_shuffle)
565 .eval()
566 .reverse(kernel_reverse);
567}
568
569} // end namespace Eigen
570
571#endif // __NNFW_CKER_EGIEN_EIGEN_BACKWARD_SPATIAL_CONVOLUTIONS_H__
IndexList< type2index< 0 >, type2index< 0 >, type2index< 1 >, type2index< 1 > > ReverseColMajor
Computes the backprop for the input of a 2D convolution.
IndexList< type2index< 1 >, type2index< 1 >, type2index< 0 >, type2index< 0 > > ReverseRowMajor
::nncc::core::ADT::tensor::Index TensorIndex
Definition TensorIndex.h:25