Dropout
训练时随机'屏蔽'一些神经元,反而能让网络更鲁棒——一种反直觉的正则。
从权重衰退到一个新问题
前一课的 权重衰退 这样对抗过拟合: 在 loss 上加一个 L2 惩罚项,把所有权重 整体"压小" , 压制"靠极端权重才能记住噪声"的那一面。
但过拟合还有 另一面 。训练时神经元彼此竞争—— 谁恰好在更多样本上响应得最强,它的梯度就被反复加强、权重一路涨大, 其余神经元几乎没机会贡献。久而久之就会形成下面这种典型症状:
过度依赖少数神经元 ——某几个神经元承担"特别重要"的角色, 一旦它们的输入被噪声扰动,整体输出就会跟着剧烈波动。
权重衰退对这种 责任分布不均 几乎帮不上忙—— L2 把所有权重按 同一比例 整体压小, 原本极不均衡的责任结构只是被等比缩放,并不会变得更均匀。 问题不在权重的 绝对大小 ,而在 责任的分布 本身。 那么换一个思路:
能不能直接强迫每个神经元都"自食其力"—— 训练时随机让它的某些同伴失效,看它能不能独立完成任务?
这正是 Dropout 的思路,由 Geoffrey Hinton 等人在 2012 年提出 。它的实现非常简洁——下一节就来看具体怎么做。
Dropout 机制
具体做法非常简洁——在训练时,对某层的每个输出独立地做:
- 以概率 把它 设为 0 (屏蔽)
- 剩下没被屏蔽的,乘以 缩放
用公式表达:
其中 是 dropout 概率,常见取值 (隐藏层)或 (输入层)。
为什么要乘 缩放? 屏蔽掉一部分后,整体激活的"强度"变小了,乘上这个因子正好把 期望值 拉回原值, 避免后续层接收到的输入 分布偏移 。
这里的"分布偏移"指的是:输入数据的 统计分布 (例如均值、方差)发生了改变。 网络在训练中习惯于某一种分布,一旦分布在中途变了, 后续层就要"重新适应",训练会被拖慢、收敛变差甚至失败。 乘上 让"开 / 关 Dropout"前后下层接收到的输入分布大致保持一致, 训练 / 推理两边的行为也才能对得上。
训练时,每个隐藏层的神经元独立按概率 被屏蔽(灰色), 它发出的所有连边在本次前向 / 反向中被截断;保留下来的神经元 用同一份 系数放大输出,保持下一层接收到的激活期望不变。 输入层与输出层不做 Dropout。
训练 vs 推理:必须切换状态
Dropout 只在 训练 时启用, 推理 时必须关掉 ——否则同一份输入每次预测都会带上随机性,结果无法稳定使用。所以这个"开关"必须由你 在代码里显式切换 ,框架不会自动判断。
PyTorch 为此提供了一对方法—— model.train() 与 model.eval() , 分别在训练循环和评估循环里调用一次:
- model.train() :所有 Dropout 层启用屏蔽
- model.eval() :所有 Dropout 层 透明传递 (输入直接 = 输出,不屏蔽不缩放)
实践:使用 nn.Dropout
PyTorch 提供 nn.Dropout 层, 像 nn.ReLU 一样在网络结构中夹一层就行:
Playground:扫描 看 Dropout 效果
下面这个 playground 在浏览器里实时训练一个小 MLP—— 训练数据只有 个点(特意取很少,方便诱发过拟合), 而验证集有 个点。 切换不同的 , 观察网络中被屏蔽的神经元、决策边界、以及训练 / 验证 loss 曲线的变化。
总体趋势上,随着 从 0 逐渐增大,模型会从 过拟合 过渡到 较好的泛化 , 继续增大则会被削弱到 欠拟合 。 请自行选择不同的 ,看一下效果吧。
Dropout vs 权重衰退
本章学到的两种正则手段,机制完全不同:
实战中常常一起用 ——既给优化器加 weight_decay=1e-3 ,又在隐藏层间插 nn.Dropout(0.3) ,两者作用互补、不冲突。
整章总结:MLP 完整能力包
结构
多层 [线性 + 激活] 堆叠 → 表达任意非线性
训练
反向传播 + 自动微分(计算图)→ 梯度自动求; mini-batch SGD → 高效大数据训练
评估
训练 / 验证 / 测试三划分;学习曲线诊断欠 / 过拟合
正则化
权重衰退(L2)+ Dropout