Line data Source code
1 : // SPDX-License-Identifier: Apache-2.0
2 : /**
3 : * Copyright (C) 2024 UGyeong Song <thddnrud@snu.ac.kr>
4 : *
5 : * @file conv2d_transpose_layer.h
6 : * @date 13 October 2024
7 : * @see https://github.com/nnstreamer/nntrainer
8 : * @author UGyeong Song <thddnrud@snu.ac.kr>
9 : * @bug No known bugs except for NYI items
10 : * @brief This is Transposed Convolution Layer Class for Neural Network
11 : *
12 : */
13 : #include <algorithm>
14 : #include <cstring>
15 : #include <limits>
16 : #include <string>
17 :
18 : #include <conv2d_transpose_layer.h>
19 : #include <cpu_backend.h>
20 : #include <layer_context.h>
21 : #include <lazy_tensor.h>
22 : #include <nntr_threads.h>
23 : #include <nntrainer_error.h>
24 : #include <nntrainer_log.h>
25 : #include <node_exporter.h>
26 : #include <profiler.h>
27 : #include <tensor_dim.h>
28 : #include <thread>
29 : #include <util_func.h>
30 :
31 : namespace nntrainer {
32 :
33 : static constexpr size_t SINGLE_INOUT_IDX = 0;
34 :
35 : namespace {
36 :
37 0 : static TensorDim calcCol2ImOutputDim(const TensorDim &out,
38 : const TensorDim &kdim) {
39 :
40 0 : return TensorDim({kdim.getFeatureLen(), out.width() * out.height()});
41 : } // [in_channel*kernel_h*kernel_w, out_w*out_h]
42 :
43 : /**
44 : * @brief reconstruct image data from 2d column matrix
45 : *
46 : * @param[in] in input data
47 : * @param[in] kdim kernel dimesion for define number of row
48 : * @param[in] padding padding information
49 : * @param[in] mstride stride value : x, y direction
50 : * @param[in] dilation kernel dilation factor : x, y each
51 : * @param[out] image image tensor to put
52 : */
53 0 : static void col2im_transpose(
54 : const Tensor &col_matrix, const TensorDim &kdim,
55 : const std::array<unsigned, 4> &padding,
56 : const std::array<props::Stride, CONV2D_TRANSPOSE_DIM> &mstride,
57 : const std::array<props::Dilation, CONV2D_TRANSPOSE_DIM> &dilation,
58 : Tensor &image) {
59 0 : auto [pt, pb, pl, pr] = padding;
60 :
61 0 : unsigned int channel = image.channel();
62 0 : int in_height = image.height();
63 0 : int in_width = image.width();
64 :
65 0 : unsigned int k_height = kdim.height();
66 0 : unsigned int k_width = kdim.width();
67 :
68 : /// effective kernel height considering dilation
69 0 : unsigned int eff_k_height = (k_height - 1) * dilation[0] + 1;
70 : /// effective kernel width considering dilation
71 0 : unsigned int eff_k_width = (k_width - 1) * dilation[1] + 1;
72 :
73 0 : unsigned int height = (in_height - 1) * mstride[0] + eff_k_height;
74 0 : unsigned int width = (in_width - 1) * mstride[1] + eff_k_height;
75 :
76 0 : unsigned int out_height = height - pt - pb; // col_matrix.height
77 0 : unsigned int out_width = width - pl - pr; // col_matrix.width
78 :
79 0 : image.setZero();
80 :
81 : int h_stride_end = height - eff_k_height - pt;
82 : int w_stride_end = width - eff_k_width - pl;
83 :
84 : /// get a patch, size of kernel
85 : /// hs is height_strided, ws is width_strided
86 0 : unsigned int owidth = col_matrix.width();
87 : unsigned int base_im_w = 0;
88 :
89 : unsigned int H = k_height;
90 : unsigned int W = k_width;
91 0 : unsigned int C = image.channel();
92 :
93 : int out_i = -1;
94 0 : for (unsigned int oh = 0; oh < out_height; ++oh) {
95 0 : for (unsigned int ow = 0; ow < out_width; ++ow) {
96 0 : out_i++;
97 : int out_j = -1;
98 : // half_cpu o = bias->buf[oc];
99 0 : for (unsigned int c = 0; c < C; ++c) {
100 0 : for (unsigned int r = 0; r < H; ++r) {
101 0 : for (unsigned int s = 0; s < W; ++s) {
102 0 : out_j++;
103 0 : if ((oh - (r * dilation[0] - pt)) % mstride[0] != 0)
104 0 : continue;
105 0 : if ((ow - (s * dilation[1] - pl)) % mstride[1] != 0)
106 0 : continue;
107 0 : unsigned int h = (oh - (r * dilation[0] - pt)) / mstride[0];
108 0 : unsigned int w = (ow - (s * dilation[1] - pl)) / mstride[1];
109 0 : if (h >= H || w >= W)
110 0 : continue;
111 0 : float *val = image.getAddress<float>(0, c, h, w);
112 0 : *val += col_matrix.getValue<float>(0, 0, out_i, out_j);
113 : // out_data[(out_i)*owidth + out_j] += in.getValue<float>(0,c,h,w)
114 : }
115 : }
116 : }
117 : }
118 : }
119 0 : }
120 :
121 : /**
122 : * @brief reform the data to 2d matrix
123 : * a region is sampled considering @a padding, @a mstride of unit, @a kdim
124 : * Each region is mapped to one column
125 : *
126 : * @param [in] in input data
127 : * @param [in] kdim kernel dimension for defined number of row
128 : * @param [in] padding padding information
129 : * @param [in] mstride stride value : x, y direction
130 : * @param [in] dilation kernel dilation factor : x, y each
131 : * @param [out] out out tensor
132 : */
133 0 : static void im2col_transpose(
134 : const Tensor &in, const TensorDim &kdim,
135 : const std::array<unsigned int, 4> &padding,
136 : const std::array<props::Stride, CONV2D_TRANSPOSE_DIM> &mstride,
137 : const std::array<props::Dilation, CONV2D_TRANSPOSE_DIM> &dilation,
138 : Tensor &out) {
139 0 : auto [pt, pb, pl, pr] = padding;
140 :
141 0 : unsigned int channel = in.channel();
142 0 : int in_height = in.height();
143 0 : int in_width = in.width();
144 :
145 0 : unsigned int k_height = kdim.height();
146 0 : unsigned int k_width = kdim.width();
147 :
148 : /// effective kernel height considering dilation
149 0 : unsigned int eff_k_height = (k_height - 1) * dilation[0] + 1;
150 : /// effective kernel width considering dilation
151 0 : unsigned int eff_k_width = (k_width - 1) * dilation[1] + 1;
152 :
153 0 : unsigned int height = (in_height - 1) * mstride[0] + eff_k_height;
154 0 : unsigned int width = (in_width - 1) * mstride[1] + eff_k_height;
155 :
156 0 : unsigned int out_height = height - pt - pb;
157 0 : unsigned int out_width = width - pl - pr;
158 :
159 0 : out.reshape(
160 0 : TensorDim({out_height * out_width, in.channel() * k_height * k_width}));
161 :
162 : float *out_data = out.getData();
163 :
164 : int h_stride_end = height - eff_k_height - pt;
165 : int w_stride_end = width - eff_k_width - pl;
166 :
167 : /// get a patch, size of kernel
168 0 : unsigned int owidth = out.width();
169 : unsigned int base_im_w = 0;
170 :
171 : unsigned int H = k_height;
172 : unsigned int W = k_width;
173 0 : unsigned int C = in.channel();
174 :
175 : int out_i = -1;
176 0 : for (unsigned int oh = 0; oh < out_height; ++oh) {
177 0 : for (unsigned int ow = 0; ow < out_width; ++ow) {
178 0 : out_i++;
179 : int out_j = -1;
180 : // half_cpu o = bias->buf[oc];
181 0 : for (unsigned int c = 0; c < C; ++c) {
182 0 : for (unsigned int r = 0; r < H; ++r) {
183 0 : for (unsigned int s = 0; s < W; ++s) {
184 0 : out_j++;
185 0 : if ((oh - (r * dilation[0] - pt)) % mstride[0] != 0)
186 0 : continue;
187 0 : if ((ow - (s * dilation[1] - pl)) % mstride[1] != 0)
188 0 : continue;
189 0 : unsigned int h = (oh - (r * dilation[0] - pt)) / mstride[0];
190 0 : unsigned int w = (ow - (s * dilation[1] - pl)) / mstride[1];
191 0 : if (h >= H || w >= W)
192 0 : continue;
193 0 : out_data[(out_i)*owidth + out_j] += in.getValue<float>(0, c, h, w);
194 : }
195 : }
196 : }
197 : }
198 : }
199 0 : }
200 :
201 : } // namespace
202 :
203 : enum ConvParams { weight, bias };
204 :
205 0 : Conv2DTransposeLayer::Conv2DTransposeLayer(
206 0 : const std::array<unsigned int, CONV2D_TRANSPOSE_DIM * 2> &padding_) :
207 : LayerImpl(),
208 0 : padding(padding_),
209 : conv_props(
210 0 : props::FilterSize(), std::array<props::KernelSize, CONV2D_TRANSPOSE_DIM>(),
211 0 : std::array<props::Stride, CONV2D_TRANSPOSE_DIM>(), props::Padding2D(),
212 0 : std::array<props::Dilation, CONV2D_TRANSPOSE_DIM>()) {
213 : wt_idx.fill(std::numeric_limits<unsigned>::max());
214 0 : }
215 :
216 0 : void Conv2DTransposeLayer::finalize(InitLayerContext &context) {
217 0 : NNTR_THROW_IF(context.getNumInputs() != 1, std::invalid_argument)
218 : << "Convolution layer takes only one input";
219 :
220 : const TensorDim &in_dim = context.getInputDimensions()[0];
221 :
222 : auto &weight_regularizer =
223 : std::get<props::WeightRegularizer>(*layer_impl_props);
224 : auto &weight_regularizer_constant =
225 : std::get<props::WeightRegularizerConstant>(*layer_impl_props);
226 : auto &weight_initializer =
227 : std::get<props::WeightInitializer>(*layer_impl_props);
228 : auto &weight_decay = std::get<props::WeightDecay>(*layer_impl_props);
229 : auto &bias_decay = std::get<props::BiasDecay>(*layer_impl_props);
230 : auto &bias_initializer = std::get<props::BiasInitializer>(*layer_impl_props);
231 : auto &disable_bias = std::get<props::DisableBias>(*layer_impl_props);
232 :
233 0 : unsigned int filter_size = std::get<props::FilterSize>(conv_props);
234 : auto &kernel_size =
235 : std::get<std::array<props::KernelSize, CONV2D_TRANSPOSE_DIM>>(conv_props);
236 : auto &stride =
237 : std::get<std::array<props::Stride, CONV2D_TRANSPOSE_DIM>>(conv_props);
238 : auto &dilation =
239 : std::get<std::array<props::Dilation, CONV2D_TRANSPOSE_DIM>>(conv_props);
240 :
241 : TensorDim kernel_dim =
242 0 : TensorDim(filter_size, in_dim.channel(), kernel_size[0], kernel_size[1]);
243 0 : TensorDim bias_dim = TensorDim(1, filter_size, 1, 1);
244 :
245 : padding = std::get<props::Padding2D>(conv_props)
246 0 : .compute(in_dim, kernel_dim, {stride[0], stride[1]},
247 : {dilation[0], dilation[1]});
248 :
249 0 : wt_idx[ConvParams::weight] = context.requestWeight(
250 : kernel_dim, weight_initializer, weight_regularizer,
251 : weight_regularizer_constant, weight_decay, "filter", true, 0);
252 :
253 0 : if (disable_bias.empty() || disable_bias.get() == false) {
254 0 : wt_idx[ConvParams::bias] =
255 0 : context.requestWeight(bias_dim, bias_initializer, WeightRegularizer::NONE,
256 : 1.0f, bias_decay, "bias", true, 0);
257 : }
258 :
259 0 : auto [pt, pb, pl, pr] = padding;
260 :
261 0 : unsigned int channel = in_dim.channel();
262 0 : int in_height = in_dim.height();
263 0 : int in_width = in_dim.width();
264 :
265 0 : unsigned int k_height = kernel_size[0];
266 : unsigned int k_width = kernel_size[1];
267 :
268 : /// effective kernel height considering dilation
269 0 : unsigned int eff_k_height = (k_height - 1) * dilation[0] + 1;
270 : /// effective kernel width considering dilation
271 : unsigned int eff_k_width = (k_width - 1) * dilation[1] + 1;
272 :
273 0 : unsigned int height = (in_height - 1) * stride[0] + eff_k_height;
274 0 : unsigned int width = (in_width - 1) * stride[1] + eff_k_height;
275 :
276 0 : unsigned int out_height = height - pt - pb;
277 0 : unsigned int out_width = width - pl - pr;
278 :
279 0 : TensorDim out_dim;
280 0 : out_dim.batch(in_dim.batch());
281 0 : out_dim.channel(filter_size);
282 0 : out_dim.height(out_height);
283 0 : out_dim.width(out_width);
284 0 : context.setOutputDimensions({out_dim});
285 :
286 0 : NNTR_THROW_IF(height < kernel_size[0] || width < kernel_size[1],
287 : std::invalid_argument)
288 : << "Failed to initialize: in size + padding is smaller than effective "
289 : "kernel";
290 :
291 : unsigned int IM = std::numeric_limits<int>::max();
292 :
293 0 : NNTR_THROW_IF(height - padding[0] - kernel_size[0] > IM ||
294 : width - padding[2] - kernel_size[1] > IM,
295 : std::invalid_argument)
296 : << "Failed to initialize: Calculated patch end is over int max";
297 0 : }
298 :
299 0 : void Conv2DTransposeLayer::forwarding(RunLayerContext &context, bool training) {
300 : int status = ML_ERROR_NONE;
301 :
302 0 : unsigned int filter_size = std::get<props::FilterSize>(conv_props);
303 : auto &stride =
304 : std::get<std::array<props::Stride, CONV2D_TRANSPOSE_DIM>>(conv_props);
305 : auto &dilation =
306 : std::get<std::array<props::Dilation, CONV2D_TRANSPOSE_DIM>>(conv_props);
307 :
308 0 : Tensor &input_ = context.getInput(SINGLE_INOUT_IDX);
309 0 : Tensor &hidden_ = context.getOutput(SINGLE_INOUT_IDX);
310 :
311 0 : Tensor &filter_kernel = context.getWeight(wt_idx[ConvParams::weight]);
312 :
313 : /** Calculate Convolution 2D Transpose
314 : *
315 : * This is the 2D Matrix Shape [ height ] x [ width ]
316 : * . Height : filter_size
317 : * . Width : Input Channel * Kernel_size[0] * Kernel_size[1]
318 : *
319 : * imKernel
320 : * +------|------|------+
321 : * |------|------|------|
322 : * [filter_size (height)] |------|------|------|
323 : * |------|------|------|
324 : * +------|------|------+
325 : * [Input Channel * Kernel_size[0]
326 : * * Kernel_size[1] (width)]
327 : *
328 : *
329 : * After im2Col with channel_mode true (in : input)
330 : *
331 : * This is the 2D Matrix Shape [ height ] x [ width ]
332 : * . Height : Input Channel * Kernel_size[0] * Kernel_size[1]
333 : * . Width : output_dim.height * output_dim.width
334 : *
335 : * +-|-|-|-| |-|-|-|-+
336 : * [Input Channel | | | | | | | | | |
337 : * * Kernel_size[0] |_|_|_|_| |_|_|_|_|
338 : * * Kenel_size[1] | | | | | .... | | | | |
339 : * (height)] |_|_|_|_| |_|_|_|_|
340 : * | | | | | | | | | |
341 : * +_|_|_|_| |_|_|_|_+
342 : * [ output_dim.height
343 : * * output_dim.width (width) ]
344 : *
345 : * Output Dimention
346 : * -> [Channel ( = filter_size = output_dim.channel )]
347 : * x [output_dim.height x output_dim.width]
348 : */
349 0 : const TensorDim &in_dim = input_.getDim();
350 0 : const TensorDim &out_dim = hidden_.getDim();
351 0 : const TensorDim &filter_dim = filter_kernel.getDim();
352 0 : TensorDim filter_dim_squeezed{filter_kernel.batch(),
353 0 : filter_kernel.getDim().getFeatureLen()};
354 :
355 0 : filter_kernel.reshape(filter_dim_squeezed);
356 :
357 : /**
358 : * Below sets the pad area values to zero
359 : * it is faster to do this way than seting selective area to zero
360 : */
361 0 : auto forwarding_job = [&](unsigned int s, unsigned int e, unsigned int pid,
362 : void *user_data) {
363 : Tensor result = Tensor(
364 0 : calcCol2ImOutputDim(out_dim, filter_dim)); // result is temporary data
365 0 : result.setZero();
366 0 : for (unsigned int b = s; b < e; ++b) {
367 0 : Tensor out = hidden_.getBatchSlice(b, 1);
368 0 : out.reshape({filter_size, out_dim.width() * out_dim.height()});
369 0 : Tensor in_sub = input_.getBatchSlice(b, 1);
370 :
371 0 : im2col_transpose(in_sub, filter_dim, padding, stride, dilation, result);
372 0 : filter_kernel.dot(result, out, false, true);
373 0 : }
374 0 : result.deallocate();
375 0 : };
376 :
377 0 : auto workers = ParallelBatch(forwarding_job, in_dim.batch(), nullptr);
378 :
379 0 : if (workers.getNumWorkers() > 1) {
380 0 : workers.run();
381 : } else {
382 0 : forwarding_job(0, in_dim.batch(), 0, nullptr);
383 : }
384 :
385 0 : filter_kernel.reshape(filter_dim);
386 : if (auto &disable_bias = std::get<props::DisableBias>(*layer_impl_props);
387 0 : disable_bias.empty() || disable_bias.get() == false) {
388 0 : Tensor &bias_kernel = context.getWeight(wt_idx[ConvParams::bias]);
389 0 : status = hidden_.add_i(bias_kernel);
390 0 : if (status != ML_ERROR_NONE) {
391 0 : throw std::invalid_argument("[Conv2DTranspose] adding bias failed");
392 : }
393 : }
394 0 : }
395 :
396 0 : void Conv2DTransposeLayer::calcDerivative(RunLayerContext &context) {
397 0 : unsigned int filter_size = std::get<props::FilterSize>(conv_props);
398 : auto &stride =
399 : std::get<std::array<props::Stride, CONV2D_TRANSPOSE_DIM>>(conv_props);
400 : auto &dilation =
401 : std::get<std::array<props::Dilation, CONV2D_TRANSPOSE_DIM>>(conv_props);
402 :
403 0 : const Tensor &derivative = context.getIncomingDerivative(SINGLE_INOUT_IDX);
404 0 : Tensor &input_derivative = context.getOutgoingDerivative(SINGLE_INOUT_IDX);
405 0 : Tensor &filter_kernel = context.getWeight(wt_idx[ConvParams::weight]);
406 :
407 0 : TensorDim filter_dim = filter_kernel.getDim();
408 0 : TensorDim filter_dim_squeezed{filter_kernel.batch(),
409 0 : filter_kernel.getDim().getFeatureLen()};
410 :
411 0 : filter_kernel.reshape(filter_dim_squeezed);
412 :
413 : /// for each batch
414 : /// filter_kernel^T X derivaitive -> column matrix
415 : /// col2im(column matrix) to reconstruct the original image
416 :
417 0 : auto compute_derivative = [&](unsigned int s, unsigned int e,
418 : unsigned int pid, void *user_data) {
419 : Tensor result =
420 0 : Tensor(calcCol2ImOutputDim(derivative.getDim(), filter_dim));
421 :
422 0 : for (unsigned int b = s; b < e; ++b) {
423 0 : Tensor deriv_sub = derivative.getBatchSlice(b, 1);
424 0 : Tensor in_deriv_sub = input_derivative.getBatchSlice(b, 1);
425 0 : deriv_sub.reshape(
426 0 : {filter_size, derivative.width() * derivative.height()});
427 0 : filter_kernel.dot(deriv_sub, result, true, false);
428 0 : col2im_transpose(result, filter_dim, padding, stride, dilation,
429 : in_deriv_sub);
430 0 : }
431 0 : result.deallocate();
432 0 : };
433 :
434 0 : auto workers = ParallelBatch(compute_derivative, derivative.batch(), nullptr);
435 :
436 0 : if (workers.getNumWorkers() > 1) {
437 0 : workers.run();
438 : } else {
439 0 : compute_derivative(0, derivative.batch(), 0, nullptr);
440 : }
441 :
442 0 : filter_kernel.reshape(filter_dim);
443 0 : }
444 :
445 0 : void Conv2DTransposeLayer::calcGradient(RunLayerContext &context) {
446 0 : unsigned int filter_size = std::get<props::FilterSize>(conv_props);
447 : auto &stride =
448 : std::get<std::array<props::Stride, CONV2D_TRANSPOSE_DIM>>(conv_props);
449 : auto &dilation =
450 : std::get<std::array<props::Dilation, CONV2D_TRANSPOSE_DIM>>(conv_props);
451 :
452 0 : const Tensor &derivative = context.getIncomingDerivative(SINGLE_INOUT_IDX);
453 0 : Tensor &input_ = context.getInput(SINGLE_INOUT_IDX);
454 :
455 0 : Tensor &delK = context.getWeightGrad(wt_idx[ConvParams::weight]);
456 0 : delK.setZero();
457 :
458 0 : TensorDim filter_dim = delK.getDim();
459 0 : TensorDim filter_dim_squeezed{filter_dim.batch(), filter_dim.getFeatureLen()};
460 :
461 0 : delK.reshape(filter_dim_squeezed);
462 :
463 : /**
464 : * no need to set zero for im2col_result, as its lifespan is ITERATION,
465 : * so its zero padded values will still be zero
466 : */
467 :
468 0 : TensorDim out_dim_squeezed{filter_size,
469 0 : derivative.width() * derivative.height()};
470 0 : auto workers = ParallelBatch(input_.batch());
471 : /// input -(im2col)-> column_matrix -> filter x (column_matrix) = output
472 : /// so delK = dy x column_matrix ^ T;
473 0 : if (workers.getNumWorkers() > 1) {
474 :
475 0 : TensorDim delK_ext = filter_dim_squeezed;
476 0 : delK_ext.batch(input_.batch());
477 :
478 0 : Tensor delK_par = Tensor(delK_ext);
479 0 : delK_par.setZero();
480 :
481 0 : auto calc_grad_job = [&](unsigned int s, unsigned int e, unsigned int pid,
482 : void *user_data) {
483 : Tensor result =
484 0 : Tensor(calcCol2ImOutputDim(derivative.getDim(), filter_dim));
485 0 : result.setZero();
486 0 : for (unsigned int b = s; b < e; ++b) {
487 0 : Tensor deriv_sub = derivative.getBatchSlice(b, 1);
488 0 : Tensor delK_sub = delK_par.getBatchSlice(b, 1);
489 0 : deriv_sub.reshape(out_dim_squeezed);
490 :
491 0 : Tensor in_sub = input_.getBatchSlice(b, 1);
492 :
493 : /**
494 : * @todo this result can be cached from the forward iteration at the
495 : * expense of memory. In this case, memory of im2col_result must be
496 : * saved for the whole batch. try this while benchmarking.
497 : */
498 0 : im2col_transpose(in_sub, filter_dim, padding, stride, dilation, result);
499 0 : deriv_sub.dot(result, delK_sub, false, false);
500 0 : }
501 0 : result.deallocate();
502 0 : };
503 :
504 0 : workers.setCallback(calc_grad_job, nullptr);
505 :
506 0 : workers.run();
507 :
508 0 : for (unsigned int b = 0; b < input_.batch(); ++b) {
509 0 : Tensor delK_sub = delK_par.getBatchSlice(b, 1);
510 0 : delK.add_i(delK_sub);
511 0 : }
512 :
513 0 : } else {
514 : Tensor result =
515 0 : Tensor(calcCol2ImOutputDim(derivative.getDim(), filter_dim));
516 0 : result.setZero();
517 :
518 0 : for (unsigned int b = 0; b < input_.batch(); ++b) {
519 0 : Tensor deriv_sub = derivative.getBatchSlice(b, 1);
520 0 : deriv_sub.reshape(out_dim_squeezed);
521 :
522 0 : Tensor in_sub = input_.getBatchSlice(b, 1);
523 :
524 : /**
525 : * @todo this result can be cached from the forward iteration at the
526 : * expense of memory. In this case, memory of im2col_result must be saved
527 : * for the whole batch. try this while benchmarking.
528 : */
529 0 : im2col_transpose(in_sub, filter_dim, padding, stride, dilation, result);
530 0 : deriv_sub.dot(result, delK, false, false, b == 0 ? 0.0f : 1.0f);
531 0 : }
532 0 : result.deallocate();
533 0 : }
534 0 : delK.reshape(filter_dim);
535 : if (auto &disable_bias = std::get<props::DisableBias>(*layer_impl_props);
536 0 : disable_bias.empty() || disable_bias.get() == false) {
537 0 : Tensor &delBias = context.getWeightGrad(wt_idx[ConvParams::bias]);
538 0 : derivative.sum({0, 2, 3}, delBias);
539 : }
540 0 : }
541 :
542 0 : void Conv2DTransposeLayer::exportTo(
543 : Exporter &exporter, const ml::train::ExportMethods &method) const {
544 0 : LayerImpl::exportTo(exporter, method);
545 0 : exporter.saveResult(conv_props, method, this);
546 0 : }
547 :
548 0 : void Conv2DTransposeLayer::setProperty(const std::vector<std::string> &values) {
549 0 : auto remain_props = loadProperties(values, conv_props);
550 0 : LayerImpl::setProperty(remain_props);
551 0 : }
552 :
553 : } /* namespace nntrainer */
|