本文是《深度学习基础:从反向传播到残差》系列的第 4 篇(共六篇)。上一篇:优化器——从 SGD 到 AdamW 与学习率调度;下一篇:CNN——从 LeNet 到 ResNet,再到 ViT

L2 导读讲过经典机器学习的第一定律:模型容量超过数据量就会过拟合。一个 160 万参数的网络在 4000 个样本上训练,按这条定律应该背下每一个样本、在测试集上一败涂地。第九章的实验里它做到了前半句——训练错误率 0.0%——却没有做到后半句:测试错误率 14.5%,是所有宽度里最好的。而一个参数只有样本两倍的小网络,测试错误率 43%。

这个现象——参数越多泛化越好,越过某个点之后——在 2017 年前后被系统地记录下来(Zhang 等 2017;Belkin 等 2019),它推翻的不是”过拟合”这个概念,而是”容量”这个度量。本篇讲三件事:为什么深网络在参数远多于样本时不过拟合(隐式正则化与 double descent);显式正则化手段各自在做什么(dropout、weight decay、早停、label smoothing);以及什么时候过拟合会回来——因为它一定会回来,SFT 训几个 epoch 之后模型开始逐字背答案,就是它。全篇的核心问题是:

一个参数量是样本量几百倍的网络为什么不过拟合?同一个网络在什么数据规模、多少个 epoch 之后会开始过拟合?怎么提前看到?

一、总览:两个体制

1. 参数 / 数据比决定你在哪个体制

泛化问题的第一步是算一个数:参数量除以训练 token(或样本)数。它把深度学习分成两个体制:

场景 参数 \(N\) 数据 \(D\) \(N / D\) 体制
本文实验:MNIST 子集,宽 2048 1.6M 4000 样本 407 极度过参数化——但不过拟合
Llama-3-8B 预训练 8.0B 15T token 0.0005 数据远多于参数,每个 token 只见一次
Chinchilla 最优 \(N\) \(20N\) 0.05 数据仍多于参数
SFT:8B 模型,1 万条指令 8.0B 约 5M token 1600 极度过参数化——会过拟合
奖励模型:8B,10 万偏好对 8.0B 约 50M token 160 极度过参数化——reward hacking 的来源

LLM 预训练在第二行:每个参数只”分到”两千个 token,模型远远背不下数据,过拟合不是主要风险,所以几乎不用 dropout、只训一个 epoch(第六章)。SFT 与奖励模型在第四、五行:与本文实验同一个体制,参数是数据的几百倍。同一个体制里本文实验不过拟合、SFT 却会——差别在于训练了多久、任务是什么、数据有没有噪声。这就是本文要讲清楚的事。

2. 本文的章节安排

主题 内容
经典视角与它失效的地方 容量、偏差-方差、过拟合;参数远多于样本时经典理论的预言与现实
Double descent 实测 11 个宽度的 U 形 + 第二次下降;插值阈值处的尖峰;为什么越过之后再变好
隐式正则化 SGD 偏好平坦解、最小范数解;随机标签也能背下来说明容量不是度量
显式正则化 weight decay 作为先验、dropout 的期望等价与集成视角、早停、label smoothing;实测四种配置
为什么 LLM 预训练不用 dropout、只训一个 epoch 数据体制;多 epoch 的收益递减(Muennighoff 等 2023)
过拟合回来的时候 小语言模型三种数据量下的 train / held-out 曲线与逐字记忆率;SFT 与奖励模型
诊断 三个信号:gap、held-out 拐点、记忆探针;loss 涨而准确率不变的含义
实验 代码与完整结果
本文小结  

二、经典视角与它失效的地方

1. 经典的预言

L2 导读的偏差 - 方差分解说:模型太简单,偏差大(欠拟合);模型太复杂,方差大(过拟合);测试误差随容量呈 U 形,最优点在中间。经典理论里”容量”由参数量(或 VC 维一类的度量)决定,所以规则是:参数量不要超过样本量太多,超过了就加正则化。

2. 现实

第九章的实验:4000 个 MNIST 样本,标签有 20% 被随机替换(制造噪声,让”背下训练集”与”学到规律”能区分),两层 MLP 宽度从 2 扫到 2048,Adam 训 6000 步到收敛:

宽度 参数量 参数 / 样本 训练错误率 测试错误率 测试 loss
2 1.6K 0.4 54.5% 60.3% 1.95
4 3.2K 0.8 26.0% 42.6% 2.07
8 6.4K 1.6 3.4% 43.4% 5.48
16 12.7K 3.2 0.0% 35.4% 4.44
32 25K 6.4 0.0% 27.3% 2.55
64 51K 12.7 0.0% 22.4% 1.82
128 102K 25 0.0% 19.3% 1.35
256 204K 51 0.0% 16.8% 1.14
512 407K 102 0.0% 16.0% 1.08
1024 814K 204 0.0% 15.4% 1.01
2048 1.63M 407 0.0% 14.5% 0.95

从宽度 16 起训练错误率就是 0——网络把 20% 的错误标签也全部背下来了。经典理论说此后测试误差应该越来越差。实际上:宽度 8 到 16 附近确实有一个尖峰(测试 loss 从 2.07 冲到 5.48),然后一路变好,到 2048 时是全表最好。参数是样本 407 倍的网络,泛化最好。

经典理论没有错,错的是用参数量当容量。深网络的”有效容量”由训练过程决定——优化器会在所有能拟合训练集的解里挑一个特定的,而这个解的复杂度远低于参数量所允许的。这就是隐式正则化(第四章)。在讲它之前,先看清那个尖峰。

三、Double descent

1. 曲线的三段

上面的表画出来是 Belkin 等 2019 命名的 double descent

测试误差
  │
  │ ╲                    ╱╲
  │  ╲                  ╱  ╲
  │   ╲                ╱    ╲
  │    ╲              ╱      ╲
  │     ╲____________╱        ╲___________
  │                                        ‾‾‾‾‾‾‾‾‾‾‾‾‾
  │      欠拟合      │ 插值阈值 │      过参数化:第二次下降
  └──────────────────┴─────────┴──────────────────────────▶ 模型容量
                   训练误差刚到 0 的地方

第一段是经典的 U 形(宽度 2 → 4:偏差下降);尖峰在插值阈值——模型刚好能把训练集(含噪声标签)拟合到零误差的地方(宽度 8 → 16);越过阈值后是第二次下降,越宽越好。Nakkiran 等 2019 在 ResNet、Transformer 上都观察到同样的形状,并发现它也随训练时长出现(epoch-wise double descent):训练更久有时先变差再变好。

2. 尖峰为什么出现

在插值阈值上,能拟合全部训练样本的解几乎唯一——参数刚够用,网络没有选择,只能扭曲自己去穿过每一个点,包括 20% 的错误标签。穿过错误标签需要在那些点附近做出剧烈的弯曲,弯曲波及周围的测试样本,测试误差爆炸;测试 loss 比错误率更敏感(5.48),因为那些被扭曲的区域给出了自信而错误的预测。

越过阈值后,能拟合训练集的解变成一个高维的集合,优化器可以从中挑最”平缓”的那个:穿过错误标签的点,但用最小的弯曲,对周围的影响最小。宽度越大,这个集合越大,能找到的解越平缓。这就是第二次下降。

3. 对实践的含义

两条。第一,不要停在插值阈值附近:一个”刚刚好能拟合”的模型往往是最差的选择,要么明显小(接受欠拟合),要么明显大(进入第二次下降)。第二,加数据可能让同一个模型变差:数据量增加会把插值阈值往右推,一个原本在第二次下降区间的模型可能被推回尖峰上(Nakkiran 等的 “more data hurts”)。这两条在小数据微调时都会遇到。

四、隐式正则化

1. 优化器挑了哪个解

过参数化时训练集对解的约束不够,剩下的自由度由优化器填上。SGD(以及 Adam)不是任意地填,它有系统性的偏好:

最小范数解。 对线性模型(以及某些条件下的宽网络),从零初始化出发的梯度下降收敛到所有插值解中范数最小的那个——梯度始终落在数据张成的子空间里,垂直方向的分量从不被更新。范数小意味着函数平缓。

平坦极小值。 SGD 的噪声(上一篇第二章,尺度 \(\eta / \sqrt{B}\))让参数在尖锐的极小值里待不住——一点扰动就跳出去——而在平坦的极小值里能停下。平坦的极小值对参数扰动不敏感,通常也对输入扰动不敏感,泛化更好。这解释了一个反复被观察到的现象:大 batch(噪声小)训出来的模型泛化略差,即使训练 loss 相同;也解释了为什么学习率衰减要留到最后——早期的大噪声是在挑盆地。

先学简单的。 网络在训练早期先拟合低频、简单的模式,晚期才去拟合高频、复杂的(包括噪声标签)。这就是早停有效的原因:在网络开始拟合噪声之前停下。

2. 随机标签也能背下来

Zhang 等 2017 做了一个干净的实验:把 CIFAR-10 的标签全部打乱成随机的,标准的 CNN 照样能把训练集拟合到零误差。结论是网络的容量足以记住任何数据,所以容量不是它在真实数据上泛化好的原因。泛化好,是因为真实数据里有结构、优化器先学结构、结构比噪声容易拟合。

这个实验也给出了本篇最有用的一个诊断工具:记忆探针——如果模型能逐字复现训练样本,它在背,不是在学(第七、八章)。

五、显式正则化

隐式正则化是免费的;显式正则化是额外加的约束,每种都可以理解为”把模型拉向某种简单解的先验”。

1. Weight decay:高斯先验

给参数一个均值 0、方差 \(\sigma^2\) 的高斯先验 \(p(\theta) \propto \exp(-\|\theta\|^2 / 2\sigma^2)\),最大后验估计(L0 导读第三章的 MAP)在负对数似然上多出一项 \(\frac{1}{2\sigma^2}\|\theta\|^2\)——这就是 \(L_2\) 正则,\(\lambda = 1/\sigma^2\)。它说的先验是”参数应该小”,也就是”函数应该平缓”。上一篇第五章讨论了它在 Adam 下的正确形式(AdamW)与它在 LLM 里作为”范数平衡点”的含义;这里补一个数字:第九章的实验里,1000 个样本、AdamW \(\lambda = 0.5\),300 个 epoch 后测试 loss 是 0.42,不加是 0.55——weight decay 把训练后期测试 loss 的上升压平了大半。

2. Dropout:期望等价与集成

Dropout(Srivastava 等 2014)训练时以概率 \(p\) 把每个激活置零,并把剩下的乘 \(1 / (1 - p)\)(inverted dropout,让期望不变:\(\mathbb{E}[\tilde a] = (1 - p) \cdot a / (1 - p) = a\));推理时什么都不做。两种理解:

集成视角。 每个 batch 用的是一个随机子网络,\(n\) 个神经元有 \(2^n\) 个子网络,训练在同时训所有子网络的一个加权平均;推理时用完整网络,近似于这 \(2^n\) 个子网络的集成(L2 导读第二章:集成降方差)。

噪声视角。 Dropout 给激活注入乘性噪声,逼迫每个神经元不依赖特定的其他神经元(防止”共适应”),学到的特征更冗余、更稳健。

代价:训练时有效容量变小(每步只有 \((1 - p)\) 的神经元在工作),收敛更慢;对已经有足够数据的场景收益小甚至为负——第六章讲 LLM 为什么不用它。第九章的实验里 \(p = 0.5\) 在前 10 个 epoch 与 weight decay 效果相当(测试 loss 0.39),但训到 300 个 epoch 后反而是最差的(0.62):dropout 拖慢了过拟合的速度,没有改变它的终点。

3. 早停

在验证 loss 开始上升时停止训练。对线性模型可以证明它等价于 \(L_2\) 正则,正则强度反比于训练步数 × 学习率(\(\lambda \approx 1 / (\eta t)\));对深网络它是第四章”先学简单的”的直接利用。它几乎没有代价,只要求留出验证集并定期评估——这是横切”实验方法论”里最便宜的一条规则。第九章:不加正则化时最优停止点在第 10 个 epoch(测试 loss 0.40),之后一路涨到 0.55。

4. Label smoothing 与数据增强

Label smoothing(Szegedy 等 2016)把 one-hot 标签换成 \((1 - \epsilon)\) 在正确类、\(\epsilon / (C - 1)\) 均分给其他类,\(\epsilon\) 常取 0.1。它阻止 logits 无限拉大(one-hot 下 loss 只有在 logit 差趋于无穷时才到零),改善校准;代价是模型的置信度被人为压低,对需要校准概率的下游(蒸馏、奖励模型)要小心。数据增强(图像的裁剪翻转、文本的回译与同义替换)是给数据加先验:”这些变换不改变标签”;它是 CV 里最强的正则化,在 LLM 预训练里几乎不用——数据够多,且语言的”标签不变的变换”难以定义。

5. 实测:四种配置

1000 个训练样本、宽 512、AdamW \(\eta = 10^{-3}\)、300 个 epoch:

配置 第 10 epoch 测试 loss 第 300 epoch 测试 loss 第 300 epoch 训练 loss 最优早停点 准确率(300 ep)
无正则化 0.402 0.548 0.000 ep 10 89.1%
weight decay 0.5 0.392 0.422 0.000 ep 40 89.6%
dropout 0.5 0.391 0.617 0.000 ep 10 89.5%
两者都加 0.386 0.421 0.000 ep 10 89.8%

四种配置的准确率都在 89% 上下,几乎没有差别;差别全在测试 loss——它随训练持续上升,而准确率不动。这是过拟合在深网络上最常见的形态:模型没有把更多测试样本判错,而是对判错的那些越来越自信(logits 越来越大)。第八章会把它作为一个诊断信号。

六、为什么 LLM 预训练不用 dropout、只训一个 epoch

1. 数据体制

回到第一章的表:Llama-3-8B 每个参数分到约 1900 个 token。这个体制里模型是欠拟合的——训练 loss 与验证 loss 几乎重合(差在 0.01 nat 量级),加更多数据、更多算力 loss 还会降(这正是 scaling law 描述的)。过拟合不是风险,容量才是瓶颈。任何减少有效容量的正则化(dropout 最典型)都在浪费算力。GPT-2 还用 dropout 0.1;GPT-3 之后的大模型几乎全部去掉;Llama、Qwen、DeepSeek 的 config 里没有 dropout。

Weight decay 保留了下来,但如上一篇所述,它在这里的角色主要是控制参数范数与有效学习率,不是防过拟合。

2. 一个 epoch

数据多到训不完一遍时,多 epoch 没有意义——每个 token 只见一次,也就不存在”背下来”。当数据受限、必须重复时,Muennighoff 等 2023 的实验给出了量化结论:重复到 4 个 epoch 以内,loss 与用同样多的全新数据几乎没有差别;之后收益迅速递减,到几十个 epoch 时重复数据的价值趋近于零。他们据此修正了 scaling law,给出了数据受限下参数与 epoch 的最优分配。当前实践:高质量数据(代码、数学、教科书)重复 2–4 个 epoch 是常见做法,网页数据基本只过一遍。

3. 与本文实验的关系

第一章的表说清了为什么第二章的 MLP 与 LLM 预训练看起来矛盾却不矛盾:前者在过参数化体制,靠隐式正则化不过拟合,但如果训得足够久、数据有噪声,测试 loss 仍会缓慢上升(第五章表里的”无正则化”一行);后者在数据主导体制,根本没到能过拟合的地方。SFT 把一个 8B 的模型放回第一种体制——下一章。

七、过拟合回来的时候

1. 小语言模型上的三种数据量

第九章训了一个字符级的 MLP 语言模型(上下文 8 个字符,约 12 万参数),语料是 Python 标准库的源码,训练数据分别取 2 万、20 万、200 万字符,每个 epoch 后测 held-out loss,并做一个记忆探针:随机取训练文本里的 200 个位置,给模型 8 个字符的前缀,贪心生成 32 个字符,统计与原文逐字相同的比例。

训练字符数 参数 / 字符 held-out loss 最低点 最低值 此后 逐字记忆率
20K 5.8 第 16 epoch 2.41 涨到 2.98(40 ep);训练 loss 降到 0.44 2.5%(8 ep)→ 5.5%(16 ep)→ 10%(40 ep)
200K 0.58 第 8 epoch 1.61 微涨到 1.62(10 ep);gap 从 −0.14 涨到 +0.51 ≤ 1%
2M 0.06 第 10 epoch 仍在降 1.60 0%

三行是同一个模型、同一份代码,只有数据量不同:2 万字符时第 16 个 epoch 起 held-out loss 掉头上升、训练 loss 继续降、模型开始逐字背源码;20 万字符时拐点推到第 8 个 epoch、记忆率几乎为零;200 万字符时 10 个 epoch 还没到拐点。数据每多 10 倍,能安全训练的 epoch 数就多几个,而”背下来”只在参数明显多于数据时发生。

2. SFT

SFT 就是第一行的体制:8B 模型、几千到几万条指令(几百万 token),参数是 token 的上千倍。典型现象:训练 loss 平滑下降,验证 loss 在第 2–3 个 epoch 后回升,模型对训练集里的问题逐字复述回答、对稍微改写的问题失去泛化。当前实践因此把 SFT 控制在 2–3 个 epoch;数据量大(几十万条以上)时 1–2 个;学习率比预训练小一个量级(上一篇第六章)也是同一个考虑——不要把预训练学到的表示冲掉。检测手段就是上面的记忆探针:拿训练集的 prompt 让模型生成,与训练集的回答比对。

3. 奖励模型

奖励模型是第一章表的最后一行,也是 L2 导读讨论过的 reward hacking 的来源:几万到几十万偏好对训一个 8B 模型,1 个 epoch 之后验证准确率就不再涨、2 个 epoch 后开始降,是常见的经验。它比 SFT 更敏感,因为它的输出是一个标量,”背下来”的方式是记住表面特征(长度、格式、语气)——这些特征在训练集上与偏好相关,被 RL 优化后放大到荒谬。经典解法都适用:更多更多样的数据、早停、集成、正则化(KL 惩罚)。

八、诊断

信号 看什么 含义
训练 / 验证 gap 两条 loss 曲线的差 gap 持续扩大是过拟合的定义;gap 为负(第七章 200K 那行的前两个 epoch)说明验证集比训练集”容易”,正常
验证 loss 的拐点 held-out loss 从降到升的那个 epoch 早停的位置;数据越少拐点越早
测试 loss 涨而准确率不变 第五章的表 模型在变得过度自信,不是在判错更多样本;校准变差;对需要概率的下游(judge、RM)有害
记忆探针 给训练样本的前缀,看模型是否逐字复现 直接检测”背下来”;SFT 与预训练的污染检测都用它
参数 / 数据比 第一章的表 训练开始前就能算出来:比值大于 1 就要准备早停与验证集,大于 100 就要预期几个 epoch 内过拟合

最后一条最便宜、也最常被忽略:训练之前先算参数 / 数据比,就知道该不该担心过拟合。预训练不必担心,SFT 与 RM 一定要。

九、实验

1. 代码

在系列基座上加两个层:Dropout(inverted,训练 / 推理两种模式)与 Embedding(查表;反向用 np.add.at 把梯度散射回对应的行)。三组实验:

# double descent:4000 样本、20% 标签噪声、宽度扫描、Adam 全 batch 6000 步
for w in [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]:
    net = Net([Linear(784, w), ReLU(), Linear(w, 10)])
    ...  # 训到收敛,记录训练 / 测试错误率与测试 loss

# 正则化:1000 样本、宽 512、四种 (wd, dropout) 组合、300 epoch,记录 train / test loss 曲线

# 小语言模型:Embedding(V, 16) → Linear(8*16, 512) → ReLU → Linear(512, V)
# 数据 2 万 / 20 万 / 200 万字符,每 epoch 测 held-out loss 与逐字记忆率

2. 结果

Double descent 与正则化的完整表在第二、五章。小语言模型 2 万字符一组的逐 epoch 数字:

epoch 训练 loss held-out loss 逐字记忆率(32 字符)
1 3.166 3.091 0.0%
4 2.353 2.596 0.5%
8 1.790 2.431 2.5%
16 1.213 2.408 5.5%
24 0.850 2.527 5.5%
32 0.606 2.734 7.0%
40 0.438 2.975 10.0%

三组实验在笔记本 CPU 上共约 6 分钟(double descent 一组最慢,宽度 2048 的全 batch 训练占了大半)。

3. 值得自己动手的扩展

  • 把标签噪声从 20% 改成 0% 重跑 double descent,看尖峰是否消失(没有噪声时插值阈值上的解不需要扭曲);
  • 固定宽度 64,扫训练步数 100 到 20000,看 epoch-wise double descent;
  • 在小语言模型上把参数量放大 10 倍(宽 5120),看 200K 字符那一行的拐点提前到哪里、记忆率升到多少。

十、本文小结

  • 先算参数 / 数据比:LLM 预训练是 0.0005(数据主导,欠拟合体制),SFT 与奖励模型是几百到上千(过参数化体制)。两个体制的规则不同。
  • 过参数化体制里深网络不过拟合,是因为优化器挑解:最小范数、平坦极小值、先学简单的——隐式正则化。容量不是度量,网络能背下随机标签(Zhang 等 2017)。
  • Double descent:测试误差随容量先降、在插值阈值处尖峰(实测宽度 8 处测试 loss 5.48)、再一路下降(宽度 2048 最好,参数是样本的 407 倍)。不要停在阈值附近;加数据可能让同一模型变差。
  • 显式正则化各是一种先验:weight decay 是高斯先验(实测把测试 loss 的上升从 0.55 压到 0.42);dropout 是集成 / 噪声(拖慢过拟合,不改变终点);早停利用”先学简单的”(最便宜);label smoothing 改善校准但压低置信度。
  • LLM 预训练不用 dropout、只训一个 epoch,因为在数据主导体制里容量是瓶颈;数据受限时重复到 4 个 epoch 以内几乎无损,之后收益递减(Muennighoff 等 2023)。
  • 过拟合在 SFT 与奖励模型上回来:小语言模型实验里 2 万字符时第 16 个 epoch 起 held-out loss 回升、逐字记忆率升到 10%;20 万字符拐点在第 8 个 epoch;200 万字符 10 个 epoch 未到拐点。
  • 诊断:训练 / 验证 gap、held-out 拐点、测试 loss 涨而准确率不变 = 过度自信、记忆探针。
  • 下一篇回到结构史:卷积网络解决了什么、ResNet 留下了什么、ViT 怎么把图切成 token。

配套代码:deep-learning-foundations/04_regularization.py——dd / reg / lm 三个子实验,完整跑约 15 分钟,--quick 两分钟;字符级语料用的是 Python 自带的标准库源码,不需要下载。

下一篇

CNN:从 LeNet 到 ResNet,再到 ViT

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/regularization-and-generalization.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×