填充

填充(padding):在输入图像的边界填充元素(通常填充元素是 0)

Untitled

填充 php_h 行和 pwp_w 列,输出形状为:

(nhkh+ph+1)×(nwkw+pw+1)\left(n_{h}-k_{h}+p_{h}+1\right) \times\left(n_{w}-k_{w}+p_{w}+1\right)

通常取 ph=kh1,pw=kw1p_h=k_h-1,p_w=k_w-1

  • khk_h 为奇数时在上下两侧填充 ph/2p_h/2
  • khk_h 为偶数时,在上上侧填充 ph/2\lceil p_h/2\rceil,在下侧填充 ph/2\lfloor p_h/2\rfloor

卷积神经网络中卷积核的高度和宽度通常为奇数,选择奇数的好处是,保持空间维度的同时,可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列。

步幅

步幅(stride):每次滑动的步长(滑动元素的数量)

垂直步幅为 3,水平步幅为 2 的二维互相关运算:

Untitled

当垂直步幅为 shs_h、水平步幅为 sws_w 时,输出形状为:

(nhkh+ph+sh)/sh×(nwkw+pw+sw)/sw\left\lfloor\left(n_{h}-k_{h}+p_{h}+s_{h}\right) / s_{h}\right\rfloor \times\left\lfloor\left(n_{w}-k_{w}+p_{w}+s_{w}\right) / s_{w}\right\rfloor

如果 ph=kh1,pw=kw1p_h=k_h-1,p_w=k_w-1,则输出形状简化为:

(nh+sh1)/sh×(nw+sw1)/sw\left\lfloor\left(n_{h}+s_{h}-1\right) / s_{h}\right\rfloor \times\left\lfloor\left(n_{w}+s_{w}-1\right) / s_{w}\right\rfloor

如果输入的高度和宽度可以被垂直和水平步幅整除,则输出形状将为:

(nh/sh)×(nw/sw)\left(n_{h} / s_{h}\right) \times\left(n_{w} / s_{w}\right)

总结

  • 填充和步幅是卷积层的超参数
  • 填充在输入周围添加行/列,来控制输出的减少量
  • 步幅是每次滑动核窗口时行/列的步长,可以成倍减少输出形状

代码实现

在所有侧边填充 1 个像素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
X = torch.rand(size=(8, 8))
comp_conv2d(conv2d, X).shape
1
torch.Size([8, 8])

填充不同的高度和宽度

1
2
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
1
torch.Size([8, 8])

将高度和宽度的步幅设置为2

1
2
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
1
torch.Size([4, 4])

一个稍微复杂的例子

1
2
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
1
torch.Size([2, 2])