深度学习正则化:防止过拟合的关键技术
模型的训练目标是在训练集上最小化损失,但训练 loss 最低不代表泛化性能最好——当模型容量足够大时,它有能力记住训练集的每个样本包括噪声,导致验证集 loss 反而上升。这种现象称为过拟合。正则化是抑制过拟合、缩小训练误差与泛化误差之间差距的技术手段。
一、过拟合:正则化要解决的问题
1.1 什么是过拟合
模型容量(参数量)足够大时,它有能力"记住"训练集的每个样本——包括其中的噪声。表现为训练 loss 持续下降,但验证 loss 在某个 epoch 后开始上升:
| Epoch | Train Loss | Val Loss | 状态 |
|---|---|---|---|
| 4 | 0.068 | 0.082 | val loss 最低 |
| 5 | 0.056 | 0.083 | val 开始上升 |
| 10 | 0.026 | 0.094 | train↓ val↑ = 过拟合 |
1.2 过拟合的本质
模型在训练集上学到了"只对训练集有效"的模式,而不是数据的通用规律。一个记住了所有考试题答案的学生,换一套题就不会做。
正则化的目标:让模型在训练集上"学个差不多"就行,别学到连噪声都记住了。
二、Dropout:随机丢弃神经元
2.1 原理
训练时,每个 forward 随机将一部分神经元的输出置为零。比例由 p 控制(如 p=0.1 表示丢弃 10%)。被丢弃的神经元在当前步不参与前向传播和反向传播。
正常输出: [0.7, 0.3, 0.5, 0.8, 0.2]
Dropout(p=0.4): [0.7, 0.0, 0.5, 0.0, 0.0] ← 随机 40% 置零
每次丢弃的神经元不同,模型不能依赖任何单个神经元,被迫学习冗余表示——同一信息存在多个路径中,任何一个被丢掉也不影响整体。
推理时关闭 Dropout,所有神经元参与计算,输出乘以 1−p1-p1−p 补偿训练时的缩放(PyTorch 内部自动处理)。
2.2 代码
import torch.nn as nn
dropout = nn.Dropout(p=0.1)
在模型中的典型位置:Attention 输出之后、FFN 输出之后、Embedding 之后。
# 典型用法:在 Attention / FFN 的输出后各加一层 Dropout
self.attn_dropout = nn.Dropout(p=0.1)
self.resid_dropout = nn.Dropout(p=0.1)
2.3 常见错误
| 错误 | 后果 |
|---|---|
验证时忘记 model.eval() | Dropout 仍在丢弃,验证结果不稳定 |
| p 设太大(如 0.5) | 模型欠拟合,训练 loss 降不下来 |
| p 设太小(如 0.01) | 正则化效果几乎为零 |
| 推理时忘了关 Dropout | 输出不确定,同一输入每次结果不同 |
model.train() 和 model.eval() 的作用就是切换 Dropout 的开关:
model.train() # Dropout 开启
model.eval() # Dropout 关闭
三、Weight Decay(L2 正则化):惩罚大权重
3.1 原理
在损失函数上加一个惩罚项,让模型权重不要太大:
L总=L任务+λ∑iwi2L_{\text{总}} = L_{\text{任务}} + \lambda \sum_i w_i^2L总=L任务+λi∑wi2
λ\lambdaλ 是惩罚系数(即代码里的 weight_decay)。权重越大,惩罚越多。梯度更新时等价于:
wi←wi−η∂L∂wi−ηλwiw_i \leftarrow w_i - \eta \frac{\partial L}{\partial w_i} - \eta \lambda w_iwi←wi−η∂wi∂L−ηλwi
最后多了一项 −ηλwi-\eta \lambda w_i−ηλwi,每步把权重往零的方向拉一点。
3.2 为什么大权重 = 过拟合
大权重意味着模型对某些输入极度敏感,容易拟合训练数据中的噪声。小权重让决策边界更平滑,泛化更好。
3.3 代码
# PyTorch 原生
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.1)
3.4 哪些参数应该做 weight decay
不是所有参数都应该被惩罚:
| 参数类型 | 是否 weight decay | 原因 |
|---|---|---|
| Linear 的 weight | ✅ | 这是需要惩罚的权重矩阵 |
| Embedding 的 weight | 看情况 | 有的实现会做,有的不做 |
| LayerNorm 的 gamma/beta | ❌ | 归一化参数,不该被惩罚 |
| 所有 bias | ❌ | bias 是偏移量,不是权重 |
一种通用做法是按参数维度分组:2D 及以上做 decay,1D 不做:
decay_params = [p for n, p in param_dict.items() if p.dim() >= 2]
nodecay_params = [p for n, p in param_dict.items() if p.dim() < 2]
optim_groups = [
{'params': decay_params, 'weight_decay': weight_decay},
{'params': nodecay_params, 'weight_decay': 0.0}
]
3.5 L1 vs L2
| L1(Lasso) | L2(Ridge) | |
|---|---|---|
| 惩罚项 | ∑∣wi∣\sum \lvert w_i \rvert∑∣wi∣ | ∑wi2\sum w_i^2∑wi2 |
| 梯度 | sign(w)\text{sign}(w)sign(w)(w≠0w\neq 0w=0 时;w=0w=0w=0 处不可导,次梯度 ∈[−1,1]\in[-1,1]∈[−1,1]) | 2w2w2w(与权重大小成正比,越大缩得越多) |
| 效果 | 部分权重被精确压到 0(稀疏化) | 所有权重按比例缩小,趋近 0 但难精确到 0 |
| 深度学习 | 少用(稀疏性不利 GPU 并行) | 主流 |
一个常见误区:很多人以为 AdamW 的 weight_decay 就是 L2 正则化,两者并不等价。L2 正则化是把 λw2\lambda w^2λw2 加进损失,再由优化器按梯度更新;而 AdamW 采用解耦权重衰减(decoupled weight decay),直接对权重做 w←(1−ηλ)ww \leftarrow (1-\eta\lambda)ww←(1−ηλ)w,再叠加自适应梯度更新。在 SGD 下两者等价,但在 Adam 这类自适应优化器下,L2 的惩罚会被各参数的自适应学习率放大或缩小,效果不如解耦权重衰减——这正是 AdamW 被提出的动机(Loshchilov & Hutter, 2019)。
四、Early Stopping:训练到该停就停
4.1 原理
训练过程中监控验证集 loss,一旦 loss 不再下降(或开始上升),立即停止训练。在最优的那个 epoch 保存模型。
4.2 代码
best_val_loss = float('inf')
for epoch in range(1, EPOCHS + 1):
train_loss = train_one_epoch(...)
val_loss = evaluate(...)
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), 'best_model.pt')
else:
# val loss 连续 N 个 epoch 不降,可以提前停
pass
4.3 常见错误
| 错误 | 后果 |
|---|---|
| 只保存最后一个 epoch 的模型 | 过拟合后最优权重被覆盖 |
| 没有验证集 | 无法判断何时该停 |
| patience 设太短(如 1) | 正常的 loss 波动就触发了停止 |
| patience 设太长(如 50) | 白训几十个 epoch |
五、Data Augmentation:数据增强
5.1 原理
通过对训练数据做随机变换,"制造"出更多训练样本。模型每次看到的是经过变换的数据,不能死记原始样本,被迫学习变换不变的特征。
5.2 图像任务的典型手段
# PyTorch 图像增强
transform = transforms.Compose([
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.RandomCrop(32, padding=4), # 随机裁剪
transforms.ColorJitter(0.2, 0.2, 0.2), # 颜色抖动
transforms.ToTensor(),
])
5.3 文本任务的典型手段
| 手段 | 做法 | 示例 |
|---|---|---|
| 同义词替换 | 随机替换几个词为同义词 | “我喜欢猫” → “我喜爱猫” |
| 回译 | 翻译成另一种语言再翻回来 | “I love cats” → “我爱猫” |
| 随机删除 | 随机删掉一些词 | “我 喜欢 吃 苹果” → “我 吃 苹果” |
| 随机交换 | 交换相邻词的顺序 | “我 喜欢 苹果” → “喜欢 我 苹果” |
文本增强的效果通常不如图像显著,因为文本的词序和语义关系更敏感。
六、Label Smoothing:标签平滑
6.1 原理
把 one-hot 硬标签 [0, 0, 1, 0] 变成软标签 [0.033, 0.033, 0.9, 0.033]。防止模型对自己的预测过于自信——过于自信 = 过拟合。
6.2 公式
ysmooth=(1−ϵ)⋅yonehot+ϵKy_{\text{smooth}} = (1 - \epsilon) \cdot y_{\text{onehot}} + \frac{\epsilon}{K}ysmooth=(1−ϵ)⋅yonehot+Kϵ
其中 ϵ\epsilonϵ 是平滑系数(通常 0.1),KKK 是类别数。
6.3 代码
# PyTorch 内置
loss_fn = nn.CrossEntropyLoss(label_smoothing=0.1) # PyTorch >= 1.10
6.4 直觉
硬标签告诉模型"正确答案概率是 1,其他全是 0"。但世界上没有 100% 确定的事,有些其他类别也有道理。标签平滑让模型承认"其他类别也有一定概率",避免在训练集上过拟合到极端自信。
七、LayerNorm / BatchNorm:归一化也附带正则化
7.1 原理
归一化的主要目的是稳定训练,但会附带正则化效果。BatchNorm 使用 mini-batch 的均值和方差做归一化,引入了 batch 内的统计噪声——每个 batch 的统计量不同,等价于给输入加了一点随机扰动,有轻微的正则化作用。
LayerNorm 使用单个样本的统计量,没有这种噪声,正则化效果更弱。
7.2 定位
归一化不是正则化的主要手段,但附带了一些效果。不要把 BatchNorm 当正则化用——它的首要任务是让训练稳定。
八、总结
| 技术 | 机制 | 实现难度 | 效果强度 | 大模型使用情况 |
|---|---|---|---|---|
| Dropout | 随机丢弃神经元 | 一行代码 | 强 | ✅ GPT、LLaMA 全用 |
| Weight Decay | 惩罚大权重 | 优化器参数 | 中 | ✅ AdamW 标配 |
| Early Stopping | 验证 loss 不降就停 | 训练策略 | 强 | ✅ 训练必备 |
| Data Augmentation | 变换数据造更多样本 | 数据预处理 | 强(图像)/弱(文本) | 图像任务必备 |
| Label Smoothing | 硬标签变软标签 | 损失函数参数 | 弱 | ✅ 大模型训练常用 |
| BatchNorm | batch 统计噪声 | 模型结构 | 弱(附带) | 视觉模型用,LLM 不用 |
实际训练中通常组合使用。大模型训练的标准配置是 Dropout + Weight Decay + Early Stopping。
正则化的核心思想只有一个:让模型在训练集上学到"差不多"就行,别学过头。 所有技术都是这个思想的实现手段。
更多推荐
所有评论(0)