Browse this library

Chinese source article

2.1.1 · L0: 任务内的修订、搜索与验证

L0 只改当前任务的输出, 可保留状态不变. 修订, 搜索和验证能把这一题答得更好, 但能好多少取决于证据从哪来: 模型自己判断时, 改动可能让答案变差.

View original Markdown ↗

模型给出一个答案以后, 在不改参数, 不改提示模板, 不改任何可保留状态的前提下, 还能不能把这一题做得更好? 这是 L0 要回答的问题. 修订, 搜索和验证都在做这件事, 差别在信号从哪来: 模型自己写的反馈, 多次采样的投票, 还是执行结果, 测试和检索到的资料.

1. L0 的边界

1.1. 定义

沿用 1.2 的记号. 当前任务的轨迹记为 τ\tau, 输出记为 yy, 可保留状态记为 A=(θ,Σ,U,C)\mathcal{A} = (\theta, \Sigma, U, C). 一次 L0 转移写成

(τ,  y,  A)  ⟼  (τ′,  y′,  A′),(τ′,y′)̸≡act(τ,y),A′≡actA.(\tau,\; y,\; \mathcal{A}) \;\longmapsto\; (\tau',\; y',\; \mathcal{A}'), \qquad (\tau', y') \not\equiv_{\mathrm{act}} (\tau, y), \qquad \mathcal{A}' \equiv_{\mathrm{act}} \mathcal{A}.

第一个条件说输出或产生它的轨迹在实际起作用的语义上变了. 第二个条件说可保留状态没变. 两个条件合起来, 就是「这一题的答案变了, Agent 没变」.

1.2. 任务内的临时状态

这个边界允许任务内存在相当多的临时状态. 自我批评, 多候选搜索, 验证, 甚至很长的工作记忆, 都可以在 L0 内部运行. 综述举的例子是 Focus: 它把一条很长的软件工程轨迹压缩成一个紧凑的知识块, 并删掉更早的交互, 让 Agent 在同一个任务里继续工作. 这样能延长有效的推理长度, 但压缩出来的知识块不跨任务保留, 所以仍然是 L0.

不跨任务保留有两面. 一面是隔离: 一次有害的修改通常随这一题结束而丢弃, 不会污染之后的任务. 另一面是能力上限: 一次正确的修正也不会留下来, 下一道同类题要重新发现一遍.

1.3. 迭代修订: Self-Refine

机制:Self-Refine (Madaan et al., 2023) 用同一个模型 M\mathcal{M} 完成三件事: 生成初稿, 给初稿写反馈, 按反馈改写. 三件事各用一个少样本提示: pgenp_{\text{gen}}, pfbp_{\text{fb}}, prefinep_{\text{refine}}. 不需要训练. 初稿由输入 xx 和生成提示拼接后得到 (∥| 表示拼接):

y0=M(pgen ∥ x).(1)y_0 = \mathcal{M}\left(p_{\text{gen}} \,\|\, x\right). \tag{1}

第 tt 轮的反馈由同一个模型对当前输出 yty_t 写出:

fbt=M(pfb ∥ x ∥ yt).(2)fb_t = \mathcal{M}\left(p_{\text{fb}} \,\|\, x \,\|\, y_t\right). \tag{2}

改写时, 模型拿到的不只是最近一轮的输出和反馈, 而是全部历史:

yt+1=M(prefine ∥ x ∥ y0 ∥ fb0 ∥ ⋯ ∥ yt ∥ fbt).(3)y_{t+1} = \mathcal{M}\left(p_{\text{refine}} \,\|\, x \,\|\, y_0 \,\|\, fb_0 \,\|\, \cdots \,\|\, y_t \,\|\, fb_t\right). \tag{3}

停止条件 stop(fbt,t)\mathrm{stop}(fb_t, t) 有两种: 到达指定轮数, 或者从反馈里提取一个停止信号, 比如让模型在反馈里给一个分数. 论文实验里最多迭代 4 轮, 返回最终一轮的 yty_t. 式 (2) 的提示示例是「输入, 输出, 反馈」三元组, 论文要求示例里的反馈「可执行」和「具体」. 可执行指反馈里包含一个可能改善输出的具体动作; 具体指反馈指出输出中需要改的那一处. 论文里代码优化的例子是: 初稿用循环求 1+2+⋯+N1+2+\cdots+N, 反馈指出这是暴力求和, 应该用 n(n+1)/2n(n+1)/2, 改写后的代码直接返回 (n*(n+1))//2.

结果:论文在 7 个任务上用 GPT-3.5 (text-davinci-003), ChatGPT (gpt-3.5-turbo) 和 GPT-4 做了对比. 下表摘录 GPT-4 一列 (数字是各任务自己的指标, 偏好类任务是被偏好的比例):

任务 基线 + Self-Refine 指标
对话回复 25.4 74.6 GPT-4 偏好
代码可读性 27.4 56.2 命名恰当的变量占比 (GPT-4 判)
约束生成 15.0 45.0 概念覆盖率
代码优化 27.3 36.0 被优化的程序占比
数学推理 (GSM8K) 92.9 93.1 解题率

前四行的增益大, 最终一行几乎没有. GPT-3.5 在数学推理上是 64.1 到 64.1, 完全没变. 论文自己给了数学推理没有增益的原因: 数学里的错误常常只在某一行或某一步运算, 而一条看起来前后一致的推理链会让模型认为「everything looks good」. ChatGPT 在 94% 的样本上给出的反馈就是这句话. 论文附录 H.1 报告, 如果由外部来源告诉模型当前答案错了, 数学推理上的增益会超过 5 个百分点.

这组数字说明了 Self-Refine 起作用的条件. 对话回复, 代码可读性, 约束生成这几类任务, 模型自己就能看出初稿哪里不满足要求: 回复没有接住用户的话, 变量名不清楚, 漏了某个必须出现的概念. 数学推理的错误, 模型自己看不出来. 反馈环节识别不出错误时, 改写环节没有东西可改.

消融: 反馈和轮数:论文做了两组消融. 第一组换掉反馈的质量:

任务 Self-Refine 反馈 泛泛的反馈 无反馈
代码优化 27.5 26.0 24.8
情感反转 43.2 31.2 0
缩写生成 56.4 54.0 48.0

「泛泛的反馈」指让模型写一句笼统的改进意见. 「无反馈」指模型仍然迭代改写, 但不显式写反馈. 情感反转一栏从 43.2 掉到 0, 说明这个任务里, 模型不先说出「哪里没有反转」, 改写就完全失败. 第二组看轮数. 三个任务上, 各轮的得分 (三个模型平均) 如下:

任务 y0y_0 y1y_1 y2y_2 y3y_3
代码优化 22.0 27.0 27.9 28.8
情感反转 33.9 34.9 36.1 36.8
约束生成 29.0 40.3 46.7 49.7

代码优化的总增益是 28.8−22.0=6.828.8 - 22.0 = 6.8, 其中第一轮贡献 27.0−22.0=5.027.0 - 22.0 = 5.0, 占 74%. 后两轮各只加 0.9. 约束生成的增益分布更平: 三轮依次是 11.3, 6.4, 3.0. 多数增益来自前一两轮. 论文还测了较弱的 Vicuna-13B. 它能生成初稿, 但不能稳定地按要求格式写反馈; 即使直接给它人工写好的反馈, 它也常常重复原来的输出, 或者编出一段不相干的对话. Self-Refine 要求模型同时具备三种能力: 生成, 诊断, 按诊断改写. 缺一种, 循环就转不起来.

角色分离?Self-Refine 让同一个模型扮演生成者和批评者. 一种变体是把角色拆开. 综述提到的 Table-Critic 把表格推理的修订拆成判官 (Judge), 批评者 (Critic), 修订者 (Refiner) 三个角色: 判官定位错误, 批评者写批评, 修订者改. 这让修复过程更明确. 但拆角色不改变一件事: 如果三个角色由同一个模型扮演, 用同一套判断标准, 它们的盲区也相同. 判官看不出的错误, 批评者和修订者同样看不出. 综述对这一类方法的结论是, 重复提示和角色分离都不能保证得到独立的诊断.

2. 有外部依据的修复

2.1. 执行, 测试和证明

模型自己写的反馈有盲区. 一个直接的办法是换一个反馈来源: 让程序跑一遍, 让测试判一遍, 让证明助手检查一遍. 这些信号不经过模型的判断, 能暴露模型看不出的错误. 综述列了几类代表系统. SELF-DEBUGGING 根据程序的执行结果修改程序. AlphaCodium 和 Agentless 把反思, 错误定位, 构造测试, 执行和补丁验证组合成一条流水线. Baldur 用证明助手 Isabelle 返回的错误信息修复证明. AgentCoder 把写代码, 设计测试, 执行分给不同的角色.

这些检查比模型自己的批评更有区分力: 一个测试失败了就是失败了, 不受模型「觉得对不对」影响. 它们的局限也很明确. 测试不完整时, 错误的程序可以通过全部测试; 规格写得不完整时, 证明通过也只说明满足了写下来的那部分规格.

2.2. 把成本算进来以后: Olausson 等人的实验

Olausson 等人 (2023) 在 HumanEval 和 APPS 上研究了 Code Llama, GPT-3.5 和 GPT-4 的自我修复 (self-repair): 程序没通过测试, 模型读错误信息, 写一段解释, 再生成修复后的程序. 这篇论文的关键设置是把修复的成本算进去. 自我修复要多调用几次模型. 如果把这些调用换成直接多采样几个程序, 结果可能同样好. 论文的结论是: 按相同的采样预算比较, 自我修复的增益通常不大, 在不同数据子集之间差别很大, 有时完全没有. 论文认为瓶颈在反馈环节: 模型对自己代码的解释质量不够. 为了验证这一点, 他们请 16 名有经验的程序员, 为 GPT-4 写错的 40 个程序各写两份反馈, 再让 GPT-4 按人写的反馈修复:

难度 GPT-4 自己的反馈 人写的反馈
入门 42.64% 62.21%
Interview 级 19.33% 45.67%
竞赛 3.67% 14.67%
总体 33.30% 52.60%

总体修复成功率从 33.3% 提到 52.6%, 是原来的 52.6/33.3≈1.5852.6 / 33.3 \approx 1.58 倍. 修复程序的仍然是 GPT-4, 变的只是反馈. 这和 Self-Refine 在数学推理上的结论一致: 诊断环节决定修复的上限.

环境观察和局部证据?不能执行的任务, 可以从环境里拿证据. Inner Monologue 和 DEPS 根据成功信号, 场景描述, 人类反馈和观察到的失败修改计划. Kestrel 和 Reflect-R1 在修改答案之前, 先检索相关的局部视觉或时间证据. 这类证据能暴露模型口头自我批评发现不了的错误. 可靠性仍取决于覆盖范围: 观察只覆盖访问过的状态, 检索只覆盖找到的资料. 一个早期就走错方向的轨迹, 在单条轨迹内往往改不回来. 这时 L0 方法就从修订单个候选, 转向同时保留多个候选.

3. 搜索与验证: 从采样, 树到 CodeT

3.1. Self-Consistency

Self-Consistency (Wang et al., 2022) 替换的是 CoT 提示里的贪心解码. 贪心解码只走一条推理路径. Self-Consistency 从模型里采样 mm 条推理路径 r1,…,rmr_1, \dots, r_m, 每条路径给出一个最终答案 a1,…,ama_1, \dots, a_m, 再对推理路径做边缘化, 取出现次数最多的答案:

a^=arg⁡max⁡a∑i=1m1(ai=a).\hat{a} = \arg\max_{a} \sum_{i=1}^{m} \mathbb{1}\left(a_i = a\right).

这里 1(⋅)\mathbb{1}(\cdot) 在括号内条件成立时取 1, 否则取 0. 求和统计的是有多少条路径得出答案 aa. 推理路径本身不参与比较, 只有最终答案参与投票, 这就是「对推理路径边缘化」的意思. 手算一个例子. 一道应用题采样 5 条路径, 最终答案依次是

[a1,a2,a3,a4,a5]=[18,  18,  26,  18,  9].[a_1, a_2, a_3, a_4, a_5] = [18,\; 18,\; 26,\; 18,\; 9].

按答案计数: 18 出现 3 次, 26 出现 1 次, 9 出现 1 次. 输出 a^=18\hat{a} = 18. 如果贪心解码恰好走的是第 3 条路径, 输出就是 26. 这个方法依赖一个假设: 一道复杂推理题通常有多种思考方式, 但正确答案只有一个; 错误的推理路径各错各的, 不容易落到同一个错误答案上. 假设成立时, 正确答案会在投票中占多数. 论文在 PaLM-540B 上的结果 (算术推理, 准确率):

数据集 CoT + 贪心 Self-Consistency 增益
GSM8K 56.5 74.4 +17.9
AQuA 35.8 48.3 +12.5
SVAMP 79.0 86.6 +7.6
ASDiv 74.0 81.9 +7.9

增益在基础较弱, 题目多步的数据集上更大. 小模型上增益较小: UL2-20B 在 GSM8K 上只从 4.1 到 7.3. 模型几乎答不对时, 投票里也没有多少正确答案可选.

3.2. Tree of Thoughts

Self-Consistency 采样的是完整路径, 中间步骤不做任何选择. Tree of Thoughts (Yao et al., 2023) 把推理拆成若干个「思维」(thought), 每个思维是一段连贯的文本, 作为解题的一个中间步骤. 推理过程变成在思维构成的树上搜索. 一个 ToT 系统要定四件事: 怎么把问题拆成思维步骤; 在每个节点上怎么生成候选的下一步; 怎么评估一个中间状态; 用什么搜索算法. 评估和生成都由同一个语言模型完成.

论文用 Game of 24 做主实验: 给 4 个数, 用加减乘除和括号, 每个数恰好用一次, 算出 24. 例如输入 4 9 10 13, 一个解是 (10−4)×(13−9)=24(10 - 4) \times (13 - 9) = 24. 这个任务被拆成 3 步, 每步用两个剩下的数做一次运算. 在每个节点上, 模型提出若干可能的下一步; 然后模型对每个候选状态给出 sure / maybe / impossible 三档评价, 判断从这个状态还能不能凑出 24. 评价的依据可以是几步前瞻, 也可以是「剩下的数太大或太小」这类常识. 每个候选评价采样 3 次. 搜索用广度优先, 每一步保留得分最高的 bb 个状态. 测试集是 4nums.com 按人类解题时间排序后第 901–1000 号, 偏难的 100 题. GPT-4 的结果:

方法 成功率
直接输入输出 (IO) 7.3%
CoT 4.0%
CoT + Self-Consistency (k=100k=100) 9.0%
IO + 迭代修订 (k=10k=10) 27%
ToT, b=1b=1 45%
ToT, b=5b=5 74%
IO, 100 次取最好 33%
CoT, 100 次取最好 49%

表里最终两行要单独读. 「100 次取最好」是指采样 100 次, 只要有一次对就算对. 这需要事先知道正确答案才能挑出那一次, 论文把它称为 oracle 设置. 即使给了这个条件, CoT 也只有 49%, 低于不知道答案的 ToT (b=5b=5) 的 74%. CoT + Self-Consistency 只有 9.0%, 原因在任务结构. Game of 24 的第一步就决定了后面能不能凑出 24, 而 CoT 从左到右生成, 第一步选错了无法回退. 论文的错误分析显示, 约 60% 的 CoT 样本在生成第一步 (例如「4+9」这样的前三个词) 之后就已经不可能凑出 24. 投票只能在完整答案之间选, 多数路径在第一步就走进死路时, 投票也选不出正确答案. ToT 在每一步都评估, 把 impossible 的状态剪掉, 并保留多个 maybe 状态, 解决的正是这个问题.

ToT 的代价是调用次数. 每个节点要生成候选, 每个候选要评价 3 次, 广度 b=5b=5 时每一步要处理更多节点. 论文把 ToT 在 b=1b=1 到 55 时每题访问的节点数, 和 IO/CoT「kk 次取最好」的 kk 放在同一张图里, 把一次采样算作访问一个节点. 在这张图上, CoT 取到 100 次也只有 49%, 低于 b>1b>1 的 ToT. 但在比较任何搜索方法时, 都要先对齐调用预算, 这一点在第 5 节还会出现.

搜索的共同局限?综述把 beam search, RAP, Graph of Thoughts, rStar, LATS 也放在这一类. 它们改的是搜索拓扑和提议, 评估的分工. 共同的局限是: 投票和由模型给出的价值评估, 都可能放大候选之间共有的错误. 如果多数候选犯了同一个错, 投票会选中这个错; 如果评估者和生成者有同样的误解, 价值评估会给错误的中间状态打高分. 另一类方法决定把推理算力花在哪里. 快慢推理切换, 按难度分配 TestingTime 算力, CodeMonkeys 和 S* 在不确定的样本上, 或并行的「编辑, 测试」轨迹上分配计算. 这些方法的收益同样有条件: 预测的后果要准, 复用的结构要和任务匹配, 选择器要能认出有价值的候选.

3.3. CodeT 的双重执行一致性

用模型生成很多个程序以后, 要从中选一个. 最自然的办法是跑测试, 但大多数编程题没有现成的测试用例, 人工写测试成本很高. CodeT (Chen et al., 2022) 的做法是让同一个预训练模型既生成程序, 也生成测试用例, 再用执行结果做选择. 这立刻带来一个问题: 测试也是模型写的, 也可能写错. 一个程序没通过某个测试, 可能是程序错了, 也可能是测试错了. CodeT 用「双重执行一致性」(dual execution agreement) 处理这个问题.

候选代码与测试同时生成?把每个生成的程序 xx 在每个生成的测试 yy 上跑一遍. 程序 xx 通过测试 yy, 就记一对 (x,y)(x, y). 能通过同一组测试的程序被认为功能相同, 即使写法不同. 一个一致集 (consensus set) S\mathcal{S} 由一组功能相同的程序 Sx\mathcal{S}_x 和它们都能通过的测试 Sy\mathcal{S}_y 组成. 一致集的得分是

f(S)=∣Sx∣⋅∣Sy∣,f(\mathcal{S}) = |\mathcal{S}_x| \cdot |\mathcal{S}_y|,

∣Sx∣|\mathcal{S}_x| 是集合里的程序数, ∣Sy∣|\mathcal{S}_y| 是集合里的测试数. 得分等于集合里「程序, 测试」对的个数. 算法从得分最高的一致集里取一个程序作为输出. 论文的示例: 题目是「返回一个数的平方」. 一个一致集有程序 {x1,x2}{x_1, x_2} 和测试 {y1,y2,y3}{y_1, y_2, y_3}, 得分 2×3=62 \times 3 = 6; 另一个一致集只有程序 {x3}{x_3}, 但它通过了 {y2,y3,y4,y5}{y_2, y_3, y_4, y_5} 四个测试, 得分 1×4=41 \times 4 = 4. 输出从第一个集合里取. 这个得分同时用了两种一致性. ∣Sx∣|\mathcal{S}_x| 大, 说明很多独立采样的程序实现了同一个功能, 这是 Self-Consistency 的思路. ∣Sy∣|\mathcal{S}_y| 大, 说明这个功能通过了很多测试. 论文附录做了消融: 只用 f′(S)=∣Sx∣f'(\mathcal{S}) = |\mathcal{S}_x|, 或只用 f′′(S)=∣Sy∣f''(\mathcal{S}) = |\mathcal{S}_y|, 效果都不如两者相乘.

结果与局限?零样本设置下, code-davinci-002 加上 CodeT 的 pass@1:

基准 基线 CodeT
HumanEval 47.0% 65.8%
MBPP 58.1% 67.7%
APPS 入门 27.2% 34.6%
CodeContests 0.7% 2.1%

CodeT 的测试和程序来自同一个模型. 如果模型对题意有系统性的误解, 它生成的程序和测试会按同一种误解彼此一致, 一致集的得分反而很高. 综述对执行类验证的结论是: 执行给出的行为关系是可检查的, 但受测试覆盖率限制; 当检查过程和候选共享同一个误解时, 一致性检查能排除不一致, 但证明不了正确.

综述同类列出的还有: MBR-EXEC 按采样到的行为给程序分组; LEVER 把执行结果和一个学习得到的验证器结合; RARR 和 Chain-of-Verification 用另外检索或另外问出的事实证据修改答案; CRITIC 把工具交互检查推广到搜索引擎和代码解释器. 没有可执行或有出处的检查时, 一些方法从模型自身的分布构造重建或一致性测试, 例如反向自验证. 它们能拒绝自相矛盾的答案, 但对和模型共享误解的错误无能为力.

4. 自我纠错为什么会让结果变差

Huang 等人的实验设置?Huang 等人 (2023, Google DeepMind) 把自我纠错分成两种条件. 有 oracle 标签时, 用正确答案决定什么时候停止纠错: 当前答案已经对了, 就不再改. 内在自我纠错 (intrinsic self-correction) 时, 不用任何标签, 由模型自己决定保留还是修改之前的答案. 他们选的数据集, 都是之前的工作报告过自我纠错有明显增益的: GSM8K, CommonSenseQA, HotpotQA. 每道题最多纠错两轮. 每一轮先让模型审查自己的答案, 再根据审查结果给出新答案, 所以第 nn 轮结束时一共调用了 1+2n1 + 2n 次模型.

4.1. 有标签和没有标签

有 oracle 标签时 (准确率, %):

模型 条件 GSM8K CommonSenseQA HotpotQA
GPT-3.5 标准提示 75.9 75.8 26.0
GPT-3.5 自我纠错 (oracle) 84.3 89.7 29.0
GPT-4 标准提示 95.5 82.0 49.0
GPT-4 自我纠错 (oracle) 97.5 85.5 59.0

没有标签时:

模型 条件 调用次数 GSM8K CommonSenseQA HotpotQA
GPT-3.5 标准提示 1 75.9 75.8 26.0
GPT-3.5 纠错第 1 轮 3 75.1 38.1 25.0
GPT-3.5 纠错第 2 轮 5 74.7 41.8 25.0
GPT-4 标准提示 1 95.5 82.0 49.0
GPT-4 纠错第 1 轮 3 91.5 79.5 49.0
GPT-4 纠错第 2 轮 5 89.0 80.0 43.0

两张表的模型, 提示, 数据都一样. 有标签时, 标签挡住了一种情况: 模型把原本正确的答案改错. 没有标签时这种情况不受限制, 准确率全部下降. GPT-3.5 在 CommonSenseQA 上从 75.8 掉到 38.1, 掉了一半. 论文还测了 GPT-4-Turbo 和 Llama-2-70b-chat, 并换了三种不同措辞的审查提示, 结论都一样: 没有标签时, 自我纠错一致地降低推理准确率. Llama-2 在 GSM8K 上从 62.0 降到 36.5.

论文分析了 GPT-3.5 在 GSM8K 上两轮纠错后答案的变化: 74.7% 的情况下答案没变; 在答案变了的情况里, 从对改成错的次数多于从错改成对. 论文的解释是, 模型判断不了自己推理的对错. 如果初始提示已经设计合理, 初稿相对于这个提示已经是模型能给出的最好回答; 审查提示相当于再加一段提示, 它没有提供关于题目的新信息, 反而可能把模型推离对原问题的最优回答.

4.2. 公平比较需要固定什么

自我纠错要多调用模型. 和它比较的基线也应该用同样多的调用次数. 论文用这个标准重新测了多智能体辩论 (多个模型副本互相批评对方的答案). 在 GSM8K 上, 用 gpt-3.5-turbo-0301, 3 个智能体:

方法 响应数 准确率
标准提示 1 76.7
Self-Consistency 3 82.5
辩论第 1 轮 6 83.2
Self-Consistency 6 85.3
辩论第 2 轮 9 83.0
Self-Consistency 9 88.2

响应数相同时, 辩论不如直接多采样再投票. 论文的判断是, 辩论带来的增益来自多次生成之间的一致性选择, 和 Self-Consistency 同源, 不能算作「纠错」.

初始提示也要固定?第三个问题出在提示设计上. Self-Refine 的约束生成任务要求一句话里用上 20–30 个给定概念. 原论文的初始提示没有明确要求用上全部概念, 改进出现在反馈环节: 反馈指出漏了哪些概念, 改写时补上. Huang 等人在初始提示里加了一句「写一段合理的话, 包含上面的全部概念」:

条件 调用次数 概念覆盖率
原论文: 标准提示 1 44.0
原论文: 自我纠错 7 67.0
改进后的标准提示 1 81.8
改进后的标准提示 + 自我纠错 7 75.1

把要求写进初始提示, 一次调用就达到 81.8, 超过原论文自我纠错 7 次调用后的 67.0. 在这个更好的初稿上再做自我纠错, 覆盖率反而降到 75.1.

这些实验怎样改变结论?三组实验对应三个评估错误: 用了推理时拿不到的标签; 没有对齐调用次数; 初始提示没把要求写全. 去掉这三个错误以后, 推理任务上的内在自我纠错没有增益, 还常常有害. 这不等于任务内的推理没用. Self-Consistency 和 ToT 的增益在对齐预算后仍然存在, 执行反馈和人写的反馈也确实提高了修复成功率. 能起作用的方法, 都带进了一份模型自己的判断以外的信息: 多次独立采样之间的一致性, 程序的执行结果, 人的诊断. 综述的说法是, 更多的推理不能代替那些能把「修好了」和「新引入了错误」区分开的证据.

5. 证据来源与 L0 的上限

5.1. 证据从哪来

把前面的方法按「谁提供证据」排成一张表:

方法 证据来源 能看到什么 看不到什么
Self-Refine, 内在自我纠错 同一个模型的判断 风格, 格式, 明显遗漏 模型自己意识不到的错误
角色分离 (Table-Critic) 同一模型的不同角色 错误位置更明确 各角色共有的盲区
Self-Consistency, 辩论 多次独立采样的一致性 偶然性错误 多数路径共有的错误
ToT 的状态评估 模型对中间状态的评价 明显走不通的分支 评价本身的误判
CodeT 模型写的测试的执行结果 程序与测试是否一致 程序与测试共有的误解
SELF-DEBUGGING, Baldur 执行器, 证明助手 测试或规格覆盖的行为 测试和规格没覆盖的部分
人写的反馈 人的诊断 模型看不出的错误 取决于人的投入

从上往下, 证据越来越独立于产生候选的那个模型. 这张表没有给方法排好坏, 因为证据的可用性取决于任务: 对话回复没有执行器, 数学证明没有现成的人工反馈. 在此基础上, 综述还给出了三条要求. 第一, 覆盖范围要事先声明. 执行只观察到测试过的行为, 检索只观察到找到的资料, 环境反馈只观察到访问过的状态, 固定的 rubric 只观察到写进去的标准. 一个检查对它覆盖之外的属性不提供证据.

第二, 停止规则要和证据源绑在一起. 同一个外部检查被反复调用, 每次失败都触发一次修改, 检查就从「审计」变成了「开发反馈」, 循环会逐渐贴合这个检查本身. 所以每个证据源都要配套停止, 放弃作答, 上交给人和预算对齐的规则. 第三, 比较的对象要选对. 应当比较的是: 同一个任务, 同样的预算, 同样的证据协议下, 被采纳的候选和改动前的候选. 拿到不同数量或不同种类反馈的两个系统之间的比较, 说明不了修订本身的作用. 第 5.2 节的辩论与初始提示对照, 都是这一条的具体例子.

5.2. 候选覆盖、选择准确率与结论边界

覆盖范围和停止规则都合格时, L0 的证据可以支持一个关于「这道题上被选中的输出」的结论. 它不能单独支持「这个 Agent 在之后的独立任务上会做得更好」, 因为按第 1 节的边界, 可保留状态 A\mathcal{A} 没有变. 这是 L0 和 L1 的分界. 一个系统如果把 L0 过程中筛选出来的好答案拿去训练模型, 改动就落到了 θ\theta 上, 并且会影响之后的任务. 这时有用的改动和错误都会被带进之后的任务. 2.2 讲的单模型自训练, 就是从这一步开始的: STaR 让模型对训练题生成推理过程, 用标准答案筛掉推错的, 再拿留下的推理过程微调模型. 筛选这一步和本篇的验证是同一个问题, 区别在于筛选结果被写进了参数.

候选覆盖和选择准确率?任务内搜索的端到端成功率可以拆成两部分. 令 GG 表示候选集合中至少有一个正确答案, SS 表示系统最终选中正确答案, 则

Pr⁡(S)=Pr⁡(G)Pr⁡(S∣G).\Pr(S)=\Pr(G)\Pr(S\mid G).

生成器决定覆盖率 Pr⁡(G)\Pr(G), 验证器决定条件选择率 Pr⁡(S∣G)\Pr(S\mid G). 最终 pass@1 上升时, 不拆这两项就无法知道增益来自多采样覆盖了正确轨迹, 还是验证器更会识别正确轨迹. oracle 选择器给出覆盖上限: 候选中只要有正确答案就选中. 将实际选择结果与 oracle 比较, 差距就是选择器尚未利用的空间. 若 oracle@k 随 kk 快速上升、实际结果停滞, 继续增加样本只会制造更多选择负担; 应改验证. 若实际结果已经贴近 oracle, 瓶颈在生成分布.

候选之间通常相关. 假设单次正确率为 pp, 独立采样 kk 次的覆盖率为 1−(1−p)k1-(1-p)^k; 语言模型从同一提示采样时会重复相似推理, 实际增长更慢. 提高温度或使用不同角色能增加多样性, 也可能降低单候选质量. 有效策略优化的是质量—多样性乘积, 不单独追求答案数量. 选择器也会随候选数退化. 当错误答案共享流畅叙述或相同最终值, 多数投票会把相关错误放大. 判官面对大量近似候选时, 细微逻辑差异更难识别. 因而应同时画 oracle@k、实际 select@k 与成本曲线, 找到边际收益转负的位置.

Self-Consistency 的主要贡献通常落在覆盖与聚合. 它从同一模型采样多条推理, 按最终答案多数投票. 若正确推理有多种表述、错误答案较分散, 投票有效; 若错误来自共同误解并集中到同一答案, 投票会稳定选择错误. 最终答案一致性无法检查推理过程是否正确. CodeT 增加执行一致性, 把程序与生成测试形成的通过关系用于分组. 它提供了与语言表面不同的证据, 但生成测试仍来自模型, 可能遗漏所有候选共享的漏洞. 双重执行一致性提高选择率, 不会让候选集合中凭空出现正确程序; 生成与选择两项仍需分别报告.

自我反馈的贝叶斯视角?把初稿正确性记作隐变量 Z∈{0,1}Z\in{0,1}, 模型产生反馈 FF. 有用反馈应改变后验 P(Z=1∣F)P(Z=1\mid F), 并给出能提高修订成功率的方向. 反馈写得长、列出很多问题, 不代表它含有关于 ZZ 的信息. 若模型在正确答案上也经常生成批评, 假阳性率 P(F=错midZ=1)P(F=\text{错}mid Z=1) 较高, 修订会破坏正确答案. 若模型对错误答案经常说没有问题, 假阴性率高, 修订不启动. 强基座正确率 P(Z=1)P(Z=1) 已经很高时, 即使反馈器表面准确, 假阳性造成的损失也可能超过修复收益.

设错误答案被修对概率为 pp, 正确答案被改错概率为 qq, 初稿准确率为 aa, 一轮后

a′=a+(1−a)p−aq.a'=a+(1-a)p-aq.

净收益条件为 (1−a)p>aq(1-a)p>aq. 当 aa 接近 1, 左边可修复样本变少, 右边暴露的正确样本仍多. 这解释了 Huang 等人在强模型上观察到的退化, 也说明停止门控比「多想一轮」更关键. 标准答案门控能将 qq 压低, 因为只有已知错误时才修订. 但它引入了推理时 oracle, 实验问题变成「知道答案错以后能否修复」. 部署时没有 oracle, 需要可获得的外部信号, 如程序执行、约束检查、检索证据或校准过的验证器.

模型置信度可以作为门控, 前提是经过校准. 语言模型口头说「我有 90% 把握」常受措辞和提示影响. 更可靠的做法是在留出集上估计风险—覆盖曲线: 只对低置信样本启动修订, 比较被覆盖部分的修复率与破坏率. 阈值随任务类型变化, 统一阈值可能在数学题有效、开放问答失效. 反馈还要区分诊断与修改建议. 「第三步符号错误」可以定位问题, 「把答案改成 42」直接提供候选. 前者需要修订器具备修复能力, 后者可能只是另一次答案采样. 消融时分别提供错误位置、错误类型和完整建议, 才能知道收益来自验证信息还是额外生成.

6. 搜索系统的验证与停止

6.1. 验证器与停止策略

Tree of Thoughts 把中间思维当作状态, 生成若干后继并用价值函数剪枝. 设真实可达成功概率为 V∗(s)V^\ast(s), 评估器给出 V^(s)=V∗(s)+ε(s)\widehat V(s)=V^\ast(s)+\varepsilon(s). 搜索效果取决于排序误差, 尤其是早期剪枝是否误删通向正确答案的分支. 束宽 bb 增大能降低误删概率, 成本按深度迅速增长. 若每层扩展 mm 个后继, 无剪枝节点数为 mdm^d; 束搜索控制为约 bdbd, 代价是评估器一次错误可能永久丢掉正确路径. 回溯和保留多样分支能缓解, 需要更多预算.

价值评估与生成器共享模型时, 错误相关. 模型生成一条符合自身偏好的错误思路, 又容易给它高分. 换独立判官可能降低相关性, 但判官若来自相似训练数据仍会共享盲点. 程序执行、状态约束和环境反馈提供异质证据, 能在适用任务上切断共同偏差. 搜索树的「状态」常只是一段自然语言, 不满足真正 Markov 性. 两段看似相同的思路可能隐含不同未写信息, 价值函数难以准确预测后续; 冗长状态又占用上下文并增加噪声. 让状态包含可验证中间量, 如方程、程序变量或环境观察, 能提高评估可辨识性.

树搜索结果超过直接采样时, 还要对齐调用数. 同样总 token 下, 深搜一条路径、广采多条完整答案和自一致性投票各有不同覆盖. 若 Tree of Thoughts 使用更多模型调用和工具反馈, 增益不能全归到树结构. 性能—成本曲线比单个最高分更有信息. 搜索不会自动带来学习. 整棵树在任务结束后丢弃, 仍是 L0 的 TestingTime 计算. 将成功路径蒸馏进参数进入 L1, 将搜索启发写成跨任务技能进入 L2, 修改以后怎样搜索的更新器才触及 L3. 计算深度与演化深度是两条轴.

外部验证器的有效域?程序执行器能确定代码是否运行、输出是否满足测试, 无法判断测试没有表达的需求. 数学符号工具能核对等式, 无法自动确认建模假设. 检索能提供来源, 来源本身可能过时或不适用. 外部验证器强在其有效域内提供与模型不同的证据. 把目标性质写成 P(y,x)P(y,x), 验证器检查近似性质 P^(y,x)\widehat P(y,x). 若 P^=1\widehat P=1 能可靠推出 P=1P=1, 验证器精度高; 若 P=1P=1 时容易得到 P^=1\widehat P=1, 召回高. 单元测试往往精度较高、召回有限: 通过的实现大概率满足已测行为, 正确实现也可能因环境问题失败.

验证器被反复查询后会成为优化目标. 候选可以硬编码测试、删除日志标记、捕获异常或利用计时误差. 隐藏实现只能增加利用难度, 候选若能影响验证器输入通道, 仍可能绕过. 独立采集日志、随机生成测试和限制查询次数能减轻. 验证器组合采用分层逻辑. 语法检查先淘汰无效候选, 单元测试检查局部功能, 属性测试覆盖不变量, 隐藏集测泛化, 人工审查处理规格歧义. 前级便宜高召回, 后级昂贵高精度. 全部压成一个总分会让强项抵消硬失败.

执行反馈还有诊断价值. 只返回通过/失败, 泄漏少但修复效率低; 返回具体堆栈和失败输入, 帮助定位也让候选更容易拟合测试. 开发阶段可以提供详细反馈, 最终审计使用新的隐藏测试与有限反馈, 分开优化与确认角色. 外部验证不意味着绝对正确. 运行环境、数值精度、随机性和依赖版本都会影响结果. 保存环境镜像、重复运行和对照已知实现, 才能区分候选错误与验证基础设施故障. 遇到证据冲突时, 先检查各自有效域与环境.

停止策略决定修订会不会过头?迭代修订需要回答何时停. 固定轮数简单, 却会让已经正确的答案继续暴露在破坏风险下; 依赖模型自评停止, 又把最困难的正确性判断交回同一个模型. 理想停止器比较下一轮的预期收益与成本. 令当前答案正确概率为 ptp_t, 再修订一轮将错误修对的概率为 rtr_t, 将正确改错的概率为 qtq_t, 调用成本为 ctc_t. 期望净收益近似为

Vt=(1−pt)rt−ptqt−λct.V_t=(1-p_t)r_t-p_tq_t-\lambda c_t.

只有 Vt>0V_t>0 才值得继续. 强模型或后期轮次的 ptp_t 较高, 修复空间缩小, qtq_t 即使不大也会让净收益转负. 现实系统不知道 pt,rt,qtp_t,r_t,q_t, 可以在留出集上按任务类型和反馈信号估计. 程序测试失败时 ptp_t 很低, 继续修复通常合理; 所有测试通过但模型口头犹豫时, 外部证据应压过自评. 对开放任务, 多个低相关验证器一致指出具体错误, 比一句笼统「还可以改进」更适合作为继续条件.

停止规则要与返回规则配套. Self-Refine 常返回最终一轮, 即使中间版本更好也会丢失. 保存每轮候选, 由独立验证器选最佳, 可以避免单调性假设; 选择器若不可靠, 候选变多又增加选择偏差. 另一种策略是只在外部指标明确改善时提交新版本, 否则保留在任答案. 循环检测也很重要. 修订可能在两个答案之间来回切换, 或不断改写措辞而语义不变. 对规范化答案、程序 AST 或执行行为计算相似度, 检测周期与无效变化, 可以提前停止. 这类停止信号不判断答案正确, 只判断继续迭代缺少新信息.

停止本身属于 L0 控制逻辑. 如果系统从多次任务中学到新的停止策略并跨任务保留, 变化落在脚手架 Σ\Sigma; 如果它学会修改以后怎样优化停止策略, 才进入改进器 UU. 单次任务中动态决定轮数不会自动提升演化层级.

验证器要同时校准分数和排序?验证器有两个用途: 判断单个候选是否可信, 以及在多个候选间排序. 校准关注分数语义, 排序关注相对次序. 一个判官可能把所有概率报得过高, 排序仍然好; 也可能总体准确率高, 在顶部候选间无法区分. 候选选择最关心 top-k 区域. 自然样本上的 AUC 很高, 不代表从一百个高分候选中选冠军可靠. 搜索会把候选集中到判官决策边界与漏洞区域, 应在经过优化的候选分布上测 precision@k、pairwise accuracy 和 regret.

选择 regret 定义为 oracle 最佳真实效用与被选候选真实效用之差:

Regret⁡=max⁡iJ(yi)−J(yarg⁡max⁡iJ^(yi)).\operatorname{Regret}=\max_i J(y_i)-J(y_{\arg\max_i\widehat J(y_i)}).

它同时反映判官是否把关键排序弄错和错误代价有多大. 两个判官 pairwise accuracy 相同, 一个只错在近似等价候选, 另一个把最好选成最差, regret 完全不同. 校准允许风险控制. 在留出集上估计「判官分数至少 ss」的真实错误率, 选择满足目标风险的阈值. 候选分布随模型、提示和搜索方法变化后, 旧校准可能失效, 需要重测. 判官被搜索器反复利用时, 高分区的实际错误率往往上升. 评判解释可以辅助诊断, 不能当作正确性的独立证据. 模型能为错误分数生成连贯理由, 理由与分数来自同一内部状态. 将理由转成可执行检查、引用定位或明确反例, 再由外部工具验证, 才增加新信息.

参考文献

  1. Kaiqi Wang, Wenjin Hou, Yuchen Yan 等 (2026). Diving into Reliable Self-Evolving Agents: A Survey. §3 Output-Level Self-Evolution, 式 (6), 表 3.
  2. Aman Madaan, Niket Tandon, Prakhar Gupta 等 (2023). Self-Refine: Iterative Refinement with Self-Feedback. NeurIPS 2023. 式 (1)–(4), 表 1, 表 2, 图 4, 附录 H.
  3. Jie Huang, Xinyun Chen, Swaroop Mishra 等 (2023). Large Language Models Cannot Self-Correct Reasoning Yet. ICLR 2024. 表 2–8, 图 1.
  4. Theo X. Olausson, Jeevana Priya Inala, Chenglong Wang, Jianfeng Gao, Armando Solar-Lezama (2023). Is Self-Repair a Silver Bullet for Code Generation?. ICLR 2024. 表 1.
  5. Xuezhi Wang, Jason Wei, Dale Schuurmans 等 (2022). Self-Consistency Improves Chain of Thought Reasoning in Language Models. ICLR 2023. 表 2.
  6. Shunyu Yao, Dian Yu, Jeffrey Zhao 等 (2023). Tree of Thoughts: Deliberate Problem Solving with Large Language Models. NeurIPS 2023. §4.1, 表 2.
  7. Bei Chen, Fengji Zhang, Anh Nguyen 等 (2022). CodeT: Code Generation with Generated Tests. ICLR 2023. §2, 附录 I.

上一篇: 1.1.3 · 证据, 接受与控制

下一篇: 2.1.2 · L1: 单模型自训练