3.TransposedConv2d实现 (含dilation)

[C++ 基于Eigen库实现CRN前向推理]

第三部分:TransposedConv2d实现 (含dilation)

1. TransposedConv2d介绍

原先一直不明确反卷积具体是怎么操作的,查了一些资料发现反卷积其实是由卷积实现的。
先来看一个简单示例,这是一个input=(2,2),kernel=(3,3), no padding, no strides(即为1)的反卷积。
可以发现,反卷积其实就是通过在输入上进行零填充,再进行普通卷积达到上采样的目的。
那如何填充,由padding,stride和dilation决定,我们一步步考虑。

2022.11.28更新:文中提到的可视化中,卷积核在参数位置上需要进行180°旋转才是实际中的卷积核。因为卷积操作会让卷积核参数以180°翻转再乘上某个参数得出结果,因此需要预先对卷积核进行翻转。因为样例权重初始化为1,所以写的时候没注意,在实际运行时发生错误,特此更新。另外,pytorch中的反卷积参数存储方式为(in_channels,out_channels,kernel_1,kernel_2),前两维与卷积参数的位置(out_channels,in_channels)相反,如果按照卷积方式进行,在C++中需要注意一下。
在这里插入图片描述

1.1 反卷积的Padding参数

在传统卷积中,我们的 padding 范围为 [0, k − 1] ,p = 0 被称为 No padding,p = k − 1被称为 Full Padding。
而在反卷积中的 p ′ 刚好相反,也就是 p ′ = k − 1 − p
也就是当我们传p=0时,实际上反卷积的padding_size = k - 1, 传p=k-1时,padding_size = 0

如上图,p=0 ===> 则在TransposedConv中p ’ = 3-1-0 = 2,在外圈填充了2圈0
输入(2,2)的输出尺寸称为(4,4),达到上采样的效果。

1.2 反卷积的Stride参数

在反卷积中,Stride其实不是跳步的含义,具体的操作是在两个相邻行或列之间填充stride-1个0。
比如下图为input=(2,2),kernel=(3,3) stride=(2,2),padding=(0,0)时的示意图:
在这里插入图片描述
但在反卷积中,实际的stride都是1,所以上述情况下,输出map大小为(5,5)

1.3 反卷积中的output_padding参数

output_padding参数也是在数据边缘填充数据,与padding不同的是,padding是在输入图上进行填充,再进行卷积操作。
而output_padding是对卷积操作后的结果进行填充。

使用目的:为了补偿由卷积-反卷积操作中造成的维度损失。如图在8*8的图上进行卷积时,其实最右边和最下边的一行是没有参与卷积运算的,这是因为stride为2,再走2步就超出图片范围了。所以7x7和8x8最终的结果都为3x3。而反卷积的时候默认恢复成7×7,由此造成了一个维度的损失。
在这里插入图片描述
那么如果我们想让3x3的反卷积得8x8而不是7x7,那么我们就需要在输出图片边缘补充数据,具体补几行就是output_padding指定的。所以output_padding的作用就是:在输出图像右侧和下侧补值,用于弥补stride大于1带来的缺失。其中output_stadding必须小于stride。

我们拿pytorch的代码测试一下:

	input = torch.arange(1, 10).reshape(1, 1, 3, 3).float()
    kernel = torch.ones(1, 1, 2, 3).float()
    bias = torch.randn(1).float()
	th_out = F.conv_transpose2d(input, kernel, bias, stride=(2, 2), padding=(0, 1), dilation=(1, 1),
                                output_padding=(1, 1))
	print(input)
    print(kernel)
    print(th_out)
    

在这里插入图片描述
可以看到,pytorch的反卷积在进行output_padding的时候,并不是进行零填充,在行方向应用的是bias(图中为0.5917),在列方向应用为卷积最后的最后一列的复制。

1.4 反卷积中的dilation参数

网络上大部分的资料都是带洞(正)卷积,一直不清楚反卷积中的dilation是怎么进行填充的。
我们拿pytorch做一个带洞反卷积的测试,我们用input(3,3),kernel(2,2)进行反卷积:

	input = torch.arange(1, 10).reshape(1, 1, 3, 3).float()
    kernel = torch.ones(1, 1, 2, 2).float()
    bias = torch.zeros(1).float()
	th_out = F.conv_transpose2d(input, kernel, bias, stride=(1, 1), padding=(0, 0), dilation=(1, 2))
	print(input)
    print(kernel)
    print(th_out)

正常情况下,如果不带洞,按照前面两小结的结论,输出应该是
在这里插入图片描述
但在列方向使dilation=2之后,发现输出变为
在这里插入图片描述
发现多了长度变长了1,事实上,一个为kernel的卷积核,在进行dilation卷积的时候,对输入图的切片范围会变为(kernel-1)dilation+1,具体可以看图。考虑到反卷积是对输入图的填充,在输入图前后各填充 (kernel - 1) * (dilation - 1)的零。
在这里插入图片描述
所以对输入图填充后输入和输出分别为,这里是我用实现的C++版本输出的,input=(3,3),kernel=(3,3),stride=(1,2),dilation=(1,2),padding=(0,1)。
行方向:可以看到所有的pad形式,红色标出的是行方向的填充,这里padding[0]=0,则实际的填充数为kernel[0]-1-0=2,其次dilation=1,stride=1,没有额外的填充,所以总共就是上下各2行。
列方向:padding[1]=1,所以实际的填充数为kernel[1]-1-1 =1,即为左右蓝色的两列。另外stride[1]=2,则在每个元素间填充stride[1]-1列,即为中间标为绿色的列。最后dilation[1]=2,则两边分别填充(kernel[1]-1)
(dilation[1]-1)列,即各填充2*1列,如黄色所示。
填充完毕后,就可以进行stride=(1,1),dilation=(1,2)的正卷积。

0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 2 0 3 0 0 0
0 0 0 4 0 5 0 6 0 0 0
0 0 0 7 0 8 0 9 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0

0 3 0 6 0 5 0
0 12 0 21 0 16 0
0 27 0 45 0 33 0
0 24 0 39 0 28 0
0 15 0 24 0 17 0

2. 基于Eigen的C++实现
2.1 Layer_TransposedConv2d.h
//
// Created by Koer on 2022/10/31.
//

#ifndef CRN_LAYER_TRANSPOSEDCONV2D_H
#define CRN_LAYER_TRANSPOSEDCONV2D_H

#include "tuple"
#include "mat.h"
#include "Eigen/CXX11/Tensor"

class Layer_TransposedConv2d {
   
public:
    Layer_TransposedConv2d();

    Layer_TransposedConv2d(
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值