对应 Maps to:CMU 17-630 Prompt Engineering(Prof. Travis Breaux),第 6 周的第二个主题 · Auto-Prompting。原课 Schedule 对这一讲的说明(意译):各种”自我提示”的技术,包括指令归纳、自我纠正、验证链和 LLM-as-a-judge。 依据说明:原课的 slides 和阅读清单不公开。Schedule 点名的四项——指令归纳(§2)、自我纠正(§5)、验证链(§7)、LLM-as-a-judge(§8)——各占一节;APE 与 OPRO(§3)、提示优化里的过拟合(§4)、Self-Refine 与反馈的质量(§6)是讲义补充的。材料来自 Honovich et al.(2023,Instruction Induction)、Zhou et al.(2023,APE)、Yang et al.(2024,OPRO)、Madaan et al.(2023,Self-Refine)、Huang et al.(2024,LLMs Cannot Self-Correct Reasoning Yet)、Dhuliawala et al.(2024,CoVe)、Zheng et al.(2023,MT-Bench / LLM-as-a-judge)、Dubois et al.(2024,长度控制的 AlpacaEval)、Verga et al.(2024,PoLL)等;引用的数字都核对过原文。全部实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture10/(只用 Python 标准库;1 个模块 + 6 个脚本;不需要 API key;每个脚本 20 秒以内) 预计阅读时间:3.5 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 把”自动提示”的各种方法放进同一个框架:一个生成器提出候选,一个评估器打分挑选;说出每种方法里谁是生成器、谁是评估器。
- 解释指令归纳为什么本质上是一个假设搜索问题,为什么例子要覆盖边界情况。
- 写出 APE 和 OPRO 的流程,并能估计”在开发集上挑第一名”会虚高多少;知道怎样用验证集、提前停止来防止过拟合。
- 区分内在的自我纠正和有外部反馈的自我纠正;说清楚 Huang et al.(2024)的三个批评:标准答案泄漏、不公平的初始提示、和同样调用次数的 self-consistency 比较。
- 说出验证链(CoVe)有效的条件(短回答比长回答准、验证时看不到草稿),以及它去不掉哪类幻觉。
- 测量一个 LLM 判官的位置偏差、长度偏差、自我偏好,并用交换顺序、长度控制、评审团、参考答案去修正。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 前置讲 P3:置信区间、配对检验、赢家诅咒 | 代码生成里的自我调试(第 11 讲) |
| 第 2 讲:上下文学习,提示对说法和示例的敏感性 | 多智能体辩论(第 15 讲) |
第 4 讲:思维链、self-consistency、SimReasoner | 评估设计、benchmark 与指标(第 17 讲) |
| 第 5 讲:长尾知识、稳定的幻觉、SelfCheckGPT | |
| 第 6 讲:RLHF 与谄媚;第 8 讲:CRITIC(用工具验证) |
0.3 本讲地图 Map
§1 为什么要"自动":生成器 + 评估器
§2 指令归纳:从例子猜指令 ← 代码 01
§3 APE 与 OPRO:生成很多、挑一个;把 LLM 当优化器 ← 代码 01、02
§4 提示优化里的过拟合:赢家诅咒、轨迹、真假改进、补救 ← 代码 02
§5 自我纠正:没有外部反馈时"再想想"为什么不行 ← 代码 03
§6 Self-Refine:反馈的质量决定一切 ← 代码 03
§7 验证链 CoVe:把长回答拆成可以单独核对的说法 ← 代码 04
§8 LLM-as-a-judge:偏差与修正 ← 代码 05
§9 在真实模型上 ← 代码 06(可选)
§10 小结 §11 常见误区 §12 练习 §13 延伸阅读 §14 下一讲预告
0.4 记号与术语 Notation & terms
| 术语 | 本讲义里的意思 |
|---|---|
| 指令归纳(instruction induction) | 给模型看几个输入—输出的例子,让它写出”产生这些例子的指令” |
| 执行准确率(execution accuracy) | 让模型照着一条指令做题,做对的比例——评价一条指令的标准 |
| 开发集 / 验证集 / 测试集 | 开发集:搜索时打分用;验证集:最后在少数候选里做决定用;测试集:只用来报告,从不参与挑选 |
| 虚高(optimism) | 被挑中的候选在开发集上的分数 − 它在测试集上的准确率 |
| 内在的自我纠正(intrinsic self-correction) | 没有任何外部信息,只让同一个模型审视并修改自己的回答 |
| 外部反馈(external feedback) | 来自模型之外的信号:程序、测试、检索结果、计算器、人 |
| 判官(judge) | 给回答打分或在两个回答之间选一个的模型 |
| 位置偏差 / 长度偏差 / 自我偏好 | 判官偏爱放在某个位置的、更长的、自己(家族)写的回答 |
0.5 本讲的实验装置 · The setup
这一讲的大部分实验在一个词表变换世界(autoprompt_lab.py)里做。每道题是一串小写单词,8 个隐藏任务由 1–3 个操作组成:
输入 bee turtle lemon banana bee lemon pear
dedupe_sort(去重→排序) banana bee lemon pear turtle
short_upper(删短词→大写) TURTLE LEMON BANANA LEMON PEAR
- 执行模型
SimExecutor读英文指令,真的一句一句地执行。每个操作有几种说法,说法越含糊,越容易在某道题上被”读歪”:Capitalize the words.在 40% 的题上被读成”只大写首字母”,Remove short words.在 40% 的题上连 4 个字母的词也删掉。读没读歪由(这句话, 这道题)的哈希决定,所以两个共用同一句话的提示错在同样的题上——和真实模型一样,提示之间是相关的。 - 万能句:
Take a deep breath and work on this step by step.、Be careful.、You are an expert at word games.这类和任务无关的句子。模拟器里只有一句(Double-check each word before answering.)有真实的好处,一句有真实的坏处,其余的期望效果是零——但它们会”扰动”一部分题,改变错在哪些题上。§4 会看到这正是搜索会”发现”的东西。 - 归纳模型
SimInducer看几个例子,在 314 个”操作组合”假设里真的搜索能解释所有例子的那些,偏爱简单的,再写成英文。
另外三套装置:第 4 讲的多步算术推理者 SimReasoner(§5),一个约束生成任务(§6),一个长尾世界里的列表题模型 SimListLLM(§7),以及一个有三个偏差旋钮的判官 SimJudge(§8)。模拟器的每种行为都来自一个显式的旋钮,它们能把论证变具体,但不能证明真实模型就是这样;每次用它们,讲义都给出论文里真实模型的数字作对照。
1. 为什么要”自动” · Why automate
第 2 讲你看到,同一个任务,换一种模板、换几个示例、换一个标签词,准确率就能差 20 个百分点。提示工程的日常工作就是在一个巨大的离散空间里搜索:试一种说法,看看结果,再改。这件事人做起来又慢又依赖直觉。一个自然的想法是:既然模型会写字,能不能让它自己来写提示、改提示、检查答案?
这一讲的所有方法都可以放进同一个框架:
┌──────────── 生成器:提出候选(提示 / 回答 / 修改)
循环 ──── ┤
└──────────── 评估器:给候选打分,决定留下谁、要不要再改
| 方法 | 生成器 | 评估器 |
|---|---|---|
| 指令归纳、APE(§2–§3) | LLM 看例子写指令 | 开发集上的执行准确率 |
| OPRO(§3) | LLM 读历史候选和分数,写新候选 | 开发集上的分数 |
| 内在的自我纠正(§5) | 同一个 LLM 重写答案 | 同一个 LLM 的判断 |
| Self-Refine(§6) | 同一个 LLM 按反馈改写 | LLM 写的反馈(或程序) |
| 验证链 CoVe(§7) | LLM 写草稿 | 同一个 LLM 单独回答的验证问题 |
| LLM-as-a-judge(§8) | 任何系统 | 另一个(或同一个)LLM |
这张表里藏着这一讲的主线:自动化放大的是评估器。生成器可以很便宜地提出成百上千个候选,最后留下哪一个,完全由评估器决定。评估器有噪声(开发集太小,§4),评估器和生成器犯同样的错(同一个模型检查自己,§5、§7),评估器有偏差(判官偏爱长回答,§8)——这些缺陷都会被搜索放大:搜得越多,越可能挑中一个”恰好骗过评估器”的候选。
所以读这一讲的每一节时,都问同一个问题:这里的评估器可靠吗?它和生成器独立吗?
2. 指令归纳:从例子猜指令 · Instruction induction
2.1 任务和评价方式
Honovich et al.(2023)提出了”指令归纳”这个任务:给模型看几个输入—输出的例子,然后写 “The instruction was”,让它续写出产生这些例子的指令。他们整理了 24 个小任务(变复数、找反义词、翻译、判断情感……)。
怎样评价一条归纳出来的指令?他们的做法是执行它:把这条指令交给一个模型,让它在新输入上照着做,看做对多少——执行准确率。这个评价方式很重要:一条指令好不好,不看它和人写的指令字面像不像,而看它能不能让模型做对。
他们报告:InstructGPT 的执行准确率达到人写指令的 65.7%,原始的 GPT-3 只有 9.8%。会不会归纳指令,取决于模型是否足够大、是否经过指令微调(第 3 讲)。
2.2 归纳是一个假设搜索
为什么归纳会错?因为几个例子不能唯一地确定一条规则。看这个例子:
Input: bee turtle lemon banana Output: banana bee lemon turtle
“按字母排序”能解释它;“先去重再排序”也能(这个例子里没有重复的词);“先倒序再排序”也能。归纳模型必须在所有能解释例子的规则里挑一个——人会挑最简单的那个(奥卡姆剃刀),模型通常也是。
SimInducer 把这个过程显式地做出来:在 314 个”操作组合”假设里,找出能解释全部例子的那些,按”越简单越可能”随机挑一个,再写成英文(还有 15% 的概率漏看一个例子——模型并不总是认真对待每个例子)。01_instruction_induction.py 演示 1:8 个任务、每种例子个数各归纳 40 次。
| 例子个数 k | 函数上正确 [95% CI] | 平均执行准确率 | 其中函数上正确的那些 |
|---|---|---|---|
| 1 | 66.2% [60.9, 71.2] | 61.6% | 76.1% |
| 2 | 77.2% [72.3, 81.4] | 67.0% | 75.4% |
| 3 | 86.2% [82.0, 89.6] | 69.8% | 73.8% |
| 5 | 94.1% [90.9, 96.2] | 72.4% | 73.7% |
| 8 | 96.9% [94.3, 98.3] | 74.5% | 75.0% |
| 人写的指令 | 100.0% | 91.2% | 91.2% |
| 最清楚的说法 | 100.0% | 93.2% | 93.2% |
“函数上正确”是指归纳出的操作组合在 300 道题上和隐藏任务完全一样。一个例子时只有 66%;例子多了,能解释所有例子的规则越来越少,到 8 个例子时 97%。
2.3 错在哪里:例子没有暴露的操作
演示 2 只给 2 个例子,看错在哪里:
| 错误类型 | 占错误的比例 |
|---|---|
| 多了操作 | 47.3% |
| 漏了操作:dedupe | 37.0% |
| 漏了操作:drop_short | 6.8% |
| 操作对、顺序错 | 5.8% |
| 其他 | 3.0% |
“漏了”和”多了”是同一个原因:例子没有暴露某个操作的效果。例子里恰好没有重复的词,“去重”做不做都一样——模型可能不写它(漏了),也可能写一个对这两个例子没有影响的操作(例子里恰好没有短词时,“删短词”就是这样一个操作)。
怎么办?与其多给例子,不如挑好例子:同样是 2 个,挑”有重复词、有短词”的例子,函数上正确从 81.8% 提到 89.1%,接近随机给 3 个例子的水平。这和第 2 讲”示例的选择影响上下文学习”是同一件事,只是这里影响的是”从示例里归纳出的指令”。实践上:给模型(或者给人)看的例子要覆盖边界情况——重复、空输入、特殊字符、最长的情况。
2.4 两层正确:规则对,说法也要对
表的最后一列值得多看一眼:即使函数上正确,执行准确率也只有约 75%,比人写的指令(91.2%)低一大截。原因是归纳模型按”顺口”而不是按”清楚”挑说法:Capitalize the words.(顺口,但在 40% 的题上被读成首字母大写)比 Write every word in uppercase. 更常被写出来;它还可能多写一个对结果没影响、却可能被读歪的操作,或者加一句万能句。
所以一条指令要过两关:规则对(逻辑层面),说法清楚(执行它的模型层面)。第二关和执行模型有关——对这个模型含糊的说法,对另一个模型可能很清楚。这正是下一节”在开发集上挑”的理由:哪种说法好,要让执行模型自己来告诉你。
3. APE 与 OPRO:生成很多、挑一个 · Generate many, select one
3.1 APE:自动提示工程师
Zhou et al.(2023)的 APE(Automatic Prompt Engineer)把指令归纳变成一个搜索过程:
- 提议:从训练数据里抽几组例子,让 LLM 为每组写一条指令(正向生成,“The instruction was”;他们也试了让模型在一段文字中间填空的方式);
- 打分:每条候选在一小份开发数据上执行,算执行准确率(或者正确答案的对数概率);
- 挑选:留下分数最高的;
- (可选)迭代:让 LLM 给高分候选写”意思相近的变体”,再打分——他们叫迭代的蒙特卡洛搜索。
01_instruction_induction.py 演示 3:每个任务 20 条候选(每条看 5 个例子),50 道开发题上挑第一名,1000 道测试题报告。
| 任务 | 随手拿一条 | APE 第一名:开发 → 测试 | + 两轮换说法 | 人写的 | 最清楚的说法 |
|---|---|---|---|---|---|
| sort | 84.5% | 98% → 95.6% | 98% → 95.6% | 96.2% | 97.2% |
| dedupe_sort | 77.7% | 96% → 94.1% | 96% → 94.1% | 93.4% | 95.4% |
| dedupe_rev_upper | 63.3% | 94% → 90.4% | 98% → 91.1% | 90.4% | 91.1% |
| short_sort_comma | 69.3% | 92% → 88.7% | 96% → 93.3% | 86.7% | 90.9% |
| 8 个任务平均 | 71.1% | 95% → 92.0% | 97% → 92.9% | 91.2% | 93.2% |
三点:
- “挑”本身就很有价值:随手拿一条平均 71.1%,挑出来的第一名 92.0%,和人写的指令(91.2%)差不多。候选里有一部分漏了操作、用了含糊的说法,开发集把它们筛掉了。
- 迭代的收益很小:两轮换说法,92.0% → 92.9%。APE 的原论文也发现迭代搜索只带来很小的改进,大部分收益来自第一轮”生成很多、挑一个”。
- 开发集分数偏高:第一名在开发集上平均 95%,测试上 92.0%。这不是偶然——§4 专门讲它。
APE 的原论文报告,自动生成的指令在大多数 Instruction Induction 任务上达到或超过人写的指令(论文不同版本报告的任务数不完全一样)。它还用同样的方法搜索零样本思维链的触发句,找到了 “Let’s work this out in a step by step way to be sure we have the right answer.”,在他们测的数学题上比 “Let’s think step by step.” 更好一点。
3.2 OPRO:把 LLM 当优化器
APE 的候选彼此独立地生成。Yang et al.(2024)的 OPRO(Optimization by PROmpting)更进一步:把优化过程本身交给 LLM。它的”元提示”(meta-prompt)里放着:
- 到目前为止最好的 20 条指令和它们的分数,按分数升序排列(最好的在最后,离模型要写的地方最近);
- 几个任务例子;
- 一句要求:“写一条和上面都不同、分数更高的指令”。
每一步让优化器 LLM 写 8 条新指令,在训练集的一个固定子集上打分,加进历史,重复上百步。直觉上,LLM 读到”这几条得分高、那几条得分低”,能猜出好指令的共性,往那个方向改——一种用自然语言做的”梯度下降”。
OPRO 报告,优化出来的提示比人设计的提示在 GSM8K 上最多高 8 个百分点、在 Big-Bench Hard 的任务上最多高 50 个百分点。最有名的例子:在 PaLM 2-L 上,它找到的 “Take a deep breath and work on this problem step-by-step.” 在 GSM8K 测试集上得到 80.2%,而 “Let’s think step by step.” 是 71.8%。
autoprompt_lab.opro 是一个简化版:优化器从历史前 20 名里按名次加权挑一条,做一次修改(换一句的说法、加一句万能句、删一句、交换相邻两句、加一个操作句)。这个”优化器”不会推理,但它保留了 OPRO 的结构:只看分数,往高分的方向走。§4 会看到这种结构的问题。
3.3 一个提醒:这些句子为什么有效?
“Take a deep breath” 对一个模型有效,并不意味着它对所有模型都有效,也不意味着我们知道它为什么有效。从搜索的角度看,任何一句在开发集上碰巧有帮助的话都可能被留下来。要判断它是不是真的有用,需要下一节的工具。
4. 提示优化里的过拟合 · Overfitting in prompt optimization
4.1 赢家诅咒
前置讲 P3 §5 讲过赢家诅咒:从 N 个候选里挑开发集分数最高的那个,它的开发集分数是有偏的——它之所以得第一,一部分是因为真的好,一部分是因为在这几道题上运气好。
直觉:开发集分数 = 真实水平 + 噪声,噪声的大小约为 。N 个候选里的最大值,大约在平均值之上 个噪声单位( 是标准正态;N = 5、20、100 时分别约 1.16、1.87、2.51)。p = 0.9、n = 20 时一个噪声单位是 6.7 个百分点——100 个候选时,独立的情况下最多可以虚高 16 个百分点。
02_prompt_overfitting.py 演示 1:每个任务准备一个”像样的”候选池(测试准确率都在 80% 以上,差别主要在说法和万能句),从里面抽 N 条,在 n 道开发题上挑第一名,重复 100 次:
| 开发集 n | 候选数 N | 第一名的测试准确率 | 第一名的开发集分数 | 虚高 | 比 N 条里最好的差 |
|---|---|---|---|---|---|
| 20 | 1 | 88.3% | 88.2% | −0.1 | 0.0 |
| 20 | 5 | 89.7% | 95.1% | +5.4 | 1.9 |
| 20 | 100 | 90.4% | 98.8% | +8.5 | 4.2 |
| 50 | 100 | 92.0% | 97.3% | +5.3 | 2.6 |
| 200 | 100 | 93.7% | 95.6% | +1.8 | 0.8 |
- 只有 1 条候选时没有”挑”,开发集分数是无偏的。候选越多,虚高越大;开发集越大,虚高越小。
- 实际的虚高(8.5)比独立假设下的估计小,因为候选彼此高度相关(共用同样的句子、错在同样的题上),而且分数有 100% 的天花板。但方向完全一致。
- 开发集小的时候,挑出来的也不是真正最好的那条(差 4.2 个百分点)——小开发集既虚报了成绩,又挑错了人。
4.2 迭代优化的轨迹
OPRO 式的迭代优化,比”从 100 条里挑一条”搜得更狠:60 步、每步 8 条,一共试几百条候选,而且每一步都在上一步的第一名附近找。演示 2 比较两种打分集:
| 第几步 | 20 题:开发 / 测试 / 虚高 | 200 题:开发 / 测试 / 虚高 |
|---|---|---|
| 1 | 75.6% / 74.2% / +1.4 | 81.1% / 79.0% / +2.2 |
| 6 | 87.5% / 85.8% / +1.7 | 93.6% / 91.9% / +1.7 |
| 11 | 95.6% / 91.0% / +4.6 | 95.1% / 92.5% / +2.6 |
| 21 | 97.5% / 90.0% / +7.5 | 95.8% / 93.5% / +2.3 |
| 60 | 98.8% / 90.5% / +8.2 | 96.8% / 93.5% / +3.3 |
两件事同时成立:
- 优化是有效的。20 题打分时,测试准确率从 74.2% 涨到 90.5%——从一条一般的归纳指令改成了不错的指令。
- 开发集夸大了进步。开发集分数涨了 23 个百分点,测试只涨了 16 个;多出来的 8 个只存在于那 20 道题上。即使用 200 题打分,第 21 步之后测试准确率就不再上升(93.5%),开发集分数却还在涨——继续搜索只是在拟合这 200 道题的噪声。
OPRO 的原论文对此很坦率:他们在 GSM8K 上只用训练集的 3.5% 打分,训练准确率常常比测试高 5–20 个百分点,过拟合确实存在;他们建议用更大的打分集和提前停止。
(一个我们原本以为会出现、结果没有出现的现象:20 题的搜索留下的万能句并不比 200 题的多——1.6 句 vs 2.4 句。20 题的分数很快碰到天花板,20 道里只错 0–1 道,搜索再也区分不出新候选,于是停止了”改进”。判断过拟合要看虚高,不要数句子。)
4.3 被”发现”的句子,哪些是真的?
演示 3 把每句万能句加在最清楚的指令前面,在 5000 道题上和不加的版本配对比较,再看同一句话在 20 道题上的”效果”:
| 万能句 | 5000 题上的效果 | 20 题上的”效果”范围 | 20 题上看起来有用的比例 |
|---|---|---|---|
| Double-check each word before answering. | +3.20 | −5 ~ +20 | 46% |
| Take a deep breath and work on this step by step. | −0.98 | −15 ~ +10 | 15% |
| This is very important to my career. | −1.03 | −10 ~ +10 | 22% |
| Answer precisely. | −0.82 | −10 ~ +10 | 25% |
| Ignore anything that seems irrelevant. | −4.81 | −20 ~ +5 | 2% |
(没用的句子在 5000 题上略为负,是因为每多一句话有一点点长度代价。)
- 没用的句子在 20 道题上有 1–2 成的时候”看起来有用”,有时 +10 个百分点——这就是它们在搜索里被留下的原因。
- 反过来,真正有用的那一句在 20 道题上也只有不到一半的时候”看起来有用”。小开发集既会放进假的改进,也会错过真的改进。
- 判断一句话是不是真的有用,要在同一组很多道题上做配对比较(P3 的 McNemar 检验),而不是看它在一次搜索里赢了没有。
4.4 怎样防
演示 4:仍然用 20 道题做搜索,比较几种补救(8 个任务平均):
| 做法 | 挑选时看到的分数 | 测试准确率 | 虚高 | 每个任务的执行次数 |
|---|---|---|---|---|
| 直接取第一名 | 98.8% | 90.5% | +8.2 | 8560 |
| 前 10 名在 200 道验证题上重新打分 | 91.7% | 91.7% | +0.0 | 10560 |
| 目标里加长度 / 万能句惩罚 | 99.4% | 93.6% | +5.8 | 6822 |
| 用验证集挑”哪一步的第一名”(提前停止) | 91.3% | 91.1% | +0.2 | 9135 |
| (对照)直接用 200 题打分 | 96.8% | 93.5% | +3.3 | 79225 |
- 验证集重新打分是最便宜、最稳的补救:大部分搜索用小集合,最后在少数几条里做决定时用大集合。验证集只用来”挑一次、挑 10 条里的一条”,挑的次数少,诅咒就弱——它给出的分数几乎没有虚高。
- 长度惩罚在这里效果最好(93.6%),但要小心这个结论:模拟器的万能句按设定大多没用,惩罚当然划算;真实模型上你不知道哪句话有用,惩罚也可能挡住真正有用的那一句,惩罚强度本身又是一个要调的超参数。
- 直接用 200 题打分和惩罚差不多好,而且不依赖”哪些句子没用”的假设,代价是 9.3 倍的执行次数。
把这一节总结成几条规矩:
- 搜索用开发集,最后的决定用验证集,报告用测试集;测试集从不参与任何挑选(第 17 讲)。
- 搜得越多(候选越多、步数越多),开发集要越大,或者越要依赖验证集。
- 报告提示的效果时,报测试集上的数字和置信区间,而不是”开发集第一名的分数”。
- 对搜索”发现”的具体句子保持怀疑:用配对比较确认它真的有用,换模型时重新测。
5. 自我纠正:没有外部反馈时”再想想”为什么不行 · Self-correction
5.1 一个诱人的想法
人做题时会检查:算完再验算一遍,发现错了就改。能不能让模型也这样?一类工作让模型先回答,再让它”审视你的回答、找出问题、改进它”,报告了不错的提升。
Huang et al.(2024)的论文题目就是结论:Large Language Models Cannot Self-Correct Reasoning Yet。他们区分了两种情况:
- 内在的自我纠正:没有任何外部信息,只让同一个模型审视自己。
- 有外部反馈的纠正:有来自模型之外的信号——标准答案、程序执行结果、检索到的事实、工具。
他们发现,在推理任务上,内在的自我纠正通常没有帮助,甚至会变差;一些早期工作报告的提升,来自用标准答案决定什么时候停(答对了就不再改),这在实际中不可能做到。
5.2 在多步算术上
03_self_correction.py 演示 1:400 道 4 步的算术题,第 4 讲的 SimReasoner(medium 规模)。第一次回答是少样本思维链(一次采样);之后每一轮,模型”把题重新想一遍”(新的一次采样),两次答案不同时决定改不改。
| 做法 | 准确率 [95% CI] | 调用次数 | 对 → 错 | 错 → 对 |
|---|---|---|---|---|
| 第一次回答 | 58.5% [53.6, 63.2] | 1.0 | ||
| 中性的”检查一下”,2 轮 | 58.8% [53.9, 63.5] | 5.0 | 68 | 69 |
| “找出问题并改进”(Huang et al. 的提示),2 轮 | 51.2% [46.4, 56.1] | 5.0 | 114 | 85 |
| 用标准答案决定停不停(作弊),2 轮 | 90.5% [87.2, 93.0] | 2.2 | 0 | 128 |
| 程序逐行检查算式(外部反馈),2 轮 | 87.5% [83.9, 90.4] | 1.4 | 2 | 118 |
| self-consistency:5 次采样投票 | 83.2% [79.3, 86.6] | 5.0 | 7 | 106 |
逐行读:
- 中性的纠正:改对 69 道,改错 68 道,正好抵消。模型第二次想出来的答案并不比第一次更可靠;两个答案不一样时,它分不清哪个对。
- 施压的提示(“Review your previous answer and find problems with your answer”)更糟:被要求”找出问题”时,它在答案一致时也会”找到”并不存在的问题,把对的改错(114 道)。
- 用标准答案停止看起来巨大地进步了——可是它只在答错的题上继续改,永远不会把对的改错(对 → 错 = 0)。这个数字衡量的是”在已知答错时再试一次”的价值,不是自我纠正的价值。
- 外部反馈(一个只会检查算术的程序,把第一处算错的那一行指给模型):87.5%,平均只多 0.4 次调用。
- 同样的调用次数给 self-consistency(5 次投票):83.2%,远好于 2 轮自我纠正(也是 5 次调用)。
Huang et al. 在 GPT-3.5-Turbo 上的数字:GSM8K 75.9% → 自我纠正 2 轮后 74.7%,CommonSenseQA 75.8% → 41.8%(多选题更容易被”改”);用标准答案停止时 GSM8K 是 84.3%。他们还比较了多智能体辩论和同样回答数的 self-consistency:GSM8K 上 9 个回答,辩论 83.0%,self-consistency 88.2%。
一个在写这一讲时踩到的坑:第一版实验里,第一次回答用的是贪心解码,“重想”用的是采样。
SimReasoner的贪心解码按设计走一条更容易出错的解题路径(第 4 讲为了演示 self-consistency 这样设计的),于是”重想”本身就更准,中性的纠正看起来有效(52% → 56%)。这正是一种常见的比较错误:纠正前后的回答必须来自同一个分布,否则你测到的是”换一种解码方式”的效果,而不是”纠正”的效果。
5.3 为什么:生成和验证一样难
演示 2 把”再想一遍、答案不一样就报警”当作一个错误检测器:
| 检测器 | 召回(错的题里报警) | 误报(对的题里报警) | 精确率 |
|---|---|---|---|
| 自我审视 | 93.4% | 37.2% | 64.0% |
| 程序逐行检查算式 | 79.5% | 1.7% | 97.1% |
自我审视能发现大部分错误,但也在 37% 对的题上误报——报警里只有 64% 真的有错。报警之后它又分不清该信哪个答案,所以没用。程序检查几乎不误报,代价是只能发现算术错误,读错题、漏掉步骤它看不见(检查器只对算术负责)。
这引出一个判断自我纠正能否有效的简单标准:验证比生成容易多少?
- 自己验证自己的推理:验证和生成一样难,用的是同一个模型、同样的知识、同样的误解。验证帮不了生成。
- 检查算术、检查格式、跑单元测试(第 11 讲)、查一个事实(第 7、8 讲):验证比生成容易得多,而且来自模型之外。这时反馈才真的有用。
第 8 讲的 CRITIC(用工具验证,100%)和没有工具的自我批判(65%)就是这条分界线的两边。
5.4 公平地比较一个自我纠正方法
Huang et al. 的批评可以整理成三条检查清单:
- 有没有用到标准答案?哪怕只是用它决定”什么时候停”。
- 初始提示是不是故意写弱了?如果纠正的提示里包含了初始提示没有的信息(“别忘了每个概念都要用上”),提升可能只是因为第二个提示更好(§6.3)。
- 和同样调用次数的基线比了吗?5 次调用的自我纠正,应该和 5 次采样的 self-consistency 比,而不是和 1 次调用比。
6. Self-Refine:反馈的质量决定一切 · Self-Refine
6.1 方法
Madaan et al.(2023)的 Self-Refine 用同一个 LLM 扮演三个角色:生成初稿 → 写反馈 → 按反馈改写,循环到反馈说”没问题”为止。他们在 7 个任务上(对话回复、代码优化、代码可读性、数学推理、情感反转、缩写生成、约束生成)报告了平均约 20 个百分点的绝对提升。
和 §5 的”再想想”不同,Self-Refine 的任务大多是开放式的生成,“好”有很多个维度(更礼貌、更可读、用上全部给定的词),反馈可以很具体。他们的消融说明了具体反馈的重要:把具体反馈换成笼统的反馈或者不给反馈,分数都下降(例如情感反转任务 43.2 → 31.2 → 0)。
6.2 在约束生成上:反馈从哪里来
03_self_correction.py 演示 3:CommonGen-Hard 式的约束生成——一句话要用上 15 个给定的词,300 组。第一次生成每个词有 75% 的概率被用上;改写时被指出的遗漏以 90% 的概率补上,但会顺手弄丢已有的词(每个 3%)。
| 反馈来自 | 第一次 | 改 1 轮 | 改 4 轮 | 全部用上 | 调用次数 |
|---|---|---|---|---|---|
| 程序(精确检查哪些词没用上) | 76.2% | 95.1% | 99.7% | 96.7% | 2.8 |
| 好的模型批评者(指出 80% 的遗漏,误报 2%) | 75.7% | 91.0% | 98.0% | 75.7% | 5.9 |
| 差的模型批评者(指出 40% 的遗漏,误报 5%) | 75.4% | 82.2% | 89.9% | 21.0% | 6.3 |
| 没有具体反馈(“Make it better.”) | 75.4% | 77.5% | 79.6% | 3.3% | 9.0 |
- 反馈来自程序时,Self-Refine 几乎完美:约束生成正是”验证比生成容易得多”的任务——哪些词没用上,字符串匹配就能检查。
- 好的批评者覆盖率也很高,但”全部用上”只有 76%:最后剩下的一两个词它常常指不出来。差的批评者更慢,而且一旦它说”没问题”,循环就停了。
- 没有具体反馈时,改写加上的只比丢掉的多一点,调用次数却最多。
6.3 公平的对照:先把初始提示写好
表里没有的一行:如果初始提示本来就写清楚”全部用上”呢? 在模拟里,把它假设成”每个词被用上的概率从 0.75 提到 0.92”(这是一个旋钮,不是测出来的):一次调用就是 92.8% 的覆盖率,超过差的批评者改 4 轮(89.9%)。
这正是 Huang et al.(2024)对 Self-Refine 的一个质疑:他们把 CommonGen-Hard 的初始提示改成明确要求”包含全部概念”,一次调用就得到 81.8%,高于 Self-Refine 原文里自我纠正之后的 67.0%;在这个更好的初始回答上再做自我纠正,反而降到 75.1%。他们的建议是:设计初始提示和设计纠正提示要花同样的功夫。
带走的经验:Self-Refine 有效的前提是反馈本身可靠。可靠反馈的来源是能用程序检查的约束、能跑的测试、能查的事实;“让同一个模型再想一遍”通常不是。并且,评估一个纠正方法时,先确认初始提示已经尽力写好了。
7. 验证链 CoVe · Chain-of-Verification
7.1 方法
第 5 讲讲过,长回答里的幻觉很难检测。Dhuliawala et al.(2024)的 CoVe(Chain-of-Verification)把一个长回答拆成一条条可以单独核对的说法:
- 写草稿:正常地回答(例如 “Name some politicians who were born in Boston.”);
- 计划验证问题:为草稿里的每条说法写一个问题(“Where was X born?”);
- 回答验证问题;
- 写最终回答:只保留和验证答案一致的说法。
关键在第 3 步怎么执行,论文比较了三种:
- 联合(joint):验证问题和答案接在草稿后面,一次生成写完;
- 两步(two-step):先列问题;再在看不到草稿的一次调用里回答全部问题;
- 分解(factored):每个问题单独一次调用。
7.2 列表题上
04_chain_of_verification.py:一个长尾的虚构世界,60 个国家、10 种语言,问”哪些国家说 L 语?“。模型对热门国家更了解;长回答很”松”,会把和 L 语沾点边的国家都联想进去。
| 做法 | 精确率 | 每题对的 | 每题错的 | 每题调用次数 |
|---|---|---|---|---|
| 只要草稿(基线) | 0.28 | 3.25 | 8.53 | 1.0 |
| 联合 | 0.35 | 3.15 | 5.88 | 3.0 |
| 两步 | 0.60 | 2.98 | 1.98 | 4.0 |
| 分解 | 0.64 | 2.98 | 1.68 | 14.8 |
两步和分解把错的从每题 8.5 个降到 2 个以下,也去掉了少量对的(短回答也会答错);联合执行几乎没用。Dhuliawala et al. 在 Wikidata 列表题上(Llama 65B)的数字:少样本基线精确率 0.17,CoVe 联合 0.29、两步 0.36、分解 0.32;每题对的从 0.59 降到 0.38,错的从 2.95 降到 0.68。方向一致;在他们的实验里两步比分解略好。
7.3 为什么有效:短回答比长回答准,而且不能看草稿
演示 2 回答两个问题。第一,草稿里的每个国家,单独问它说什么语言,模型答对多少?
| 草稿里的国家 | 个数 | 单独问答对 | 接在草稿后面问答对 |
|---|---|---|---|
| 知道这个国家,草稿说错了 | 204 | 99.5% | 36.8% |
| 不知道这个国家,草稿说错了 | 137 | 8.0% | 2.2% |
| 知道这个国家,草稿说对了 | 108 | 99.1% | 100.0% |
| 不知道这个国家,草稿说对了(碰巧猜对) | 22 | 54.5% | 81.8% |
| 全部 | 471 | 70.7% | 43.3% |
草稿里只有 27.6% 的国家真说 L 语,可是单独问,模型答对 70.7%。同一个模型,长回答里的说法和单独问时的回答不一样——很多长回答里的错误,是模型其实知道答案的说法。论文报告的是同样的现象:基线列表里约 17% 正确,但单独问每个实体时约 70% 答对。CoVe 利用的正是这个差距。
第二,接在草稿后面问,答对率降到 43.3%:草稿就在上下文里,模型倾向于重复它(“知道这个国家、草稿说错了”的那一类,接在草稿后面问只有 36.8% 答对)。论文也观察到联合方式会重复草稿里的幻觉。这是第 2 讲(模型会照抄上下文里的模式)和第 6 讲(模型顺着已有的说法走)的又一个例子。CoVe 的要点不是”问问题”,而是问的时候不让模型看见草稿。
7.4 CoVe 去不掉的幻觉
演示 3 改变一个旋钮:模型不知道的国家里,有多大比例”每次都编同一个答案”(稳定的幻觉,第 5 讲):
| 稳定幻觉的比例 | 草稿的精确率 | CoVe(分解)的精确率 | 每题错的:草稿 → CoVe |
|---|---|---|---|
| 0.00 | 0.27 | 0.89 | 8.62 → 0.33 |
| 0.35 | 0.27 | 0.76 | 8.60 → 0.88 |
| 0.70 | 0.28 | 0.64 | 8.53 → 1.68 |
| 1.00 | 0.27 | 0.57 | 8.68 → 2.33 |
模型对不知道的事每次都随机编时,单独问编出来的多半和草稿不一样,CoVe 能去掉它;每次都编同一个答案时,验证时还是同样的相信,CoVe 无能为力。这和第 5 讲 SelfCheckGPT 的局限一样:用模型自己检查自己,只能发现它说法不一致的错误;它一贯相信的错误,需要外部信息(检索、工具)才能发现。
所以 CoVe 主要修的是”长回答特有的松散”,而不是”模型不知道”。它的代价是调用次数:分解执行每条说法一次调用(这里每题 14.8 次),好在验证问题都很短,而且彼此独立、可以并行。
8. LLM-as-a-judge · Using LLMs to evaluate LLMs
8.1 为什么需要判官,判官怎样用
开放式的回答(“写一封邮件""解释一个概念”)没有唯一的标准答案,人工评估又慢又贵。Zheng et al.(2023)系统地研究了用强 LLM 当判官:在 MT-Bench(80 道多轮问题)和 Chatbot Arena 的人类投票上,GPT-4 判官和人类专家的一致率(不算平局)是 85%,人和人之间是 81%。从那以后,LLM 判官成了评估开放式生成的标准做法——也成了 §3 那些自动搜索的评估器。
三种常见用法:
- 成对比较(pairwise):给两个回答,选一个更好的(或平局);
- 单答案打分(single-answer grading):给一个回答打 1–10 分;
- 参考答案引导(reference-guided):给出参考答案,让判官对照着判。
Zheng et al. 也系统地列出了判官的偏差。下面逐个测量,用的是 SimJudge:它给”A(放在前面)比 B 好”打分 , 足够大选 A、足够小选 B,否则平局。“人”是一个只看质量 的模拟标注者。
8.2 位置偏差
05_llm_as_judge.py 演示 1:600 对回答,交换顺序问两遍。
| 位置偏差 | 两遍一致 | 两遍都选放在前面的 | 两遍都选放在后面的 |
|---|---|---|---|
| 0.0 | 75.7% | 5.7% | 6.5% |
| 0.3 | 74.8% | 13.3% | 3.0% |
| 0.8 | 57.5% | 33.0% | 0.3% |
| 1.5 | 39.5% | 52.3% | 0.0% |
即使完全没有位置偏差,也只有约四分之三的判决两遍一致——判官有噪声,两个回答质量接近时,换个顺序就可能换个结论。位置偏差越大,“谁在前面选谁”越多。Zheng et al. 用默认提示测的数字:GPT-4 两遍一致 65.0%(30.0% 偏向前面),GPT-3.5 46.2%,Claude-v1 23.8%(75.0% 偏向前面)。
修正:每一对都问两遍,两遍一致才算数,不一致记为平局(swap_aggregate)。代价是调用次数翻倍。
8.3 长度偏差
演示 2:把一个回答原样重复一遍(内容不变、长度翻倍),和原来的比。
| 长度偏差 | 选了重复过的那个 | 平局 |
|---|---|---|
| 0.0 | 23.0% | 61.3% |
| 0.3 | 42.0% | 52.0% |
| 0.6(本讲默认) | 56.3% | 43.3% |
| 1.2 | 84.7% | 14.7% |
Zheng et al. 的”重复列表”攻击(把回答里的列表换个说法再重复一遍):GPT-3.5 和 Claude-v1 选了重复版本的比例都是 91.3%,GPT-4 是 8.7%。强的判官可以不受这种攻击影响——但要测过才知道。
长度偏差不一定是”判官笨”:人也常常偏爱长一点、详细一点的回答,而判官的偏好正是从人的偏好数据里学来的(第 6 讲)。问题在于它奖励的是长度本身,而不是长度带来的信息。
8.4 排行榜:长度控制与评审团
演示 3:AlpacaEval 式的排行榜——6 个系统各和一个参照系统比 400 道题,判官属于家族 A。
| 系统 | 家族 | 平均质量 | 典型长度 | 人 | 判官(问一遍) | 交换 + 长度控制 | 评审团 B/C/D | 评审团 + 长度控制 |
|---|---|---|---|---|---|---|---|---|
| A-chat | A | +0.30 | 260 | 51.5% | 60.5% | 61.0% | 54.5% | 54.4% |
| B-large | B | +0.55 | 180 | 59.8% | 57.8% | 57.4% | 57.5% | 60.0% |
| C-concise | C | +0.45 | 110 | 50.0% | 45.2% | 60.1% | 46.8% | 57.5% |
| C-verbose | C | +0.00 | 520 | 37.8% | 48.2% | 46.2% | 52.6% | 46.7% |
| D-long | D | +0.20 | 700 | 46.0% | 60.9% | 44.4% | 63.5% | 48.5% |
和人的排名的斯皮尔曼相关:问一遍 0.37、交换顺序 0.49、交换 + 长度控制 0.66、评审团 0.43、评审团 + 长度控制 0.89(两位模拟的人类标注者之间 0.83)。只有 6 个系统,这些相关本身噪声很大,看方向即可。
- 长度偏差扭曲了排名:D-long(质量中等、特别长)人给 46.0%,判官给 60.9%;C-concise(质量好、很简洁)被压低。
- 长度控制(Dubois et al. 2024 的做法):对每个系统拟合一个逻辑回归 ,然后报告”假如长度一样”的胜率 。D-long 降到 44.4%,C-concise 升到 60.1%。他们报告,长度控制把 AlpacaEval 和 Chatbot Arena 排名的斯皮尔曼相关从 0.94 提到 0.98。注意它的假设:长度本身不带来质量。这个模拟里同一系统内长度和质量确实无关;真实数据里长一点的回答有时确实更好,长度控制会把这部分真实的好处也一起扣掉。
- 自我偏好:和判官同一家族的 A-chat,判官给的胜率比人高约 9.5 个百分点。换成 B/C/D 三个家族的评审团(Verga et al. 2024 的 PoLL:用几个不同家族的较小模型投票,报告比单个大判官更接近人,还便宜七倍以上),A-chat 的虚高降到 3.0。评审团里每个判官仍偏爱自己的家族,只是偏爱互相抵消了一部分;评审团也修不了长度偏差(D-long 63.5%),要和长度控制一起用。
一个容易被忽略的细节:判官和人在非平局判决上的一致率(77.5%)甚至高于两位模拟的人类之间(72.1%),可排名照样被长度偏差扭曲。“和人的一致率高”说明判官像人,不说明它没有系统性的偏差。
8.5 数学题:判官看不出自己也会犯的错
演示 4:300 道算术题,判官对候选的回答说”对”还是”错”;统计错的回答被判成”对”的比例。
| 被判的回答 | 判官 | 不解题,读一遍就判 | 先自己解一遍 | 对照参考答案 |
|---|---|---|---|---|
| family-A | family-A(同一家族) | 77.9% | 11.5% | 0.0% |
| family-A | family-C | 79.5% | 4.9% | 0.0% |
| family-B | family-A | 80.5% | 0.8% | 0.0% |
| family-B | family-C | 73.7% | 1.5% | 0.0% |
- 不解题直接判:读一遍推理很难看出读错题、漏掉步骤,失败率约 80%。Zheng et al. 的 20 道数学题上,GPT-4 用默认提示把 14 道错的判成对的(70%),先自己做一遍降到 6 道(30%),给参考答案降到 3 道(15%)。
- 先自己解一遍:不同家族的判官失败率降到 0.8–4.9%,同一家族时是 11.5%。原因不是”喜欢自己”,而是它们共享同样的误解——判官自己解出来的也是那个错的答案,于是判”对”。这是自我偏好的一种机制:看不出自己也会犯的错。
- 对照参考答案:失败率 0。能给参考答案时就给。
8.6 古德哈特:当判官成为优化目标
演示 5:让 B-large 把回答写成原来的 0.5、1、2、4 倍长(太长稀释内容、太短丢内容)。
| 回答长度 | 人给的胜率 | 判官(交换顺序) | 判官 + 长度控制 |
|---|---|---|---|
| × 0.5 | 52.0% | 38.9% | 43.5% |
| × 1 | 61.2% | 55.4% | 56.7% |
| × 2 | 54.0% | 58.9% | 57.1% |
| × 4 | 48.2% | 63.0% | 53.1% |
人最喜欢 × 1,判官最喜欢 × 4。如果把判官的分数当作优化目标——APE / OPRO 的评估器,或者 RLHF 的奖励(第 6 讲)——优化器会把回答越写越长。这就是古德哈特定律:指标一旦成为目标,就不再是好的指标。长度控制之后判官选 × 2,仍和人不一致(差别在噪声以内),但不再一路偏向最长的;而且长度控制的胜率是”假如长度和参照一样”的外推——× 2、× 4 的回答几乎都比参照长,这个估计落在数据之外,并不稳。
8.7 用判官之前的检查清单
- 在一份人工标注的小集合上测判官和人的一致率,尤其是在你关心的那些差别上(长短、风格、对错)。
- 成对比较时交换顺序问两遍。
- 报告时做长度控制,或者至少报告长度和胜率的关系。
- 判官和被评的系统不要同一家族;条件允许时用不同家族的评审团。
- 有标准答案的任务(数学、代码、事实)用参考答案或直接执行,不要让判官凭感觉。
- 把判官当优化目标时,定期用人的评估检查有没有被”刷分”。
9. 在真实模型上 · On a real model
06_real_llm.py(可选,需要 API key,约 220 次短调用,预算上限 1.00 美元)在真实的 Claude 上做两个小实验:
- A. 指令归纳 + APE:两个任务(dedupe_sort、short_upper),每个任务换 4 组例子归纳出 4 条候选,12 道开发题挑第一名,第一名和人写的指令各做 24 道测试题。要看的是:候选有没有漏掉”例子里不一定看得出来”的操作;第一名在开发集和测试集上的分数差多少;人写的指令错在哪里(往往就是含糊的那一句——真实模型怎样理解 “Capitalize the words.”?)。
- B. 判官:12 对(一个正确输出、一个某一步做错的输出),交换顺序各问一遍。这些题有唯一的正确答案,强的判官应该几乎总是一致地选对。
--toy 模式用 SimAutoPromptLLM 跑同一段代码,验证流程能跑通;它的数字由旋钮决定。每格只有十几道题,区间很宽:真实模型上的结果用来检验直觉,不要过度解读(编写讲义的环境里没有运行真实模型)。
10. 小结 · Summary
- 自动化放大的是评估器。指令归纳、APE、OPRO、自我纠正、Self-Refine、CoVe、LLM 判官都是”生成器 + 评估器”;评估器有噪声、和生成器同源、或者有偏差时,搜索会把缺陷放大。
- 指令归纳是假设搜索:例子少时规则不唯一(1 个例子 66% 函数上正确,8 个 97%);没暴露的操作会被漏掉或多写。挑覆盖边界情况的例子比多给例子更管用。规则对之后,说法也要对。
- APE / OPRO 有效:生成很多、在开发集上挑一个,把随手一条的 71% 提到 92%;OPRO 一步步地改,把一条一般的指令从 74% 改到 90% 以上。
- 但开发集分数会虚高:100 条候选、20 道开发题,第一名虚高 8.5 个百分点;OPRO 在 20 题上最后虚高 8.2。搜索会留下碰巧在开发题上有用的句子,也会错过真的有用的句子。用验证集做最后的决定,用测试集报告。
- 内在的自我纠正通常没用:中性的”再想想”改对和改错正好抵消,施压的提示更糟;“用标准答案决定何时停”是作弊;同样的调用次数给 self-consistency 更好。验证比生成容易得多、而且来自模型之外时(程序、测试、检索),反馈才有用。
- Self-Refine 的效果取决于反馈:程序反馈几乎完美,笼统的反馈几乎没用;比较时先把初始提示写好。
- CoVe:长回答里的很多错误,单独问时模型能答对(27.6% vs 70.7%);验证时不能让模型看见草稿。它去不掉稳定的幻觉。
- LLM 判官有位置、长度、自我偏好三种偏差:交换顺序、长度控制、不同家族的评审团、参考答案分别修正它们;判官和人”一致率高”不等于没有偏差;判官成为优化目标时会被刷分。
11. 常见误区 · Common pitfalls
| 误区 | 实际情况 |
|---|---|
| “APE 找到的提示在开发集上 98%,就报 98%” | 挑选会抬高开发集分数;要报从没参与挑选的测试集上的数字 |
| “搜索留下的那句’Take a deep breath’一定有用” | 在小开发集上,没用的句子也常常’看起来有用’;用大样本配对比较确认,换模型重新测 |
| “开发集越小越好,搜得快” | 开发集越小,虚高越大、挑错的概率越大;至少用一份验证集做最后的决定 |
| “让模型检查自己的答案总没坏处” | 没有外部反馈时,改对和改错大致抵消,施压的提示还会把对的改错 |
| “自我纠正把准确率从 58% 提到 90%” | 先问:是不是用标准答案决定了什么时候停?和同样调用次数的 self-consistency 比了吗? |
| “Self-Refine 有效,说明模型会自我改进” | 它有效的前提是反馈具体、可靠;笼统的反馈几乎没用,初始提示写好了收益也会缩小 |
| “CoVe 能消除幻觉” | 它去掉的是长回答里’模型其实知道’的错误;模型一贯相信的错误,需要外部信息 |
| “验证问题接在草稿后面问也行” | 看得见草稿时,模型倾向于重复草稿;要单独问 |
| “GPT-4 和人的一致率 85%,所以判官可信” | 一致率高说明它像人,不说明没有位置、长度、自我偏好这些系统性偏差 |
| “判官说长回答更好,那就写长一点” | 判官奖励长度本身;把它当优化目标会被刷分(古德哈特定律) |
12. 练习 · Exercises
练习 1(计算) 你在 30 道开发题上比较 50 条候选提示,它们的真实准确率都约为 80%。(a)用”噪声单位 × E[max]“的估计,第一名的开发集分数大约虚高多少?(N = 50 时 E[max] ≈ 2.25。)(b)要把这个估计降到 2 个百分点以内,开发集要多大?(c)为什么实际的虚高通常比这个估计小?
参考答案:(a)噪声单位 ,即 7.3 个百分点;第一名约虚高 2.25 × 7.3 ≈ 16 个百分点(独立、水平相同时的上限式估计)。(b)要 , 道题。(c)候选彼此相关(共用句子、错在同样的题上),有效的”独立候选数”比 50 小;分数有天花板;候选的真实水平不完全相同时,第一名更可能是真正好的那个。演示 1 里 20 题、100 条候选只虚高 8.5,比独立估计(约 16.8)小一半。
练习 2(设计) 你要用 OPRO 为一个客服分类任务优化系统提示。标注数据一共 600 条。设计数据划分、搜索预算和最后的报告方式。
参考答案(一种可能):划分为搜索用的开发集 150 条、验证集 150 条、测试集 300 条(测试集在整个过程中只用一次)。搜索:每步 8 条候选、最多 40 步,用开发集打分;每 5 步把当前前 5 名在验证集上重新打分,验证集分数连续 3 次不上升就停止(提前停止)。最后:在验证集上挑第一名,在测试集上报告准确率和 Wilson 区间,并和起点提示做 McNemar 配对检验。额外:把最后的提示里每一句”和任务无关”的话去掉,在验证集上看分数是否下降(消融),只保留确实有用的句子。
练习 3(判断) 一篇论文说:“我们的自我纠正方法在 GSM8K 上把准确率从 76% 提到 88%。“列出你会检查的至少三件事。
参考答案:(1)纠正过程有没有用到标准答案(哪怕只是决定什么时候停);(2)纠正提示里有没有初始提示没有的信息(例如更详细的解题要求)——如果有,应该把它放进初始提示再比;(3)和同样调用次数的 self-consistency 比较了吗(纠正用了几次调用?);(4)初始回答和纠正后的回答是否用同样的解码方式(贪心 vs 采样);(5)改对和改错各有多少道(只看净提升会掩盖大量的来回翻转);(6)置信区间和配对检验。
练习 4(解释) §7.3 里,“知道这个国家、草稿说错了”的那类国家,单独问时 99.5% 答对,接在草稿后面问只有 36.8%。(a)为什么同一个模型会在长回答里把它知道的事说错?(b)为什么接在草稿后面问会降低准确率?(c)你能想到一种让”联合执行”变好的提示改法吗?它的局限是什么?
参考答案:(a)列表式的长回答是在”生成一串和 L 语有关的国家”,模型按联想和流畅性往下写,不是在逐个核对事实;联想到的国家未必真的说 L 语。(b)草稿就在上下文里,回答”X 国说什么语言”时,“L 语”这个词刚刚和 X 一起出现过,模型倾向于照抄(第 2 讲的归纳头、第 6 讲的顺着已有说法走)。(c)例如在验证部分的指令里写”Answer each question from your own knowledge, ignoring the draft above”,或者把验证问题改成不提 L 语的开放式问题。局限:只要草稿还在上下文里,模型就仍然能看见它,指令只能减弱、不能消除这种影响;最可靠的办法还是两步或分解执行。
练习 5(动手) 修改 autoprompt_lab.SimJudge 的默认值,让它更像 Zheng et al. 报告的 GPT-4(位置偏差小、重复列表攻击的失败率低)。重新运行 05 号脚本:排行榜和人的排名相关会怎样变?长度控制还有多大作用?
参考答案:例如把 pos_bias 和 len_bias 都降到 0.1(重复攻击的失败率会接近只由噪声决定的水平)。编写讲义时这样跑的结果:D-long 只被抬高约 3 个百分点(人 46.0%、判官 49.2%),“问一遍”和人的排名相关从 0.37 升到 0.89;长度控制反而把相关降到 0.77——偏差很小时,长度控制只是多估了一个参数、多引入了一份估计噪声。自我偏好没有变:A-chat 仍被抬高约 9 个百分点,换成评审团降到约 3。这说明修正方法是针对具体偏差的:先测出你的判官有哪种偏差、有多大,再决定用哪种修正;“每种修正都用上”并不总是更好。
练习 6(批判) 有人说:“我们用 GPT 类模型当判官,用 OPRO 优化我们的客服回答提示,判官胜率从 50% 提到了 78%。“指出至少两个可能的问题,并提出检查方法。
参考答案:(1)古德哈特:优化器可能学会了迎合判官的偏差(写得更长、更像判官家族的风格),而不是真的更好——检查回答长度随优化的变化,做长度控制的胜率;(2)同一家族:如果被优化的模型和判官同一家族,自我偏好会抬高胜率——换一个不同家族的判官或评审团重新评;(3)过拟合:78% 是不是开发集上的分数?要在没参与优化的测试问题上报告;(4)位置偏差:评估时是否交换了顺序;(5)最终检查:抽一部分让人盲评,看人的偏好是否同向变化。
13. 延伸阅读 · Further reading
指令归纳与自动提示
- Honovich et al. (2023). Instruction Induction: From Few Examples to Natural Language Task Descriptions. ACL.
- Zhou et al. (2023). Large Language Models Are Human-Level Prompt Engineers(APE). ICLR.
- Yang et al. (2024). Large Language Models as Optimizers(OPRO). ICLR.
- Pryzant et al. (2023). Automatic Prompt Optimization with “Gradient Descent” and Beam Search(用自然语言的”梯度”改提示)。
- Fernando et al. (2023). Promptbreeder: Self-Referential Self-Improvement via Prompt Evolution;Guo et al. (2024). Connecting Large Language Models with Evolutionary Algorithms Yields Powerful Prompt Optimizers(EvoPrompt)。
- Khattab et al. (2024). DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines(把多阶段提示当作可优化的程序)。
自我纠正与自我改进
- Huang et al. (2024). Large Language Models Cannot Self-Correct Reasoning Yet. ICLR.
- Madaan et al. (2023). Self-Refine: Iterative Refinement with Self-Feedback. NeurIPS.
- Shinn et al. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning(第 14 讲)。
- Gou et al. (2024). CRITIC: Large Language Models Can Self-Correct with Tool-Interactive Critiquing(第 8 讲)。
- Pan et al. (2024). Automatically Correcting Large Language Models: Surveying the Landscape of Diverse Automated Correction Strategies(综述)。
验证
- Dhuliawala et al. (2024). Chain-of-Verification Reduces Hallucination in Large Language Models. Findings of ACL.
- Manakul et al. (2023). SelfCheckGPT(第 5 讲)。
LLM-as-a-judge
- Zheng et al. (2023). Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena. NeurIPS Datasets and Benchmarks.
- Dubois et al. (2024). Length-Controlled AlpacaEval: A Simple Way to Debias Automatic Evaluators.
- Verga et al. (2024). Replacing Judges with Juries: Evaluating LLM Generations with a Panel of Diverse Models(PoLL)。
- Wang et al. (2023). Large Language Models are not Fair Evaluators(位置偏差与校正)。
- Panickssery et al. (2024). LLM Evaluators Recognize and Favor Their Own Generations(自我偏好)。
14. 下一讲预告 · Next lecture
这一讲反复出现一个结论:自我纠正要有可靠的外部反馈才有用。有一个领域天然就有这样的反馈——代码:写出来的程序可以运行,可以跑测试,报错信息会精确地指出哪里错了。第 11 讲进入原课第 7 周:代码生成。
- Codex 与 HumanEval:怎样用执行而不是字面相似度来评估生成的代码;pass@k 的无偏估计,以及为什么直接用 会估错;
- 为什么 BLEU 不适合代码:一个差一个字符的程序可能完全错,两个写法完全不同的程序可能都对;
- 沙箱:怎样安全地运行模型写的代码(进程、超时、资源限制——给没有系统背景的读者从头讲);
- 自我调试(Chen et al. 2024):把测试结果和报错信息交给模型,让它改——这一讲的”外部反馈”在代码上的样子;
- 从代码模型到软件工程智能体:SWE-bench、SWE-agent 与”智能体–计算机接口”。