训练越来越好,新数据却越来越差?
本课一问:第 5 课加了非线性,四句组合题的训练 loss 降到 0.0006。模型越强,训练 loss 就能压得越低。可训练 loss 低,就等于模型学好了吗?
这一课先不碰:dropout(我们的项目模型没有用它);验证集怎么切(C0 章首页);模型该多大、数据该多少(C6);评测集被训练数据污染(C14)。
从已有出发
第 1 课的迷你模型训练 400 步,7 个例子全对,平均 loss 0.030;第 5 课 4 句话 loss 0.0006。可那 7 句、4 句就是它见过的全部——「小猫 喜欢 吃 ___」答对,可能是学到了「猫爱吃鱼」,也可能只是把这 7 句背下来了。例子只有这么几句时,分不清这两种情况,所以这一课换到真语料:要有大量「没见过」的文本,才量得出区别。
- 手写规则 / 把例子背成一张表
- 带参数的模型:用例子调数字
- 交叉熵:正确答案概率的负对数
- 梯度下降
- 反向传播(反向模式自动微分)
- 非线性多层网络
- 验证集、早停与正则化
- 深度学习为什么是经验性的
- 词向量:按编号查表
先自己设计:训练 loss 越低越好
最自然的做法:训练 loss 还在降,就继续训。下面是我们自己从 0 训练的 3.7M 参数模型(04-实践项目),和基线配置完全相同,只把步数从 400 拉长到 1,500。训练 loss 和验证 loss 都在固定的 228 个窗口上评估,口径完全一样:
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
验证集:一份不参与训练的数据(项目里是书末约 10%,59,664 字),只用来前向、量 loss,从不拿它的梯度更新参数。训练集上的 loss 回答「背没背下来」,验证集上的 loss 回答「在没见过的文本上怎么样」——我们要的是后者。
早停:每隔一段评估一次验证 loss,留下它最低时的那份参数。这条曲线上就是第 300 步:再往后训,每一步都在让模型在新文本上变差。
试三件事:① 先在紫色框里猜,再看完整曲线;② 把指针移到第 300 步和第 1,500 步,读训练与验证的差:0.87 → 6.20;横轴下面紫色的是 epoch——1,500 步时 50 万 token 已经被看了 24.5 遍;③ 勾上「小批次训练 loss」:训练时实际优化的是这条毛糙的线,它和固定窗口评估的训练 loss 走势一致。
它在背什么:浏览器里再做一次
项目模型太大,不能在页面里重训。换回真语料小网络,故意只给它 2,000 字训练,训 3,000 步,再看验证集上发生了什么:
数字来源:浏览器实时训练(纯 JS,Float64)。模型、语料、种子写在上方;同一组对照里所有 run 共用初始化和取批顺序,只改被比较的那一项。换种子重跑能看到噪声多大——差异小于种子间波动时不下结论。
试三件事:① 默认(2,000 字、λ = 0):验证 loss 在第 100 步最低(3.935),3,000 步时升到 11 以上;看表格最后一列,「九成把握却猜错」从 0.3% 涨到三成以上;把指针移到曲线上,提示框里是那一步模型对两个验证集上下文的前 3 名,越往后越自信、越离谱;② 训练集换成 6,000 字、再换成全部 23,865 字:过拟合来得越来越晚、越来越轻(全部数据时 3,000 步 3.646);③ 回到 2,000 字,λ 依次换成 0.001、0.003、0.01:3,000 步后的验证 loss 分别是 5.503、4.064、3.789,训练 loss 也不再降到很低。
验证 loss 升上去,主要不是模型「变笨」了:第一名猜对的比例从 33.6% 降到 22% 左右,降得不多;真正的变化在把握上——平均最大概率从 32.2% 涨到接近 80%,三分之一的验证位置上它九成把握押在一个错字上。第 2 课说过,−ln 对自信的错误罚得最重:押错时 p(正确答案)接近 0,−ln p 就爆炸。背下来的续写被自信地用在了新文本上。
一个旁注:2,000 字、λ = 0 这一组的终点在不同浏览器上会差零点几(作者的 Chrome 上 3,000 步时 11.46,node 上 11.27;前 1,300 步逐位相同)。训练把模型推到很尖锐的地方之后,浮点运算最后一位的差别会被一步步放大——所以页面上对这一组只说「11 以上」。其余几组(加了 λ、或数据更多)在两处逐位一致。
改进:三件对抗过拟合的事
- 验证集 + 早停:上面两张图的绿色竖线。它不改模型,只决定留哪一份参数。
- 更多数据:2,000 → 23,865 字,同样的模型背不下来,只好学共性。这是最根本的办法,也最贵(C6、C7)。
- 正则化:在 loss 里加一项,惩罚「参数太大」,让模型没法把每个例子都拟合得很细。最常用的是权重衰减:
另一种常用的正则化是 dropout(训练时每次随机关掉一部分单元),我们的项目模型没有用它。
常见误解一:「验证 loss 上升,说明模型把学过的东西忘了。」
训练 loss 还在降,它没忘;它是在更细地记训练集,并把这些记忆自信地套到新文本上。上面的「九成把握却猜错」从 0.3% 涨到三成以上,就是这件事。
常见误解二:「过拟合 = 模型太大。」
模型太大、数据太少、训得太久,三者任何一个都会让它出现:项目模型 3.7M 参数不算大,是 50 万 token 被看了 24.5 遍。而且「更大的模型更容易过拟合」这条经典直觉在现代深度学习里经常不成立——模型大过某个程度之后,测试误差会再次下降(双下降)。岔路课展开。
常见误解三:「验证集可以反复用来挑超参数。」
每用验证集挑一次(学习率、λ、步数……),验证集的信息就漏进一点到你的选择里;挑得足够多,你会在验证集上也过拟合。所以要再留一份只在最后看一次的测试集。C14 讲评测数字该信几分。
常见误解四:「训练 loss 越低越好。」
训练 loss 只说明对训练集拟合得多好。要看的永远是验证 loss;两条线的差距越拉越大,就是警报。
🔍 这三种办法是谁提出的?
用一份独立数据决定何时停止迭代、防止「过度训练」,在 1989 年的神经网络论文里已经出现(Morgan 与 Bourlard,NIPS 2);Prechelt 1998 年的《Early Stopping — But When?》系统比较了各种停止准则。给权重加惩罚项在 1980 年代后期的反向传播研究里已在使用(如 Hanson 与 Pratt,NIPS 1988),Krogh 与 Hertz 1991 年从理论上解释了权重衰减为什么能改善泛化。Dropout 由 Hinton 等 2012 年提出、Srivastava 等 2014 年系统发表。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 训练 loss 只量「背没背下来」;要一个量「在没见过的数据上怎么样」的读数,并据此决定何时停、要不要加约束 |
| ② 数学上为什么可能有效? | L2 正则等价于给参数加一个高斯先验后的最大后验估计 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条−ln 高斯先验 = (λ/2)‖θ‖² + 常数;这是等价关系,不说明它一定改善泛化。 出处待补 ;「参数小 → 函数平滑 → 不容易贴着噪声走」是动机 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条对线性模型有严格的偏差-方差分析;对深度网络只是动机。 出处待补 |
| ③ 实验上呢? | 项目模型:验证最低在第 300 步,之后升到 6.906 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条单种子;只有 50 万 token,过拟合来得很早,更大的语料上拐点会推迟很多(实验导出的 caveat 原文)。 出处待补 ;浏览器里:数据越少越早过拟合,λ = 0.003 把 3,000 步后的验证 loss 从 11 以上拉回 4.064 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条真语料小网络,种子 1,纯梯度下降 η = 1。 出处待补 |
一句话
训练 loss 只说明背没背下例子;没见过的数据要用验证集单独量——验证 loss 开始上升的那一刻,就该停下或加正则。
先停在这里:记住「看验证 loss,不看训练 loss;最低点停下」。
再深一层:在组件里把 3 种数据量 × 4 个 λ 都跑一遍,画一张自己的表。然后去岔路课:为什么「模型越大越过拟合」这条直觉会失灵。
下一个缺口
前六课都在数上打转——可输入的是词,离散的词怎么进网络? → 第 7 课:离散的词怎么进网络?
岔路:深度学习为什么是经验性的?——这一课的每个结论(第 300 步最好、λ = 0.003 管用)都是跑出来的,不是算出来的。
你可能会问
- 「大模型预训练也会这样过拟合吗?」 预训练的数据通常多到每条只看一遍左右,很难背下来,所以训练 loss 和验证 loss 基本贴在一起;过拟合更常出现在数据少的阶段(微调、重复多遍的高质量数据)。数据重复多少遍会出问题,C7 讲。
- 「基线 400 步的验证 loss 4.626,比这条曲线上的最低点 4.771 还低,为什么?」 两个 run 的学习率调度按各自的总步数拉长(400 步 vs 1,500 步),基线在第 400 步时学习率已经降到了底。同一步数上的 loss 不能直接比;学习率调度在 C5 第 5 课。
- 「为什么是在第 300 步、4.9 个 epoch?」 没有公式,是这个模型、这份数据、这组超参数下跑出来的。换一个条件就要重新量——这就是为什么验证集要一直开着。