摘要
本文探讨了卷积和转置卷积的尺寸计算过程,并通过 Pytorch 进行了验证。
卷积
如上图所示,输入特征图为 5×5 ,补零为1( padding = 1 ), 使用 3×3 卷积核,卷积核移动的步幅为1( stride = 1 ),则经过卷积操作后,输出特征图的尺寸仍为 5×5 。可以看到,特征图的尺寸未发生变化,而通道数则取决于卷积核的个数。正是这个良好的特性, 使得 3×3 卷积被广泛地应用于卷积神经网络的前端特征提取部分。
以上所涉及到的特征图尺寸计算,可用下面的公式来表示:
[W_{ ext {out }}=leftlfloorfrac{W_{ ext {in }}+2 P-F}{S}ightfloor+1
]
其中:
$ W_{ ext {in }} $ 为输入的特征图宽度或者高度
$ W_{ ext {out }} $ 为输出的特征图宽度或者高度
(P) 为 padding取值
$ F $ 为卷积核的大小(一般卷积核为方形的)
(S) 为卷积核移动的步幅
(lfloorcdotfloor) 为向下取整函数
一般情况下,很少出现需要用到向下取整的情况,所以,上式又可以写作:
[W_{ ext {out }}=frac{W_{ ext {in }}+2 P-F}{S}+1
]
对上面的图,进行验证:
[5=frac{5 + 2*1 – 3}{1}+1
]
所以,只要合理地设置卷积核、补零、以及移动步幅,就能利用卷积对特征图的尺寸大小进行调整。
转置卷积
对于卷积来说,似乎无论怎么卷积,输出特征图的大小只能保持在输入特征图大小的范围内,是一种下采样操作。
但是,其实只要调整卷积的一些参数,就可以将小的特征图通过 “卷积” 变成大的特征图,实现上采样操作。这便是所谓的 转置卷积 。
如上图所示,输入特征图为 5×5 ,补零为2( padding = 2 ), 使用 3×3 卷积核,卷积核移动的步幅为1( stride = 1 ),则经过卷积操作后,输出特征图的尺寸为 7×7。
关于转置卷积这个名词的解释,借鉴的是线性代数中的转置操作。假设输入特征图为 (X) , 输出特征图为 (Y) ,卷积操作为 (C),那么从输出特征图到输入特征图的关系可以表示为:
[Y=CX
]
如果反过来,已知输出特征图 (Y),要求输入特征图 (X),则可以由下式得到:
[X=C^TY
]
这里的 (C^T) 就是我们所谓的转置卷积,或者说反卷积。
上述转置卷积过程,输入特征图与输出特征图之间的尺寸换算关系仍然可以用前面的公式来计算,即:
[W_{ ext {out }}=frac{W_{ ext {in }}+2 P-F}{S}+1
]
对上面的图进行验证:
[7=frac{5 + 2*2 – 3}{1}+1
]
转置卷积一般可用于上采样操作,放大特征图。假设需要完成 n 倍上采样操作,则这些参数之间应该满足:
[W=frac{nW+2 P-F}{S}+1
]
然后可以推算出:
[left{ {egin{array}{*{20}{l}}
{S = n} \
{F – S – 2P = 0}
end{array}} ight.
]
上采样倍数一般是2的倍数,于是有:
[left{ {egin{array}{*{20}{l}}
{S = 2k} \
{P = t} \
{F = 2left( {k + t} ight)}
end{array}} ight.
]
上式中,(k),(t) 取1到正无穷的整数。
实例验证
卷积与转置卷积在 Pytorch
中 均为内置的函数,我们使用 Pytorch
对上面的结论进行验证,加深理解。
import torch
# ------ 随机初始化一个 256×256 的输入特征图 -------- #
x = torch.randn((1, 1, 256, 256))
x.shape
# torch.Size([1, 1, 256, 256])
# ------ 验证 3×3 卷积 -------- #
Conv_3_by_3 = torch.nn.Conv2d(in_channels=1,
out_channels=1,
kernel_size=3,
padding=1,
stride=1)
y1 = Conv_3_by_3(x)
y1.shape
# torch.Size([1, 1, 256, 256])
# ------ 验证2倍上采样 F=2, P=0, S=2 -------- #
Conv_transpose_1 = torch.nn.ConvTranspose2d(in_channels=1,
out_channels=1,
kernel_size=2,
padding=0,
stride=2)
y2 = Conv_transpose_1(x)
y2.shape
# torch.Size([1, 1, 512, 512])
# ------ 验证2倍上采样 F=4, P=1, S=2 -------- #
Conv_transpose_2 = torch.nn.ConvTranspose2d(in_channels=1,
out_channels=1,
kernel_size=4,
padding=1,
stride=2)
y3 = Conv_transpose_2(x)
y3.shape
# torch.Size([1, 1, 512, 512])
可以看到,运行得到的结果与前面所分析的一样。