Judge 说 53% 的报告有效,真实是多少?
Judge 是一台有误差的测量仪器。先拿人工标注量出它的 TPR 和 TNR,再用它们把"Judge 判的通过率"校正回"真实通过率",区间要同时算上标注集和评测集两边的抽样误差。
准确率和 κ 告诉你 Judge 大概靠不靠谱;校正和门禁要用的是 TPR、TNR,以及漏判、误杀各自的代价。
1问题:Judge 的数字能直接用吗?
BugHunt-Bench 里,测试 Agent 每次运行会提交一批 Bug 报告。人工逐条看太贵,于是让一个 LLM Judge 读报告和注入 Bug 清单,判 PASS(有效报告) 或 FAIL(误报)。新版 Agent 提交了 1000 条报告,Judge 判了 530 条 PASS:有效率 53%?
不一定。Judge 自己会判错:把误报判成有效,把有效报告判成误报。两种错不会互相抵消,除非刚好一样多。所以要先回答三个问题:
- Judge 在哪类样本上错、错多少?(混淆矩阵、TPR、TNR、κ)
- 两种错,哪一种更贵?(漏判和误杀的成本,阈值怎么定)
- 知道了 Judge 的错误率,怎么从 53% 反推真实有效率,区间多宽?(Rogan-Gladen 校正)
上一讲「Bug 报告写得好不好,打 1~5 分还是判 pass / fail?」讲的是两个人工标注者之间的一致性,这一讲把 Judge 当成"第三个标注者",拿人工标注当真值来校准它。本章所有数据都是合成数据:标注集的四格计数是设定的,新版本的 1000 条报告用固定种子模拟,所以真实有效率已知(45.0%),可以检验校正到底对不对。
2混淆矩阵:四个格子,两种错
人工标了 200 条报告(120 条有效、80 条误报),同一批也让 Judge 判一遍:
| Judge 判 PASS | Judge 判 FAIL | 合计 | |
|---|---|---|---|
| 人工 PASS(有效) | TP = 108 | FN = 12(误杀) | 120 |
| 人工 FAIL(误报) | FP = 16(漏判) | TN = 64 | 80 |
| 合计 | 124 | 76 | 200 |
约定 PASS 为正类(和 judgy 等工具一致)。本章的两个中文词:
- 漏判:误报被 Judge 放过(FP)。漏判率 = FP / (FP + TN) = 1 − TNR = 16 / 80 = 20%。
- 误杀:有效报告被 Judge 打掉(FN)。误杀率 = FN / (TP + FN) = 1 − TPR = 12 / 120 = 10%。
注意:英文里的 miss rate 通常指 FNR = 1 − TPR(本章的误杀率),和本章的"漏判"方向相反;代码里因此用 false_pass_rate / false_fail_rate 命名。
这和第 2 周评估者–优化者那里的用法一致:漏判是坏稿子被放行,误杀是好稿子被打回。
| 指标 | 算式 | 值 | 回答的问题 |
|---|---|---|---|
| 准确率 | (TP + TN) / n | 172 / 200 = 86.0% | 整体判对多少,混在一起 |
| TPR(灵敏度) | TP / (TP + FN) | 108 / 120 = 90.0% | 真有效的报告,Judge 认出多少 |
| TNR(特异度) | TN / (TN + FP) | 64 / 80 = 80.0% | 真误报,Judge 拦下多少 |
| Youden J | TPR + TNR − 1 | 0.700 | 比瞎猜强多少;0 表示和随机一样 |
3Cohen's κ:扣掉"碰巧一致"
κ(Cohen, 1960, Educational and Psychological Measurement 20(1):37–46)衡量两个评判者的一致程度,扣掉随机也会有的那部分:
$$\kappa = \frac{p_o - p_e}{1 - p_e},\qquad p_e = \pi_H\,\pi_J + (1-\pi_H)(1-\pi_J)$$\(p_o\) 是实际一致的比例,\(p_e\) 是两边按各自的 PASS 比例(人工 \(\pi_H\)、Judge \(\pi_J\))独立乱判时的期望一致率,也就是上一讲「Bug 报告写得好不好,打 1~5 分还是判 pass / fail?」里的"随机期望一致率"。上面的例子:
| 量 | 算式 | 值 |
|---|---|---|
| \(p_o\) | (108 + 64) / 200 | 0.8600 |
| \(p_e\) | 0.60 × 0.62 + 0.40 × 0.38 | 0.5240 |
| κ | (0.86 − 0.524) / (1 − 0.524) | 0.7059 |
自己写的实现和 scikit-learn 1.1.1 的 cohen_kappa_score 结果一致(0.7059),单元测试里另外用 50 组随机数据逐个对照。
κ 会随有效率变
同一个 Judge(TPR 90%、TNR 80% 不变),只改真实有效率:
| 真实有效率 | 准确率 | κ |
|---|---|---|
| 30% | 83.0% | 0.634 |
| 60% | 86.0% | 0.706 |
| 90% | 89.0% | 0.534 |
| 97% | 89.7% | 0.283 |
有效率往 PASS 一侧偏(90%、97%)时,准确率越来越高,κ 却越来越低;往另一侧偏(30%),两者都降。准确率总是朝 TPR、TNR 里占多数那一类的值靠,κ 则在两端都会被"碰巧一致"压低。极端情况:真实有效率 95%,Judge 永远判 PASS,准确率 95%,κ = 0,TNR = 0(一条误报都拦不住)。Feinstein 和 Cicchetti(1990, J Clin Epidemiol 43(6):543–549)专门讨论过这类悖论;续篇(Cicchetti & Feinstein, 1990, 同卷 551–558)建议 κ 要和正、负两类各自的一致率一起报。
4漏判和误杀,哪个更贵?
两种错的代价取决于 Judge 用在哪:
| 用途 | 漏判(误报被放过) | 误杀(有效报告被打掉) |
|---|---|---|
| 离线评测,算 Agent 的有效率 | 有效率虚高 | 有效率虚低 |
| 线上自动关闭"无效"报告 | 开发多看一条误报 | 一个真 Bug 被扔掉,往往贵得多 |
| 自动给开发提单 | 开发被误报打扰,信任流失 | 少提一个单,还有人工兜底 |
设真实有效率为 \(\pi\),漏判一次成本 \(c_\text{miss}\),误杀一次成本 \(c_\text{kill}\)。每条报告的期望成本:
$$\text{Cost} = (1-\pi)(1-\text{TNR})\,c_\text{miss} + \pi(1-\text{TPR})\,c_\text{kill}$$Judge 如果输出一个分数(或者能调 prompt 的松紧),阈值往上调,TNR 升、TPR 降,反之亦然。最优阈值取决于 \(\pi\) 和成本比,不是固定的 0.5。下面用双正态模型算(有效报告分数 ~ N(d′, 1),误报 ~ N(0, 1),d′ = 2.123,此时阈值 0.84 正好对应 TPR 90%、TNR 80%;\(\pi\) = 45%):
| 成本比 cmiss : ckill | 最优阈值 | TPR | TNR | 每千条成本 | 阈值 0.84 时 |
|---|---|---|---|---|---|
| 1 : 1 | 1.156 | 83.3% | 87.6% | 143.1 | 155.0 |
| 1 : 10(误杀贵) | 0.072 | 98.0% | 52.9% | 349.8 | 560.0 |
| 5 : 1(漏判贵) | 1.914 | 58.3% | 97.2% | 264.2 | 595.0 |
成本以较便宜的那种错为 1 个单位。等方差双正态时最优阈值有解析解(对成本求导令其为 0):
$$t^* = \frac{d'}{2} + \frac{1}{d'}\ln\frac{(1-\pi)\,c_\text{miss}}{\pi\,c_\text{kill}}$$5Rogan-Gladen:从 Judge 通过率反推真实通过率
设真实有效率为 \(\theta\)。Judge 判 PASS 的报告来自两部分:有效报告被认出来,加上误报被漏判:
$$p_\text{obs} = \text{TPR}\cdot\theta + (1-\text{TNR})(1-\theta)$$这是 \(\theta\) 的一次函数,反解:
$$\hat\theta = \frac{p_\text{obs} + \text{TNR} - 1}{\text{TPR} + \text{TNR} - 1}$$这就是流行病学里用筛查结果估计患病率的 Rogan-Gladen 估计量(Rogan & Gladen, 1978, American Journal of Epidemiology 107(1):71–76;原文全文在付费墙后,没读到;公式与 Chen et al. 2026 arXiv 2601.05420 第 2.2 节式 (6)、judgy 库 core.py 的实现一致)。分母就是 Youden J,必须 > 0。
新版本 1000 条:Judge 判 PASS 530 条,\(p_\text{obs}\) = 53.0%。
$$\hat\theta = \frac{0.530 + 0.800 - 1}{0.700} = 47.1\%$$合成数据里真实有效率是 45.0%。不校正的 53.0% 偏高 8 个点。其中约 6.5 个点是系统误差:真实有效率 45% 时,Judge 通过率的期望是 0.9 × 0.45 + 0.2 × 0.55 = 51.5%,因为漏判率(20%)高于误杀率(10%),误报又占了一半多;剩下约 1.5 个点是这 1000 条的抽样波动。如果直接对 53.0% 套第 1 周的 Wilson 区间(「30 次挂了 2 次,失败率在什么范围?」),得到 [49.9%, 56.1%],连 45% 都罩不住:Wilson 只管抽样误差,管不了 Judge 的系统误差。
6区间:误差有三个来源
\(\hat\theta\) 里有三个估出来的量,各带抽样误差:\(p_\text{obs}\)(来自 n 条未标注报告),TPR(来自标注集里 \(m_1\) = 120 条有效报告),TNR(来自 \(m_0\) = 80 条误报)。三者来自互不重叠的样本,相互独立。
delta 法
对 \(\hat\theta\) 求三个偏导:\(\partial\theta/\partial p = 1/J\),\(\partial\theta/\partial\text{TPR} = -\theta/J\),\(\partial\theta/\partial\text{TNR} = (1-\theta)/J\)。一阶近似:
$$\operatorname{Var}(\hat\theta) \approx \frac{1}{J^2}\left[\frac{p(1-p)}{n} + \theta^2\frac{\text{TPR}(1-\text{TPR})}{m_1} + (1-\theta)^2\frac{\text{TNR}(1-\text{TNR})}{m_0}\right]$$例子里三项分别占方差的 26%、17%、57%,95% 区间 [38.4%, 55.9%],宽度差不多是 Wilson 的 3 倍,这才是诚实的宽度。方差的大头来自 TNR,三个原因叠在一起:误报只标了 80 条(有效报告 120 条);TNR(1 − TNR) = 0.16 大于 TPR(1 − TPR) = 0.09;\((1-\theta)^2\) 的权重是 \(\theta^2\) 的约 1.26 倍。按每多一条样本算,多跑未标注报告的边际收益最小(但它不用人工,便宜):未标注从 1000 翻到 2000,方差约降 13%;有效报告的标注从 120 翻到 240,只降约 8.5%。本例最划算的是扩大随机标注量,让误报那一类变多,或者按错误分析找到的误报类别分层抽样。注意:标注前不知道哪些是误报,如果按 Judge 的判定去多抽"Judge 判 FAIL"的样本,就是按 Judge 输出分层,算 TPR、TNR 时要按各层的抽样率加权,否则有偏。
bootstrap
不想推公式就用 bootstrap(第 1 周「新 prompt 从 82% 涨到 86%,是真的变好了吗?」那一章讲过重抽的原则):标注集整体重抽一次,算 TPR、TNR;未标注集也重抽一次,算 \(p_\text{obs}\);代入公式、截断到 [0, 1];重复 2 万次取 2.5 和 97.5 百分位。两边都重抽得到 [37.6%, 55.4%]。
开源库 judgy(ai-evals-course/judgy,commit e6cd4fe,src/judgy/core.py 第 120–153 行)的 bootstrap 只重抽标注集,\(p_\text{obs}\) 当常数:照这个做法得到 [38.6%, 54.2%],judgy 本身跑出 [38.7%, 54.2%](差别来自随机数)。未标注 1000 条时覆盖率约 90%(名义 95%),100 条时只有 64%:未标注集越小,偏窄越严重。重复 2000 次的模拟(真实有效率 45%,标注 120 + 80 条):
| 未标注条数 | 直接 Wilson | delta 法 | bootstrap(两边重抽) | bootstrap(只重抽标注集) |
|---|---|---|---|---|
| 1000 | 1.1% | 95.0% | 93.8% | 89.7% |
| 100 | 71.9% | 94.6% | 94.0% | 64.1% |
表里是"95% 区间罩住真实有效率"的比例,2000 次模拟的蒙特卡洛误差在覆盖率 95% 附近约 ±1 个百分点,在 64% 附近约 ±2.1 个百分点。
7截断:算出负数怎么办
\(\hat\theta\) 落在 [0, 1] 的条件是 \(1-\text{TNR} \le p_\text{obs} \le \text{TPR}\)。\(p_\text{obs}\) 比漏判率还低(Judge 判的 PASS 比"全是误报时漏判出来的"还少),就会算出负数;比 TPR 还高,就会超过 1。真实有效率接近 0 或 1 时,光是抽样波动就能把 \(p_\text{obs}\) 推出这个范围。
例:某个坏掉的版本,200 条报告 Judge 只判了 30 条 PASS,\(p_\text{obs}\) = 15% < 漏判率 20%。\(\hat\theta = (0.15 + 0.8 - 1)/0.7 = -7.1\%\),截断为 0。delta 区间 [0.0%, 8.0%],bootstrap [0.0%, 6.3%]。
截断有代价:它把所有负的估计都抬到 0,均值就偏高了。模拟 2000 次(未标注 200 条):
| 真实有效率 | 点估计 < 0 的比例 | 未截断的均值 | 截断后的均值 |
|---|---|---|---|
| 2% | 39.2% | 1.4% | 3.9% |
| 10% | 9.0% | 9.9% | 10.3% |
▶演示 1:混淆矩阵、κ 和有效率
改四个格子的计数,指标实时重算。下面的滑块保持 Judge 的 TPR、TNR 不变,只改真实有效率,看准确率和 κ 怎么跟着变。
准确率 κ TPR、TNR(不随 π 变)
▶演示 2:Rogan-Gladen 校正和区间
TPR、TNR 和标注条数取演示 1 的混淆矩阵。拖动 Judge 判 PASS 的比例和未标注报告条数;bootstrap 每次 2 万个重抽样本、固定种子;和正文(Python 跑的)用的随机数不同,区间端点会差零点几个百分点。
横轴真实有效率 θ,纵轴 Judge 通过率。直线 p = TPR·θ + (1 − TNR)(1 − θ);从 pobs 水平画过去,交点就是 θ̂。
三种区间对比;灰色竖线是不校正的 pobs。
▶演示 3:阈值和成本
双正态模型:误报的 Judge 分数 ~ N(0, 1),有效报告 ~ N(d′, 1),分数 ≥ 阈值判 PASS。成本以较便宜的那种错为 1 个单位。
误报的分数分布(右侧阴影 = 漏判) 有效报告(左侧阴影 = 误杀) 期望成本,圆点 = 最优阈值
✎练习
8面试要点与代码
一句话讲清楚
Judge 是有误差的测量仪器。先人工标一批(两类都要够),算 TPR、TNR,不只看准确率和 κ,因为 κ 会随有效率变;按漏判、误杀的代价选阈值;再用 Rogan-Gladen 公式 θ̂ = (pobs + TNR − 1)/(TPR + TNR − 1) 把 Judge 通过率校正成真实通过率,区间同时算标注集和评测集的抽样误差,越界就截断并留原值。
追问准备
- 标多少条?看 delta 公式里哪一项最大。本例 TNR 那一项占 57%,要扩大随机标注量让误报变多(或按误报类别分层抽样;按 Judge 判定分层要加权)。两类各几十条是下限,少于这个 TPR、TNR 本身的 Wilson 区间就很宽。
- 换了 Judge 的 prompt 呢?TPR、TNR 作废,在同一批标注数据上重新跑。两个 Judge 版本对比是配对数据,用第 1 周的 McNemar 检验。
- J 很小会怎样?分母接近 0,区间爆炸;J ≤ 0 时公式无意义,judgy 直接报错。先改 Judge,不要硬校正。
- 能不能只用 Judge 判过的样本加少量人工标注做得更好?有,比如 prediction-powered inference(PPI)一类方法;Chen et al. 2026 比较了它和 Rogan-Gladen 的渐近方差。本章没有实测,只做指引。
核心代码(Python,只依赖 numpy)
def rogan_gladen(p_obs, tpr, tnr, clip=True):
j = tpr + tnr - 1
if j <= 0:
raise ValueError("TPR + TNR - 1 <= 0:Judge 不比随机好,不能校正")
theta = (p_obs + tnr - 1) / j
return min(max(theta, 0.0), 1.0) if clip else theta
def rg_delta_ci(p_obs, n, m_pass, m_fail, tpr, tnr, z=1.959964):
j = tpr + tnr - 1
theta = (p_obs + tnr - 1) / j
var = (p_obs * (1 - p_obs) / n
+ theta ** 2 * tpr * (1 - tpr) / m_pass
+ (1 - theta) ** 2 * tnr * (1 - tnr) / m_fail) / j ** 2
half = z * var ** 0.5
return theta, max(theta - half, 0.0), min(theta + half, 1.0)
print(rogan_gladen(0.53, 0.9, 0.8)) # 0.4714285714285714
print(rg_delta_ci(0.53, 1000, 120, 80, 0.9, 0.8)) # (0.4714..., 0.38401..., 0.55883...)
完整代码(混淆矩阵、κ、两种 bootstrap、成本和阈值、覆盖率模拟)和 14 个单元测试在学习目录 week06_Judge与错误分析/code/judge_calibration/,Python 3.9 + numpy 1.22 跑通;κ 和 scikit-learn 1.1.1 对照一致,点估计和 judgy 一致。
公式显示依赖 KaTeX(CDN),断网时公式会显示成原始 LaTeX 源码,交互部分不受影响。