📑 本页目录(点开跳转)
10 · 正则化全家桶
⏱ 36 分钟 | ⭐ 核心
🎯 一句话
正则化 = 一切「故意让模型别学得太顺」的手段。 反直觉但成立:给模型制造困难,它反而学得更好。
🤔 先想明白:为什么"制造困难"会有用
模型的目标:让训练集上的 loss 最小
你的目标: 让【没见过的数据】上的 loss 最小
⚠️ 这两个目标【不一样】
模型会走捷径:
· 记住训练集里那个"图片右下角有水印的都是猫"
· 记住"编号 7823 这个样本的答案是 3"
→ 这些在训练集上完美,在新数据上一文不值
正则化就是在堵这些捷径。
三种堵法,对应后面七件武器的三个族:
| 堵法 | 做什么 | 武器 |
|---|---|---|
| 让捷径不可靠 | 每次训练时随机破坏一部分信息 | Dropout、数据增强 |
| 让捷径变贵 | 给"复杂的解"加罚款 | 权重衰减、L1 |
| 不给记忆的时间 | 在模型开始死记硬背之前停下 | 早停 |
🔑 数学原理第 2 章会告诉你: 权重衰减的真身是「给参数加一个均值为 0 的高斯先验」—— 也就是"在没有强证据时,我倾向于相信参数应该接近 0"。 正则化不是 hack,是把你的先验信念写进目标函数。
🧰 七件武器(按性价比排序)
① 加数据 —— 永远的第一名 ⭐
第 5 章说过:唯一能同时降方差又不增偏差的手段。 有数据就别折腾别的。
其他所有正则化:降方差,但【会抬高偏差】(模型被限制了)
加数据: 降方差,偏差【不变】 ⭐ 白赚
② 数据增强 —— 免费造数据
图像:翻转、裁剪、旋转、颜色抖动、Mixup、CutMix
文本:同义词替换、回译、随机删词
音频:变速、变调、加背景噪声
通用:给输入加轻微噪声
💡 本质:告诉模型「这些变化下答案不变」——把先验知识注入进去。
⚠️ 增强必须"语义保持",这是最容易翻车的地方:
❌ 错误的增强 为什么错 手写数字做水平翻转 6 会变成镜像,2 会变成不认识的字符 医学影像做左右翻转 "病灶在左肺"是关键信息,翻转后标签就错了 文本做随机打乱词序 "狗咬人"和"人咬狗"不是一回事 时间序列做随机重排 直接破坏了因果顺序 判断标准一句话:增强后,一个人类专家会不会改变他的答案?会 → 别用。
🔗 Kaggle 第 10 章和第 8 章有大量实战增强策略。
③ 早停 Early Stopping —— 零成本,必用
验证集 loss ▲
│╲
│ ╲___
│ ╲___╱╲ ← 从这开始过拟合
│ ↑ 停在这,回滚到最佳权重
└────────────► epoch
import torch
best, patience, wait = float('inf'), 10, 0
for epoch in range(200):
train_one_epoch()
val = evaluate()
if val < best:
best, wait = val, 0
torch.save(model.state_dict(), "best.pt") # ⭐ 保存最佳
else:
wait += 1
if wait >= patience:
model.load_state_dict(torch.load("best.pt")) # ⭐ 回滚
break
⚠️ 两个必坑:
| 坑 | 后果 |
|---|---|
| 忘了回滚(只 break 不 load) | 你保留的是"已经开始过拟合的那个模型",白停了 ⭐ |
| patience 太小(比如 2) | 学习率调度会让 loss 暂时上升,你会在它还能变好的时候停掉 |
💡 patience 怎么定:至少要大于学习率调度的一个周期。 用
ReduceLROnPlateau时,patience 要比它的 patience 大 2~3 倍。
④ 权重衰减 L2 / AdamW
第 3 章讲过原理。深度学习里通过 weight_decay=0.01 实现。
⚠️ 两个必须知道的细节:
① 偏置和 LayerNorm/BatchNorm 的参数【不加】衰减
→ 衰减它们没意义(不影响模型复杂度)还有害(LN 的 γ 被拉向 0 会破坏归一化)
② Adam + L2 ≠ AdamW ⭐
Adam 里把 L2 加进梯度 → 会被自适应学习率缩放 → 大梯度参数被罚得少
AdamW 把衰减【直接作用在权重上】,绕开自适应缩放 → 这才是对的
# ⭐ 正确的分组写法(几乎所有大模型训练脚本都这么写)
import torch
decay, no_decay = [], []
for n, p in model.named_parameters():
if p.ndim <= 1 or "bias" in n: # 1 维的都是 bias/norm 参数
no_decay.append(p)
else:
decay.append(p)
opt = torch.optim.AdamW([
{"params": decay, "weight_decay": 0.01},
{"params": no_decay, "weight_decay": 0.0}, # ⭐
], lr=3e-4)
⑤ Dropout —— 训练时随机"停工"
训练时:每个神经元以概率 p 被临时置 0
测试时:全部保留(框架自动处理缩放)
💡 为什么有用:
· 逼迫网络不能依赖某个特定神经元 → 学到冗余表示
· 相当于每次训练一个不同的子网络 → 隐式集成
💡 "隐式集成"有多大规模:n 个神经元 → 2ⁿ 个可能的子网络。 一个 1000 神经元的层,Dropout 相当于在集成 2¹⁰⁰⁰ 个网络的平均。 🔗 这就是数学原理第 8 章说的"集成降方差"在起作用。
经验值:
| 位置 | p |
|---|---|
| 全连接层 | 0.2 – 0.5 |
| 卷积层 | 通常不用或 ≤0.1(BN 已经起了正则作用,且卷积参数本来就少) |
| Transformer | 0.1(attention 和 FFN 后各一个) |
| 输入层 | 很少用,用也 ≤0.1 |
⚠️ 最常见的两个 bug:
① 忘了 model.eval() → 测试时 Dropout 还在随机丢,结果每次都不一样
② 忘了 model.train() → 恢复训练时 Dropout 没开,正则失效
💡 一个自检技巧:同样的输入跑两次,如果输出不同 → 你忘了
eval()。⭐ 上面那句「框架自动处理缩放」,缩放到底放在哪一边? 这是 Dropout 唯一的考点,而这一节故意跳过了它:丢掉 30% 之后这一层输出的期望只剩 70%, 补法有两种(训练时除 $1-p$ 的 inverted dropout,还是推理时整层乘 $1-p$),现代框架为什么全选前者。 🔗 附录 C 第 6 题把这 8 行写出来了,含实测输出(训练均值 1.0018 vs 推理 1.0000)。
⑥ BatchNorm / LayerNorm —— 主要作用是稳定训练
BatchNorm:对一个 batch 内的同一个特征做归一化 → CNN 常用
LayerNorm:对一个样本的所有特征做归一化 → Transformer/RNN 必用 ⭐
一个 batch 的数据(4 个样本 × 3 个特征):
特征1 特征2 特征3
样本1 [ a1 a2 a3 ]
样本2 [ b1 b2 b3 ] BatchNorm:↓ 竖着归一化(跨样本)
样本3 [ c1 c2 c3 ] LayerNorm:→ 横着归一化(跨特征)⭐
样本4 [ d1 d2 d3 ]
为什么 Transformer 用 LayerNorm 不用 BatchNorm:
| 原因 | 说明 |
|---|---|
| 序列长度可变 | batch 内不同样本长度不同,padding 位会污染统计量 |
| 推理时 batch=1 | BN 只能用训练时存的 running mean/var,一旦分布有偏移就崩 ⭐ |
| LN 不跨样本 | 每个样本独立归一化,batch=1 和 batch=1000 结果完全一样 ⭐ |
⚠️ BN 的三个坑:
| 坑 | 解法 |
|---|---|
| 小 batch(<16)统计不准,效果会崩 | 改用 GroupNorm 或 LayerNorm |
| BN 层之前的卷积不需要 bias | BN 会减去均值,bias 直接被抵消 → 白算(bias=False) |
| 微调时 BN 的 running stats 还在更新 | 冻结 backbone 时记得 bn.eval(),否则统计量被新数据带跑 ⭐ |
⭐ 「BN 竖着算、LN 横着算」是结论,白板上要写的是那 15 行: 🔗 附录 C 第 4 题(LayerNorm)和第 5 题(BatchNorm)。 那边补了这一节没说的两件事:方差要用有偏(分母 n),但
running_var存的是无偏(× n/(n−1))—— 这一行是手写实现和 PyTorch 对不齐的头号原因;以及else分支漏了会怎样(batch=1 时方差恒为 0,输出直接爆掉)。
⑦ 标签平滑 Label Smoothing
硬标签:[0, 1, 0] → 模型被迫无限自信
平滑后:[0.03, 0.94, 0.03]
✅ 防过度自信、改善校准、常小幅提分
💡 为什么硬标签有害:要让 softmax 输出精确的 1,那个 logit 必须趋于 +∞—— 模型会把权重越推越大来追求这个永远达不到的目标,这本身就是一种过拟合。
⚠️ 两个不该用的场景:
- 需要精确概率的任务(如风控打分、竞价)——平滑会系统性压低置信度
- 知识蒸馏的教师模型——你需要它输出真实的概率分布
🧭 怎么组合(可以照抄的配方)
| 场景 | 配方 |
|---|---|
| 表格数据 MLP | 早停 + weight_decay(0.01) + Dropout(0.2~0.3) |
| 树模型 / GBDT | max_depth + min_child_weight + subsample + colsample + 小 learning_rate + 早停 ⭐ |
| CNN 图像 | 早停 + 数据增强 ⭐ + BN + weight_decay(1e-4) |
| Transformer/NLP | 早停 + LayerNorm + Dropout(0.1) + AdamW + warmup + 标签平滑 |
| 微调预训练模型 | 小学习率 + 早停 + 冻结前几层(正则化基本靠预训练本身)⭐ |
| 数据极少 | 加数据 > 强增强 > 强正则 > 换更小的模型 / 用预训练 |
🔑 调试顺序:先确认模型能过拟合(在小数据上跑到训练 loss ≈ 0), 再加正则往回收。一上来就堆正则,你分不清是欠拟合还是模型有 bug。 —— 这条是第 11 章的核心方法论。
⚠️ 一个重要提醒:正则化不是越多越好
正则太弱 → 过拟合(训练好测试差)
正则太强 → 欠拟合(两边都差)← 新手常见!
加了 Dropout(0.5) + weight_decay(0.1) + 强增强 + 早停
→ 模型什么都学不会,训练 loss 都降不下去
每次只加一种,看验证集怎么变。
🩺 一张诊断表
| 训练 loss | 验证 loss | 诊断 | 该动哪件武器 |
|---|---|---|---|
| 低 | 高 | 过拟合 | 加正则(按性价比顺序:数据→增强→早停→衰减→Dropout) |
| 高 | 高 | 欠拟合 | 减正则 / 加大模型 / 训久一点 / 调学习率 |
| 低 | 低 | ✅ 健康 | 可以试着加大模型再来一轮 |
| 高 | 低 | ⚠️ 异常 | 多半是 bug:验证集太简单、泄漏、或 Dropout 没关(训练 loss 被 Dropout 抬高了)⭐ |
💡 最后一行值得记住:验证 loss 比训练 loss 还低时,先怀疑 Dropout 和 BN—— 训练时它们在给模型"加难度",所以训练 loss 天然偏高。轻微的这种现象是正常的。
🔗 和站内其他章的关系
| 相关的地方 | 这里的对应 |
|---|---|
| 第 3 章 L1/L2 正则 | ④ 权重衰减的线性模型版 |
第 4 章 GBDT 的 learning_rate、树深限制 |
树模型的正则化手段 |
| 第 5 章"加数据只降方差" | 为什么加数据排第一 |
| 数学原理第 2 章 | 正则化 = 先验(λ = σ²/τ²)⭐ |
| 数学原理第 9 章 | 正则化 = 缩小假设空间 = 降低样本需求 |
| Kaggle 第 2 章 | 竞赛实战版(含 FGM/R-Drop 等进阶手段) |
| 推荐算法第 5 章 MF 的 λ | L2 正则 |
| 附录 C 第 4、5、6 题 ⭐ | 这一章讲的是这三个东西在干什么;面试要的是它们长什么样——LayerNorm / BatchNorm / Dropout 各 10~20 行手写实现,三个最容易写错的点都标出来了 |
✅ 检查点
- 正则化在"堵"什么?三种堵法各对应哪些武器?
- 七件武器里性价比最高的是哪个?为什么它特殊?
- 数据增强翻车的判断标准是什么?举两个错误增强的例子。
- 早停有哪两个必踩的坑?
- 为什么 Adam + L2 ≠ AdamW?哪些参数不该加 weight decay?
- Dropout 为什么有效?测试时怎么处理?怎么自检忘没忘
eval()? - 为什么 Transformer 用 LayerNorm 而不是 BatchNorm?(说出两条)
- 为什么 BN 层前的卷积不需要 bias?
- 标签平滑在哪两个场景不该用?
- 训练 loss 高但验证 loss 低,该怀疑什么?
👀 答案
- 堵捷径(模型记住训练集特有的东西)。三种:让捷径不可靠(Dropout、增强)、让捷径变贵(权重衰减、L1)、不给记忆时间(早停)。
- 加数据——唯一同时降方差又不抬高偏差的手段;其他所有正则化都是拿偏差换方差。
- 标准:增强后一个人类专家会不会改变答案。错误例子:手写数字水平翻转(6 变镜像)、医学影像左右翻转("病灶在左肺"是关键信息)、文本打乱词序、时间序列重排。
- ①忘了回滚最佳权重(只 break 不 load,保留的是已过拟合的模型)②patience 太小,学习率调度让 loss 暂时上升时被误停。
- Adam 里 L2 加进梯度会被自适应学习率缩放,导致大梯度参数罚得少;AdamW 把衰减直接作用在权重上绕开缩放。偏置和 LayerNorm/BatchNorm 参数不加(p.ndim <= 1 的都是)。
- 逼迫网络不依赖特定神经元、学到冗余表示;且相当于隐式集成 2ⁿ 个子网络。测试时全部保留、框架自动缩放,但必须调
model.eval()。自检:同一输入跑两次,输出不同就是忘了 eval()。 - ①序列长度可变,padding 会污染 batch 统计 ②推理时 batch=1,BN 只能用 running stats,分布一偏移就崩;LN 不跨样本,batch=1 和 1000 结果完全一样。
- BN 会减去均值,bias 直接被抵消,白算。设
bias=False。 - ①需要精确概率的任务(风控、竞价)——会系统性压低置信度 ②知识蒸馏的教师模型——需要真实概率分布。
- 多半是 bug 或正常的 Dropout/BN 效应:训练时 Dropout/BN 在加难度,训练 loss 天然偏高(轻微正常)。严重时怀疑验证集太简单或数据泄漏。
🛑 可以停在这里
⚡ 走神救援
正则化=堵模型走捷径,三种堵法:让捷径不可靠(Dropout/增强)、让捷径变贵(权重衰减)、不给记忆时间(早停)。七武器按性价比:①加数据(唯一降方差不抬高偏差的)②数据增强(判断标准:人类专家会不会改答案——手写数字别水平翻、医学影像别左右翻)③早停(两坑:忘了回滚最佳权重、patience太小被学习率调度误停)④权重衰减(Adam+L2≠AdamW,AdamW 绕开自适应缩放;p.ndim<=1 的 bias/norm 参数不加)⑤Dropout(隐式集成 2ⁿ 个子网络;同一输入跑两次输出不同=忘了eval())⑥BN/LN(Transformer 必须 LN——LN 不跨样本所以 batch=1 也一样;BN 前的卷积不要 bias;微调时冻结 backbone 记得 bn.eval())⑦标签平滑(风控和蒸馏教师别用)。调试铁律:先确认模型能过拟合,再加正则往回收。诊断表:训练低验证高=加正则;两边都高=减正则;训练高验证低=先怀疑 Dropout/BN。
下一节 👉 11-训练调试手册.md ⭐⭐