Skip to content

Commit e205219

Browse files
authored
Add support for Conv1DTranspose and Conv2DTranspose layers, closes #435 (#436)
1 parent 2e46813 commit e205219

15 files changed

Lines changed: 286 additions & 43 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ list(APPEND CMAKE_MODULE_PATH "${FDEEP_TOP_DIR}/cmake")
66

77
include(cmake/hunter.cmake) # default off
88

9-
project(frugally-deep VERSION 0.16.3)
9+
project(frugally-deep VERSION 0.17.0)
1010

1111
message(STATUS "===( ${PROJECT_NAME} ${PROJECT_VERSION} )===")
1212

FAQ.md

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -458,29 +458,6 @@ int main()
458458
}
459459
```
460460

461-
Why are `Conv2DTranspose` layers not supported?
462-
-----------------------------------------------
463-
464-
The combination of `UpSampling2D` and `Conv2D` layers seems to be the better alternative:
465-
https://distill.pub/2016/deconv-checkerboard/
466-
467-
Basically, instead of this:
468-
469-
```python
470-
x = Conv2DTranspose(8, (3, 3), strides=(2, 2), padding='same')(x)
471-
```
472-
473-
one uses that:
474-
475-
```python
476-
x = Conv2D(8, (3, 3), padding='same')(UpSampling2D(2)(x))
477-
```
478-
479-
In case you are not in the position to change your model's
480-
architecture to make that change,
481-
feel free to implement `Conv2DTranspose` in frugally-deep and
482-
submit a [pull request](https://github.com/Dobiasd/frugally-deep/pulls). :)
483-
484461
How can I use `BatchNormalization` and `Dropout` layers with `training=True`?
485462
-----------------------------------------------------------------------------
486463

INSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Just add a *conanfile.txt* with frugally-deep as a requirement and chose the gen
6363

6464
```
6565
[requires]
66-
frugally-deep/v0.16.3@dobiasd/stable
66+
frugally-deep/v0.17.0@dobiasd/stable
6767
6868
[generators]
6969
cmake

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Would you like to build/train a model using Keras/Python? And would you like to
4343
* `AveragePooling1D/2D/3D`, `GlobalAveragePooling1D/2D/3D`
4444
* `TimeDistributed`
4545
* `Conv1D/2D`, `SeparableConv2D`, `DepthwiseConv2D`
46+
* `Conv1DTranspose`, `Conv2DTranspose`
4647
* `Cropping1D/2D/3D`, `ZeroPadding1D/2D/3D`, `CenterCrop`
4748
* `BatchNormalization`, `Dense`, `Flatten`, `Normalization`
4849
* `Dropout`, `AlphaDropout`, `GaussianDropout`, `GaussianNoise`
@@ -59,7 +60,6 @@ Would you like to build/train a model using Keras/Python? And would you like to
5960
* `Embedding`, `CategoryEncoding`
6061
* `Attention`, `AdditiveAttention`, `MultiHeadAttention`
6162

62-
6363
### Also supported
6464

6565
* multiple inputs and outputs
@@ -72,7 +72,6 @@ Would you like to build/train a model using Keras/Python? And would you like to
7272

7373
### Currently not supported are the following:
7474

75-
`Conv2DTranspose` ([why](FAQ.md#why-are-conv2dtranspose-layers-not-supported)),
7675
`Lambda` ([why](FAQ.md#why-are-lambda-layers-not-supported)),
7776
`Conv3D`, `ConvLSTM1D`, `ConvLSTM2D`, `Discretization`,
7877
`GRUCell`, `Hashing`,

include/fdeep/convolution.hpp

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ namespace internal {
225225
const shape2& strides,
226226
padding pad_type,
227227
std::size_t input_shape_height,
228-
std::size_t input_shape_width)
228+
std::size_t input_shape_width,
229+
bool transposed)
229230
{
230231
// https://www.tensorflow.org/api_guides/python/nn#Convolution
231232
const int filter_height = static_cast<int>(filter_shape.height_);
@@ -242,15 +243,27 @@ namespace internal {
242243
out_height = fplus::ceil(static_cast<float>(in_height) / static_cast<float>(strides_y) - 0.001);
243244
out_width = fplus::ceil(static_cast<float>(in_width) / static_cast<float>(strides_x) - 0.001);
244245
} else {
245-
out_height = fplus::ceil(static_cast<float>(in_height - filter_height + 1) / static_cast<float>(strides_y) - 0.001);
246-
out_width = fplus::ceil(static_cast<float>(in_width - filter_width + 1) / static_cast<float>(strides_x) - 0.001);
246+
if (transposed) {
247+
out_height = fplus::ceil(static_cast<float>(in_height + filter_height - 1) / static_cast<float>(strides_y) - 0.001);
248+
out_width = fplus::ceil(static_cast<float>(in_width + filter_width - 1) / static_cast<float>(strides_x) - 0.001);
249+
} else {
250+
out_height = fplus::ceil(static_cast<float>(in_height - filter_height + 1) / static_cast<float>(strides_y) - 0.001);
251+
out_width = fplus::ceil(static_cast<float>(in_width - filter_width + 1) / static_cast<float>(strides_x) - 0.001);
252+
}
247253
}
248254

249255
int pad_top = 0;
250256
int pad_bottom = 0;
251257
int pad_left = 0;
252258
int pad_right = 0;
253259

260+
if (transposed) {
261+
pad_top = filter_height - 1;
262+
pad_bottom = filter_height - 1;
263+
pad_left = filter_width - 1;
264+
pad_right = filter_width - 1;
265+
}
266+
254267
if (pad_type == padding::same) {
255268
int pad_along_height = 0;
256269
int pad_along_width = 0;
@@ -296,7 +309,7 @@ namespace internal {
296309

297310
const auto conv_cfg = preprocess_convolution(
298311
filter_mat.filter_shape_.without_depth(),
299-
strides, pad_type, input.shape().height_, input.shape().width_);
312+
strides, pad_type, input.shape().height_, input.shape().width_, false);
300313

301314
// The padding step usually (on a VGG19 net) only takes about 1% of the overall runtime.
302315
// So the increased code complexity of doing it inside the convolution step
@@ -312,5 +325,32 @@ namespace internal {
312325
in_padded);
313326
}
314327

328+
inline tensor convolve_transposed(
329+
const shape2& strides,
330+
const padding& pad_type,
331+
const convolution_filter_matrices& filter_mat,
332+
const tensor& input)
333+
{
334+
assertion(filter_mat.filter_shape_.depth_ == input.shape().depth_,
335+
"invalid filter depth");
336+
337+
const auto input_dilated = dilate_tensor(strides, input, pad_type == padding::same);
338+
339+
const auto conv_cfg = preprocess_convolution(
340+
filter_mat.filter_shape_.without_depth(),
341+
shape2(1, 1), pad_type, input_dilated.shape().height_, input_dilated.shape().width_,
342+
true);
343+
344+
const auto in_padded = pad_tensor(0, 0, 0,
345+
conv_cfg.pad_top_, conv_cfg.pad_bottom_, conv_cfg.pad_left_, conv_cfg.pad_right_,
346+
input_dilated);
347+
348+
return convolve_accumulative(
349+
conv_cfg.out_height_, conv_cfg.out_width_,
350+
1, 1,
351+
filter_mat,
352+
in_padded);
353+
}
354+
315355
}
316356
}

include/fdeep/depthwise_convolution.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ namespace internal {
8585

8686
const auto conv_cfg = preprocess_convolution(
8787
filter_mat.filter_shape_.without_depth(),
88-
strides, pad_type, input.shape().height_, input.shape().width_);
88+
strides, pad_type, input.shape().height_, input.shape().width_, false);
8989

9090
const auto in_padded = pad_tensor(0, 0, 0,
9191
conv_cfg.pad_top_, conv_cfg.pad_bottom_, conv_cfg.pad_left_, conv_cfg.pad_right_,

include/fdeep/filter.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,15 @@ namespace internal {
6262

6363
inline filter dilate_filter(const shape2& dilation_rate, const filter& undilated)
6464
{
65-
return filter(dilate_tensor(dilation_rate, undilated.get_tensor()),
65+
return filter(dilate_tensor(dilation_rate, undilated.get_tensor(), false),
6666
undilated.get_bias());
6767
}
6868

6969
inline filter_vec generate_filters(
7070
const shape2& dilation_rate,
7171
const tensor_shape& filter_shape, std::size_t k,
72-
const float_vec& weights, const float_vec& bias)
72+
const float_vec& weights, const float_vec& bias,
73+
bool transpose)
7374
{
7475
filter_vec filters(k, filter(tensor(filter_shape, 0), 0));
7576

@@ -90,6 +91,10 @@ namespace internal {
9091
for (auto& filt : filters) {
9192
filt.set_params(*it_filter_val, *it_filter_bias);
9293
filt = dilate_filter(dilation_rate, filt);
94+
if (transpose) {
95+
filt = filter(reverse_height_dimension(filt.get_tensor()), filt.get_bias());
96+
filt = filter(reverse_width_dimension(filt.get_tensor()), filt.get_bias());
97+
}
9398
++it_filter_val;
9499
++it_filter_bias;
95100
}

include/fdeep/import_model.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "fdeep/layers/centercrop_layer.hpp"
3838
#include "fdeep/layers/concatenate_layer.hpp"
3939
#include "fdeep/layers/conv_2d_layer.hpp"
40+
#include "fdeep/layers/conv_2d_transpose_layer.hpp"
4041
#include "fdeep/layers/cropping_3d_layer.hpp"
4142
#include "fdeep/layers/dense_layer.hpp"
4243
#include "fdeep/layers/depthwise_conv_2d_layer.hpp"
@@ -403,6 +404,36 @@ namespace internal {
403404
dilation_rate, weights, bias);
404405
}
405406

407+
inline layer_ptr create_conv_2d_transpose_layer(const get_param_f& get_param,
408+
const nlohmann::json& data,
409+
const std::string& name)
410+
{
411+
const std::string padding_str = data["config"]["padding"];
412+
const auto pad_type = create_padding(padding_str);
413+
414+
const shape2 strides = create_shape2(data["config"]["strides"]);
415+
const shape2 dilation_rate = create_shape2(data["config"]["dilation_rate"]);
416+
417+
const auto filter_count = create_size_t(data["config"]["filters"]);
418+
float_vec bias(filter_count, 0);
419+
const bool use_bias = data["config"]["use_bias"];
420+
if (use_bias)
421+
bias = decode_floats(get_param(name, "bias"));
422+
assertion(bias.size() == filter_count, "size of bias does not match");
423+
424+
const float_vec weights = decode_floats(get_param(name, "weights"));
425+
const shape2 kernel_size = create_shape2(data["config"]["kernel_size"]);
426+
assertion(weights.size() % kernel_size.area() == 0,
427+
"invalid number of weights");
428+
const std::size_t filter_depths = weights.size() / (kernel_size.area() * filter_count);
429+
const tensor_shape filter_shape(
430+
kernel_size.height_, kernel_size.width_, filter_depths);
431+
432+
return std::make_shared<conv_2d_transpose_layer>(name,
433+
filter_shape, filter_count, strides, pad_type,
434+
dilation_rate, weights, bias);
435+
}
436+
406437
inline layer_ptr create_separable_conv_2D_layer(const get_param_f& get_param,
407438
const nlohmann::json& data,
408439
const std::string& name)
@@ -1145,6 +1176,8 @@ namespace internal {
11451176
{ "Identity", create_identity_layer },
11461177
{ "Conv1D", create_conv_2d_layer },
11471178
{ "Conv2D", create_conv_2d_layer },
1179+
{ "Conv1DTranspose", create_conv_2d_transpose_layer },
1180+
{ "Conv2DTranspose", create_conv_2d_transpose_layer },
11481181
{ "SeparableConv1D", create_separable_conv_2D_layer },
11491182
{ "SeparableConv2D", create_separable_conv_2D_layer },
11501183
{ "DepthwiseConv2D", create_depthwise_conv_2D_layer },

include/fdeep/layers/conv_2d_layer.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace internal {
3030
const float_vec& weights, const float_vec& bias)
3131
: layer(name)
3232
, filters_(generate_im2col_filter_matrix(
33-
generate_filters(dilation_rate, filter_shape, k, weights, bias)))
33+
generate_filters(dilation_rate, filter_shape, k, weights, bias, false)))
3434
, strides_(strides)
3535
, padding_(p)
3636
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2016, Tobias Hermann.
2+
// https://github.com/Dobiasd/frugally-deep
3+
// Distributed under the MIT License.
4+
// (See accompanying LICENSE file or at
5+
// https://opensource.org/licenses/MIT)
6+
7+
#pragma once
8+
9+
#include "fdeep/convolution.hpp"
10+
#include "fdeep/filter.hpp"
11+
#include "fdeep/layers/layer.hpp"
12+
#include "fdeep/shape2.hpp"
13+
#include "fdeep/tensor_shape.hpp"
14+
15+
#include <fplus/fplus.hpp>
16+
17+
#include <cstddef>
18+
#include <string>
19+
#include <vector>
20+
21+
namespace fdeep {
22+
namespace internal {
23+
24+
class conv_2d_transpose_layer : public layer {
25+
public:
26+
explicit conv_2d_transpose_layer(
27+
const std::string& name, const tensor_shape& filter_shape,
28+
std::size_t k, const shape2& strides, padding p,
29+
const shape2& dilation_rate,
30+
const float_vec& weights, const float_vec& bias)
31+
: layer(name)
32+
, filters_(generate_im2col_filter_matrix(
33+
generate_filters(dilation_rate, filter_shape, k, weights, bias, true)))
34+
, dilation_rate_(dilation_rate)
35+
, strides_(strides)
36+
, padding_(p)
37+
{
38+
assertion(k > 0, "needs at least one filter");
39+
assertion(filter_shape.volume() > 0, "filter must have volume");
40+
assertion(strides.area() > 0, "invalid strides");
41+
}
42+
43+
protected:
44+
tensors apply_impl(const tensors& inputs) const override
45+
{
46+
const auto& input = single_tensor_from_tensors(inputs);
47+
return { convolve_transposed(strides_, padding_, filters_, input) };
48+
}
49+
convolution_filter_matrices filters_;
50+
shape2 dilation_rate_;
51+
shape2 strides_;
52+
padding padding_;
53+
};
54+
55+
}
56+
}

0 commit comments

Comments
 (0)