返回 AICAP-180
B12 · Day 112RLVR + 模型策略 memo

Reward hacking 谱系

昨天(Day 111)我们建立了 RLVR 的乐观面——确定性 grader 取代学习型 RM,省标注、无 RM 漂移。今天必须给这份乐观泼冷水:可验证奖励不等于免疫作弊。在 B1→B18 能力曲线上,这是从「会设计奖励」走向「会审计奖励」的一跳——能列出 reward hacking 的谱系、并在自己的代码里指出哪条路径正在自评循环,才算真正握住了 eval 严谨性。今天还有一个具体动机:本仓

阶段: B12 · RLVR + 模型策略 memo(Day 111-120) 标签: #reward-hacking #llm-as-judge #eval-rigor #aml-copilot

今日导引(由浅入深)

昨天(Day 111)我们建立了 RLVR 的乐观面——确定性 grader 取代学习型 RM,省标注、无 RM 漂移。今天必须给这份乐观泼冷水:可验证奖励不等于免疫作弊。在 B1→B18 能力曲线上,这是从「会设计奖励」走向「会审计奖励」的一跳——能列出 reward hacking 的谱系、并在自己的代码里指出哪条路径正在自评循环,才算真正握住了 eval 严谨性。今天还有一个具体动机:本仓 src/aml/evalBaseline.ts 是一个典型的「同模型既答又判」的循环 baseline,今天把它的 hack 类型逐行审计出来,就是 B14 src/aml/groundTruthEval.ts(预测源无关的 ground-truth 评测)要修的那个问题的动机记录。最小可判定产出:一份 evalBaseline-hack-audit.md,≥3 条具体 hack 标注、定位到 evalBaseline.ts 行号。

1. 机理精读

核心命题。 RLVR 把奖励钉在确定性判定上,确实消除了「RM 漂移」,但没有消除「策略钻判定空子」。只要判定函数与「真实质量」之间存在任何间隙,优化压力就会把策略推向「最大化判定分、而非最大化质量」的方向。这就是 reward hacking——在任何 RL(含 RLVR)里都成立。

五类谱系(综述 arXiv:2604.15149, 2026):

  1. grader 漏洞:钻判定逻辑本身的实现空子。
    • 例如 verifier 用 output.includes(target) 而非严格相等,策略就学会「把正确答案塞进一堆垃圾里」蒙混过关。
    • 或者判题器对边界输入(空串、超长、特殊字符)处理有 bug,被策略系统性利用。
    • 防御:verifier 用严格相等 + 充分的边界测试;本仓 exactMatchReward===(trim 后)而非 includes,正是这条。
  2. 格式套利:命中格式正则却语义错。
    • formatReward 只验「像不像」不验「对不对」——输出一个合法 JSON 但字段值全错,照样拿满格式分。
    • RLVR 里格式分权重一旦偏高,策略就退化成「专攻格式、放弃内容」。
    • 防御:用 composeRewards 把 correctness 权重压过 format(如 0.7/0.3)。
  3. 长度偏置:堆 token 骗判分。
    • 如果 judge 隐含偏好「更长更详细」,策略就学会注水。
    • 这是 LLM-as-judge 最普遍的偏置之一,与内容质量无关。
    • 防御:judge prompt 显式要求「不因长度加分」,并用 κ 校准检测偏置。
  4. 自评循环:模型既生成答案又给自己打分,正反馈失真。
    • 同一个模型(或同一套规则)既是被评者又是评判者时,它在生成端的系统性偏差会被评判端「认可」,于是高分被自我强化,分数与外部真实质量脱钩。
    • 这是本仓 evalBaseline.ts 的病——assessCase 既预测又被同源金标评。
  5. 目标错位代理(proxy misalignment):奖励是真实目标的代理,优化代理偏离了真实目标。
    • 经典如「奖励召回率」导致模型把一切都标成可疑(FPR 爆炸),代理(高召回)达成了、真实目标(有用的风控)反而崩了。
    • 防御:奖励要同时约束 precision/FPR(多目标 composeRewards),别只盯单一代理。

五类谱系速查表:

#类型表现本仓对应
1grader 漏洞钻判定实现空子(includes 而非严格相等、边界 bug)evalBaseline 第 47 行分母为 0 返回 0
2格式套利命中正则却语义错(合法 JSON 但字段全错)formatReward 只验格式不验内容
3长度偏置堆 token 骗 judge 高分LLM-judge 隐含偏好「更长更详细」
4自评循环既答又判,正反馈失真evalBaseline assessCase 既预测又被同源金标评
5目标错位代理优化代理偏离真实目标「合成集 recall」≠「真实风控能力」

为什么自评循环最隐蔽。 grader 漏洞、格式套利、长度偏置都还能从外部观察到(输出明显畸形);但自评循环的输出可能看起来完全正常——问题出在「评判维度」和「被评维度」来自同一来源,导致系统性偏差互相确认。本仓 evalBaseline.ts 让规则引擎 assessCase 既做预测、又把预测拿去对「同一作者的合成金标」算 recall/FPR——预测器和金标生成器同源,于是「评测」其实在自我确认规则覆盖的那些模式,对规则没覆盖的真实世界 typology 完全无感。一个具体后果:如果合成数据生成器和 assessCase 共享「structuring = 多笔 $9,800 存款」这条隐含定义,那评测会显示规则对 structuring 召回率极高,但这只是同义反复——真实世界里 structuring 的形态远比这一条丰富,规则在生产环境会大量漏报,而 evalBaseline 永远测不出这个漏洞。

怎么切断。 唯一可靠的解法是让评判源与被评源相互独立,三道闸门:

  1. ground truth 来自外部标注集(IBM AMLworld / Elliptic / PaySim),不是自己生成的合成数据——切断「金标 = 预测器同源」的循环。
  2. 评判用独立 grader(不是被评模型自己)——切断「既答又判」。
  3. 若不得不用 LLM-as-judge,必须配 judge-human Cohen's κ 校准,量化 judge 与人类金标的「超越随机的一致性」,κ 太低就说明 judge 不可信;且校准集 N≥50 才能让 κ 的 CI 收窄到可用。

这正是 B14 groundTruthEval.ts 的设计哲学:binaryEval{label, predicted} 对,二者都可以来自外部(label 来自公开标注集,predicted 来自真实 LLM),从结构上切断了 evalBaseline 的同源循环。换句话说,从「同一个规则世界自说自话」升级为「外部真相 × 外部模型预测」,评测才第一次有了外部效度。

2. 推导 / 手算 / 代码走读

src/aml/evalBaseline.ts 走读(循环 baseline 的病灶):

  • 第 9 行 import { assessCase } from './typology':预测来自规则引擎 assessCase
  • 第 19 行 evalRuleBaseline(ds: AmlDataset):入参 ds合成数据集,其 c.label 是金标。
  • 第 27 行 const predicted = assessCase(c).topTypology ?? 'normal'预测也由 assessCase 产生。→ 病灶:同一套规则既造预测(第 27 行)又被同源合成金标(c.label)评判。这命中谱系第 (4) 类 自评循环——预测器 assessCase 与金标生成器是同一作者/同一规则世界,评测在自我确认。
  • 第 28-29 行 confusion[\${c.label}->${predicted}`]:混淆矩阵 key = label->predicted`,但 label 与 predicted 同源,混淆矩阵只反映「规则对自己造的数据」的表现,无外部效度。
  • 第 31-37 行:对 label === 'normal' 统计 normalFp(normal 被误判为任一 typology);对非 normal 统计 correct。→ 这里 recall/FPR 看似严谨,但整个分布是规则覆盖范围的镜像——规则没考虑到的洗钱模式根本不在合成数据里,命中谱系第 (5) 类 目标错位代理(「在合成集上的 recall」是「真实风控能力」的乐观代理)。
  • 第 47 行 normalFalsePositiveRate: normalN === 0 ? 0 : normalFp / normalN:分母为 0 返回 0,会把「无 normal 样本」与「真实 0% FPR」混为一谈——边界判定的潜在 grader 漏洞(谱系第 1 类的轻量版)。

对照 src/agent/eval/cohensKappa.ts(切断循环的工具):

  • 第 22 行 cohensKappa(a, b):吃两个 rater 的 label 数组,算 po(观测一致)、pe(随机期望一致)、kappa = (po - pe) / (1 - pe)(第 48 行)。
  • 第 48 行注释「Degenerate margins ... pe==1: kappa undefined」:处理「两个 rater 都只用同一类」的退化情形——这正是自评循环的极端形态(评判维度坍缩),κ 会暴露它。
  • 第 53 行 cohensKappaWithCI:bootstrap 出 95% CI,注释第 4 行点明「small N => wide CI — the reason P1 wants N>=50」。→ κ + CI 是「judge 是否可信」的量化闸门:用独立人类金标当 rater A、LLM-judge 当 rater B,κ 高才说明 judge 没在自我确认。

对照 src/aml/groundTruthEval.ts(修复方向):

  • 文件头注释(第 3-8 行)直接点名病因:「The circular weakness of evalBaseline.ts is that the SAME rule engine that predicts is graded against the SAME author's synthetic generator. This module is prediction-source-agnostic」。
  • 第 38 行 binaryEval(items: LabeledPrediction[], normalLabel='normal'):吃 {label, predicted} 对,二者来源无关(label 可来自 IBM AMLworld/Elliptic/PaySim,predicted 可来自真实 LLM)。
  • 第 51-61 行:算 tp/fp/tn/fn → recall/precision/fpr/f1,分母为 0 时返回 0(注释第 36-37 行提醒查原始 tp/fp 字段区分「无支持」与「真实 0」)。
  • 第 65 行 binaryEvalWithCI:bootstrap 出 recall/precision/fpr 的 95% CI。→ 这就是把 evalBaseline 的同源循环换成「外部 label × 外部预测 + CI」的结构性修复。

κ 量化自评循环的手算直觉: 设 LLM-judge(rater B)与人类金标(rater A)在 10 个样本上比对,judge 因自评倾向系统性偏向「pass」。若 judge 给 9 个 pass、人类只认 6 个 pass:

  • 观测一致 po(两者标签相同的比例)可能看着不低(如 0.7);
  • 但随机期望一致 pe 也高(judge 几乎全 pass,碰巧一致概率大);
  • kappa = (po − pe)/(1 − pe) 会把这部分「碰巧一致」扣掉,暴露真实一致性低。 这就是为什么不能只看 po(准确率口径)而要看 κ——po 会被 judge 的偏置抬高,κ 才反映「超越随机的真一致」。N=10 时 κ 的 CI 极宽,所以 P1 要 N≥50 才敢信点估。

3. 今日实战

  1. 打开 src/aml/evalBaseline.ts,逐行定位自评循环:第 9 行(预测源)、第 27 行(同源预测)、第 28-29 行(同源混淆矩阵)、第 31-37 行(recall/FPR 只反映规则覆盖)、第 47 行(边界 0 处理)。
  2. 对每处标注命中的 hack 谱系类型(4 自评循环 / 5 目标错位代理 / 1 grader 漏洞)。
  3. 对照 src/agent/eval/cohensKappa.ts(第 22/48/53 行)说明 κ + CI 如何量化 judge 可信度,对照 src/aml/groundTruthEval.ts(第 3-8 行注释 + 第 38 行 binaryEval)说明 prediction-source-agnostic 如何从结构上切断循环。
  4. evalBaseline-hack-audit.md,≥3 条具体 hack 标注,每条带 evalBaseline.ts 行号 + 谱系类号 + 修复方向(指向 groundTruthEval.ts)。

4. 今日实测 / 产出

  • 产出evalBaseline-hack-audit.md(≥3 条具体 hack 标注,定位到 evalBaseline.ts 行号)。审计条目示例口径:
    • 「evalBaseline.ts:27 assessCase(c).topTypology — 谱系#4 自评循环:预测器与金标同源 → 修复指向 groundTruthEval.ts:38 binaryEval 预测源无关。」
    • 「evalBaseline.ts:31-37 recall/FPR — 谱系#5 目标错位代理:合成集 recall 是真实风控能力的乐观代理 → 修复用外部标注集。」
    • 「evalBaseline.ts:47 分母为 0 返回 0 — 谱系#1 grader 漏洞轻量版:『无 normal 样本』与『真实 0% FPR』被混淆 → groundTruthEval 注释提示查原始 tp/fp 字段。」
  • 这是 src/aml/groundTruthEval.tsbinaryEval 预测源无关、recall/precision/FPR + bootstrap CI)要修的「循环 baseline」问题的动机记录
  • judge-human κ 校准的目标样本量:κ≥50 手标——待跑(需 ≥50 手标)
  • cohensKappa.tsgroundTruthEval.ts 的算法均为纯函数、已 built+tested;本日只做审计,不引入新测量数字

5. 常见误区 / 陷阱

  1. 以为 verifiable reward 免疫作弊。可验证只消除了「RM 漂移」,没消除「钻判定空子」。grader 与真实质量的任何间隙都会被优化压力放大。
  2. 把单模型自评当 ground truth。evalBaseline.ts 的病根。预测器与金标同源时,「评测」是自我确认,对规则盲区完全无感——分数好看但无外部效度。
  3. 只看格式分不看语义分。格式套利:合法 JSON 但内容全错照样满分。RLVR 里格式分权重过高会让策略退化成「专攻格式」。
  4. 用 LLM-judge 却不做 κ 校准。LLM-judge 自带长度偏置 + 自评倾向。不配 judge-human κ(且 N≥50)就报 judge=pass 数字,等于把一个未校准的近似当真相。
  5. 把「合成集 recall 高」当能力证明。目标错位代理。合成集是规则覆盖范围的镜像,recall 高只说明规则对自己造的数据有效,不代表生产环境有效——必须拿外部标注集复测。

6. 学习资源(每条带 YYYY-MM)

  • RLVR / reward-hacking 综述 — arXiv:2604.15149(2026-,五类 hack 谱系来源)
  • Anthropic《Demystifying evals》— 2026-01(LLM-as-judge 偏置、judge 校准、独立 grader 必要性)
  • DeepSeek-R1 — arXiv:2501.12948(2025-01,verifiable reward 的实践语境)
  • 本仓实现:src/aml/evalBaseline.ts(循环 baseline 病灶)、src/aml/groundTruthEval.ts(prediction-source-agnostic 修复)、src/agent/eval/cohensKappa.ts(κ + CI judge 校准)

SOTA检查 (2026-06 更新)

  • 当前主流:「自评 / LLM-as-judge 循环偏置」是 2026 eval-rigor 的核心警示,仍当前。现役做法 = 独立 grader(预测源 ≠ 评判源)+ judge-human Cohen's κ 校准,κ≥50 手标作为最低校准集规模。
  • 是否仍 SOTA:是。prediction-source-agnostic 评测 + bootstrap CI(本仓 groundTruthEval.ts 的形态)是当前外部效度评测的主流形态。
  • 过时黑名单
    • 不要把单模型自评当 ground truth(谱系#4);
    • 不要在未校准 LLM-judge 上直接宣称提升(必须先有 judge-human κ);
    • 不要拿同源合成集的 recall 当真实风控能力的证据(谱系#5 目标错位代理);
    • 不要用 includes 类宽松匹配做 verifier(谱系#1 grader 漏洞)。
  • 下次复查点:综述 arXiv:2604.15149 的更新版本;Anthropic eval 系列后续(judge 校准、对抗性 reward-hacking 检测)的新方法;本仓 judge-human κ 手标(待跑,≥50)落地后回填真实 κ 值。

衔接

  • 昨天:Day 111 — RLVR 原理(确定性 grader 取代学习型 RM;29 任务可验证性分类)
  • 今天:可验证奖励也会被作弊——五类 reward hacking 谱系,审计 evalBaseline.ts 的自评循环,指向 groundTruthEval.ts 的独立 grader 修复。
  • 明天:Day 113 — GRPO 奖励信号(组内归一化优势省 critic,走读 grpo.ts,把 5 个任务映射到 verifiable vs judge 奖励)