6.3 填充和步幅
填充
当卷积核的高度和宽度大于1时,卷积操作的输出的图像尺寸会变小,特别是在连续的多层卷积后,输出变得越来越小。这样一来,原始图像的边界丢失了许多有用的信息。填充是解决这个问题的一种办法,即,在输入图像的边界填充元素,通常是0.
通常,如果我们添加ph行填充(大约一半在顶部,一半在底部)和pw列填充(左侧大约一半,右侧一半),则输出形状将为(nh−kh+ph+1)×(nw−kw+pw+1)。这意味着输出的高度和宽度将分别增加ph和pw。请注意,这里如果上下各填充了一行,ph等于2.
在许多情况下,我们需要设置ph = kh-1 和 pw = kw - 1(代入上式,输出则变成nh ×nw)使输入和输出具有相同的高度和宽度。 这样可以在构建网络时更容易地预测每个图层的输出形状。假设kh是奇数,我们将在高度的两侧填充ph/2行。 如果kh是偶数,则一种可能性是在输入顶部填充(kh / 2)向上取整行,在底部填充(kh / 2)向下取整行。同理,我们填充宽度的两侧。举个例子:如果卷积核是3*3的,就填充2行2列,分别在上下各填充1行、左右各填充1行。
接下来我们用一个例子来说明如何填充能使得输出的尺寸和输入是一样的:
import torch from torch import nn # 为了方便起见,我们定义了一个计算卷积层的函数。 # 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数 def comp_conv2d(conv2d, X): # 若X是二维的,(1,1)+X.shape返回的是一个四维张量:(1,1,X,shape) X = X.reshape((1, 1) + X.shape) Y = conv2d(X) #经Y=conv2d(X)输出的Y是一个4维的,这里将Y的前两个维度砍掉 #Y.shape是4维的,(1,1,?,?),Y.shape[2:]是访问Y从下标为2开始的元素,于是下面的语句相当于Y.reshape(?,?) return Y.reshape(Y.shape[2:]) # 请注意,对于pytorch来说,这里是每边都填充了1行或1列,因此总共添加了2行或2列 # padding=1可以理解为填充了"1"圈。 conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1) X = torch.rand(size=(8, 8)) comp_conv2d(conv2d, X).shape
输出:
在上面的例子中,尺寸为8*8的矩阵X经过kernel_size = 3, padding = 1的卷积核,得到输出Y的尺寸依然是8*8。 这是因为padding的数量正是3 - 1 = 2(每边padding1,两边就是2)。
卷积核的尺寸不是正方形时,我们仍然可以通过padding来使得输出与输入的尺寸相等:
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
输出:
步幅
在前面的例子中,我们默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。我们将每次滑动元素的数量称为步幅(stride)。
当kernel_size = 3,padding = 1(padding一圈),stride = 2时,输出图像的尺寸是输入的一半:
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
输出: