📕 第5章 卷积神经网络
📄 一句话概括

过适的表现是「训练集上几乎全对,测试集上差一大截」。 治法之一是 dropout:训练时随机把一部分神经元的输出置零。 实测两层各 128 个单元、60 个训练样本:不加 dropout 测试 0.6344,加 0.3 升到 0.7211, 加 0.5 是 0.7128。训练准确率三行都是 1.0000。 关键实现细节:训练时要除以 (1 − 丢弃率),推理时什么都不做。

📝 本节和前面几节的关系

5.10 的网络在训练集小的时候会过适,这一节治它。 第 4 章 4.4 节见过过适的样子(训练误差 0.0060、验证 0.1935),这里给的是治法。 dropout 和 5.7 的 ReLU、5.8 的池化一样,都是在特征图上做逐元素操作的层。

一、过适长什么样

训练准确率 1.0000,测试 0.6344。差的这 36 个点就是过适。 它不是「模型太大」这么简单, 而是模型把训练集里那些噪声的具体形状也当成了特征。60 个样本配两万多个参数, 除了背没有别的办法。 判据要看走势,不是看一次: 两者一起涨是正常的;训练还在涨、测试开始跌,那才是过适。只看单点差距容易误判。

二、dropout 在做什么

每次更新前,按概率 p 把隐层的一部分输出置零。这些神经元这一步不参与,也不更新。 等于每一步都在训练一个「残缺的网络」。指数多个子网络的参数是共享的。 推理时用完整网络,近似这些子网络预测的平均。 这就是它为什么能减方差——平均会抵消掉那些只在特定子网络上成立的巧合。

三、为什么要除以 (1 − 丢弃率)

训练时如果只是置零,留下的神经元的期望输出会变成原来的 (1 − p) 倍。 推理时不丢弃,输出就整体偏大。所以要除以 (1 − p),把期望拉回来。 这叫 inverted dropout,现在所有框架的默认实现都是它,因为推理时不用做任何缩放,部署时少一步。

四、丢弃率怎么选

实测 0.3 和 0.5 差不多(0.7211 对 0.7128)。再往上就伤到容量了。 经验值:隐层 0.2 到 0.5,输入层最多 0.2。 卷积层很少用 dropout,那边更常用批归一化——因为卷积的权重共享本身已经是正则。 小数据加大模型时收益最大。数据够多时 dropout 基本没用,因为过适本来就不严重。

五、dropout 之外的办法

早停:测试误差开始上升就停。最便宜,几乎总要开。 权重衰减:在损失里加一项权重平方和,压住大权重。 数据增强:这个最有效,它直接扩大训练集的分布,而不是在模型上打补丁。 批归一化:本身有轻微的正则效果。 用了它之后 dropout 常常可以去掉,这也是 2015 年之后 dropout 用得少的原因。

六、动手 Demo:训练时随机掐掉一半神经元

训练集每类只留 20 张,测试集每类 200 张。 网络两层隐层各 128 个单元,在两处隐层上加 dropout,比较丢弃率 0、0.3、0.5 的效果。 每行是三个随机种子的平均。

# 训练时的 dropout:按概率置零,再除以 (1 - 丢弃率)
H0 = np.tanh(Ftr @ W1 + b1)
m = (rg.random(H0.shape) > drop) / (1.0 - drop)
H = H0 * m

# 反向传播:梯度也要经过同一个掩码
dH = (G @ W2.T) * (1.0 - H0 * H0) * m

# 推理时什么都不做,直接用完整的 H0
score = np.tanh(Fte @ W1 + b1) @ W2 + b2

实跑输出:

--------------------------------------------------------------------
过适与丢弃:训练时随机掐掉一半神经元
--------------------------------------------------------------------

  造三类 8×8 的图(横线、竖线、对角线,位置随机、噪声 0.3),训练集每类只留 20 张,
  测试集每类 200 张。
  网络两层隐层各 128 个单元,参数 24707 个。
  **60 个训练样本要喂饱两万多个参数,除了背下来没有别的办法。**

  下面每一行是三个随机种子的平均,免得拿一次抽样说事:

  丢弃率    训练准确率    测试准确率    训练减测试
  --------------------------------------------------
  0.0       1.0000        0.6344        +0.3656
  0.3       1.0000        0.7211        +0.2789
  0.5       1.0000        0.7128        +0.2872

  **三行的训练准确率全是 1.0000——训练集已经被完整背下来了。**差别全在测试那一列。

  丢弃率 0 时测试 0.6344;加 0.3 的丢弃升到 0.7211,加 0.5 是 0.7128。
  **训练减测试那一列从 +0.3656 缩到 +0.2789,这才是 dropout 起作用的样子:**
  **它没能让训练更准(已经满分),它让测试更准。**

  原理是每次更新都换一个「残缺的网络」,等于同时训练了指数多个子网络,
  推理时用完整网络近似它们的平均。**它逼着每个单元都不能依赖别人,必须自己有点用。**

  有一个必须记住的实现细节:训练时要除以 (1 − 丢弃率),推理时什么都不做。
  **这样两边的期望值才对得上。**漏掉这个除法,推理时输出会整体偏大。

  注意丢弃率不是越高越好:0.3 和 0.5 差不太多,再往上就伤到容量了。
  **隐层常用 0.2 到 0.5,输入层最多 0.2;卷积层很少用它,那边更常用批归一化。**

三行的训练准确率全是 1.0000。训练集已经被完整背下来了,这一列没有信息量。 差别全在测试那一列:0.6344、0.7211、0.7128。加 dropout 之后涨了 8 到 9 个点。 训练减测试那一列从 +0.3656 缩到 +0.2789。 这才是 dropout 起作用的样子:它没能让训练更准(已经满分),它让测试更准。 每行都是三个随机种子的平均。只看一个种子的话,波动能有 3 到 5 个点,很容易得出相反结论。 0.5 比 0.3 略低一点,但这点差别在噪声范围内,不该当成「0.3 更好」的证据。调这种超参, 得看趋势,不能看小数点后第三位。

30 秒实验

  1. 把训练集从每类 20 张加到每类 200 张,dropout 还有用吗?
  2. 如果在输入层(64 维像素)也加 0.5 的 dropout,会更好还是更差?
  3. 把训练轮数从 3000 降到 300,三行的准确率会怎么变?

常见误区

  1. 以为 dropout 会让训练准确率更高。它一般会让训练准确率降低,换来测试准确率升高。 两个都涨,那不是 dropout 的功劳。
  2. 忘了推理时要关掉 dropout。忘了关,同一个输入每次预测都不一样。 这是上线后最难查的 bug 之一。
  3. 以为丢弃率越高正则越强。超过一定程度就把有效容量砍没了,测试准确率反而掉。
自测三问(点开对答案) 1. dropout 训练时和推理时分别做什么? —— 训练时按概率置零、再除以 (1 − p);推理时什么都不做,用完整网络。这就是 inverted dropout。 2. 为什么要除以 (1 − 丢弃率)? —— 保持期望输出不变。不除的话训练时的输出期望是 (1 − p) 倍,推理时数值就整体偏大。 3. 什么时候 dropout 收益最大? —— 小数据、大模型、过适明显的时候。数据够多、或者用了批归一化之后,它的收益会变得很小。

最后一节把前面提到但没展开的几个东西收一收:1×1 卷积、深度可分离卷积、im2col。 它们都是「让卷积更便宜」的工程手段。

相关笔记