对应 Maps to:Stanford CS329Z Engineering AI Agents(Fall 2026),Lecture 11 · Mon Nov 2 · Data Selection & Quality。阅读材料:Yang et al., SWE-smith: Scaling Data for Software Engineering Agents(NeurIPS D&B 2025);Shankar et al., Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences(UIST 2024)/ Zhou et al., LIMA: Less Is More for Alignment(NeurIPS 2023)。 依据说明:写作时(2026-10-16)这一讲的 slides 还没有公布。本讲依据课表上的主题描述(找出信息量最大的数据、过滤与选择策略、小而精的 benchmark、标注实践、质量评估、从智能体轨迹构建数据集)和阅读材料来写,并引用第 12 讲阅读材料里的 tinyBenchmarks(Polo et al., ICML 2024)讲”小而精的 benchmark”。引用的论文数字都核对过原文摘要。全部实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture11/(只用 Python 标准库;6 个脚本 + 5 个模块 + 1 个可选脚本;不需要 API key;每个脚本十几秒以内) 预计阅读时间:3 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲你应该能够:
- 解释”少而精”什么时候胜过”多而杂”(LIMA 的发现和它的前提),并在一个具体任务上量出标签质量和数据量各值多少。
- 用项目反应理论(IRT)理解一道题的难度和区分度;说明 tinyBenchmarks 怎样用几十到一百道题估计整个 benchmark 的分数,以及 IRT 怎样帮你找出坏题。
- 设计一条数据清洗流水线:去重、规则过滤、基于模型的噪声过滤、按来源过滤、多样性选择、主动学习——知道每一步能修什么、修不了什么(尤其是系统性的错误)。
- 组织一次标注:写可操作的指南、用 Cohen’s / Fleiss’ κ 衡量一致性(并知道一致不等于正确)、用多数投票或 Dawid–Skene 聚合、把专家放在有分歧的地方。
- 复述 SWE-smith 的数据流水线(注入 bug → 用测试验证 → 写 issue → 拒绝采样轨迹),和 EvalGen 对”谁来验证验证者”的回答(断言要和人的打分对齐、标准会漂移)。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 10 讲:轨迹、反馈、数据飞轮、标准漂移、去重 | benchmark 的有效性与统计(第 12 讲) |
| 第 9 讲 §5:蒸馏和学生模型;第 8 讲 §6.3:Cohen’s κ | LLM 评审的偏差与校准(第 13 讲) |
| 第 2 讲 §4.2:预训练数据的抽取、过滤、去重 | 编程智能体与 SWE-bench(第 15 讲) |
0.3 本讲地图 Map
§0.6 开场:数据的"选择"比"收集"更难
§1 质量还是数量:LIMA 的启示 ← 代码 01
§2 找出信息量最大的数据:IRT、坏题、tinyBenchmarks ← 代码 02
§3 过滤与选择:去重、规则、模型、来源、多样性、主动学习 ← 代码 03
§4 标注的实践:指南、一致性、聚合、仲裁 ← 代码 04
§5 从代码仓库和智能体轨迹里造数据:SWE-smith ← 代码 05
§6 谁来验证验证者:EvalGen 与标准漂移 ← 代码 06
§7 用真实模型试一试 ← 代码 07(可选)
§8 一份数据质量清单
§9 小结 §10 常见误区 §11 练习 §12 延伸阅读 §13 下一讲预告
0.4 术语 Terms
| 术语 | 本讲里的意思 |
|---|---|
| 项目反应理论(IRT) | 用”模型能力 θ、题目难度 b、区分度 a”解释每个模型在每道题上的对错:P(对) = sigmoid(a·(θ − b)) |
| 区分度(discrimination) | 题目把强弱模型分开的能力;负的区分度意味着”越强越错”——多半是题本身有问题 |
| 锚点(anchor points) | 从一个 benchmark 里挑出的、能代表一类题的少数题目 |
| 置信学习(confident learning) | 用模型的预测找出”和其余数据冲突”的标签,当作可能的错标 |
| 主动学习(active learning) | 每轮挑模型最没把握的样本请人标注 |
| Cohen’s / Fleiss’ κ | 两个 / 多个标注者扣除”碰巧一致”之后的一致性 |
| Dawid–Skene | 不知道真实标签时,用 EM 同时估计每个标注者有多准和每条的真实标签 |
| 拒绝采样(rejection sampling) | 让模型多次尝试,只留下通过检查的输出(或轨迹)当训练数据 |
| 断言(assertion / validator) | 自动检查 LLM 输出的规则:一段代码或一个 LLM 评审问题 |
| 覆盖率 / 误报率(coverage / false failure rate) | 坏的输出里断言抓住的比例 / 好的输出里断言错判的比例 |
0.5 本讲的代码
五个相对独立的实验:
- 数据池(
selection_lab.py):第 9 讲报销审核任务上的 6,000 张单子 + 900 条重复。标签来自三种来源——好老师(写全政策的程序,约 4% 随机错误)、弱标注者(只懂常识的起点程序,约 48% 错误,而且是系统性的)、众包(一半的时候随便填);另有 5% 残缺的记录。学生模型(第 9 讲的 softmax 回归)真的在训练,每个数字是 3 个随机种子的平均。 - 排行榜(
irt_lab.py):100 个模型 × 1,200 道题(6 个科目),按 IRT 模型生成,掺 8% 的坏题(标错答案、谁都做不对、谁都做得对)。 - 标注(
annotation_lab.py):第 10 讲客服世界的 400 条对话,5 个水平和习惯不同的标注者,两版指南。 - SWE-smith(
swe_smith_lite.py):一个真实的小 Python 仓库(textkit:字符串、列表、数字三个模块,28 个测试),真的注入 bug、真的在子进程里跑测试。 - EvalGen(
evalgen_lab.py):第 10 讲的客服对话上 10 个候选断言,一个会发生标准漂移的打分者。
提醒:数据池的噪声、排行榜的生成方式、标注者的水平、LLM 断言的准确率都是参数或旋钮;训练、过滤、IRT 拟合、κ、Dawid–Skene、变异测试、覆盖率的计算都是真的。
0.6 开场:数据的”选择”比”收集”更难
第 10 讲的结尾,我们手里有了很多数据:上线后的轨迹、标注过的对话、合成的请求、LLM 给的标签。问题变成了:哪些值得用?
直觉上”数据越多越好”。可是第 10 讲已经看到,“用户没抱怨”的轨迹里混着违规,用它们做示范是在教模型犯错;合成数据里六成是重复的;LLM 的标签漏报很多。这一讲的几个故事都在回答同一个问题:
- LIMA:1,000 条精挑细选的示范,就能做出不错的对话模型——质量胜过数量;
- tinyBenchmarks:一百道挑好的题,就能估计整个 MMLU 的分数——信息量胜过题量;
- SWE-smith:自动造出 5 万个任务,但每一个都要用测试验证过——可验证的质量才能规模化;
- EvalGen:自动生成的评估器也要人来验证,而人的标准本身也在变——验证者也需要验证。
1. 质量还是数量 · Quality vs. quantity
1.1 LIMA 的发现
LIMA(Zhou et al., NeurIPS 2023)只用 1,000 条精心挑选的提示和回答微调 65B 参数的 LLaMA,没有做强化学习或人类偏好建模。在受控的人工评估里,LIMA 的回答有 43% 的情况和 GPT-4 相当或更好,对 Bard 是 58%,对用人类反馈训练过的 DaVinci003 是 65%。论文的解释是:大语言模型几乎所有的知识都是在预训练里学到的,指令微调只需要很少的数据,就能教会模型用什么格式、什么风格和用户交流(后来常被称为”表面对齐假设”)。
两个关键词:精心挑选(来自社区问答的高质量回答,加上作者手写的示范,强调多样性和一致的风格)、格式与风格(不是新知识)。
1.2 在报销审核上量一量
代码 01 的数据池:
| 来源 | 条数 | 标签错误率 | 错误的性质 |
|---|---|---|---|
| 好老师(写全政策的程序) | 3,236 | 4.1% | 随机 |
| 弱标注者(只懂常识的起点程序) | 1,921 | 47.7% | 系统性(不知道公司规定) |
| 众包(一半的时候随便填) | 1,386 | 34.5% | 随机 |
| 残缺记录(缺金额 / 被截断,标签随便填) | 357 | 70.3% | 随机 |
(其中 900 条是完全重复的;整个数据池的标签错误率 25.8%。)同样多的数据,标签质量不同:
| 标签来源 | 300 条 | 1,000 条 | 2,800 条 | 全部 |
|---|---|---|---|---|
| 从数据池里随机挑 | 70.4% | 71.7% | 84.1% | 83.3%(6,900 条) |
| 只用好老师的标签 | 86.3% | 92.7% | 95.6% | 95.4%(2,812 条) |
| 人工逐条标注(真实标签) | 89.2% | 93.7% | 97.8% | 98.2%(5,688 条) |
- 质量胜过数量:1,000 条好老师的标签(92.7%)比全部 6,900 条混杂数据(83.3%)还高。
- 但数量也不是不重要:人工标注从 300 条的 89.2% 涨到 5,688 条的 98.2%。我们的学生模型要学的是它本来不知道的规则(公司政策),不是 LIMA 说的”格式和风格”——要模型学会新知识,数据量仍然重要。
- “少而精”的前提是:精。
2. 找出信息量最大的数据 · Finding maximally informative data
2.1 IRT:题目也有”参数”
评估一个模型要跑完整个 benchmark,往往要几千上万道题。但题和题的信息量差别很大:所有模型都做对的题、所有模型都做错的题,对区分模型毫无帮助。项目反应理论(IRT,来自心理测量学)给每道题两个参数:
P(模型 m 做对题 i) = sigmoid(a_i · (θ_m − b_i))
- θ_m:模型的能力;b_i:题目的难度(能力等于 b 时,做对的概率是 50%);
- a_i:区分度——a 越大,能力稍有差别做对的概率就差很多,这道题越能把强弱模型分开;a 接近 0,做没做对和能力无关;a 是负的,意味着越强的模型越容易”做错”——多半是标准答案标错了。
一道题在能力 θ 处的 Fisher 信息 = a²·p·(1 − p):区分度高、难度和模型水平相当的题,信息量最大。
2.2 IRT 也是数据质量的工具
代码 02 在 70 个”已知的模型”上拟合 IRT(从零写的交替梯度上升):
| 题的类型 | 题数 | 已知模型的平均正确率 | 拟合出的区分度 a | 对中等水平模型的信息量 |
|---|---|---|---|---|
| 正常题 | 1,109 | 50% | +1.13 | 0.306 |
| 标准答案标错了 | 31 | 56% | −1.13 | 0.308 |
| 谁都做不对 | 32 | 0% | +0.15 | 0.002 |
| 谁都做得对 | 28 | 99% | +0.14 | 0.002 |
区分度为负的 30 道题,全部确实标错了答案(共 31 道)。标错的题平均正确率 56%,看起来完全正常——只看正确率是找不出它们的,要看”谁做对了”:强模型反而做错。谁都做不对 / 谁都做得对的题信息量几乎为 0。
(在这个排行榜上,去掉这些坏题,30 个新模型的排名和真实能力的一致性没有变——Kendall τ 都是 0.949:坏题只占 8%,1,200 道题足够把它们冲淡。坏题更大的代价是浪费评估预算、扭曲分数的绝对值;在小 benchmark 里比例一高,就可能伤到排名。)
2.3 tinyBenchmarks:一百道题估计整个 benchmark
tinyBenchmarks(Polo et al., ICML 2024)的问题:只让一个新模型做很少的题,怎样估计它在整个 benchmark 上的准确率?论文报告,只用 100 道挑好的题,就能把一个模型在整个 MMLU(14K 题)上的准确率估计到约 2% 以内;他们发布了 Open LLM Leaderboard、MMLU、HELM、AlpacaEval 2.0 的”小版本”。方法分两部分:
怎样挑题:
- 随机 / 按科目分层随机;
- 锚点:把每道题表示成一个向量(“已知的那些模型在这道题上的对错”,或者 IRT 拟合出的题目参数),聚成 k 簇,每簇取中心的那道题当代表,权重 = 簇的大小——做对 / 做错模式相同的题提供的信息是重复的,每种模式挑一道就够了。
怎样估计:
- 加权平均:Σ 权重 × 对错;
- p-IRT:用看过的题估计这个新模型的能力 θ,再用拟合好的题目参数”预测”它在没看过的题上的做对概率;
- gp-IRT:加权平均和 p-IRT 的凸组合,权重 λ 按 IRT 的偏差和观察值的方差决定——题越少越信 IRT,题越多越信观察值。
代码 02(30 个新模型;每格:平均绝对误差(百分点)/ 和完整排名的 Kendall τ;随机、分层是 10 次抽样的平均):
| 选题 | 估计 | k = 30 | k = 60 | k = 120 |
|---|---|---|---|---|
| 随机 | 加权平均 | 7.2 / 0.72 | 4.9 / 0.77 | 3.2 / 0.85 |
| 随机 | p-IRT | 5.4 / 0.70 | 3.9 / 0.76 | 2.7 / 0.84 |
| 按科目分层 | 加权平均 | 7.4 / 0.76 | 4.5 / 0.79 | 3.3 / 0.84 |
| 按科目分层 | p-IRT | 5.2 / 0.72 | 3.7 / 0.79 | 2.6 / 0.85 |
| 锚点(做对模式聚类) | 加权平均 | 6.4 / 0.69 | 5.2 / 0.79 | 2.8 / 0.85 |
| 锚点(做对模式聚类) | p-IRT | 5.7 / 0.69 | 3.3 / 0.83 | 2.5 / 0.86 |
| 锚点(IRT 参数聚类) | 加权平均 | 7.7 / 0.75 | 5.1 / 0.71 | 3.2 / 0.83 |
| 锚点(IRT 参数聚类) | p-IRT | 5.0 / 0.73 | 4.1 / 0.77 | 2.6 / 0.89 |
(gp-IRT 和 p-IRT 几乎一样,表里省略。)读法:
- IRT 的估计器比选题方法更重要:同样的随机题,p-IRT 比加权平均好 1–2 个百分点。它把 1,200 道题的结构信息(哪些题难、哪些区分度高)“借”给了只做了几十道题的新模型。
- 锚点并不总比随机好:做对模式聚类 + 加权平均在 k=30 时比随机好,k=60 时反而差——一次聚类的结果有偶然性;论文里锚点的优势更稳定。
- gp-IRT ≈ p-IRT 是因为排行榜本来就是按 IRT 生成的,IRT 的偏差极小(λ ≈ 0.01);真实数据上 λ 会大得多。这也提醒我们:IRT 方法依赖”新模型和已知模型行为相似”——一个和以往模型都不一样的新模型,IRT 的预测会偏。
- 用到 120 道题,最好的组合误差在 2.5 个百分点左右,和论文报告的量级一致。
2.4 信息量随模型水平变化
| 模型水平 | 最简单的三分之一 | 中间三分之一 | 最难的三分之一 |
|---|---|---|---|
| 弱模型(θ = −1.5) | 0.231 | 0.153 | 0.056 |
| 中等(θ = 0) | 0.215 | 0.463 | 0.195 |
| 强模型(θ = +1.5) | 0.056 | 0.168 | 0.230 |
强模型在最简单的题上几乎得不到信息:一个 benchmark 的题对当前的模型太简单,它就失去了区分能力——饱和。这也是”自适应测试”的思路:先估计水平,再挑对这个水平最有信息量的题(第 12 讲还会谈 benchmark 的寿命)。
3. 过滤与选择 · Filtering and selection strategies
3.1 一步步清洗
代码 03 在代码 01 的数据池上一步步清洗(每一步都在上一步的结果上做):
| 步骤 | 剩下的条数 | 标签错误率 | 学生模型的测试准确率 |
|---|---|---|---|
| 原始数据池 | 6,900 | 25.8% | 83.3% |
| 去掉完全重复的 | 6,000 | 25.8% | 87.6% |
| 规则过滤(缺金额、被截断的) | 5,688 | 23.3% | 87.2% |
| 基于模型的噪声过滤 | 5,088 | 16.9% | 89.0% |
| (如果知道来源)只留好老师的标签 | 2,812 | 3.9% | 95.4% |
- 去重:标签错误率没变,准确率却涨了 4 个点。重复的记录让一部分样本(包括错标的)被多算几遍,等于悄悄改了数据的权重。(第 2 讲讲过预训练数据的去重;第 10 讲讲过 MinHash 近似去重。)
- 规则过滤:去掉残缺的记录,错误率下降;准确率在训练的随机波动之内没有明显变化。
- 基于模型的噪声过滤:思路来自”置信学习”——把数据分成几份,每份用其余几份训练的模型来预测;模型很有把握地不同意某个标签(给它的概率 < 0.15),这个标签就可能错了。
3.2 噪声过滤抓到了谁
| 阈值 | 去掉的条数 | 其中真是错标的 | 抓到的错标:好老师 | 弱标注者 | 众包 | 测试准确率 |
|---|---|---|---|---|---|---|
| 0.05 | 335 | 89% | 18 / 111 | 41 / 801 | 238 / 416 | 89.2% |
| 0.15 | 600 | 78% | 41 / 111 | 131 / 801 | 294 / 416 | 89.0% |
| 0.30 | 1,056 | 64% | 58 / 111 | 285 / 801 | 329 / 416 | 84.9% |
众包的随机错标抓到了七成,弱标注者的错标只抓到 16%(阈值 0.15)——几百条单子都错成一样,模型把系统性的错误当成了规律。阈值调高能多抓一些,也误删更多好数据,准确率反而下降。
第一条经验:随机的噪声,模型能自己发现;系统性的错误,要靠知道数据从哪来。只留好老师的标签(95.4%)比任何不看来源的清洗都好。记录每条数据的出处(谁标的、用什么标的、什么时候标的)是数据质量里最便宜的一步——第 7 讲防记忆投毒的”来源标记”是同一个道理。
3.3 多样性
预算固定时,能不能挑得更聪明?k-center 贪心每次挑”离已挑集合最远”的样本,让数据尽量覆盖不同的情形:
| 条数 | 随机挑 | 按多样性挑(k-center) |
|---|---|---|
| 300 | 86.8% | 86.1% |
| 1,000 | 94.1% | 93.1% |
在这个任务上没有帮助:k-center 偏向罕见的特征组合,常见情形的样本相对变少。多样性有用的前提是”多样”的维度正是任务关心的维度——LIMA 的多样性是按话题和任务类型人工把关的,不是按某个距离自动挑的。
3.4 主动学习
没有标签、请人标注要花钱时,每轮挑模型最没把握的样本(两个最可能的类别概率之差最小)去标注。两种策略从同样的 100 条随机样本出发,每轮标 100 条:
| 每轮挑哪些去标注 | 100 | 200 | 300 | 400 | 500 | 600 | 700 |
|---|---|---|---|---|---|---|---|
| 随机挑 | 79.2% | 88.0% | 88.5% | 91.2% | 93.8% | 93.3% | 95.0% |
| 挑模型最没把握的 | 79.2% | 84.0% | 89.3% | 92.0% | 93.5% | 94.5% | 95.5% |
收益小、也不稳定:200 条时反而更差;之后大多领先不到 1 个点。模型还很差时,它”没把握”的地方未必是有信息的地方;而且每轮只训练一次,单次训练就有一两个点的波动。主动学习在标注很贵、模型已经不错时才值得用。
4. 标注的实践 · Annotation practices
第 10 讲的数据飞轮要人工审核对话:“这次对话成功了吗?“代码 04 让 5 个标注者标第 10 讲的 400 条客服对话(真实失败 128 条,其中 37 条是”用户不会抱怨”的违规:多给补偿、没验证 PIN……)。
4.1 指南决定了能标出什么
两版指南:v1 是一句话”客户的问题解决了吗?“;v2 是逐条的检查清单(先查客户、只能改到工作日、补偿要有停电记录且不超过当前上限、改地址前验证 PIN……)。
| 标注者 | v1:准确率 | v1:看出”看不见的失败” | v2:准确率 | v2:看出”看不见的失败” |
|---|---|---|---|---|
| A(认真) | 89% | 5% | 95% | 86% |
| B(认真) | 87% | 5% | 96% | 92% |
| C(宽松) | 89% | 3% | 94% | 78% |
| D(新手) | 82% | 8% | 88% | 73% |
| E(乱点) | 53% | 51% | 51% | 57% |
含糊的指南下,再认真的人也几乎看不出”看不见的失败”——客户的问题确实”解决了”。写指南就是在写判分标准:它和第 10 讲 LLM 评审的提示词是同一件事。好的指南是可操作的(逐条、可检查)、有正反例、写清楚边界情形。
4.2 一致性不等于正确
| 指南 | A–D 两两的平均 Cohen κ | A–D 的 Fleiss κ | 加上乱点的 E 的 Fleiss κ | “看不见的失败”被 A–D 一致判成功 |
|---|---|---|---|---|
| v1 | 0.75 | 0.75 | 0.41 | 81% |
| v2 | 0.72 | 0.72 | 0.42 | 0% |
Fleiss’ κ 把 Cohen’s κ 推广到多个标注者:P̄ = 每条上”成对一致”的比例的平均,P_e = Σ_c p_c²(p_c 是所有标签里类别 c 的比例),κ = (P̄ − P_e)/(1 − P_e)。
v1 的一致性甚至比 v2 略高,可是 81% 的”看不见的失败”被 4 个人一致地判成了成功——大家按同一份含糊的指南,犯同一个错。κ 衡量的是标注者之间像不像,不是他们和真相像不像。 另一方面,一个乱点的人就能把 Fleiss κ 从 0.72 拉到 0.42——低 κ 常常是在提醒你某个标注者或某些题有问题。第 8 讲 MAST 报告 κ = 0.88,前提是它的失败定义经过了几轮讨论、写成了清楚的指南。
4.3 把多人的标签合成一个
| 聚合方法(指南 v2) | 准确率 | 花费(美元,每人每条 0.5) |
|---|---|---|
| 只用 A(认真) | 95.2% | 200 |
| 只用 D(新手) | 88.5% | 200 |
| A、C、D 多数投票 | 98.2% | 600 |
| 5 人多数投票(含乱点的 E) | 97.5% | 1,000 |
| 5 人 Dawid–Skene | 98.8% | 1,000 |
Dawid–Skene(1979)用 EM 同时估计每个标注者的”混淆矩阵”(真是好时他说好的概率、真是坏时他说坏的概率)和每条的真实标签:E 步按每个人的可信度算每条的后验,M 步按后验重新估计每个人的可信度。它不看真实标签就估出了每个人有多准(A 95%、B 96%、C 93%、D 89%、E 52%,真实值分别是 95%、96%、94%、88%、51%),乱点的 E 的票自动被打折。3 个靠谱的人(98.2%)胜过 5 个人里混了一个乱点的(97.5%)。
4.4 仲裁
| 做法 | 请专家的条数 | 准确率 | 花费(美元) |
|---|---|---|---|
| A、C、D 多数投票 | 0 | 98.2% | 600 |
| A、C、D 不一致时请专家(每条 5 美元) | 82 | 99.8% | 1,010 |
| 全部请专家 | 400 | 99.5% | 2,000 |
3 个人一致就采用,有分歧才请专家:花一半的钱,比全部请专家还准一点。把贵的人放在有分歧的地方——第 10 讲”把人放在 LLM 最可能错的地方”是同一个思路。但 3 个人一致地错的那些(指南没写清楚的情形),仲裁救不回来——这又回到 §4.1:先把指南写好。
用 LLM 当标注者(第 10 讲 §4.5)时,这一节的一切都适用:LLM 也需要好的”指南”(提示词),也会和人一致地犯同一个错,也可以和人一起用 Dawid–Skene 聚合;第 13 讲会讲怎样用少量人工标签校准它。
5. 从代码仓库和智能体轨迹里造数据 · Building datasets from repos and traces
5.1 SWE-smith 的流水线
训练编程智能体需要大量”一个仓库 + 一个问题 + 能判断修好没有的测试”的任务。SWE-bench 的任务来自真实的 GitHub issue 和 PR,数量有限,而且每个任务都要人工搭好能跑测试的环境。SWE-smith(Yang et al.)反过来做:从任何一个 Python 代码库出发,先构建好执行环境,再自动合成成百上千个任务——往代码里注入 bug,留下”让至少一个原本通过的测试失败”的那些。他们从 128 个 GitHub 仓库造出了 5 万个任务实例,用它们(以及智能体在这些任务上成功的轨迹)训练的 SWE-agent-LM-32B 在 SWE-bench Verified 上达到 40.2% 的 Pass@1。
代码 05 在 textkit(28 个测试)上走一遍这条流水线(变异是在源码的记号上直接替换——换运算符、常量 +1、删掉一行——每个变异真的写进文件、真的在子进程里跑测试):
| 变异的类型 | 位置 | 通过验证 | 去重之后 |
|---|---|---|---|
| 换运算符 | 25 | 20 | 13 |
| 常量 +1 | 15 | 14 | 6 |
| 删掉一行 | 14 | 10 | 2 |
| and ↔ or | 1 | 1 | 1 |
55 个变异里,4 个改完不能编译、6 个没有任何测试失败、45 个成了有效任务;同一个函数、同一组失败测试的只留一个,剩 22 个——很多变异考的是同一件事。
5.2 测试抓不到的变异
| 测试抓不到的变异 | 类型 |
|---|---|
clamp 里 < 改成 <=;> 改成 >= | 等价变异:x 等于边界时返回边界还是 x,结果一样——改了也没有 bug |
chunk 的 n <= 0 改成 n < 0 | 碰巧通过:n = 0 时不再主动报错,可下一行 range 的步长为 0 照样抛出 ValueError |
chunk 的 0 改成 1;median 的 % 改成 //;percent 的 * 改成 // | 测试不够:没有测 n = 1;median 只有 1、3、4 个元素时碰巧还对,测试正好只用了 3、4 个元素;测试里 part = 1,100 // 1 恰好等于 100 * 1 |
变异测试(mutation testing)本来就是衡量测试质量的工具:活下来的非等价变异,就是测试的盲区。第 12 讲会看到,SWE-bench Verified 被批评”测试不充分”——说的就是这一类。
5.3 一个任务要满足三件事
issue:Bug report: `truncate` in `textkit/strings.py` returns wrong results. Failing scenario(s): test_truncate_exact. ...
FAIL_TO_PASS:tests.test_strings::test_truncate_exact
PASS_TO_PASS:27 个测试
(答案,不给智能体看:textkit/strings.py:12 '<=' → '<')
- 有 bug:至少一个测试从通过变成失败(FAIL_TO_PASS);其余测试要一直通过(PASS_TO_PASS),防止”修好一个、弄坏别的”。
- 可解:把 bug 改回去,测试全部通过——22 / 22。
- 描述不泄露答案:issue 只说症状和失败的场景,不说改了哪一行。SWE-smith 用 LLM 写 issue,这一步要专门检查(代码 07 用真实的 Claude 试一试)。
5.4 拒绝采样会让数据偏向容易的任务
有了任务,下一步是让智能体去解,只留下解对了的轨迹拿来训练(拒绝采样,SWE-smith 训练 SWE-agent-LM 就是这样做的)。代码 05 用一个模拟的智能体(解对的概率按变异类型设定,“常量 +1”最难,一次 45%):
| 变异的类型 | 在任务里的比例 | 留下的轨迹里(每个任务试 1 次) | (每个任务试 4 次) |
|---|---|---|---|
| 换运算符 | 59% | 62%(5) | 62%(13) |
| 常量 +1 | 27% | 12%(1) | 24%(5) |
| 删掉一行 | 9% | 25%(2) | 10%(2) |
| and ↔ or | 5% | 0%(0) | 5%(1) |
| 合计 | 22 个任务 | 8 条轨迹 | 21 条轨迹 |
只试 1 次时,最难的那类在训练数据里的比例从 27% 掉到 12%——模型越不会的,越少出现在它的训练数据里。每个任务多试几次(第 2 讲的 pass@k),留下的轨迹从 8 条变成 21 条,难任务的比例也回来了;代价是更多的推理成本。这也是第 9 讲 BetterTogether 的另一面:用程序自己的成功轨迹训练,它学到的是”它已经能做的事”。
6. 谁来验证验证者 · Who validates the validators?
6.1 EvalGen
LLM 应用的自动评估靠断言:一段代码检查(“补偿金额 ≤ 上限”)或一个 LLM 评审问题(“客服遵守公司政策了吗?”)。让 LLM 生成候选断言很容易——但 Shankar et al. 指出,LLM 生成的评估器继承了 LLM 自己的所有毛病,还需要人来验证。他们的 EvalGen 是一个混合主动的界面:
- LLM 提出评判标准和每个标准的候选实现(Python 函数或 LLM 评审提示词);
- 人给一部分输出打”好 / 坏”;
- 系统挑出和人的打分最一致的实现:覆盖率(人判坏的输出里,断言抓住了多少)高、误报率(人判好的输出里,断言错判了多少)低。
他们的定性研究还观察到标准漂移:用户需要标准才能给输出打分,而给输出打分的过程又在帮用户形成标准;有些标准依赖于看到的具体输出,没法事先定死。
6.2 候选断言
代码 06:第 10 讲客服世界第 9 周的 300 条对话,10 个候选断言(对照真实结果):
| 候选断言 | 覆盖率 | 误报率 |
|---|---|---|
| 代码:补偿不超过当前上限 | 30% | 0% |
| 代码:改地址前验证了 PIN | 3% | 0% |
| 代码:太阳能请求查了资格 | 36% | 0% |
| 代码:先查了客户 | 30% | 0% |
| 代码:要转人工就转了 | 5% | 0% |
| 代码:回复不超过 25 个词 | 0% | 0% |
| 代码:回复里没有 “not sure” | 36% | 0% |
| LLM:客服遵守公司政策了吗? | 71% | 8% |
| LLM:客户的问题解决了吗? | 65% | 3% |
| LLM:客服礼貌吗? | 1% | 1% |
代码检查精确但窄;LLM 评审覆盖面宽但有误报;“回复不超过 25 个词""礼貌吗?“这种听起来像评估标准的断言,和这个应用真正的失败无关——LLM 提候选断言时很容易提出一堆这样的东西。
6.3 挑选,以及挑选的局限
人打了前 n 条的分之后,按 EvalGen 的思路贪心地挑(每次加一个让覆盖率提高最多、合起来误报率不超过 10% 的断言),在后 220 条没打过分的对话上对照真实结果:
| 挑中的断言 | 覆盖率 | 误报率 | |
|---|---|---|---|
| 打了 10 条 | 太阳能请求查了资格、先查了客户 | 61% | 0% |
| 打了 30 条 | 客户的问题解决了吗? | 60% | 4% |
| 打了 80 条 | 客户的问题解决了吗?、补偿不超过当前上限 | 87% | 4% |
| 对照:全部 7 个代码断言 | 100% | 0% | |
| 对照:只用 LLM”遵守公司政策了吗?” | 74% | 8% | |
| 对照:全部 10 个断言 | 100% | 13% |
- 打的分越多,挑出来的越好:80 条时覆盖率 87%、误报率 4%。全部 10 个一起用覆盖率 100%,但误报率 13%——监控系统的误报多了,大家就不看了。
- 要说清楚的一点:7 个代码断言合起来是 100% / 0%,比挑出来的还好。这是模拟世界的特点:每一种失败都在轨迹里留下了可以用代码检查的痕迹。贪心的挑选在少量例子上先选中了一个覆盖面宽的 LLM 断言,几个窄而准的代码断言就再也”加不进来”了——小样本上的挑选是短视的。真实应用里,很多质量问题(解释对不对、语气合不合适)没法用代码查,这时 LLM 断言才不可替代。
6.4 标准漂移
模拟的打分者一开始只关心”问题解决了没有”;同一类”补偿超过上限”的对话看到第 3 次(打到第 21 条)时,才意识到这也是问题。在那之前被判成”好”的那一条,用最后的标准看是坏的。所以:
- 断言集合不能一次定死,要随着打分持续更新;
- 早期打的分要回头复查;
- 这和第 10 讲的标准漂移是同一件事:评估本身是要维护的数据。
7. 用真实模型试一试 · Try it on a real model
07_real_llm.py(可选,需要 pip install anthropic 和 ANTHROPIC_API_KEY,5 次调用)把代码 05 去重后的前 5 个任务(改坏后的函数 + 失败的测试)交给 Claude,要求它写一段 GitHub issue——“只描述症状,不要提示怎么修”,再用一个简单的检查找泄露:issue 里是否出现被改动那一行的代码、是否同时提到改动前后的两个运算符、是否出现”instead of / change the”这类修改建议。
python3 07_real_llm.py
不联网检查脚本本身:python3 07_real_llm.py --toy(用模板 issue)。
读结果时想一想:Claude 看到的是改坏之后的函数,它很容易”顺手”指出哪里写错了——它有没有?关键词检查只能抓到明显的泄露,读一读 issue 本身,看它有没有用别的说法暗示修法。issue 像不像真实用户写的(有没有具体的复现例子)?编写时没有用真实 API 跑过这个脚本,这里不报告数字。
8. 一份数据质量清单 · A data-quality checklist
- 来源:每条数据记下出处(谁 / 什么程序生成或标注、什么时候、用的哪版指南或提示词)。系统性的错误只能靠来源发现。
- 去重:训练集内部去重;训练集和评估集之间去重(第 10 讲 §5.4:先去掉模板文字,再选阈值)。
- 规则过滤:格式残缺、字段缺失、明显异常的记录。
- 基于模型的检查:交叉验证找”和其余数据冲突”的标签(适合随机噪声);IRT 找”越强越错”的评估题。
- 标注:可操作的指南(检查清单 + 正反例 + 边界情形);先小规模试标、算 κ、讨论分歧、修改指南,再大规模标;多人标注用 Dawid–Skene 或至少多数投票;有分歧的交给专家。
- 选择:先保证质量,再谈数量和多样性;多样性要按任务关心的维度;主动学习在标注很贵、模型已经不错时才值得。
- 可验证:能用程序验证的(测试、执行结果、规则)就用程序验证;不能的,至少抽样人工检查。
- 评估器也要验证:自动断言要和人的打分对齐(覆盖率、误报率),并随着标准的变化更新。
9. 小结 · Summary
- 质量 vs 数量:LIMA 的 1,000 条示范说明,对齐格式和风格不需要很多数据——前提是精心挑选;要学新知识,数据量仍然重要。在报销审核上,1,000 条好标签胜过 6,900 条混杂数据。
- 信息量:IRT 给每道题难度和区分度;区分度为负的题多半标错了答案;tinyBenchmarks 用几十到一百道题加上 IRT 估计整个 benchmark 的分数——估计器(p-IRT)往往比挑题方法更重要,前提是新模型和已知模型行为相似。
- 过滤与选择:去重改变了数据的权重;基于模型的噪声过滤能抓随机噪声,抓不到系统性错误——那要靠来源;多样性要在任务关心的维度上;主动学习收益小而不稳定。
- 标注:指南决定了能标出什么;一致性不等于正确;Dawid–Skene 不看真实标签就能估出每个人有多准;仲裁把贵的人放在有分歧的地方。
- 从仓库和轨迹造数据:SWE-smith 用”注入 bug + 测试验证”把任务规模化;测试抓不到的变异暴露测试的盲区;拒绝采样让训练数据偏向容易的任务,多次尝试可以部分纠正。
- 验证验证者:自动断言要和人的打分对齐;人的标准在打分过程中形成和漂移;评估本身是要维护的数据。
10. 常见误区 · Common pitfalls
- “数据越多越好。” 6,900 条混杂数据(83.3%)不如 1,000 条好标签(92.7%)。
- “LIMA 说明 1,000 条就够了。” 它讲的是对齐格式和风格;要模型学会它本来不知道的规则,数据量仍然重要。
- “正确率正常的题就是好题。” 标错答案的题平均正确率 56%,看起来完全正常;要看”谁做对了”——强模型反而做错。
- “小 benchmark 只要随机抽题就行。” 随机抽 30 道题误差 7 个百分点;用 IRT 估计能降到 5 个左右。
- “模型能自己找出错标。” 只能找出随机的错标;系统性的错误(几百条错成一样)会被当成规律学进去。
- “标注者一致性高,标签就可靠。” 4 个人可以按同一份含糊的指南一致地犯同一个错(81% 的”看不见的失败”被一致判成功)。
- “多找几个人标,总比一个人好。” 混进一个乱点的人,5 人多数投票不如 3 个靠谱的人;要么筛人,要么用 Dawid–Skene。
- “测试全过了,代码就没问题。” 6 个变异活了下来,其中几个是测试的盲区。
- “只用成功的轨迹训练,数据就是干净的。” 它是干净的,也是有偏的:模型越不会的任务,越少出现在训练数据里。
- “LLM 生成的断言可以直接拿来评估。” 有的断言看起来合理却和真正的失败无关;要用人的打分挑,而且人的标准还在变。
11. 练习 · Exercises
练习 1(IRT 的直觉):两道题:题 1 的 a = 2.0、b = 0;题 2 的 a = 0.5、b = 0。(a) 能力 θ = 1 和 θ = −1 的两个模型在两道题上做对的概率各是多少?(b) 哪道题更能区分这两个模型?(c) 一道题的 a = −1.5 意味着什么?
参考答案
(a) 题 1:sigmoid(2) ≈ 0.88,sigmoid(−2) ≈ 0.12;题 2:sigmoid(0.5) ≈ 0.62,sigmoid(−0.5) ≈ 0.38。(b) 题 1:两个模型做对的概率差 0.76,题 2 只差 0.24——区分度高的题更能把强弱模型分开(Fisher 信息 a²·p·(1−p) 在 θ = b 附近,题 1 是 1.0,题 2 只有 0.0625)。(c) 能力越高,做对的概率越低——在正常的题上不该出现;最常见的原因是标准答案标错了(或者题目有歧义,强模型选了"更好"但不是标准的答案)。拟合 IRT 后,把 a < 0 的题拿出来人工检查,是一个很便宜的质量检查。练习 2(为什么 p-IRT 更准):一个新模型只做了 30 道随机题,做对 18 道。(a) 加权平均的估计是多少?它的标准误大约多大?(b) p-IRT 怎样利用其余 1,170 道题的信息?(c) 什么情况下 p-IRT 会比加权平均更差?
参考答案
(a) 18/30 = 60%;标准误 ≈ √(0.6 × 0.4 / 30) ≈ 9 个百分点(加上有限总体的修正略小)。(b) 先用这 30 道题的对错和它们的题目参数估计出模型的能力 θ(一个数),再用每道没做的题的参数算出 P(做对),加起来得到总体准确率。这相当于用"1,200 道题的难度结构"把 30 个观察平滑了:做对了一道很难的题,比做对一道很容易的题提供更多关于 θ 的信息。(c) 当 IRT 模型不对时:新模型和用来拟合的那些模型行为很不一样(例如一个专门针对某个科目训练的模型,一维的 θ 解释不了它);或者已知模型太少、题目参数估得不准。gp-IRT 用 λ 在两者之间权衡,正是为了防这种偏差。练习 3(去重改变了什么):代码 03 里,去掉 900 条完全重复的记录后,标签错误率没变(25.8%),准确率却从 83.3% 涨到 87.6%。给出两种可能的解释,并设计一个实验区分它们。
参考答案
解释 1:重复是随机挑的,被重复的样本里恰好错标的比例偏高,或者它们集中在某些类别上,让模型在那些类别上过拟合了错误的标签。解释 2:训练是 SGD,重复的样本被多更新几次,相当于加大了某些样本的权重,改变了决策边界(即使它们的标签是对的)。实验:(a) 只重复标签正确的样本,看准确率是否仍然下降;(b) 不删重复,而是给每条记录按"出现次数的倒数"加权训练,看能否恢复到 87.6%;(c) 换几个随机种子构造重复,看下降的幅度是否稳定。练习 4(系统性错误):代码 03 的噪声过滤只抓到了弱标注者 16% 的错标。(a) 为什么?(b) 如果不知道每条标签的来源,还有什么办法发现这类错误?
参考答案
(a) 弱标注者的错误是一致的(它不知道公司规定,所以总把"周末的非差旅支出""超标的餐费"判成批准)。几百条数据都这么标,交叉验证训练出来的模型也学到了"这些单子该批准",于是它"同意"这些错标,不会把它们当成冲突。(b) ① 用一小批高质量的人工标注做"金标准",按特征分组比较数据池的标签和金标准,看哪一组的不一致率特别高(例如周末的单子);② 看标签分布在时间、标注批次、标注者维度上的异常(某个批次 APPROVE 的比例特别高);③ 用不同来源的模型(例如写全政策的程序)重新标一遍,在两者不一致的地方集中人工检查;④ 写可执行的规则检查(周末的非差旅支出不能是 APPROVE)。共同点:引入**独立于这批数据**的信息。练习 5(κ 的陷阱):两个标注者标 100 条对话,都把 90 条判成功、10 条判失败,而且完全一致。(a) 他们的 Cohen’s κ 是多少?(b) 如果其实有 30 条失败,他们的准确率是多少?(c) 这说明了什么?
参考答案
(a) p_o = 1,p_e = 0.9² + 0.1² = 0.82,κ = (1 − 0.82)/(1 − 0.82) = 1——完美一致。(b) 最多 80%(如果他们判失败的 10 条都是真失败,另外 20 条真失败被一致判成了成功)。(c) κ 只衡量两人之间的一致,不衡量和真相的一致。按同一份含糊的指南、同样的盲点,两个人可以完美一致地错。要衡量准确性,需要一小批由专家仔细标注的金标准,或者用对抗性的例子(专门构造的边界情形)检查标注者。练习 6(Dawid–Skene):3 个标注者,A 的准确率 0.95、B 0.9、C 0.6(两类各一半,混淆矩阵对称)。一条对话 A 说”好”、B 和 C 说”坏”。(a) 多数投票判什么?(b) 按 Dawid–Skene(已知准确率)的后验,判什么?
参考答案
(a) 多数投票:坏。(b) 先验各 0.5。真是"好"时,A 说好 0.95、B 说坏 0.1、C 说坏 0.4,似然 0.95 × 0.1 × 0.4 = 0.038;真是"坏"时,A 说好 0.05、B 说坏 0.9、C 说坏 0.6,似然 0.05 × 0.9 × 0.6 = 0.027。后验 P(好) = 0.038/(0.038 + 0.027) ≈ 0.58,判"好"。A 的一票比 C 的一票可信得多;Dawid–Skene 就是按可信度加权投票(权重是 log 似然比)。练习 7(设计标注指南):为第 10 讲客服对话的”成功 / 失败”标注写一份指南的提纲:包括哪些部分、怎样处理边界情形、怎样迭代。
参考答案(之一)
① 目的和定义:"成功 = 客户的请求得到正确处理,并且没有违反任何一条政策";② 逐条检查清单(每条政策一项,写清楚怎样从轨迹里看出来:例如"issue_credit 的金额 ≤ 当前上限,上限见政策 v3");③ 正反例:每一条至少一个成功和一个失败的真实对话;④ 边界情形:客户中途改主意、请求不在服务范围内、智能体做对了事但说错了话;⑤ 不确定时怎么办:标"不确定"并写理由,而不是猜;⑥ 版本号和变更记录(政策变了,指南也要变,第 10 讲的标准漂移)。迭代:3 个人各标 50 条 → 算 κ、逐条讨论分歧 → 修改指南 → 再标 50 条,直到 κ 稳定在可接受的水平,再大规模标注;之后定期抽查。练习 8(等价变异):(a) 为什么 SWE-smith 式的流水线必须过滤掉”没有测试失败”的变异?(b) 一个变异”没有测试失败”时,怎样判断它是等价变异还是测试的盲区?(c) 如果是盲区,能把它变成一个任务吗?
参考答案
(a) 没有测试失败,就没有办法自动判断"修好了没有"——任务不可验证;而且它可能根本不是 bug(等价变异)。(b) 写一个专门的测试试图区分原代码和变异后的代码(例如对 median 用 2 个、5 个元素):如果能写出这样的测试,它就是盲区;如果对所有输入行为都一样(例如 clamp 里 `<` 改 `<=`),就是等价变异。也可以用随机输入做差分测试(同样的输入跑两个版本,比较输出)。(c) 可以:先补上那个能区分的测试(它在原代码上通过、在变异代码上失败),这个变异就成了一个有效任务——顺带也改进了仓库的测试。练习 9(拒绝采样的偏差):一个任务集里,容易的任务占 70%(模型一次解对的概率 0.8),难的占 30%(0.2)。(a) 每个任务试 1 次、只留成功的轨迹,难任务在训练数据里占多少?(b) 试 4 次(任何一次成功就留下一条)呢?(c) 还有什么办法让训练数据里有更多难任务?
参考答案
(a) 容易:0.7 × 0.8 = 0.56;难:0.3 × 0.2 = 0.06;难任务占 0.06/0.62 ≈ 9.7%(原来是 30%)。(b) 4 次里至少成功一次的概率:容易 1 − 0.2⁴ ≈ 0.998,难 1 − 0.8⁴ ≈ 0.59;占比 0.3 × 0.59 / (0.7 × 0.998 + 0.3 × 0.59) ≈ 0.177/0.876 ≈ 20%。(c) 对难任务给更多的尝试次数(按难度分配推理预算,第 9 讲的计算最优);对难任务给提示(例如告诉它是哪个文件)生成成功轨迹,训练时再去掉提示;用更强的模型生成难任务的轨迹(蒸馏);对难任务的成功轨迹加权。练习 10(开放题:给你的评估器做一次”验证”):你有一个用 LLM 评审判断”客服回答是否合规”的评估器。设计一个流程验证它,并说明之后怎样维护它。
参考答案(之一)
1) 抽 100–200 条对话(随机 + 按信号富集的失败各一部分),请 2–3 个人按可操作的指南标注,算 κ、讨论分歧,得到一批金标准;2) 在金标准上算评估器的覆盖率(召回)和误报率,按失败类型拆开看——它漏掉的往往是某几类(例如需要最新政策的);3) 把漏掉的和误报的例子加进评估器的提示词(少样本示例、政策原文),或者为那几类写代码断言;4) 在另一批没用过的金标准上复测(防止对第一批过拟合);5) 维护:每周抽样人工复查评估器的判断,记录人和评估器不一致的例子;政策变化时同步更新评估器的提示词和金标准;定期(例如每月)重新算覆盖率和误报率,下降就回到第 3 步。这就是 EvalGen 的"对齐"加上第 10 讲的数据飞轮。12. 延伸阅读 · Further reading
本讲的阅读材料
- J. Yang et al., SWE-smith: Scaling Data for Software Engineering Agents, NeurIPS D&B 2025.
- S. Shankar et al., Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences, UIST 2024.
- C. Zhou et al., LIMA: Less Is More for Alignment, NeurIPS 2023.
相关
- F. M. Polo et al., tinyBenchmarks: Evaluating LLMs with Fewer Examples, ICML 2024(第 12 讲的阅读材料)。
- C. Northcutt et al., Confident Learning: Estimating Uncertainty in Dataset Labels, JAIR 2021(基于模型的标签噪声检测)。
- A. P. Dawid, A. M. Skene, Maximum Likelihood Estimation of Observer Error-Rates Using the EM Algorithm, 1979.
- B. Settles, Active Learning Literature Survey, 2009.
- C. E. Jimenez et al., SWE-bench: Can Language Models Resolve Real-World GitHub Issues?, ICLR 2024.
13. 下一讲预告 · Next lecture
第 12 讲 评估基础与 benchmark 设计(11/4)。这两讲一直在用”评估集”:拿它挑提示词、估计成功率、判断数据好不好。下一讲正面讨论怎样造一个好的评估:为什么智能体的评估特别难;Press 的 4 元组框架——请求、环境、停止条件、判分器——每一个部件怎样设计;好 benchmark 的性质(自然、可自动判分、有挑战);Zhu et al. 的智能体 benchmark 检查清单(ABC:任务有效性、结果有效性)和他们发现的真实漏洞(τ-bench 把空回答算成功、SWE-bench Verified 的测试不充分,误差可达 100%);贴近真实的脚手架;可靠性的几个维度;以及评估的统计(置信区间、配对比较、需要多少道题)。配套代码会按 4 元组搭一个小 benchmark,故意埋下几种有效性漏洞(判分器太松、任务有捷径、环境泄露答案、停止条件太早),看它们怎样扭曲排名,再按 ABC 修好。