卷积会让图越算越小:5×5 配 3×3 得 3×3,再来一层得 1×1。十层之后就没了。 补零就是在图外面围一圈 0。补 p 圈,输出边长加 2p。 想让输出和输入一样大,p 取 (核边长 − 1) / 2。3×3 补 1 圈、5×5 补 2 圈。
5.2 节的尺寸公式是「减核边长加一」,这一节给它加一项 2p。 5.8 的池化层反过来缩尺寸,两者经常配对使用。 在 Keras 里这个参数就叫 padding,取 same 或 valid。
一、不补零会怎样
每层少 (k − 1) 个像素。3×3 的核,每边少 2,边长少 2。 10 层之后 28×28 的 MNIST 就变成 8×8,20 层就归零。这不是「小一点」,是根本堆不深。 更要紧的是边缘像素被用到的次数天生少。越往深层,边缘信息被稀释得越厉害。
二、补零的两种方式
valid:不补,输出变小。Keras 的卷积层默认就是 valid。 same:补到输出和输入一样大,前提是步幅为 1。 还有别的填充方式(复制边缘、镜像反射),但补零最简单、效果也够,所以它成了事实标准。
三、same 补零的公式
输出 = H + 2p − F + 1。要它等于 H,得 2p = F − 1,所以 p = (F − 1) / 2。 这就是为什么卷积核几乎总是奇数。偶数核算出来是半圈,没法补。 步幅大于 1 时 same 的定义变成「输出等于输入除以步幅」, 补的圈数可能上下左右不对称——这是很多人手算尺寸对不上的原因。
四、补零的副作用
补进去的 0 是「假像素」。边界上的输出有一部分是在和 0 做内积,那些位置的数值会系统性偏小。 实测:5×5 的图补 1 圈后,角上那个窗口的 9 个位置里有 5 个是补出来的 0。只有 4 个是真像素。 所以有些网络在边缘用反射填充,让边缘的统计特性和内部一致。 这是个细节,但做分割任务时边界质量差往往就是它造成的。
五、valid 还是 same
要尺寸不变就用 same,要严格不引入假像素就用 valid。 现代网络两者都有:ResNet 用 same 加步幅降采样,一些生成模型用 valid 避免边界伪影。 一句话:补零是工程妥协,不是数学必需。它换的是「能堆深」,代价是边缘的偏差。
六、动手 Demo:核滑到边上时外面补什么
一张 5×5 的图,3×3 的核,分别补 0 圈、1 圈、2 圈,看输出尺寸怎么变; 再数一数每个位置的窗口里有几个真像素。
import numpy as np
x = np.arange(25, dtype=float).reshape(5, 5)
k = np.ones((3, 3))
for p in (0, 1, 2):
xp = np.zeros((5 + 2 * p, 5 + 2 * p))
xp[p:p + 5, p:p + 5] = x
n = 5 + 2 * p - 3 + 1
print("补 %d 圈 输出 %d×%d" % (p, n, n))
# 每个位置的 3×3 窗口里有几个真像素
xp = np.zeros((7, 7))
xp[1:6, 1:6] = 1.0
for i in range(5):
print(" " + " ".join("%2d" % xp[i:i + 3, j:j + 3].sum() for j in range(5)))
实跑输出:
--------------------------------------------------------------------
以补零卷积:核滑到边上时外面补什么
--------------------------------------------------------------------
补零圈数 输出尺寸 输出元素数
------------------------------------
0 3×3 9
1 5×5 25
2 7×7 49
输入 5×5、核 3×3、步幅 1。补一圈就多出两行两列,输出从 3×3 长到 5×5。
**想让输出和输入一样大,就要补 (核边长 − 1) / 2 圈。**
3×3 补 1 圈、5×5 补 2 圈、7×7 补 3 圈。这个叫 same 补零。
另一个角度看:把 1 铺满 5×5、外面补 0,数一数每个位置的 3×3 里有几个真像素。
4 6 6 6 4
6 9 9 9 6
6 9 9 9 6
6 9 9 9 6
4 6 6 6 4
角上是 4、边上是 6、中间是 9。
**不补零的话,角上的像素只被用到 4 次,中间的像素被用到 9 次——边缘信息天生被削弱。**
补零之后次数虽然都变成 9,但角上那 9 次里有 5 次乘的是 0。
**所以补零不是「把图变大」,是给边缘像素一个公平的出场次数。**
表里三行:补 0 圈得 3×3、补 1 圈得 5×5、补 2 圈得 7×7。输入一直没变,变的是外面围了几圈 0。 补 1 圈的输出正好 5×5,这就是 same。它是「输出等于输入」的最小补法。 第二个矩阵更说明问题:把 1 铺满 5×5,数每个位置窗口里有几个真像素。角上 4、边上 6、中间 9。 不补零时,角上的像素一辈子只被用到 4 次,中间的用到 9 次。 这不是「信息少一点」,是系统性的边缘歧视。 补零之后每个位置的窗口都是 9 个数,但角上那 9 个里有 5 个是 0。 所以补零没有消除偏差,它把「用得少」换成了「用了假数据」。 这句话值得停一下:补零不是免费解决边缘问题,它只是把问题从「次数不均」变成「数值偏小」, 而后者网络能学着补偿。这就是工程妥协的样子。
30 秒实验
- 7×7 的核要补几圈才能 same?输入 32×32、步幅 2、same,输出多大?
- 如果把补零改成补 1(常数 1),输出的边缘会偏大还是偏小?
- 补 2 圈之后 5×5 变成 9×9,一个 3×3 核的输出是多少?
常见误区
- 以为补零会「丢失信息」。补零不丢任何信息,它只是加了假像素。真正丢信息的是步幅和池化。
- 以为 same 就一定输出相同尺寸。只有步幅 1 时成立。步幅 2 的 same,输出是输入的一半。
- 忘了补零会改变有效感受野。补零之后边界输出的权重有一部分花在 0 上, 它实际看到的真像素比中间少。
自测三问(点开对答案)
1. 3×3 的核要补几圈才能保持尺寸? —— 1 圈。(3 − 1) / 2 = 1。5×5 要 2 圈,7×7 要 3 圈,规律是 (F − 1) / 2。 2. 不补零时每层缩多少? —— 每边缩 k − 1。3×3 的核每层少 2,所以 28×28 堆十几层就归零了。 3. 补零的代价是什么? —— 边界上的输出有一部分在和假像素 0 做内积,数值偏小,边缘的统计特性和内部不一致。补零让尺寸不变,但有时候我们恰恰想让它变小。 下一节看步幅和空洞,怎么在不加参数的前提下把范围撑大。
- ☐ (本节完,下一节 5.6 增广范围的卷积)