第 28 章
第 6 周:Bug 报告写得好不好,打 1~5 分还是判 pass / fail?
二元 rubric 与标注一致性:1~5 分的三个问题(中间档没有定义、相关高不等于对得上、均分把严重问题平均掉),把"合格"拆成几条可以核对的是非题,怎么写一条标准;两个人独立标注后怎么量一致性:总一致率、通过类和不通过类一致率、随机期望一致率、Wilson 区间;对不上时的分歧会、改 rubric、换一批新数据,以及 criteria drift。30 份合成 Bug 报告的标注实验,一段讲解视频,一个标注对照台和一个 2×2 一致性计算器。
上一讲「测试 Agent 为什么漏掉 Bug?从读 trace 到失败分类」从 trace 里把失败归了类,知道 BugHunt 的测试 Agent 常在哪里出错。下一步是批量判断:这一轮交上来的 30 份 Bug 报告,哪些合格、哪些不合格。以后这件事要交给 LLM Judge 自动判,但在那之前得先回答一个问题:人自己判得一致吗? 两个人看同一份报告结论都不一样的话,Judge 就没有可以对齐的标准。
这一章讲两件事:评价标准怎么写(打 1~5 分,还是拆成几条 pass / fail),以及两个人的标注能不能对上、怎么量、对不上怎么办。
讲解视频
互动演示
两个演示。标注对照台:30 份合成 Bug 报告,A、B 两人的独立标注;选一条标准,看两人在哪几份上对不上,各项一致率实时计算;选 Likert,拖动"几分算合格"的阈值,看结论怎么变。2×2 一致性计算器:改四个格子或点预设,看总一致率很高时不通过类一致率可以有多低。页面底部有自动判分的练习。
1~5 分的三个问题
最顺手的做法是让人(或者 LLM)给每份报告打 1~5 分,也就是 Likert 量表。看起来比 pass / fail 信息多,实际用起来有三个问题。
中间的分数没有定义。 Anthropic 官方文档 Define success criteria and build evaluations 里的 Likert 打分示例,prompt 只定义了两端(“1: Not at all …"、“5: Perfectly …"),2、3、4 由打分者自己理解。Hamel Husain 在 Using LLM-as-a-Judge For Evaluation: A Complete Guide 里说得更直接:“What makes something a 3 versus a 4? Nobody knows”。
相关高,不等于对得上。 8 份报告,A 打 [5, 4, 4, 3, 2, 4, 3, 5],B 每份都恰好低 1 分:
| 数值 | |
|---|---|
| 皮尔逊相关 | 1.000 |
| 分数完全相同 | 0 / 8 |
| 约定 ≥ 4 分算合格:A 判合格 | 5 份 |
| 约定 ≥ 4 分算合格:B 判合格 | 2 份 |
相关系数只看两组分数是否同涨同落,对整体平移不敏感。评测要的是"两人给出同一个结论”,这得直接数结论相同的比例。
均分会把严重问题平均掉。 同一批 10 个任务,两个版本:
| 版本 | 分数 | 均分 | 1 分(完全不可用) |
|---|---|---|---|
| 旧版 | 3, 3, 4, 3, 3, 4, 3, 3, 4, 3 | 3.3 | 0 份 |
| 新版 | 5, 5, 4, 5, 1, 4, 5, 1, 4, 5 | 3.9 | 2 份 |
“均分从 3.3 涨到 3.9"说不出哪里变好、哪里变坏,新版多出来的两份不可用的报告被平均掉了。
Hamel Husain 和 Shreya Shankar 的 evals FAQ 还列了两条:Likert 需要更大的样本才能检测出统计上的差异;打分者会往中间的分数靠,以回避难下的判断。他们的建议是先用二元标签,弄清楚"坏"长什么样;数值打分是进阶做法,通常用不上。
二元 rubric:把"好不好"拆成几道是非题
“这份 Bug 报告合格吗"太大,拆成几条各自能判对错的标准,每条只回答 1(通过)或 0(不通过)。BugHunt 的第一版 rubric:
| 编号 | 标准 | 什么算通过 |
|---|---|---|
| R1 | 复现步骤 | 从明确的起点(URL、账号、初始数据)出发,按步骤能走到失败 |
| R2 | 期望与实际 | 分别写了期望结果和实际结果,实际结果是观察到的现象,不是推测 |
| R3 | 证据充分 | 附了能支撑结论的证据(第一版,故意写得含糊) |
| R4 | 不是环境问题 | 失败不是测试环境故障(网络中断、注入的 5xx、测试数据损坏)造成的 |
R4 就是第 5 周「环境坏了,测试 Agent 会把它报成 Bug 吗?」那一章担心的误报。
整份报告四条全过才算合格,不给四条加权求和。R4 不过说明报告把环境故障当成了 Bug,前三条写得再好也是误报;加权求和会让它拿到 75 分。
二元不等于丢掉"差多少"的信息。想看渐进的改进,就数子项:FAQ 里的例子是"5 个应包含的事实里包含了 4 个”,每一个仍是是非题。拆成逐条判断也是用户自己提出来的:Who Validates the Validators?(下文详述)的用户研究里,只给一个整体的赞 / 踩时,有几位参与者说很难同时记住所有标准,想按标准分别打分。
Likert 也不是不能用。Anthropic 那篇文档的建议是让 LLM 的评价"具体、可统计”,例子里既有只输出 correct / incorrect,也有按 1~5 分打,理由是纯定性的评价难以快速、大规模地评估。真要用 Likert,就给每一档都写定义,在看数据之前定好几分算合格,汇报时给分布,不只给均分。
怎么写一条标准
- 一条只问一件事。 “步骤完整且证据充分"是两道题,一道过一道不过时,标注者只能猜。
- 写可以核对的条件,不写形容词。 “充分”、“清楚”、“专业”,每个人理解不同。
- 写清楚看哪里。 判 R4 要看 trace 里这一轮有没有注入故障,而不是看报告自己怎么说。
- 给边界例子。 标注时争起来的那几条,就是最该写进 rubric 的例子。
- 说清楚"不适用"怎么算。 “不适用"是藏起来的第三个值:算通过,还是不计入分母,要写死,否则两个人各按各的处理。
- 标准从错误分析来。 上一讲归纳出的失败类别,每个高频类别对应一条标准;还没在数据里见过的问题,不急着写。
R3 的改写:
| R3 证据 | |
|---|---|
| 改写前 | 附了能支撑结论的证据 |
| 改写后 | 附了至少一条来自本次 trace 的机器证据(请求与响应状态码、控制台报错、DOM 断言结果之一),并且证据里的 URL 和步骤与报告描述一致;只有截图或录屏不算 |
改写后的版本还有一个好处:条件写到这么具体,其中一部分可以直接用代码检查(有没有附请求记录、URL 是否出现在 trace 里),剩下需要判断的部分再交给 Judge。
标注一致性:先看两个人能不能对上
同一批报告,A、B 两人各自独立标注。在一条标准上,结果排成 2×2 表:
| B 判通过 | B 判不通过 | |
|---|---|---|
| A 判通过 | a | b |
| A 判不通过 | c | d |
三个比例:
$$ \text{总一致率}=\frac{a+d}{N},\qquad \text{通过类一致率}=\frac{2a}{2a+b+c},\qquad \text{不通过类一致率}=\frac{2d}{2d+b+c} $$后两个叫特定类别一致率(proportions of specific agreement)。Cicchetti 和 Feinstein 在 High agreement but low kappa: II. Resolving the paradoxes(J Clin Epidemiol, 1990, doi:10.1016/0895-4356(90)90159-M )里建议用这样两个指标分别量化"判阳性"和"判阴性"上的一致,补总一致率的不足。分母 \(2a+b+c\) 是两人判"通过"的次数之和,所以通过类一致率衡量的是:一个人判通过时,另一个人也判通过的程度。不通过类同理。
为什么要分开看。 100 份报告,两人都判通过 85、各自单方面判不通过 5、都判不通过 5:
| 指标 | 值 |
|---|---|
| 总一致率 | 90.0% |
| 通过类一致率 | 170 / 180 = 94.4% |
| 不通过类一致率 | 10 / 20 = 50.0% |
| 随机期望一致率 | 0.9 × 0.9 + 0.1 × 0.1 = 82.0% |
总一致率 90% 主要由"都判通过"撑起来;一个人说"这份不合格"时,另一个人只有一半的时候同意。而 Bug 报告评测最关心的就是不合格的那些。
随机也能对上不少。 随机期望一致率是假设两人各按自己的通过率独立随机贴标签时的期望一致率:\(p_A p_B + (1-p_A)(1-p_B)\),\(p_A\)、\(p_B\) 是两人各自判通过的比例。上面的例子里两人都判 90% 通过,瞎猜也能对上 82%,实测的 90% 只高出 8 个百分点。两人的通过率越接近、且越偏向同一类(比如都判 90% 通过),这个基线越高;反过来,一人判 90% 通过、另一人判 10% 通过时,基线只有 0.9 × 0.1 + 0.1 × 0.9 = 18%。把实测一致率和随机期望合成一个数,就是 Cohen’s κ;下一讲「Judge 校准」会用它比较 Judge 和人,这一章先记住"要和随机基线比”。
样本量。 一致率也是一个比例,可以用第 1 周 Wilson 区间那一章的方法。30 份里对上 24 份(80%),95% 区间是 [62.7%, 90.5%],很宽。这个区间的前提是每份报告的两个标签和其他报告互相独立;同一个 Agent 运行生成的多份报告要按运行聚合,道理和「跑了 300 次,就是 300 个样本吗?」那一章一样。
动手实验:30 份合成 Bug 报告
代码在学习目录的 week06_Judge与错误分析/code/binary_rubric/,只用标准库:
| 文件 | 内容 |
|---|---|
labels.py | 30 份合成 Bug 报告,A、B 两人在 R1 |
agreement.py | 2×2 表、总一致率、通过类 / 不通过类一致率、随机期望一致率、Wilson 区间、Likert 的完全相同率和皮尔逊相关 |
analyze.py | 打印下面的结果 |
test_agreement.py | 19 个单元测试,期望值都是手算的 |
数据是合成的:报告和两人的标注都是为了演示计算方法手工构造的,R3 的分歧也是有意放进去的。所以下面的数字只说明"怎么算、怎么读”,不能当成"二元 rubric 一定比 Likert 一致"的证据。皮尔逊相关和 scipy.stats.pearsonr 核对过(0.8616)。
def pass_agreement(t: Table) -> float:
"""通过类一致率 2a / (2a + b + c):一人判通过时,另一人也判通过的程度。"""
denom = 2 * t.a + t.b + t.c
return 2 * t.a / denom if denom else math.nan
def fail_agreement(t: Table) -> float:
"""不通过类一致率 2d / (2d + b + c):一人判不通过时,另一人也判不通过的程度。"""
denom = 2 * t.d + t.b + t.c
return 2 * t.d / denom if denom else math.nan
def chance_agreement(t: Table) -> float:
"""两人各按自己的通过率独立随机贴标签,期望能有多少一致。"""
pa = (t.a + t.b) / t.n
pb = (t.a + t.c) / t.n
return pa * pb + (1 - pa) * (1 - pb)
$ python3 analyze.py
合成数据:30 份 Bug 报告,两位标注者独立标注
一、二元 rubric:逐条标准的一致性(a=都通过 b=A过B不过 c=A不过B过 d=都不通过)
标准 a b c d 总一致率 Wilson 95% 区间 通过类 不通过类 随机期望
R1 复现步骤 27 1 0 2 96.7% [ 83.3%, 99.4%] 98.2% 80.0% 84.7%
R2 期望与实际 28 1 0 1 96.7% [ 83.3%, 99.4%] 98.2% 66.7% 90.4%
R3 证据充分 24 5 1 0 80.0% [ 62.7%, 90.5%] 88.9% 0.0% 81.1%
R4 不是环境问题 26 1 0 3 96.7% [ 83.3%, 99.4%] 98.1% 85.7% 79.3%
整份(四条全过) 16 7 1 6 73.3% [ 55.6%, 85.8%] 80.0% 60.0% 53.6%
整份通过率:A 23/30 = 76.7%,B 17/30 = 56.7%
二、分歧清单(讨论会的输入)
R1 复现步骤 R11 邮编字段接受字母:A=1 B=0 (步骤从"登录后进入地址页"开始,没写账号)
R2 期望与实际 R04 修改头像后刷新丢失:A=1 B=0 (实际结果写的是"看起来没保存";附 PUT 200 和 GET 旧值)
R3 证据充分 R03 搜索框输入 emoji 后页面白屏:A=1 B=0 (步骤完整;只有一张截图)
R3 证据充分 R08 优惠券叠加超过上限:A=1 B=0 (步骤完整;只有一张截图)
R3 证据充分 R16 夜间模式按钮对比度低:A=1 B=0 (步骤完整;只有一张截图)
R3 证据充分 R19 首页轮播图偶尔不显示:A=1 B=0 (没有复现步骤;只有一张截图)
R3 证据充分 R24 订单详情金额小数位错误:A=1 B=0 (步骤完整;只有一张截图)
R3 证据充分 R29 批量删除确认框可被回车绕过:A=0 B=1 (附了 trace 编号,但 trace 里的页面和报告写的不是同一个)
R4 不是环境问题 R20 支付回调返回 502:A=1 B=0 (网关 502,trace 里这一轮没有注入故障,重试 3 次都是 502)
R3 改写后:附了至少一条来自本次 trace 的机器证据(请求与响应状态码、控制台报错、DOM 断言结果之一),并且证据里的 URL 和步骤与报告描述一致;只有截图或录屏不算
三、同一批报告的 Likert 1~5 分
均分:A 3.47,B 3.13;皮尔逊相关 0.862
分数完全相同 53.3%,相差不超过 1 分 100.0%
≥3 分算通过:A 通过 24,B 通过 22,二值化后一致率 93.3%(a=22 b=2 c=0 d=6)
≥4 分算通过:A 通过 17,B 通过 14,二值化后一致率 76.7%(a=12 b=5 c=2 d=11)
小例子:B 每份都恰好比 A 低 1 分,A=[5, 4, 4, 3, 2, 4, 3, 5]
相关 1.000,分数完全相同 0.0%,≥4 分算通过:A 5/8,B 2/8
四、偏斜的例子:100 条里两人都判通过 85、各自单方面判不通过 5、都判不通过 5
总一致率 90.0%,随机期望 82.0%,通过类 94.4%,不通过类 50.0%
五、均分会藏住什么:同一批 10 个任务,两个版本的 Likert 分数
旧版 [3, 3, 4, 3, 3, 4, 3, 3, 4, 3]:均分 3.3,1 分(完全不可用)0 份
新版 [5, 5, 4, 5, 1, 4, 5, 1, 4, 5]:均分 3.9,1 分(完全不可用)2 份
$ python3 -m unittest
...................
----------------------------------------------------------------------
Ran 19 tests in 0.001s
OK
怎么读这份结果:
- R3 是要改的那条。 总一致率 80%,和随机期望 81.1% 差不多(差距不到一份报告,Wilson 区间 [62.7%, 90.5%] 也包含 81.1%),等于没比瞎猜好;不通过类一致率 0%:B 判不通过的 5 份,A 一份都没判不通过。看分歧清单,5 份都是"只有一张截图”,A 认为截图够了,B 认为不够。这正是"充分"两个字没写清楚,改写后的 R3 直接回答了"截图算不算"。R29 是反方向的分歧:附了 trace,但 trace 和描述对不上,改写后的"证据与描述一致"也覆盖了它。
- R2 看着好,其实偏弱。 总一致率 96.7%,但只比随机期望高 6.2 个百分点,不通过类一致率 66.7%:失败样本太少(两人合计只判了 3 次不通过),一次分歧就很显眼。要检验两人在失败上能否对上,评测集里得有足够多的失败报告。
- 整份的总一致率比每一条都低。 四条全过才合格,任何一条的分歧都会传到整份结论上:四条合计 9 次分歧,整份有 8 次(R19 的 R3 分歧被两人都判不过的 R1 盖住了)。整份的总一致率 73.3%,A 判 23 份合格,B 只判 17 份。但随机期望也只有 53.6%,高出 19.8 个百分点,比任何单条都多;原始一致率低,主要是因为整份的通过率更接近一半,随机基线低。所以比较不同标准时,原始一致率要和各自的随机基线一起看。
- Likert 的阈值会改结论。 同一批分数,≥ 3 算合格时二值化后一致率 93.3%,≥ 4 时 76.7%。阈值要在看数据之前定好,否则可以挑一个让数字好看的。
对不上怎么办
- 独立标。 标之前不商量,看不到对方的标签,最好也看不到报告出自哪个版本的 Agent。
- 逐条标准算一致率:总一致率、通过类、不通过类都看,并和随机期望比;把分歧逐条列出来。
- 开分歧会。 一条条过分歧,区分是标准写得含糊(R3),还是有人看漏了。
- 改 rubric。 加一条规则或一个边界例子,给 rubric 记版本号,受影响的已标数据按新版重标。
- 换一批新报告再测。 讨论过的那批两人都记住了结论,在旧数据上重测会高估一致性。
evals FAQ 给的流程和前四步一致:独立标注、量一致性、讨论分歧、往 rubric 里补规则或例子,再按新 rubric 重标受影响的样本,迭代到一致率稳定在高位;分歧一直解决不了,由领域专家拍板并记下理由。第 5 步"换一批新数据重测"是本章额外的建议,FAQ 里没有这一条。FAQ 同时建议,中小团队优先指定一位领域专家作为质量标准的最终裁定者(他们叫 benevolent dictator),只有确实需要时才上多人标注。
标准会边标边变。 Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences(Shankar, Zamfirescu-Pereira, Hartmann, Parameswaran, Arawjo;UIST 2024, arXiv 2404.12272 )把这个现象叫 criteria drift:要有标准才能给输出打分,可给输出打分的过程又在帮人定义标准。论文在 9 位从业者的用户研究里观察到两种漂移:看到新类型的坏输出后加新标准,以及为了贴合实际输出重新解释已有标准;还有一位参与者两次为了和前面的打分保持一致而判了"坏"。所以 rubric 不可能在看数据之前一次写好,FAQ 也建议先做错误分析、再从中整理 rubric,并定期回头修订。
测开视角
- rubric 就是验收标准。 测开写用例时,“预期结果"要写成可以判定的断言,不写"页面显示正常”。rubric 的每一条就是一个断言,“证据充分"这种写法和"页面显示正常"是同一个毛病。
- rubric 要版本管理。 改了 R3,之前用旧 R3 标的数据和用新 R3 标的数据不能直接比;之后用 Judge 自动判时,Judge 的 prompt 也要跟着改,同一个版本号贯穿标注、Judge 和报表。
- 评测集要有足够多的失败样本。 失败少的时候,总一致率和随机基线都很高,不通过类一致率的分母很小,量不准。按失败类别分层抽样,比随机抽更有用。
- 人与人的一致性是 Judge 的参照线。 人和人在某条标准上都对不上时,先改标准,别急着调 Judge 的 prompt。人标好的数据就是下一讲校准 Judge 的参照。
常见错误说法
- “1~5 分比 pass / fail 信息多,所以更好”:多出来的信息如果每个人理解不同,就是噪声。要看"差多少”,就数通过了几条子项。
- “两人分数相关 0.9,标得很一致”:相关不等于一致。B 每份都比 A 低 1 分时相关是 1,分数却一份都对不上。
- “总一致率 90%,标注没问题”:失败少时总一致率主要来自"都判通过",要分开看不通过类一致率,并和随机期望一致率比。
- “rubric 应该在看数据之前一次写好”:标准会在标注中变化(criteria drift),要从错误分析出发、记版本、迭代。
- “讨论完分歧,在同一批数据上重标,一致率涨了,说明 rubric 改好了”:讨论过的数据大家都记得结论,要换一批新数据独立标。
下一讲:Judge 校准。拿人标好的标签当参照,算 Judge 的混淆矩阵、TPR / TNR 和 Cohen’s κ,再用 Judge 的判定反推真实通过率。