FA :Formula and Algorithm,CNN:Convolution Netural Network
1. 引言
在卷积神经网络中,卷积核大小(kernel size)决定了感受野,而**填充(padding)与步幅(stride)**则决定了卷积输出特征图的空间尺寸。合理设置这两个超参数,既能控制输出分辨率,也能影响计算量与信息保留程度。
本文先讲清填充与步幅的原理,再给出基于 PyTorch 的完整代码示例,帮助读者快速验证输出形状。
2. 填充(Padding)
2.1 为什么需要填充
如果不做填充,每次卷积都会让特征图变小。以输入尺寸 nh×nwn_h \times n_wnh×nw、卷积核 kh×kwk_h \times k_wkh×kw 为例,输出尺寸为:
(nh−kh+1)×(nw−kw+1) (n_h - k_h + 1) \times (n_w - k_w + 1) (nh−kh+1)×(nw−kw+1)
为了让卷积后的图像大小与原图保持一致,通常会在输入四周补零,这就是填充。
2.2 输出尺寸公式
设高度方向填充 php_hph 行、宽度方向填充 pwp_wpw 列,则输出形状为:
(nh−kh+ph+1)×(nw−kw+pw+1) (n_h - k_h + p_h + 1) \times (n_w - k_w + p_w + 1) (nh−kh+ph+1)×(nw−kw+pw+1)
当 ph=kh−1p_h = k_h - 1ph=kh−1、pw=kw−1p_w = k_w - 1pw=kw−1 时,输出尺寸与输入完全相同。
2.3 为什么卷积核多为奇数
实践中卷积核大小几乎都取奇数(3×3、5×5),原因有两点:
- 便于对称填充 :假设卷积核大小为 k×kk \times kk×k,为了让输出与原图同尺寸,需要 p=(k−1)/2p = (k-1)/2p=(k−1)/2。只有当 kkk 为奇数时,ppp 才是整数,可以在上下(左右)对称地填充。
- 有明确的中心像素:奇数卷积核存在唯一的中心位置,便于描述以该像素为中心的邻域。
当 kkk 为偶数时,p=(k−1)/2p = (k-1)/2p=(k−1)/2 为浮点数,只能一侧向上取整、另一侧向下取整,即上下(左右)填充数量不对称,实现上不够优雅。
3. 步幅(Stride)
步幅指卷积核在输入上滑动的步长。设高度步幅为 shs_hsh、宽度步幅为 sws_wsw,则输出形状为:
⌊nh−kh+ph+shsh⌋×⌊nw−kw+pw+swsw⌋ \left\lfloor \frac{n_h - k_h + p_h + s_h}{s_h} \right\rfloor \times \left\lfloor \frac{n_w - k_w + p_w + s_w}{s_w} \right\rfloor ⌊shnh−kh+ph+sh⌋×⌊swnw−kw+pw+sw⌋
直观理解:
- 步幅为 1:卷积核逐像素滑动,输出分辨率较高,计算量较大。
- 步幅大于 1:卷积核跳跃式滑动,输出尺寸成比例缩小,相当于在卷积中同时完成下采样,可替代部分池化操作。
4. 代码实践:使用 PyTorch 框架
下面通过一个辅助函数 comp_conv2d 统一验证不同参数下的输出形状。该函数在输入前补上批量大小与通道数两个维度,卷积后再去掉,便于直接观察二维结果。
python
import torch
from torch import nn
def comp_conv2d(conv2d, X):
# conv2d 作为传参传进去,在内部使用
X = X.reshape((1, 1) + X.shape) # 在维度前面加入通道数和批量大小
Y = conv2d(X) # 卷积处理是一个四维的矩阵
return Y.reshape(Y.shape[2:]) # 将前面两个维度拿掉
X = torch.rand(size=(8, 8))
# 1) 在所有侧边填充 1 个像素,输出尺寸不变
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
print(comp_conv2d(conv2d, X).shape)
# 2) 高度填充 2 行、宽度填充 1 列,卷积核 5×3,输出尺寸不变
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
print(comp_conv2d(conv2d, X).shape)
# 3) 将高度和宽度的步幅都设置为 2
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
print(comp_conv2d(conv2d, X).shape)
# 4) 一个稍微复杂的例子:卷积核 3×5,填充 (0,1),步幅 (3,4)
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
print(comp_conv2d(conv2d, X).shape)
运行结果:
text
torch.Size([8, 8])
torch.Size([8, 8])
torch.Size([4, 4])
torch.Size([2, 2])
4.1 结果解读
| 场景 | 卷积核 | 填充 | 步幅 | 输出形状 |
|---|---|---|---|---|
| 同尺寸卷积 | 3×3 | (1,1) | (1,1) | 8×8 |
| 非方形核同尺寸 | 5×3 | (2,1) | (1,1) | 8×8 |
| 步幅下采样 | 3×3 | (1,1) | (2,2) | 4×4 |
| 复杂组合 | 3×5 | (0,1) | (3,4) | 2×2 |
以第 4 个例子验证公式:高度方向 ⌊(8−3+0+3)/3⌋=2\lfloor (8 - 3 + 0 + 3) / 3 \rfloor = 2⌊(8−3+0+3)/3⌋=2,宽度方向 ⌊(8−5+1+4)/4⌋=2\lfloor (8 - 5 + 1 + 4) / 4 \rfloor = 2⌊(8−5+1+4)/4⌋=2,与输出 2×2 一致。
5. 总结
- 填充 用于控制输出尺寸,p=(k−1)/2p = (k-1)/2p=(k−1)/2 时输出与输入同尺寸;这也是卷积核普遍取奇数的原因。
- 步幅用于成倍缩小输出尺寸,可替代池化完成下采样。
- 二者可以分别对高度和宽度独立设置,PyTorch 中通过
padding=(p_h, p_w)、stride=(s_h, s_w)传入元组即可。 - 记住输出尺寸公式,就能在搭建网络时快速推算每一层的特征图大小,避免维度不匹配的问题。






