Dropout 和正则化:深度学习如何缓解过拟合?

Dropout 和正则化:深度学习如何缓解过拟合?
上篇我们聊了 Batch Normalization 和 Layer Normalization,它们解决的是训练过程中数据分布不稳定的问题。
模型能正常训练了,但还有一个更大的麻烦在等着——过拟合。
你有没有遇到过这种情况:模型在训练集上表现接近满分,准确率99%,但一放到真实环境里,效果断崖式下跌?
这就是过拟合在作祟。
过拟合:模型界的"高分低能"
过拟合(Overfitting)简单说就是:模型在训练数据上表现很好,但泛化到新数据时性能急剧下降。
我把这种情况比喻成学生刷题。
一个学生把题库里的所有题目(包括错题)都背下来了,遇到原题当然对答如流。但换一套新题?完蛋,连最基本的概念都想不起来。
这就是"死记硬背"和"真正理解"的区别。
真正的学霸不是背答案,而是掌握了解题套路,遇到没见过的题目也能举一反三。
放到深度学习里也是一样的道理。
训练集上的图片可能有特定的光照条件、拍摄角度,甚至有水印、噪点这些"无关细节"。模型把这些全记住了。
但考试(测试)的图片可能换个角度、换个背景,模型就不认识了。
泛化能力才是衡量模型价值的真正标尺。
你想想,一个只会做老师出过题目的学生,和一个能解决新问题的学生,哪个更值钱?
"剪刀差":过拟合的视觉信号
怎么一眼看出模型过拟合了?
看训练曲线和验证曲线。
训练初期,两条曲线一起往上升,说明模型在学习。但到某个点之后,情况变了:
训练曲线继续高涨,验证曲线反而掉头向下。
两条曲线像剪刀一样张开,这就是经典的"剪刀差"。
正常模型:两条曲线一起上升,然后趋于平稳,差距不大。
过拟合模型:训练曲线一路狂奔,验证曲线半路趴窝。
差距越大,过拟合越严重。

过拟合的本质原因,是模型"死记硬背"了训练数据的噪声和细节。
什么是噪声?训练图片里的光照变化、角度变化、水印噪点,这些不是本质的东西,但模型把它们全记住了。
真正有价值的规律是:猫有尖耳朵、狗有长鼻子,不管角度怎么变,这些特征都在。
记住无关的,忽略关键的——这就是过拟合。
那怎么办?
接下来介绍两把应对过拟合的利刃:Dropout 和正则化。
Dropout 是什么?神经网络也搞"随机裁员"
第一把利刃叫 Dropout。
它的思路很暴力:训练的时候,随机让一部分神经元"下线"。
具体怎么做?
每个神经元,以概率 p 被"关闭"。
被关闭的神经元不参与前向传播,也不参与后向传播。简单说就是,它在这次训练中不存在。
第二次训练,重新随机选一批神经元关闭。
第三次训练,又是新的随机组合。
就这样,每次训练都像在玩俄罗斯轮盘赌,一批幸运的神经元存活,一批倒霉的神经元被裁掉。
数学上怎么实现?
$y = f(W \cdot mask(x) + b)$
这个 mask 就是掩码函数,它的值是 0 或 1。值为 0 的神经元就被"关闭"了。
有个实现上的小细节需要注意:
# Dropout 的简化实现
def dropout(x, p):
mask = (torch.rand(x.shape) > p).float()
return x * mask / (1 - p) # Inverted Dropout为什么要除以 (1-p)?后面会解释。

这个随机性带来一个很巧妙的效果:
每个神经元都不能依赖特定的"邻居"。
你想啊,如果 A 神经元一直和 B、C 神经元配合,它们三个形成了固定的小团队。那 A 被裁掉了,B 和 C 怎么办?
B 和 C 必须能够独立扛起任务,A 也必须能够和其他神经元配合。
久而久之,每个神经元都是多面手,能独当一面。
这就像一个公司的项目组,每次开会随机让一部分人"离线"。
剩下的人必须自己搞定所有讨论,不能依赖某几个特定的人。
结果是:每个人都变强了,整个团队的韧性提高了。
Dropout 为什么有效?往下看。
从"随机裁员"到 Bagging 集成
Dropout 的本质,是Bagging 思想的一种廉价实现。
Bagging 是什么?
Bootstrap Aggregating,让多个模型对同一数据进行预测,最后综合所有模型的预测结果。
多个模型的意见取平均,比任何一个单独的模型都稳定。
就像投票表决,一万个人投票永远比一个人投票更可靠。
Dropout 的巧妙之处在于:它把单个网络变成了无数个子网络。
一个有 n 个神经元的层,理论上能采样出 $2^n$ 个不同的子网络。
Dropout 每次训练,其实是在训练一个不同的子网络。
测试的时候,对所有子网络的预测结果做平均,就起到了"集思广益"的效果。
这比训练和保存 $2^n$ 个独立模型要便宜太多了。
所以 Dropout 本质上是一种隐式的集成学习。
训练阶段:每次随机采样一个子网络进行训练,相当于训练了 $2^n$ 个模型中的某一个。
测试阶段:把所有子网络的预测结果汇总,输出平均值。

用一个例子来理解:
想象一个万人智囊团,每次开会随机抽 3000 人参与讨论一个问题。
不同的人有不同的背景和视角,讨论的结果也不同。
最后把所有会议的结论综合起来,比任何一个单独专家的意见都稳定可靠。
这就是 Dropout 的思路:用随机性换稳定性。
但有个问题:训练时随机丢弃,测试时怎么办?
Inverted Dropout:让测试结果稳定
最早期的 Dropout 实现(叫 Vanilla Dropout),在测试阶段也需要随机丢弃神经元。
这会导致一个很糟糕的问题:每次预测结果不一样。
你用同一个输入跑两次模型,输出不一样。
这在实际应用中是完全不可接受的。
后来大家发现了一个更聪明的做法,叫 Inverted Dropout。
核心改动在训练阶段:
保留所有神经元,但在输出时乘以 $\frac{1}{1-p}$ 来做缩放。
测试阶段:不做任何修改,所有神经元正常工作。
这样训练和测试的输出期望就保持一致了,而且测试结果完全稳定。
PyTorch 的 nn.Dropout(p) 默认就是 Inverted Dropout 模式。
# 训练阶段
y = x * mask / (1 - p) # 缩放
# 测试阶段
y = x # 直接用,不用改为什么叫"Inverted"(反转)?
因为以前是把操作放在测试阶段,现在把操作放在训练阶段。
就像调音师在录音的时候(训练阶段)就把音量调到标准,播放的时候(测试阶段)就不需要再额外调整了。
简单、稳定、好用,这就是 Inverted Dropout 成为主流的原因。

Dropout 是从网络结构入手解决过拟合。
还有另一种思路:从损失函数入手。这就是正则化。
L1/L2 正则化:给损失函数加"复杂度税"
正则化的思路是:在损失函数里加一个惩罚项。
损失函数原来只关心预测准不准,现在还要关心模型复杂不复杂。
L2 正则化惩罚权重的平方和:
$$L_{new} = L_{old} + \lambda \sum_{i} w_i^2$$
L1 正则化惩罚权重的绝对值和:
$$L_{new} = L_{old} + \lambda \sum_{i} |w_i|$$
这个 $\lambda$ 是正则化强度,由你手动设置。
权重越大,惩罚越重。
权重越小,惩罚越轻。
所以模型被迫保持"简洁",权重不能太大。
这就像给模型的损失函数加了一个"复杂度税"。
越复杂的模型(权重越大),交的税越重。
越简洁的模型,交的税越少。
既然复杂要交税,那模型就倾向于保持简洁,从而降低过拟合风险。
两种正则化有什么不同?
L2 正则化惩罚权重的平方,让权重趋向于小而分散。
L1 正则化惩罚权重的绝对值,能产生稀疏权重——有些权重直接变成 0。
权重变成 0 意味着什么?
意味着对应的特征被模型"忽略"了。
所以 L1 正则化自带特征选择功能。
如果你怀疑输入特征里有很多是无用的,L1 正则化可以帮助你自动找出哪些特征有用、哪些没用。
Weight Decay:L2 正则化的另一个名字
在 SGD 优化器中,L2 正则化等价于 Weight Decay(权重衰减)。
Weight Decay 的更新公式是:
$$w = w - \eta \cdot (\nabla L + \lambda w)$$
拆开看:
- $\nabla L$ 是普通梯度下降
- $\lambda w$ 是额外的权重衰减
这在数学上等价于在损失函数里加 $\lambda \sum w^2$ 项。
所以很多人把 L2 正则化和 Weight Decay 混用。
但要注意:在 Adam 等自适应学习率优化器中,L2 正则化和 Weight Decay 并不完全等价。
Adam 对每个参数有自己的学习率调整,L2 正则化的效果会被自适应学习率"吃掉"一部分。
如果你用 Adam 优化器,想做 L2 正则化,需要用 AdamW——一个专门修复了这个问题的变体。

用生活场景来理解正则化:
考试评分通常不仅看答案对不对,还要看解题思路是否简洁优雅。
同样正确的两个答案,一个用了 10 行推导,一个用了 3 行推导,3 行的通常得分更高。
因为简洁的解法更可能是"真正理解了",而不是"碰巧试对了"。
正则化就是那个给复杂解法扣分的机制。
模型如果用很复杂的方式拟合了训练数据,那它的"分数"会被扣掉一部分。
这样模型就不会去死记硬背那些只在训练数据上有效的"小聪明"解法。
实战避坑:代码层面的经验总结
理论说完了,聊聊实际代码里怎么用。
Dropout 概率怎么选?
p 通常在 0.1 到 0.5 之间。
p 太小(比如 0.01),正则化效果不明显,等于没加。
p 太大(比如 0.8),模型学不到足够的信息,严重欠拟合。
一般从 0.2 或 0.3 开始尝试,然后根据效果调整。
Dropout 只在训练阶段生效
这是一个超级容易踩的坑。
# 训练阶段
model.train() # Dropout 生效
output = model(x)
# 测试阶段
model.eval() # Dropout 关闭
output = model(x)如果在测试时忘记切换到 model.eval(),Dropout 还在随机丢弃神经元,输出结果会非常不稳定。
很多新手在这个坑里卡了好几天。
PyTorch 代码示例
import torch
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.dropout1 = nn.Dropout(0.3) # p=0.3
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = self.dropout1(x) # Dropout
x = self.fc2(x)
return x
# 优化器带 L2 正则化(通过 weight_decay)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)训练循环里一定要记得模式切换:
for epoch in range(epochs):
model.train() # 切换到训练模式
for batch in train_loader:
optimizer.zero_grad()
output = model(batch)
loss = loss_fn(output, labels)
loss.backward()
optimizer.step()
model.eval() # 切换到评估模式
with torch.no_grad():
for batch in val_loader:
output = model(batch)
# 计算验证集指标Dropout 放在哪里?
一般放在全连接层(Linear/Dense)的后面。
卷积层后面通常不太用 Dropout,因为卷积层本身就具有参数共享的特性,已经有一定的正则化效果。
放在哪里效果最好?没有标准答案,需要实验。
一般从靠近输出的层开始尝试,因为输出层附近的层最容易过拟合。

把这些配置参数想象成调节"游戏难度"的旋钮:
- Dropout p 值越大,游戏越难(模型能学到的东西越少)
- L2 weight_decay 越大,游戏越难
- 数据量越大、模型越简单,可以把难度调低一点
需要反复摸索才能找到最佳配置。
最后聊两句
Dropout 和正则化看起来是两种完全不同的技巧。
一个是从网络结构入手,一个是从损失函数入手。
但它们在做同一件事:让模型在"记忆训练数据"和"学习通用规律"之间找到平衡。
Dropout 让每个神经元都得学会独立工作,不能依赖特定的"邻居"。
正则化给复杂模型加税,迫使模型保持简洁。
一个是"随机裁员",一个是"加复杂度税"。
都能让模型不再死记硬背,而是真正学会举一反三。
理解了背后的原理,你就能举一反三,根据实际问题灵活选择或组合这些工具。
你的数据量很大、模型很轻量?Dropout 是个好选择。
你的模型很复杂、担心权重太大?L2 正则化更合适。
有时候,两者一起用效果更好。
关键在于:它们都在做同一件事——让模型不要过度拟合训练数据。
下篇预告
Dropout 和正则化解决了"模型太复杂"的问题。
但处理图像这种数据,还有另一个思路:设计更适合图像的网络结构。
这就是下篇要聊的:卷积神经网络 CNN。
CNN 是怎么工作的?为什么它特别适合处理图像?
咱们下回分解。
