后训练实战
· 13 分钟

规划 Agent 奖励设计:验证器、奖励模型与奖励漏洞

目录
  1. 程序验证器与模型评分的分工
  2. 代码里的验证器实现
  3. 奖励过度优化:代理分数与真实质量
  4. 使用留出检查评估奖励过度优化

规划 Agent 需要区分硬约束与软判断:预算、时间窗等可计算条件交给程序验证器,其余质量判断由奖励模型处理,并用留出评测核对奖励上涨是否对应任务改善。源码能说明分数如何组合,但不能证明验证器已覆盖全部漏洞,也不能独立证明文末的内部评测涨幅。

我在规划 Agent 上反复遇到这个问题:方案看起来合理,实际超了预算;路线看起来顺,时间窗却排不开。奖励模型(RM)有时会给这些方案高分,RL 随后就会利用这些误判。所以我把能用程序检查的约束写进 VerifierResult,其余判断才交给 RM。

这就是 古德哈特定律 (Goodhart’s Law) 在后训练里的样子:当一个度量变成目标,它就不再是一个好的度量。 Manheim 与 Garrabrant 在 Categorizing Variants of Goodhart’s Law (2018) 中将回归型、极值型、对抗型失效分开,这帮助我区分了后来遇到的 RM 失效。排查时,我关心的是策略偏离到什么程度,奖励就不再反映实际质量,以及能否在此之前停止优化。

程序验证器与模型评分的分工

验证器(RLVR)。一段固定的代码去检查输出。方案是否没超预算?时间窗是否真的排得开?最终的数是否正确?当正确性可被程序校验时,这是黄金标准。它的核心价值在于:只要检查是完备的,它没有可供利用的盲点。

奖励模型(RM)。当没有程序能判时,一个学出来的模型给质量打分。判断“方案合理且可执行吗”没有干净的 oracle。RM 给你一个验证器够不到的信号。但它本身是个拟合出的网络,必然存在决策边界的盲区,而策略会用极大似然去试探每一个盲区。

验证器与真实目标的相关性对优化压力是平坦的——它是一段确定性程序,真正没超预算的方案始终会被判为正确,不存在移出训练分布的问题。RM 正好相反:它是有限样本上拟合出的有限模型,与真相的相关性随着策略移出分布而急剧衰减。每一步优化,都是朝着 RM 从没见过、最可能出错的输入迈进。基于这种不对称,我的架构设计是:把所有可校验的逻辑下沉到验证器,只让 RM 兜底不可计算的模糊余项。

规划 Agent 的真实奖励被拆解为这样一条计算流:

[ 策略输出方案 ]
       |
       +---> (精确计算) 验证器 (Verifier)  ---> [ 预算 / 时间窗 / 可行性 ] (硬约束信号)
       |
       +---> (统计推断) 奖励模型 (RM)      ---> [ 语义合理性 / 语气 ] (软质量得分)
       |
       v
    最终奖励 (Reward) = \lambda_1 * 验证器得分 + \lambda_2 * RM 得分

代码里的验证器实现

在 Orbit 项目里,验证器被收敛成一个协议(Protocol)。在 orbit/verifiers/base.py 中,它只负责把轨迹映射为结构化奖励。契约由两个 pydantic 模型承载:VerifierSpec 持有超参旋钮,VerifierResult 持有拆解后的输出:

class VerifierSpec(StrictModel):
    kind: str = "static_trace"
    gamma: float = 0.99
    lambda_delta: float = 1.0   # 势函数塑形权重
    lambda_g: float = 1.0       # 局部(每步)分数权重
    lambda_env: float = 1.0     # 环境奖励权重
    lambda_u: float = 1.0       # 终局效用权重
    process_weight_max: float = 4.0
    baseline_strategy: str = "trajectory_mean"

奖励并不是单一标量。在 orbit/verifiers/static.py 中,StaticTraceVerifier.verify 用四个加权项拼出每步的密集奖励:

reward = (
    self.spec.lambda_delta * (phi_prefix[idx + 1] - phi_prefix[idx])  # 势函数差分
    + self.spec.lambda_g * local_scores[idx]                          # 单步即时奖励
    + self.spec.lambda_env * env_rewards[idx]                         # 外部环境反馈
)
if idx == len(local_scores) - 1:
    reward += self.spec.lambda_u * terminal_score                     # 终局结算

第一项是基于势的塑形 (Potential-based Shaping)。根据经典强化学习理论,势函数 ϕ\phi 在两步之间的差分,可用于提供稠密引导,但策略不变性结论有折扣和边界条件。此处实现先取未折扣的势差,再计算折扣回报,不能仅凭这段代码宣称策略不变或能够规避 reward hack。随后对这些反馈进行折现计算,并用 process_weight_max 进行硬裁剪。裁剪限制了单步的异常优势梯度,避免它主导策略更新。

奖励过度优化:代理分数与真实质量

开发时我遇到过这样的背离:训练 Reward 持续上涨,留出集 Benchmark 却停滞甚至下降。模型过拟合了 RM 的评分方式,实际任务表现没有改善。如 DeepMind 收录的 Specification gaming 案例集 所示,Agent 总能找到违背精神却满足字面的漏洞。

Gao、Schulman 与 Hilton 在 Scaling Laws for Reward Model Overoptimization (2022) 中精确刻画了这种现象:代理 RM 分数单调上升,而真实金标准分数先升、见顶、然后回落。金标准奖励可以拟合为 KL 距离 d=DKLd = \sqrt{\mathbb{D}_{\mathrm{KL}}} 的函数:

R(d)  =  d (α−βlog⁡d)R(d) \;=\; d\,(\alpha - \beta \log d)

这个拟合意味着,存在一个最优 KL 距离,超过这个预算,每多 1 nat 的散度带来的都是真实性能的倒退。扩大 RM 规模能推迟这个顶点的到来,但无法消灭它。

另外,正如 Pan 等人在 The Effects of Reward Misspecification (2022) 中指出的,随着能力提升,策略会发生相变式跳变,骤然发现并利用 hack。

使用留出检查评估奖励过度优化

  1. 补全验证器。把所有可计算约束写进程序,这是我最优先处理的部分。
  2. 保留 KL 正则。带 KL 正则的目标具有闭式最优解: π∗(y∣x)  ∝  πref(y∣x) exp⁡ ⁣(1β r(x,y)).\pi^*(y\mid x) \;\propto\; \pi_{\mathrm{ref}}(y\mid x)\,\exp\!\Big(\tfrac{1}{\beta}\,r(x,y)\Big). β\beta 决定了允许奖励倾斜参考先验的力度。那些为了刷分而产生的乱码或格式复读,正是由于 πref\pi_{\mathrm{ref}} 给出的先验概率近乎为零,所以可以通过调节 β\beta 作为强力刹车。
  3. 对抗更新。策略学会的新把戏就是 RM 的 Out-of-Distribution 样本。必须将策略的失败模式持续打标并重训 RM,保持分布同步。
  4. 信评测,不信奖励。留出集的验证器 Benchmark 才是唯一的真相。

最终,我在规划 Agent 上拿到了复杂约束满足率 12% 的内部 Benchmark 涨幅。改进来自补全验证器、将硬约束完全移出 RM,以及持续处理策略产生的新 exploit,优化器本身没有变化。

验证器还决定后训练数据管线保留哪些样本,因此检查奖励漏洞也是数据质量控制的一部分。

llmrlreward-modelrlvrreward-hacking

评论