文章目录
- 转置卷积详解
- 转置卷积理论📝📝📝
- 转置卷积实验📝📝📝
转置卷积详解
转置卷积理论📝📝📝
这里我将通过一个小例子来讲述转置卷积的步骤,并通过代码来验证这个步骤的正确性。首先我们先来看看转置卷积的步骤,如下:
- 在输入特征图元素间填充s-1行、列0(其中s表示转置卷积的步距,注意这里的步长s和卷积操作中的有些不同)
- 在输入特征图四周填充k-p-1行、列0(其中k表示转置卷积kernel_size大小,p为转置卷积的padding,注意这里的padding和卷积操作中的有些不同)
- 将卷积核参数上下、左右翻转
- 做正常卷积运算(padding=0,s=1)
是不是还是懵逼的状态呢,不用急,现在就通过一个例子来讲述这个过程。首先我们假设输入特征图的尺寸为2*2大小,s=2,k=3,p=0,如下图所示:
第一步我们需要在特征图元素间填充s-1=1 行、列 0 (即填充1行0,1列0),变换后特征图如下:
第二步我们需要在输入特征图四周填充k-p-1=2 行、列0(即填充2行0,2列0),变换后特征图如下:
第三步我们需要将卷积核上下、左右翻转,得到新的卷积核【卷积核尺寸为k=3】,卷积核变化过程如下:
最后一步,我们做正常的卷积即可【注:拿第二步得到的特征图和第三步翻转后得到的卷积核做正常卷积】,结果如下:
至此我们就从完成了转置卷积,从一个2*2大小的特征图变成了一个5*5大小的特征图,如下图所示(忽略了中间步骤):
为了让大家更直观的感受转置卷积的过程,我从Github
上down了一个此过程动态图供大家参考,如下:【注:需要动态图点击☞☞☞自取】
通过上文的讲述,相信你已经对转置卷积的步骤比较清楚了。这时候你就可以试试图1中结构,看看应用上述的方法能否得到对应的结构。需要注意的是,在第一次转置卷积时,使用的参数k=4,s=1,p=0
,后面的参数都为k=4,s=2,p=1
,如下图所示:
如果你按照我的步骤试了试,可能会发出一些吐槽,这也太麻烦了,我只想计算一下经过转置卷积后特征图的的变化,即知道输入特征图尺寸以及k、s、p算出输出特征图尺寸,这步骤也太复杂了。于是好奇有没有什么公式可以很方便的计算呢?enmmm,我这么说,那肯定有嘛,公式如下图所示:
对于上述公式我做3点说明:
- 在转置卷积的官方文档中,参数还有output_padding 和dilation参数也会影响输出特征图的大小,但这里我们没使用,公式就不加上这俩了,感兴趣的可以自己去阅读一下文档,写的很详细。🌵🌵🌵
- 对于stride[0],stride[1]、padding[0],padding[1]、kernel_size[0],kernel_size[1]该怎么理解?其实啊这些都是卷积的基本知识,这些参数设置时可以设置一个整数或者一个含两个整数的元组,*[0]表示在高度上进行操作,*[1]表示在宽度上进行操作。有关这部分在官方文档上也有写,大家可自行查看。为方便大家,我截了一下这部分的图片,如下:
- 这点我带大家宏观的理解一下这个公式,在传统卷积中,往往卷积核k越小、padding越大,得到的特征图尺寸越大;而在转置卷积中,从公式可以看出,卷积核k越大,padding越小,得到的特征图尺寸越大,关于这一点相信你也能从前文所述的转置卷积理论部分有所感受。🌿🌿🌿
现在有了这个公式,大家再去试试叭。
转置卷积实验📝📝📝
接下来我将通过一个小实验验证上面的过程,代码如下:
import torch
import torch.nn as nn
#转置卷积
def transposed_conv_official():
feature_map = torch.as_tensor([[1, 2],
[0, 1]], dtype=torch.float32).reshape([1, 1, 2, 2])
print(feature_map)
trans_conv = nn.ConvTranspose2d(in_channels=1, out_channels=1,
kernel_size=3, stride=2, bias=False)
trans_conv.load_state_dict({"weight": torch.as_tensor([[1, 0, 1],
[1, 1, 0],
[0, 0, 1]], dtype=torch.float32).reshape([1, 1, 3, 3])})
print(trans_conv.weight)
output = trans_conv(feature_map)
print(output)
def transposed_conv_self():
feature_map = torch.as_tensor([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=torch.float32).reshape([1, 1, 7, 7])
print(feature_map)
conv = nn.Conv2d(in_channels=1, out_channels=1,
kernel_size=3, stride=1, bias=False)
conv.load_state_dict({"weight": torch.as_tensor([[1, 0, 0],
[0, 1, 1],
[1, 0, 1]], dtype=torch.float32).reshape([1, 1, 3, 3])})
print(conv.weight)
output = conv(feature_map)
print(output)
def main():
transposed_conv_official()
print("---------------")
transposed_conv_self()
if __name__ == '__main__':
main()
首先我们先通过transposed_conv_official()
函数来封装一个转置卷积过程,可以看到我们的输入为[[1,2],[0,1]],卷积核为[[1,0,1],[1,1,0],[0,0,1]],采用k=3,s=2,p=0进行转置卷积【注:这些参数和我前文讲解转置卷积步骤的用例参数是一致的】,我们来看一下程序输出的结果:可以发现程序输出和我们前面理论计算得到的结果是一致的。
接着我们封装了transposed_conv_self
函数,这个函数定义的是一个正常的卷积,输入是理论第2步得到的特征图,卷积核是第三步翻转后得到的卷积核,经过卷积后输出结果如下:结果和前面的一致。
那么通过这个例子就大致证明了转置卷积的步骤确实是我们理论步骤所述。