在阅读代码的时候就看到了padding和stride两个单词,来深度学习课程详细了解一下他们的作用。
填充和步幅是卷积层的两个超参数,通过他们可以改变输出形状。
(一)填充padding
填充是指在输入的四周添加0元素。
比如原输入数组形状是Nh×Nw,卷积核形状是Kh×Kw,那么原输出数组形状就是
(Nh-Kh+1)×(Nw-Kw+1)。如果我们对输入数组进行填充高为Ph,宽为Pw,那么新的输出数组形状就是(Nh-Kh+Ph+1)×(Nw-Kw+Pw+1)。
通常我们为了方便在构造网络时推测每个层的输出形状,会设置Ph=Kh-1,Pw=Kw-1。这样输入输出形状就是一致的。
具体来说,当卷积核Kh是奇数的时候,两边均填充Ph/2;是偶数的时候,Ph就是奇数,那么下侧填充不超过Ph/2的最大值,上侧填充大于Ph/2的最小值。Kw同理。
from mxnet import nd
from mxnet.gluon import nn
# 定义一个函数来计算卷积层。它初始化卷积层权重,并对输入和输出做相应的升维和降维
def comp_conv2d(conv2d, X):
conv2d.initialize()
# (1, 1)代表批量大小和通道数均为1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
return Y.reshape(Y.shape[2:]) # 排除不关心的前两维:批量和通道
# 注意这里是两侧分别填充1行或列,所以在两侧一共填充2行或列
conv2d = nn.Conv2D(1, kernel_size=3, padding=1)
# 这是卷积核是3×3的情况,直接设置padding为1,就是高和宽的两侧填充数字均为1
# 如当kernel_size=(5,3)时,padding=(2,1)即可
X = nd.random.uniform(shape=(8, 8)) # 输入形状是8×8
comp_conv2d(conv2d, X).shape # 发现输出形状也是8×8
当两端上的填充个数相等,并使输入和输出具有相同的高和宽时,我们就知道输出Y[i,j]是由输入以X[i,j]为中心的窗口同卷积核进行互相关计算得到的。
(二)步幅
卷积窗口在滑动的过程中,每次滑动的行数和列数称为步幅。过程如下图:
从最左上角开始卷积,红色圈住的四个元素与卷积核卷积得到
输出的第一行第一个元素0: 0×0+0×1+0×2+0×3=0
然后向右移的时候步幅为2,得到黄色圈住的四个元素,与卷积核卷积得到第一行第二个元素8: 0×0+0×1+1×2+2×3=8
再向右移的时候,无法框住四个元素。
从最左上下移,步幅为3,得到紫色框住的四个元素,与卷积核卷积得到第二行第一个元素6: 0×0+6×1+0×2+0×3=6
然后右移,步幅为2,得到黑色圈住的四个元素,与卷积核卷积得到第二行第二个元素8: 7×0+8×1+0×2+0×3=6
一般来说,当高上步幅为Sh,宽上步幅为Sw时,得到的输出数组的形状是:
⌊(Nh−Kh+Ph+Sh)/Sh⌋×⌊(Nw−Kw+Pw+Sw)/Sw⌋
当设置Ph=Kh-1,Pw=Kw-1时,输出形状简化为⌊(Nh+Sh−1)/Sh⌋×⌊(Nw+Sw−1)/Sw⌋
举个例子:
conv2d = nn.Conv2D(1, kernel_size=(3, 5), padding=(0, 1), strides=(3, 4))
comp_conv2d(conv2d, X).shape
输入X为8×8,卷积核为3×5,填充是0×2,步长是3×4,那么得到是数组形状是⌊(8+0-3+3)/3⌋=2 (8+2-5+4)/ 4 = 2
得到2×2的输出数组
(三)总结
填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。
步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的1/n(n为大于1的整数)。