判分一致性校准
昨天(Day 46)我们用受控 A/B 把「v2 工具更好」变成了一个带 Δ 的数字——但这个数字完全建立在「judge 判得准」这个未经检验的前提上。
阶段: B5 · tool/context engineering + 指标树(Day 41-50) 标签: #cohens-kappa #llm-judge #calibration #bootstrap-ci
今日导引(由浅入深)
昨天(Day 46)我们用受控 A/B 把「v2 工具更好」变成了一个带 Δ 的数字——但这个数字完全建立在「judge 判得准」这个未经检验的前提上。
如果裁判本身和人类标注口径不一致,Δ 就是建在沙子上。
今天(Day 47)在 B1→B18 曲线上做的是 eval 体系的地基加固:用 Cohen's κ 量化 LLM-judge 与人工金标的一致性,给昨天的 A/B 结论上一道可信度保险。
明天(Day 48)才有资格谈跨模型复核——因为只有 judge 可信,跨模型的 Δ 方向比较才有意义。
今天的最小可判定产出:≥50 样本上 judge vs 人标的一个 κ 值 + 95% bootstrap CI(待 key 产 judge 输出)。
1. 机理精读
为什么需要 κ 而不是看「同意率」。
最朴素的 judge 评估是 raw agreement(judge 和人标判一样的比例 po)。
但 po 有个致命缺陷:当类别分布偏斜时它会虚高。
设想任务 90% 都该判 pass,一个永远输出 pass 的裁判 raw agreement 就有 90%,看起来很准,实际上毫无判别力。
Cohen's κ 的设计正是为了扣除「碰巧一致」的部分:κ = (po − pe) / (1 − pe),其中 pe 是按两个评分者各自的边际分布算出的期望偶然一致率。
κ=0 表示「只达到随机水平」,κ=1 表示「完美一致」。这是 Anthropic《Demystifying evals》(2026-01) LLM-judge 校准段落的核心论点。
κ 的阈值与含义。 业界惯例(Landis & Koch 量表):κ<0.2 微弱、0.2–0.4 一般、0.4–0.6 中等、0.6–0.8 显著、>0.8 几近完美。 本项目把 κ≥0.6 作为「judge 可接受、A/B 结论可信」的门槛。 低于 0.6 意味着裁判和人类口径差太多,此时任何基于该 judge 的 Δ 都不该被引用。
为什么要配 bootstrap CI 而不只报点估计。 κ 是在一个有限校准集上算的,N 小则估计不稳。 N=20 算出 κ=0.65 可能 CI 宽到 [0.3, 0.9]——你根本不知道真实 κ 是否 ≥0.6。 所以必须报置信区间。本仓用百分位自助法:把 N 对配对观测重抽样 B 次,每次重算 κ,取第 2.5/97.5 百分位作 95% CI。 这也是 P1 阶段坚持「N≥50」的统计原因:只有样本够大,CI 才够窄到能下结论。
same judge 约束在校准里的落点。 Day 46 的 A/B 要求 v1/v2 共用同一 judge。今天进一步要求:这个被共用的 judge,必须先通过 κ 校准。 换言之顺序是——先校准 judge(κ≥0.6)→ 再用这个 judge 跑 v1/v2 → 才能引用 Δ。 校准和 A/B 不是两件独立的事,而是同一条证据链的上下游。
边界:κ 校准的是「判分口径」,不是「任务难度」。 κ 高只说明裁判和人标一致,不说明任务集本身覆盖得好。 一个只有简单任务的 eval,即使 κ=0.9,也测不出工具的真实差异。 所以 κ 是必要条件不是充分条件——它保证「测得准」,但「测得全」要靠任务集设计(Day 46 的 tasks.ts)。
2. 代码走读:cohensKappa.ts
本仓 src/agent/eval/cohensKappa.ts 是纯函数 + RNG 可注入、单测绿、无需 key 的统计装置。
cohensKappa(a, b):对两个等长标签数组算点估计。先校验等长(不等抛'cohensKappa: rater arrays must be equal length')与非空(n===0抛'need >=1 paired observation')。- po 计算:遍历一遍统计
agree(a[i]===b[i]计数),po = agree / n,同时把所有出现过的类别收进cats。 - pe 计算:用两个
Mapc1/c2统计各评分者每类别的边际频次,pe += (c1.get(c)/n)*(c2.get(c)/n)对每个类别累加——这就是期望偶然一致率。 - κ 公式:
const denom = 1 - pe; kappa = denom===0 ? (po===1?1:0) : (po-pe)/denom。注释明确处理了退化边际(两评分者都只用同一类别,pe=1,κ 数学上无定义):完美一致报 1,否则报 0。这是常见 κ 实现忽略的边界。 cohensKappaWithCI(a, b, {bootstrap, rng}):先算点估计,然后 B 次(默认 2000)重抽样——每次对 N 个配对位置const j = Math.floor(rng()*n)同步抽ra[i]=a[j]、rb[i]=b[j](配对重抽,保持配对结构),重算 κ 收进samples。- CI 输出:
samples.sort(...)后取percentile(samples, 2.5)与percentile(samples, 97.5),返回{ kappa, po, pe, n, ci95, bootstrapSamples }。 percentile(sorted, p):线性插值百分位(非最近邻),对小样本更平滑。- RNG 可注入(
opts.rng ?? Math.random):让单测能用确定性伪随机复现,这是「数学骨架已绿」的实现保证。
判分输出与人标的对接在 runner 侧:
scripts/run-agent-eval.ts的loadHumanLabels()读agent-evals/labels.json({ "<taskId>": "pass"|"fail"|"unknown" }),JSON 非法时console.warn并跳过 κ。runTaskEval(...)把它作为humanLabels传入,跑出的报告里带judgeHumanKappa(κ + ci95 + N),runner 打印judge-human kappa: <k> 95% CI [...] (N=...)。- seed 提到的
agent-evals/labels.example.json即此人标的样例形态。
手算一个最小 κ(验证 raw agreement 的陷阱)
设 N=10 个任务,judge 与人标如下(p=pass, f=fail):
| # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| 人标 | p | p | p | p | p | p | p | p | f | f |
| judge | p | p | p | p | p | p | p | f | p | f |
- agree:第 1–7、10 共 8 个一致 →
po = 8/10 = 0.80。光看这个 raw agreement 你会以为裁判很准。 - 边际:人标 p=8、f=2(→ 0.8 / 0.2);judge p=8、f=2(→ 0.8 / 0.2)。
- pe:
pe = (0.8×0.8) + (0.2×0.2) = 0.64 + 0.04 = 0.68——因为类别严重偏向 pass,两个裁判「碰巧都判 pass」的概率高达 0.68。 - κ:
κ = (po − pe)/(1 − pe) = (0.80 − 0.68)/(1 − 0.68) = 0.12/0.32 = 0.375。 - 结论:raw agreement 0.80 看似不错,κ 却只有 0.375(中等偏下,未达 0.6 门槛)。这正是「不看 κ 会被偏斜类别骗」的数值证据——
cohensKappa()用c1/c2边际算出的 pe 把这层虚高扣掉了。N=10 时再叠加 bootstrap CI,区间会宽到根本不敢下「judge 可用」的结论,这也是 N≥50 的由来。
3. 今日实战
- 准备 ≥50 个 tool-use / AML 任务的人工金标,写进
agent-evals/labels.json(taskId → pass/fail/unknown)。 - 跑一次带 judge 的真实 eval(
pnpm eval:agent --provider openrouter --model deepseek/deepseek-chat),让 runner 产出 judge 标签。 - runner 会在报告里输出
judgeHumanKappa(κ + 95% CI + N);或离线直接调cohensKappaWithCI(judgeLabels, humanLabels)。 - 确认本次校准过的 judge 配置,与 Day 46 A/B 两组共用的是同一个——替换掉旧的 evalBaseline 自评循环。
- 无 key 时:用
cohensKappa.ts单测里的固定数组验证 κ + CI 计算路径全绿,得到逻辑可信度;真实 κ 待 key 后产 judge 输出再算。
4. 今日实测 / 产出
- κ harness 与 bootstrap CI 已绿(
cohensKappa.ts单测通过,纯函数 + RNG 可注入)。 - 本次 ≥50 样本的 κ 值「待跑(需 OPENROUTER_API_KEY 产 judge 输出)」;目标 κ≥0.6,附 95% bootstrap CI。
- 代码路径已就绪(
cohensKappaWithCI+ runner 的loadHumanLabels/judgeHumanKappa)。 - 本日不臆造 κ 数值;seed 标注待跑就写待跑。
5. 常见误区 / 陷阱
- 只看 raw agreement 不看 κ:偏斜类别下 raw agreement 虚高,κ 才扣除偶然一致——seed 明确点名的反模式。
- N 太小就下结论:N=20 的 κ 点估计 CI 可能宽到跨过 0.6,必须报 CI、争取 N≥50。
- 校准用的 judge 和 A/B 用的 judge 不是同一个:校准失去意义,A/B 的 same-judge 约束被破坏。
- 忽略退化边际:两评分者都判同一类时 pe=1,κ 无定义;本仓代码已处理,手算时别忘。
6. 学习资源(每条带 YYYY-MM)
- Anthropic, Demystifying evals — 2026-01(LLM-judge 校准段,κ 阈值与方法)。
- Cohen, J., A Coefficient of Agreement for Nominal Scales — 经典统计(κ 原始定义,奠基方法无过时风险)。
- Landis & Koch, κ 强度量表 — 经典(0.6–0.8 显著、>0.8 几近完美的解释惯例)。
- 本仓代码:
src/agent/eval/cohensKappa.ts、scripts/run-agent-eval.ts(loadHumanLabels/judgeHumanKappa)— 2026-06。
SOTA检查 (2026-06 更新)
- 当前主流:Cohen's κ 仍是 inter-rater agreement 的标准度量,2026-06 仍 SOTA 用于 LLM-judge 校准。
- 2026 趋势:多标注者场景补充 Krippendorff's α(支持 >2 标注者、缺失值、多种测量尺度);κ 仍是两评分者基准。
- 过时黑名单:只看 raw agreement / 同意率不看 κ(高基线偏差虚高);不报 CI 的单点 κ。
- 下次复查点:若引入第三方人标或多裁判,评估是否迁移到 Krippendorff's α;OpenRouter 上 judge 模型 id 当周重验。
衔接
- 昨天:Day 46 — A/B eval 设计,产出依赖 judge 的 Δ。
- 今天:用 Cohen's κ(≥0.6 + bootstrap CI)校准 judge 与人标一致性,给 A/B 结论上可信度保险。
- 明天:Day 48 — 跨模型稳健性,用第二个独立模型复核 Δ 方向是否一致。