本文是《LLM 时代的经典机器学习:只讲它在哪里重现》系列的第 1 篇(共六篇)。下一篇:线性回归与逻辑回归——奖励模型的骨架。
“机器学习”三个字里最重要的是”学习”,而学习的定义只有一句话:从有限的样本推断没见过的样本。一个模型把训练数据全背下来不叫学习,能在没见过的数据上表现好才叫——这个能力叫泛化。这一篇用一个能画出来的例子(30 个点拟合一条正弦曲线)建立整个算法地图最基础的四个概念:数据怎么划分、什么是过拟合与欠拟合、误差从哪来(偏差-方差)、什么是正则化。然后把它们对到 LLM 上:benchmark 污染是测试集泄漏,reward hacking 是在一个过拟合的评估器上做优化。
全篇的核心问题是:
benchmark 涨了 5 个点,怎么知道不是测试题泄漏?奖励模型的准确率 80%,为什么 RL 还会钻空子?
一、总览
1. 四个概念一张图
数据 ──划分──► 训练集(拟合)· 验证集(选超参数)· 测试集(只看一次)
模型容量 ↑ 训练误差 ↓↓ 验证误差 先↓后↑ 欠拟合 ──── 合适 ──── 过拟合
偏差大 方差大
误差 = 偏差² + 方差 + 不可约噪声 集成只降方差;正则化把模型拉向简单解
2. 本文的章节安排
| 章 | 主题 | 内容 |
|---|---|---|
| 二 | 学习与泛化 | 定义;训练误差 vs 泛化误差;scikit-learn 的三个方法 |
| 三 | 划分 | 训练 / 验证 / 测试各干什么;测试集为什么只能看一次 |
| 四 | 过拟合与欠拟合 | 30 个点拟合正弦:次数 1 到 25 的剪刀差;不可约误差 |
| 五 | 测试集泄漏 | 在测试集上选超参数偏乐观多少;benchmark 污染的两种形态 |
| 六 | 偏差-方差 | 分解;低次偏差大、高次方差大;集成只降方差;LLM 上的三种集成 |
| 七 | 正则化 | 任何把模型拉向简单解的项;KL 惩罚、LoRA 也是 |
| 八 | reward hacking | 在过拟合的评估器上做优化;经典解法逐条对应 |
| 九 | 自测 | 五道题 |
| 十 | 本文小结 |
配套脚本:01_learning_and_generalization.py。
二、学习与泛化
1. 定义
有一批数据 \((x_i, y_i)\):\(x\) 是输入(一段文本、一张图、一行表格),\(y\) 是想预测的东西(类别、数值、下一个 token)。监督学习是找一个函数 \(f\) 使 \(f(x) \approx y\)——但不是在这批数据上,而是在同一来源的、没见过的数据上。
两个误差:训练误差是 \(f\) 在训练数据上的平均损失;泛化误差是它在全部可能数据上的期望损失。我们能算前者、关心后者。两者的差距叫泛化差距——它是本篇一切讨论的对象。
2. scikit-learn 的三个方法
本系列的代码用 scikit-learn,它的所有模型只有三个方法:
model.fit(X_train, y_train) # 学:在训练数据上找参数
model.predict(X_new) # 用:对新输入给预测
model.score(X_test, y_test) # 评:在一批数据上算一个分数(分类是准确率,回归是 R²)
X 是 [样本数, 特征数] 的二维数组(L1 第一篇的形状),y 是 [样本数]。整个系列不需要更多接口。
三、划分
1. 三份数据
经典机器学习的第一课是把数据分成三份:
| 份 | 用来 | 能看几次 |
|---|---|---|
| 训练集 | 拟合参数(fit) |
随便 |
| 验证集 | 选超参数(模型复杂度、学习率、正则强度)、早停 | 每试一个配置看一次 |
| 测试集 | 报告最终的泛化性能 | 一次 |
为什么要三份而不是两份:验证集参与了决策(选了哪个配置),它上面的分数就带了”选择偏差”——你挑的是在它上面最好的那个,那个分数偏乐观。测试集是唯一没有参与任何决策的数据,它报出的数字才是泛化误差的无偏估计。测试集一旦参与了任何决策,它就变成了验证集。
2. 脚本里的一次划分
Xtr, Xtmp, ytr, ytmp = train_test_split(X, y, test_size=0.4, random_state=0)
Xva, Xte, yva, yte = train_test_split(Xtmp, ytmp, test_size=0.5, random_state=0)
训练 180 / 验证 60 / 测试 60
用验证集选出次数 6(验证 MSE 0.085);测试集只在最后看一次:测试 MSE 0.084
在验证集上把多项式次数从 1 试到 15、选出 6,然后测试集只看一次——0.084 与验证的 0.085 接近,说明验证集上的选择没有过度乐观。
四、过拟合与欠拟合
1. 一个能画出来的例子
用 30 个带噪声的点(\(y = \sin 2\pi x + \varepsilon\),噪声标准差 0.3)拟合多项式,次数从 1 到 25:
次数 训练 MSE 验证 MSE 判断
1 0.272 0.308 欠拟合(两个都高)
3 0.082 0.094 合适
5 0.056 0.114 合适
9 0.051 0.105 合适
15 0.049 0.138 过拟合(训练低、验证高)
25 0.040 0.313 过拟合(训练低、验证高)
噪声方差 0.09 是验证 MSE 的下限:再好的模型也降不到它以下(不可约误差)
三段:
- 欠拟合(次数 1):一条直线拟合正弦,训练误差与验证误差都高——模型太简单,连训练数据都描述不了。
- 合适(3–9):训练误差降下来,验证误差接近下限 0.09。
- 过拟合(15、25):训练误差继续降(0.040——比噪声方差还低,说明它在拟合噪声),验证误差飙升(0.313,比直线还差)。模型容量大到能把 30 个点连同它们的随机噪声一起背下来,在没见过的点上乱跳。
这张表就是模型容量 vs 泛化的全部关系:训练误差随容量单调下降,验证误差先降后升,最低点就是合适的容量。
2. 不可约误差
验证 MSE 最好也只有 0.094,降不到 0——因为数据本身有噪声方差 0.09。这部分叫不可约误差(irreducible error):不管模型多好,数据里的随机性不可能预测。L0 第六篇说训练 loss 降不到数据的熵以下、Chinchilla 的 \(E = 1.69\),是同一件事在语言模型上的形态。
3. 什么时候最容易过拟合
模型容量大、数据少。参数比样本多的时候,模型总有办法把训练数据背下来。经典的处理:更多数据、限制容量、正则化(第七章)、早停(验证误差开始升就停)。第八章会看到这四条在 LLM 上各是什么。
五、测试集泄漏
1. 在测试集上选模型,分数就不算数
如果偷懒不分验证集、直接在测试集上试次数 1 到 12 挑最好的,报出来的分数偏乐观多少?脚本重复 200 次(每次一批新的 40 个点、20 训练 20 测试):
200 次重复:'在测试集上选出的最好分数' 比 '同一模型在全新数据上的真实分数' 平均乐观 0.255(MSE),68% 的情况下真实更差
在 20 个测试点上”挑最好的”本身就是一种拟合——挑出来的那个配置恰好在这 20 个点上运气好。测试集泄漏(test set leakage)说的就是这个:测试数据以任何形式参与了训练或选择,它报出的数字就不再是泛化性能。
2. LLM 上的两种形态
规模大得多,但概念相同:
污染(contamination):预训练语料是从互联网抓的,GSM8K、MMLU 的题目与答案早就在网上,一个 15T token 的语料几乎一定包含它们的某种变体。模型”见过”测试题,分数就是记忆而不是能力。
过度调参:在同一个 benchmark 上反复调 prompt、调配方、选 checkpoint。即使题目没泄漏,benchmark 也变成了验证集——上面那个实验里的 0.255 就是它的缩小版。
处理方法是经典的加强版:n-gram 重叠检测(把测试题的 13-gram 拿到训练语料里查)、困惑度对比(模型在测试题上的 loss 异常低)、留出私有测试集与动态 benchmark(题目定期更换、或从线上新产生)、看”污染后”与”污染前”版本的分数差。L4 数据工程与 L5 评测都会碰到它,但概念在这一层:没有干净的测试集,评测数字就没有含义。
六、偏差-方差
1. 分解
一个模型的期望误差可以拆成三项:
\[\text{误差} = \underbrace{\text{偏差}^2}_{\text{模型太简单、系统性地错}} + \underbrace{\text{方差}}_{\text{对训练数据的随机性太敏感}} + \underbrace{\text{噪声}}_{\text{不可约}}\]偏差:用很多批不同的训练数据各训一个模型,它们预测的平均值离真实值多远。直线拟合正弦,不管用哪 30 个点,平均预测都是一条直线——偏差大。方差:这些模型的预测彼此之间抖动多大。25 次多项式,换一批 30 个点结果完全不同——方差大。
2. 数字
脚本用 100 批不同的 30 个点各训一个模型:
次数 1: 偏差² 0.158 方差 0.025 → 20 个模型平均后方差 0.001(偏差² 0.152 基本不变)
次数 4: 偏差² 0.003 方差 0.014 → 20 个模型平均后方差 0.001(偏差² 0.003 基本不变)
次数 9: 偏差² 0.001 方差 0.060 → 20 个模型平均后方差 0.017(偏差² 0.000 基本不变)
低次偏差大、高次方差大——这就是第四章那条”先降后升”曲线的分解:容量增加降偏差、升方差,合适的容量是两者之和最小的地方。
3. 集成只降方差
上面最后一列:把 20 个(各用不同数据训的)模型的预测平均,方差降到原来的几十分之一,偏差不变。这叫集成(ensemble);用重采样的数据训多个模型再平均叫 bagging,随机森林(第三篇)就是它。原理是 L0 第四篇的”独立量的平均,方差除以 \(n\)“。
集成在 LLM 上以三种形态出现:
- self-consistency:同一个问题采样多次、多数投票,把解码的随机性平均掉,数学题上常有几个点的提升;代价是推理成本乘以采样次数;
- judge 集成:多个 judge 模型或多次打分取平均,降低单个 judge 的随机性——但不能降低它们共同的系统偏差(比如都偏好长回答,第六篇);
- 模型融合:把同一个基座的多个微调版本的权重平均(model soup),在多任务上常优于任何单个版本,前提是它们在同一个 loss 盆地里。
4. 为什么多 seed 的结果差异那么大
小模型、小数据上的实验方差很高,两个配方的差异可能小于 seed 之间的差异——L1 第五篇里 20 步训练换个 seed 差 0.14 就是这个。这是横切”实验方法论”里”多 seed 报均值与方差”的理论依据:你看到的单个数字里有一大块是方差。
七、正则化
正则化(regularization)是给模型加一个”不要太相信训练数据”的先验——任何把模型拉向某个简单解的项。经典形态:
- weight decay(L0 第二篇的 \(\frac{\lambda}{2}\lVert W \rVert^2\)):拉向零;
- dropout:训练时随机关掉一部分神经元,逼模型不依赖任何单个特征(L3 第四篇);
- 早停:验证误差开始升就停,不让模型有时间背;
- 数据增强:人为制造更多样本。
L3 会讲它们在深度网络里的具体形态;这里要建立的是概念:看到一个新方法里的约束项,先问它在把模型拉向什么。RLHF 的 KL 惩罚是正则化(先验是参考模型,L0 第六篇),LoRA 的低秩约束是正则化(先验是”改动很小”,L0 第三篇),few-shot 示例数量的限制也可以看成正则化。
八、reward hacking
1. 奖励模型是过拟合的典型
奖励模型(第二篇会讲它就是逻辑回归):一个 8B 参数的特征提取器加一个线性头,偏好数据通常只有几万到几十万对。参数与样本的比例是经典机器学习里不敢想的——第四章”容量大、数据少”的极端情形。结果是它学到的不是”什么是好回答”,而是训练集里好回答的表面特征:更长、更多列表、更客气的语气。在训练集上准确率一路上涨,在留出集上到 70–80% 就停——那就是它的验证误差开始升的地方。
2. 在过拟合的评估器上做优化
RL 阶段,策略模型专门优化这个奖励——它会找到奖励模型给高分但人类不认可的输出:把表面特征推到极端,回答越来越长、越来越多列表、越来越空洞。奖励一路上涨、真实质量下降。这就是 reward hacking,本质是在一个过拟合的评估器上做优化:评估器在训练分布之外的判断是随机的,优化器专门去那里找漏洞。
3. 经典解法逐条对应
| 经典解法 | 在 RLHF 里的形态 |
|---|---|
| 更多、更多样的数据 | 更多偏好对、覆盖更多类型的回答 |
| 正则化 | KL 惩罚:把策略拉在参考模型附近,不让它跑到奖励模型没见过的分布去 |
| 早停 | 限制 RL 的步数;监控真实质量而不只是奖励 |
| 集成(降方差) | 多个奖励模型取最小或均值 |
| 重新标注 | 定期用当前策略的输出收集新偏好、重训奖励模型(让评估器的训练分布跟上策略) |
SFT 也有同一个问题的温和版:几千条指令数据训三个 epoch 之后,模型开始逐字复述训练集的回答——这是第四章表里次数 25 那一行。
九、自测
- 训练误差 0.05、验证误差 0.30,是欠拟合还是过拟合?该往哪个方向调?
- 为什么在验证集上选出的配置,验证集分数偏乐观,而测试集分数不偏?
- 一个模型在 benchmark A 上从 60 涨到 65,团队在 A 上调了三个月的 prompt。这 5 个点可信吗?为什么?
- self-consistency 采样 10 次投票能不能修正模型”系统性地把某类题算错”?为什么?
- 把 RLHF 的 KL 系数 \(\beta\) 调大,从偏差-方差 / 正则化的角度看在做什么?
答案要点:(1)过拟合;减容量、加正则、加数据、早停。(2)验证集参与了”挑最好”的决策,挑出的是在它上面运气好的配置;测试集没参与任何决策。(3)不可信,A 已经变成验证集(过度调参 = 泄漏的一种形态);要看一个没调过的私有测试集。(4)不能:集成只降方差(随机错),不降偏差(系统性错)。(5)加强正则化:把策略拉向参考模型这个”先验”,减少它在奖励模型不可靠区域的探索。
十、本文小结
- 学习 = 从有限样本推断没见过的样本;训练误差能算、泛化误差是目标;scikit-learn 只有
fit/predict/score。 - 三份数据:训练集拟合、验证集选超参数、测试集只看一次——参与了任何决策的数据都不再是测试集。
- 过拟合与欠拟合:容量增加,训练误差单调降、验证误差先降后升;30 个点拟合正弦,次数 25 的训练 MSE 0.040 低于噪声方差 0.09(在拟合噪声)、验证 MSE 0.313 比直线还差。不可约误差是下限(对应 LLM 的数据熵)。
- 测试集泄漏:在 20 个测试点上挑最好的配置,平均乐观 0.255。LLM 上两种形态:污染(测试题在预训练语料里)与过度调参(benchmark 变成验证集)。没有干净的测试集,评测数字就没有含义。
- 偏差-方差:误差 = 偏差² + 方差 + 噪声;低次偏差大、高次方差大;集成只降方差——self-consistency、judge 平均、model soup 都是集成,消不掉系统偏差;多 seed 差异大是因为方差高。
- 正则化是任何把模型拉向简单解的项:weight decay、dropout、早停、KL 惩罚、LoRA 的低秩。
- reward hacking = 在一个过拟合的评估器上做优化;经典解法(更多数据、KL 正则、早停、集成、重新标注)逐条对应。
下一篇讲两个最简单的模型——线性回归与逻辑回归——它们是一切分类头的原型,奖励模型的 loss 就是逻辑回归作用在两个回答的分差上。
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/what-is-learning-splits-generalization-and-bias-variance.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。