我们已经知道,卷积的输出形式取决于输入形式和卷积核的形式。
此外还有其他因素会影响输出的大小。假设以下情景: 有时,在应用了连续的卷积之后,我们最终得到的输出远小于输入大小。这是由于卷积核的宽度和高度通常大于1所导致的。比如,一个240×240像素的图像,经过10层5×5的卷积后,将减少到200×200像素。如此一来,原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法; 有时,我们可能希望大幅降低图像的宽度和高度。例如,如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。
1.填充
容易知道,更大的卷积核可以更快地减小输出大小,但有时我们不想输出变得很小,那么我们可以在输入的周围添加了额外的行/列,这样也可以考虑了角落里数据的特征
填充后,输出甚至比输入还大了。
填充 p h p_h ph行和 p w p_w pw列,输出形状为 ( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_h-k_h+p_h+1)\times(n_w-k_w+p_w+1) (nh−kh+ph+1)×(nw−kw+pw+1)
通常取 p h = k h − 1 , p w = k w − 1 p_h = k_h -1,p_w = k_w -1 ph=kh−1,pw=kw−1,当 k h k_h kh为奇数时,在上下两侧填充 p h / 2 p_h/2 ph/2;当 k h k_h kh为偶数时:在上侧填充 ⌈ p h / 2 ⌉ \lceil p_h/2\rceil ⌈ph/2⌉,在下侧填充 ⌊ p h / 2 ⌋ \lfloor p_h/2\rfloor ⌊ph/2⌋
2.步幅
填充减小的输出大小与层数线性相关:给定输入大小为224×224,在使用5×5卷积核的情况下,需要44层将输出降低到4×4,需要大量计算才能得到较小输出
步幅是指行/列的滑动步长,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。
例如一个高度为3,宽度为2的步幅:
给定高度 s h s_h sh和宽度 s w s_w sw的步幅,输出形状是
⌊ ( n h − k h + p h + s h ) / s h ⌋ × ⌊ ( n w − k w + p w + s w ) / s w ⌋ \lfloor (n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor (n_w-k_w+p_w+s_w)/s_w\rfloor ⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋
如果 p h = k h − 1 , p w = k w − 1 p_h = k_h -1,p_w=k_w-1 ph=kh−1,pw=kw−1则为
⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \lfloor (n_h+s_h-1)/s_h\rfloor \times \lfloor (n_w+s_w-1)/s_w\rfloor ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋
如果输入高度和宽度可以被步幅整除:
( n h / s h ) × ( n w / s w ) (n_h/s_h)\times(n_w/s_w) (nh/sh)×(nw/sw)
总结
- 填充和步幅是卷积层的超参数
- 填充在输入周围添加额外的行/列,来控制输出形状的减少量
- 步幅是每次滑动核窗口时的行/列的步长,可以成倍的减少输出形状
代码实现
import torch
from torch import nn
# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
def comp_conv2d(conv2d, X):
# 这里的(1,1)表示批量大小和通道数都是1
X = X.reshape((1, 1) + X.shape) # +是元组的连接
Y = conv2d(X)
# 省略前两个维度:批量大小和通道
return Y.reshape(Y.shape[2:])
# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
#两个1,1分别为输出通道和输入通道个数
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1) # padding=1是上下左右各添加一行
X = torch.rand(size=(8, 8))
# 输入8,填充2,则为8+2+1-3 =8 ,输出还是8行8列
print(comp_conv2d(conv2d, X).shape)
'''填充不同的高度和宽度'''
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1)) # 上下填充2,左右填充1
# 输入8行8列,对于行,填充了4,则为8+4+1-5=8 ,对于列,填充了2,则为8+2+1-3=8,输出还是8行8列
print(comp_conv2d(conv2d, X).shape)
'''步幅'''
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
print(comp_conv2d(conv2d, X).shape)
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
print(comp_conv2d(conv2d, X).shape)