🏠 总目录📚 本教程 10 · 正则化全家桶
📑 本页目录(点开跳转)

10 · 正则化全家桶

36 分钟 | ⭐ 核心


🎯 一句话

正则化 = 一切「故意让模型别学得太顺」的手段。 反直觉但成立:给模型制造困难,它反而学得更好。

BatchNorm对一个特征,跨样本归一化LayerNorm对一个样本,跨特征归一化样本特征 →样本特征 →同一批数据,两种归一化方向完全不同⭐ 这就是 Transformer 必须用 LayerNorm 的原因:它不跨样本,所以 batch=1 和 batch=1000 结果一样而 BatchNorm 推理时 batch=1,只能用训练时存的统计量 —— 分布一漂就崩
同一批数据,两种归一化方向完全不同:BatchNorm 竖着切(跨样本),LayerNorm 横着切(跨特征)。⭐ 这就是 Transformer 必须用 LayerNorm 的原因 —— 它不跨样本,batch=1 和 batch=1000 结果一样。

🤔 先想明白:为什么"制造困难"会有用

   模型的目标:让训练集上的 loss 最小
   你的目标:  让【没见过的数据】上的 loss 最小

   ⚠️ 这两个目标【不一样】

   模型会走捷径:
   · 记住训练集里那个"图片右下角有水印的都是猫"
   · 记住"编号 7823 这个样本的答案是 3"

   → 这些在训练集上完美,在新数据上一文不值

正则化就是在堵这些捷径。

三种堵法,对应后面七件武器的三个族:

堵法 做什么 武器
让捷径不可靠 每次训练时随机破坏一部分信息 Dropout、数据增强
让捷径变贵 给"复杂的解"加罚款 权重衰减、L1
不给记忆的时间 在模型开始死记硬背之前停下 早停

🔑 数学原理第 2 章会告诉你: 权重衰减的真身是「给参数加一个均值为 0 的高斯先验」—— 也就是"在没有强证据时,我倾向于相信参数应该接近 0"。 正则化不是 hack,是把你的先验信念写进目标函数。


🧰 七件武器(按性价比排序)

最小二乘解L1(Lasso):菱形顶点在坐标轴上 → 有分量被压成 0最小二乘解L2(Ridge):圆圆上没有角 → 各分量都变小但不为 0为什么 L1 产生稀疏、L2 不产生?答案是「形状」灰色椭圆是损失等高线,彩色区域是正则约束 —— 解落在两者第一次相切的地方⭐ 菱形的「角」正好落在坐标轴上,而等高线很容易先碰到角 —— 碰到角 = 那个维度的系数正好是 0圆是光滑的,没有任何位置比别处更容易被碰到,所以不会恰好取 0
灰色椭圆是损失等高线,彩色区域是正则约束 —— 解落在两者第一次相切的地方。⭐ 菱形的「角」正好在坐标轴上,等高线很容易先碰到角 —— 碰到角 = 那个系数正好是 0。圆是光滑的,没有哪里比别处更容易被碰到,所以不会恰好取 0。

① 加数据 —— 永远的第一名 ⭐

第 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 行手写实现,三个最容易写错的点都标出来了

✅ 检查点

  1. 正则化在"堵"什么?三种堵法各对应哪些武器?
  2. 七件武器里性价比最高的是哪个?为什么它特殊?
  3. 数据增强翻车的判断标准是什么?举两个错误增强的例子。
  4. 早停有哪两个必踩的坑?
  5. 为什么 Adam + L2 ≠ AdamW?哪些参数不该加 weight decay?
  6. Dropout 为什么有效?测试时怎么处理?怎么自检忘没忘 eval()
  7. 为什么 Transformer 用 LayerNorm 而不是 BatchNorm?(说出两条)
  8. 为什么 BN 层前的卷积不需要 bias?
  9. 标签平滑在哪两个场景不该用?
  10. 训练 loss 高但验证 loss 低,该怀疑什么?
👀 答案
  1. 捷径(模型记住训练集特有的东西)。三种:让捷径不可靠(Dropout、增强)、让捷径变贵(权重衰减、L1)、不给记忆时间(早停)。
  2. 加数据——唯一同时降方差又不抬高偏差的手段;其他所有正则化都是拿偏差换方差。
  3. 标准:增强后一个人类专家会不会改变答案。错误例子:手写数字水平翻转(6 变镜像)、医学影像左右翻转("病灶在左肺"是关键信息)、文本打乱词序、时间序列重排。
  4. 忘了回滚最佳权重(只 break 不 load,保留的是已过拟合的模型)②patience 太小,学习率调度让 loss 暂时上升时被误停。
  5. Adam 里 L2 加进梯度会被自适应学习率缩放,导致大梯度参数罚得少;AdamW 把衰减直接作用在权重上绕开缩放。偏置和 LayerNorm/BatchNorm 参数不加(p.ndim <= 1 的都是)。
  6. 逼迫网络不依赖特定神经元、学到冗余表示;且相当于隐式集成 2ⁿ 个子网络。测试时全部保留、框架自动缩放,但必须调 model.eval()自检:同一输入跑两次,输出不同就是忘了 eval()。
  7. ①序列长度可变,padding 会污染 batch 统计 ②推理时 batch=1,BN 只能用 running stats,分布一偏移就崩;LN 不跨样本,batch=1 和 1000 结果完全一样。
  8. BN 会减去均值,bias 直接被抵消,白算。设 bias=False
  9. ①需要精确概率的任务(风控、竞价)——会系统性压低置信度 ②知识蒸馏的教师模型——需要真实概率分布。
  10. 多半是 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 ⭐⭐

打卡记录保存在你的浏览器里,首页能看到总进度