📑 本页目录(点开跳转)
10 · 标注一致性怎么量
⏱ 76 分钟 | ⭐ 同意率 90% 的那批标注,Kappa 可能只有 0.20
🎯 一句话
一致性不是「两个人标得一样的比例」,是「他们比闭着眼睛乱按好了多少」。 上一章说「指南写不清楚,标注员就替你定义了问题」—— 这一章给你测出来的办法:一个数字,告诉你指南到底清不清楚。 而第一件要做的事,是把「同意率」这个人人都在用的指标扔掉。
🎲 一、为什么不能用同意率:一个 90% 对 0.20 的真实计算
某客服工单标注,两个人独立判断「这条是不是投诉」,一共 1,000 条:
| B 标:非投诉 | B 标:投诉 | 行合计 | |
|---|---|---|---|
| A 标:非投诉 | 883 | 50 | 933 |
| A 标:投诉 | 50 | 17 | 67 |
| 列合计 | 933 | 67 | 1000 |
同意率(两人标得一样的比例)
Po = (883 + 17) / 1000 = 0.900 ← 90%!验收报告写得很好看
但先看两个人各自标了多少条"投诉":
A 标了 67 条 B 标了 67 条
⭐ 而两人【同时】认为是投诉的,只有 17 条
也就是说:一共 117 条被至少一个人认为是投诉,
两人达成共识的只有 17 条 —— 共识率 14.5%
偶然一致率 Pe —— 如果两人各自闭着眼睛、按各自的比例(都是 6.7% 标投诉)随机乱按:
Pe = P(两人都随机标非投诉) + P(两人都随机标投诉)
= 0.933 × 0.933 + 0.067 × 0.067
= 0.870489 + 0.004489
= 0.874978 ⭐ 乱按都能有 87.5% 的同意率!
Cohen's Kappa = (Po − Pe) / (1 − Pe)
= (0.900 − 0.874978) / (1 − 0.874978)
= 0.025022 / 0.125022
= 0.2001 💀
💀 这就是「同意率 90%」这句话的真相: 那 90% 里面,87.5 个百分点是白送的 —— 来自「两人都说不是投诉」这件几乎不需要判断的事。 真正靠标注水平挣来的,只有 2.5 个百分点, 而理论上最多能挣 12.5 个百分点。2.5 / 12.5 = 0.20,这就是 Kappa。
拆开正负两类看,更直白:
| 指标 | 数值 | 含义 |
|---|---|---|
| 整体同意率 | 0.900 | 好看,但没信息量 |
| 负类专属一致率(都标"非投诉") | 0.9464 | 简单,不需要判断 |
| 正类专属一致率(都标"投诉") | 0.2537 💀 | 这才是真实水平 |
⭐ 一句话记法: 同意率问的是「你们多常一致」,Kappa 问的是「你们比按同样比例乱按好了多少」。 类别越不平衡,「乱按」的基线越高,同意率就越没有意义 —— 95% 的样本都是负类时,两人各自乱按也能拿到 90% 的同意率。
import numpy as np
def cohen_kappa_2x2(a, b, c, d):
"""a=两人都标负, b=A负B正, c=A正B负, d=两人都标正"""
n = a + b + c + d
po = (a + d) / n
p1 = (a + b) / n # A 标负类的边际比例
p2 = (a + c) / n # B 标负类的边际比例
pe = p1 * p2 + (1 - p1) * (1 - p2) # ⭐ 偶然一致率
kappa = (po - pe) / (1 - pe)
return {"同意率Po": round(po, 4), "偶然一致Pe": round(pe, 6),
"Kappa": round(kappa, 4),
# ⭐ 不平衡数据必须同时看正类专属一致率,它才反映真实水平
"正类专属一致率": round(2 * d / (2 * d + b + c), 4),
"负类专属一致率": round(2 * a / (2 * a + b + c), 4)}
print(cohen_kappa_2x2(883, 50, 50, 17))
# {'同意率Po': 0.9, '偶然一致Pe': 0.874978, 'Kappa': 0.2001,
# '正类专属一致率': 0.2537, '负类专属一致率': 0.9464}
# 用 sklearn 交叉验证同一个数字
from sklearn.metrics import cohen_kappa_score
y1 = np.array([0] * 883 + [0] * 50 + [1] * 50 + [1] * 17)
y2 = np.array([0] * 883 + [1] * 50 + [0] * 50 + [1] * 17)
print(round(float(cohen_kappa_score(y1, y2)), 4)) # 0.2001 ⭐ 完全一致
📊 二、四个指标,各管什么场景
| 指标 | 人数 | 允许缺失 | 标签类型 | 什么时候用它 |
|---|---|---|---|---|
| Cohen's Kappa | 恰好 2 人 | ❌ 两人必须标同一批全部样本 | 名义 | 双标重叠的最常见场景 |
| Fleiss' Kappa | 多人(每样本固定 k 个评分者) | ❌ 每条样本的评分数必须相同 | 名义 | ⭐ 众包:不要求是同一批人,每条随机分给 5 个人也行 |
| Krippendorff's α | 任意 | ✅ 允许缺失、允许每条评分数不同 | 名义/有序/区间/比例 | ⭐⭐ 最通用;真实标注平台的数据几乎总是不规整的 |
| 加权 Kappa | 2 人 | ❌ | 有序 | 1~5 分、严重度 P0~P3、风险等级 —— 必须用 |
怎么选:三个问题就够
───────────────────────────────────────────────
标签是有序的吗(分数 / 等级 / 严重度)?
└─ 是 → 加权 Kappa(二次权重)或 Krippendorff α(ordinal)
每条样本的标注人数一样吗?有没有缺失?
├─ 不一样 / 有缺失 → Krippendorff's α ⭐ 别硬凑成 Fleiss
└─ 一样,且恰好 2 人 → Cohen's Kappa
一样,多于 2 人 → Fleiss' Kappa
⚠️ 常见错误:多人场景下两两算 Cohen 再取平均
—— 这不等于 Fleiss,在人数不齐时会系统性偏高
⭐ Fleiss 和 Cohen 的关键区别不是人数,是「谁标的」: Cohen 要求固定的两个人标完全部样本(它会分别估计两人各自的偏好); Fleiss 只要求每条样本被 k 个人标,这 k 个人是谁无所谓。 众包平台的数据天然是 Fleiss 形态,用 Cohen 去套要么算不出来,要么算的是别的东西。
🔢 三、实跑:同一批数据,指标能差多少
① 指南清楚 vs 指南有歧义(Fleiss,20 个样本 × 3 类 × 5 人)
| 场景 | 观测一致 P̄ | 偶然一致 Pe | Fleiss' Kappa |
|---|---|---|---|
| 指南清楚(多数样本 5 人全票或 4:1) | 0.840 | 0.342 | 0.757 ✅ |
| 指南有歧义(多数样本 2:2:1 / 3:1:1) | 0.255 | 0.349 | −0.145 💀 |
💀 Kappa 可以是负数,而且负数有明确含义: 比随机还差。 这几乎从不是"标注员太笨",而是 一部分人在按某种系统性的反向理解在标 —— 比如把"是否包含广告"读成了"是否是好评"。 看到负 Kappa,第一件事是去看混淆矩阵有没有明显的对角线错位,不是去换人。
② 有序标签上,加不加权差 0.45(同一份 400 条 1~5 分打分数据,两人)
| 指标 | 数值 | 说明 |
|---|---|---|
| 同意率 | 0.5175 | 只有完全相同才算对 |
| 无权 Kappa | 0.3964 💀 | 「差一档」和「差四档」被当成同样的错 |
| 线性加权 Kappa | 0.6747 | 惩罚与差距成正比 |
| 二次加权 Kappa | 0.8510 ⭐ | 差一档几乎不罚,差三档罚很重 |
⚠️ 有序标签上用无权 Kappa,会把一份很好的标注判成"一般": 这份数据里两人绝大多数分歧只差 1 档 —— 对「情感强度 1~5 分」这类任务, 差 1 档基本无害,差 3 档才是真问题。 无权 Kappa 给 0.3964(看着该返工),二次加权给 0.8510(其实相当好)。 ⭐ 但反过来也要小心:二次加权容易把中等质量的标注美化成优秀, 报告里要写清楚用的是哪种权重 —— 只写「Kappa = 0.85」是一句没有信息量的话。
③ Krippendorff's α(12 个单元,每单元 2~3 人,人数不齐)
| 数据 | α |
|---|---|
| 大多数单元内部一致 | 0.7071 |
| 同样规模但标得很乱 | −0.2247 |
import numpy as np
from collections import Counter
from sklearn.metrics import cohen_kappa_score
def fleiss_kappa(mat):
"""mat: N×k 矩阵,mat[i][j] = 第 i 条样本被标成第 j 类的人数
⚠️ 要求每行人数相同"""
mat = np.asarray(mat, float)
N, _ = mat.shape
n_r = mat.sum(1)[0] # 每条样本的评分人数
p_j = mat.sum(0) / (N * n_r) # 各类别的整体占比
P_i = (np.square(mat).sum(1) - n_r) / (n_r * (n_r - 1))
P_bar, P_e = P_i.mean(), np.square(p_j).sum()
return (P_bar - P_e) / (1 - P_e) # ⭐ 和 Cohen 同一个公式骨架
def krippendorff_nominal(units):
"""units: [[标注1, 标注2, ...], ...] ⭐ 允许每个单元人数不同、允许缺失"""
units = [u for u in units if len(u) >= 2]
n_total = sum(len(u) for u in units)
Do = 0.0
for u in units:
m, cnt = len(u), Counter(u)
Do += (m * (m - 1) - sum(v * (v - 1) for v in cnt.values())) / (m - 1)
Do /= n_total
allc = Counter(v for u in units for v in u)
De = (n_total ** 2 - sum(v * v for v in allc.values())) / (n_total - 1) / n_total
return 1 - Do / De # ⭐ 观测分歧 / 期望分歧
M = [[5,0,0],[4,1,0],[5,0,0],[0,5,0],[1,4,0],[0,4,1],[0,0,5],[0,1,4],[5,0,0],[4,0,1],
[0,5,0],[0,5,0],[0,0,5],[1,0,4],[5,0,0],[0,4,1],[0,0,5],[4,1,0],[0,5,0],[5,0,0]]
print(round(fleiss_kappa(M), 3)) # 0.757
U = [["a","a","a"],["a","a"],["b","b","b"],["b","a"],["c","c"],["c","c","c"],
["a","a","b"],["b","b"],["c","b"],["a","a","a"],["b","b","b"],["c","c"]]
print(round(krippendorff_nominal(U), 4)) # 0.7071
rng = np.random.default_rng(3) # 有序标签演示
t = rng.integers(1, 6, 400)
r1 = np.clip(t + rng.choice([-1, 0, 0, 0, 1], 400), 1, 5)
r2 = np.clip(t + rng.choice([-1, 0, 0, 0, 1], 400), 1, 5)
for w in (None, "linear", "quadratic"):
print(w, round(float(cohen_kappa_score(r1, r2, weights=w)), 4))
# None 0.3964 / linear 0.6747 / quadratic 0.851 ⭐ 有序标签必须加权
🛑 读到这里可以停 —— 前半章讲完了(约 26 分钟)。 后半章还有:阈值:多少算够 · Kappa 自己的三个坑 · 一致性低了怎么定位:看分歧矩阵,不看总分 · 事故复盘:评测集只有一个人标,结论被标反了 回来的时候不用重读,直接从下一节接着看就行。
📏 四、阈值:多少算够
学术上的经验分档(Landis & Koch):
| Kappa | 通常的说法 |
|---|---|
| < 0 | 比随机还差 💀 |
| 0.00 ~ 0.20 | 几乎没有一致性 |
| 0.21 ~ 0.40 | 微弱 |
| 0.41 ~ 0.60 | 中等 |
| 0.61 ~ 0.80 | 较强 |
| 0.81 ~ 1.00 | 极强 |
但工程上真正要记的是按用途分的这一张:
| 用途 | 建议下限 | 为什么 |
|---|---|---|
| 训练集(大规模、容忍噪声) | ≥ 0.60 | 低于此,标签噪声会开始吃掉模型的收益(第 11 章) |
| 评测集 | ≥ 0.80 ⭐⭐ | 评测集的一致性就是你能测出的性能上限 |
| 高风险场景(医疗 / 风控 / 内容安全) | ≥ 0.80 且必须有仲裁 | 错一条的代价不对称 |
| 主观任务(创意质量、情感强度) | 0.5 也可能是天花板 | 这时该改用加权 Kappa 或换成偏好对比 |
⭐⭐ 这一章最该记住的一句话: 人类之间的一致性,是模型性能的天花板。 两个人对同一批样本只有 0.65 的一致性,你不可能得出一个可信的「模型准确率 95%」的结论 —— 那 95% 里必然有一部分是在拟合标注噪声。 所以:先看评测集的一致性,再看模型在评测集上的分数。顺序反了,后面全是幻觉。
⚠️ 阈值必须按类别看,不能只看总体。 总体 0.72 完全可能是 「28 个类都在 0.85 以上,2 个类在 0.20 以下」—— 见第六节。
🪤 五、Kappa 自己的三个坑
① Kappa 悖论:同样的同意率,Kappa 能差 5 倍(实跑)
| 场景 | 混淆矩阵 | Po | Pe | Kappa |
|---|---|---|---|---|
| 类别均衡 | [[425,75],[75,425]] |
0.850 | 0.5000 | 0.700 ✅ |
| 类别不均衡 | [[830,75],[75,20]] |
0.850 | 0.8280 | 0.128 💀 |
⚠️ 同样的 Po = 0.850,Kappa 从 0.700 掉到 0.128 —— 分歧的绝对条数完全一样(都是 150 条)。 差别只在于类别的边际分布。这叫 prevalence effect(普遍性效应)。 ⭐ 所以在极不平衡的任务上,低 Kappa 不一定意味着标注差 —— 要把 Kappa + 正类专属一致率 + 分歧的绝对条数 三个一起报,只报一个数会误导。
② Bias effect:两人边际分布差很多时,Kappa 可能反而虚高。 Kappa 把「两人各自的标注倾向」吸收进了 Pe。 一个人标 30% 正类、另一个标 10% 正类时,Pe 被压低,Kappa 会显得比直觉好 —— 所以报 Kappa 时一定要同时报两人各自的类别分布。
③ Kappa 测的是「一致」,不是「对」。
两个标注员都把"含联系方式的好评"标成【普通评价】
→ Kappa = 1.00 完美一致
→ 但按指南,它们全都该是【广告】
💀 一致性高只说明【指南被一致地理解了】,
不说明【被正确地理解了】—— 集体误读的 Kappa 也是 1.0
⭐ 所以一致性(双标重叠)和正确性(黄金集)是两套独立的质检,
第 9 章那三种手段,一个都不能省。
🔍 六、一致性低了怎么定位:看分歧矩阵,不看总分
一个总分是没法行动的。要看分歧集中在哪一对类别上。
实跑:600 条工单,4 个类(咨询 / 投诉 / 退款 / 其他),两名标注员。
整体同意率 = 0.7683 整体 Cohen's Kappa = 0.6477
混淆矩阵(行 = A,列 = B)
咨询 投诉 退款 其他
咨询 282 0 0 51
投诉 0 22 46 0
退款 0 42 65 0
其他 0 0 0 92
按【类别对】拆解全部 139 条分歧:
投诉 ↔ 退款 88 条 占全部分歧 63.3% ⭐
咨询 ↔ 其他 51 条 占全部分歧 36.7%
其余 4 对 0 条 占全部分歧 0.0%
⭐ 这个 0.6477 不是「整体质量中等」,是「两对类别的定义坏了,其余六对完美」。 把「投诉 / 退款」合并成一类之后,同一份数据的 Kappa 直接变成 0.8624。 一个中等的总分,几乎总是少数几对类别拖的 —— 修那两对,比培训所有人有用一百倍。
⚠️ 但「合并类别」只是诊断手段,不是解决方案。 合并之后 Kappa 大涨,说明的是:在标注员眼里这两类本来就是一类。 接下来你只有两个选择 —— 要么改指南把边界写死(第 9 章的边界样例),要么真的合并它们。 留着一对标注员分不开的类别,模型也一定分不开。
import numpy as np
from sklearn.metrics import cohen_kappa_score, confusion_matrix
def disagreement_profile(a, b, labels):
"""⭐ 一致性诊断的正确起点:不是总分,是按类别对拆解的分歧"""
a, b = np.asarray(a), np.asarray(b)
cm = confusion_matrix(a, b, labels=list(range(len(labels))))
pairs = []
for i in range(len(labels)):
for j in range(i + 1, len(labels)):
n = int(cm[i, j] + cm[j, i])
if n:
pairs.append((n, labels[i], labels[j]))
pairs.sort(reverse=True)
total = sum(p[0] for p in pairs) or 1
return {"同意率": round(float((a == b).mean()), 4),
"Kappa": round(float(cohen_kappa_score(a, b)), 4),
"分歧总数": total,
# ⭐ 只要 Top-2 类别对占了 >60% 的分歧,就先修这两对,别做全员培训
"类别对": [(x, y, n, f"{n/total:.1%}") for n, x, y in pairs]}
def kappa_by_class(a, b, labels):
"""⚠️ 总体 Kappa 会把个别坏类别平均掉:逐类做 one-vs-rest"""
a, b = np.asarray(a), np.asarray(b)
return {labels[k]: round(float(cohen_kappa_score(a == k, b == k)), 4)
for k in range(len(labels))}
💀 七、事故复盘:评测集只有一个人标,结论被标反了
发生了什么
系统:短视频平台的「不适宜内容」自动审核模型
评测集:8,000 条,⚠️ 单人标注("为了省钱、也为了口径统一")
从来没有算过一致性 —— 团队认为"评测集不用算一致性"
决策:候选模型 B F1 = 0.812 现网模型 A F1 = 0.774
差 0.038,判定为显著提升 → 全量替换
上线五周后,人工复审队列的申诉量涨了 2.8 倍。复盘时才第一次去测评测集本身:
抽 1,200 条评测集样本,3 名资深审核员重标 + 1 名仲裁
Fleiss' Kappa(全部类别) 0.38 💀
「擦边内容」这一类的 Kappa 0.19 💀💀
原评测集那位标注员判为违规的比例 11.2%
三人仲裁后的违规比例 6.7%
⭐ 他系统性地比团队均值宽松了 4.5 个百分点
而模型 B 恰好也倾向宽松 —— 因为它的训练数据也大量来自同一个人。
⭐⭐ 于是这场评测测的根本不是"哪个模型更准",
而是"哪个模型更像那位标注员"。
用仲裁后的金标重新评测:
模型 A F1 = 0.781 模型 B F1 = 0.729
💀 结论完全翻转
为什么没被发现(三条)
① "评测集不用算一致性"是个流行的误解
理由听起来很合理:"一个人标口径最统一"
💀 口径统一 ≠ 口径正确。一个人标 = 把一个人的偏好固化成了金标准 ⭐
② 0.038 的差距被当成了显著提升,没做任何区间估计
实测:8,000 条评测集、正样本 546 条时,
单个模型 F1 的 bootstrap 95% 区间半宽就有 ±0.027
⭐ 0.038 只比它略大 —— 这种量级的差距必须做配对显著性检验,不能直接拍板
③ 灰度期只看模型侧指标(拦截量、拦截率),没有看申诉率
拦截量确实涨了,看起来像"新模型更能抓"
💀 而误伤和抓得准,在拦截量这一个数字上长得一模一样
代价
全量运行 5 周后回滚
误伤率(正常内容被下架) 0.31% → 0.94%
被误伤内容 4.1 万条
创作者申诉 1.7 万件
2 名头部创作者公开投诉
直接成本:
回滚 + 人工复审 4.1 万条 ≈ 21 万元
重建评测集(3 人 × 3 周 + 外部标注) ≈ 41 万元
⭐ 合计约 62 万元,新模型上线推迟 9 周
💀 这个事故的本质: 评测集只有一个标注员时,你评测的是「模型有多像这个人」,不是「模型有多准」。 而这件事在离线指标上永远看不出来 —— 因为分母和分子用的是同一个人的口径。 上一章那个 41 万的事故是训练集的歧义,这一章是评测集的歧义; 后者更贵,因为它会让你自信地做出一个反向的决策。
该补什么
| 缺失 | 补上之后 |
|---|---|
| 评测集单人标注 | 评测集必须多人标 + 仲裁,并且先报一致性再报模型分数 ⭐⭐ |
| 从不测一致性 | 评测集 Fleiss/Krippendorff < 0.80 不许用于选型决策;按类别单独看 |
| 只看总体 Kappa | 逐类 one-vs-rest:「擦边」0.19 这个数字本可以在第一天就拦住整件事 ⭐ |
| 模型分数没有区间 | F1/AUC 一律带 bootstrap 置信区间;模型对比用配对检验,不比点估计 |
| 灰度只看模型侧指标 | 补人侧指标:申诉率、复审改判率 —— 误伤和抓得准在拦截量上长得一样 ⭐ |
| 训练集和评测集同一批人标 | 评测集的标注员必须和训练集的不重叠,否则模型学到的偏好会被当成"正确" |
⭐ 最后一条最容易被当成吹毛求疵,但它正是这个事故的引信: 模型 B 在训练时学会了那位标注员的宽松倾向, 评测时又被同一位标注员的标准打分 —— 它两头都在讨好同一个人。 只要训练集和评测集共用标注员,这种自我印证就无法被离线指标发现。
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 第 9 章 · 标注体系怎么设计 | 一致性低了要回到指南去修:边界样例、排除项、试标注循环 |
| 第 11 章 · 标签噪声 | Kappa 低意味着标签噪声高;那一章讲噪声对模型的量化影响和处理办法 |
| 第 13 章 · 评测集也是数据 | 本章事故的正面写法:评测集该怎么建、为什么标准比训练集更高 |
| 第 8 章 · 数据质量怎么量 | 一致性是「准确性维度」的人工那一半,同样要进质量报告、要看趋势 |
| 上线之后 12 · AB实验你以为你懂了 | 「0.038 算不算显著」是同一类问题:点估计不带区间就做决策 |
| 大模型全景导论 11 · 评测体系 | LLM-as-judge 同样要测一致性:判官和人的 Kappa 才是这套评测的上限 |
✅ 检查点
- 那个 1,000 条的例子里,同意率 0.900 而 Kappa 只有 0.2001 —— Pe 是多少?这 90% 里有多少个百分点是"白送的"?
- 为什么正类专属一致率(0.2537)比整体同意率更能反映真实水平?
- Cohen's / Fleiss' / Krippendorff's α / 加权 Kappa 各自的适用场景是什么?Cohen 和 Fleiss 最关键的区别是什么(不是人数)?
- 多人场景下「两两算 Cohen 再平均」为什么不对?
- Fleiss Kappa 算出 −0.145 意味着什么?第一件该做的事是什么?
- 同一份 1~5 分打分数据上,无权 / 线性 / 二次加权 Kappa 分别是多少?为什么有序标签必须加权?加权又有什么风险?
- 训练集、评测集、高风险场景的 Kappa 下限建议分别是多少?「人类一致性是模型性能天花板」是什么意思?
- Kappa 悖论是什么?两个 Po 都是 0.850 的例子,Kappa 分别是多少?这时候该怎么报数?
- 两个标注员一致地把广告标成普通评价,Kappa 是多少?这说明一致性和什么是两回事?
- 那个 600 条 4 类的例子里,Kappa 0.6477 的分歧是怎么分布的?合并两类之后变成多少?为什么"合并"不是解决方案?
- 那个 62 万的事故里,评测集单人标注具体错在哪?为什么离线指标永远看不出来?
- 事故里 0.038 的 F1 差距为什么不能直接拍板?实测的 bootstrap 区间半宽是多少?
👀 答案
- Pe = 0.874978。因为两人各自只有 6.7% 标"投诉",闭眼乱按都能拿到 87.5% 的同意率。所以 90% 里 87.5 个百分点是白送的,靠标注水平挣来的只有 2.5 个百分点(最多能挣 12.5 个),2.5/12.5 = 0.2001 就是 Kappa。
- 因为整体同意率里 87.5 个百分点的贡献来自"两人都标非投诉"这件几乎不需要判断的事。两人各标了 67 条投诉,共同认定的只有 17 条;正类专属一致率 0.2537 vs 负类 0.9464 —— 需要判断的那一半,一致性其实很差。
- Cohen:恰好 2 人、且两人标同一批全部样本、名义标签。Fleiss:多人、每条样本被固定 k 人标、名义标签。Krippendorff α:任意人数、允许缺失和每条评分数不同、支持名义/有序/区间/比例,最通用。加权 Kappa:有序标签必须用。Cohen 和 Fleiss 的关键区别不是人数而是「谁标的」 —— Cohen 要求固定的两个人(会分别估计两人各自的偏好),Fleiss 只要求每条被 k 个人标、是谁无所谓,众包数据天然是 Fleiss 形态。
- 因为它不等于 Fleiss,在人数不齐时会系统性偏高。数据不规整时正确做法是用 Krippendorff's α,别硬凑成 Fleiss 或平均 Cohen。
- 比随机还差。这几乎从不是"标注员太笨",而是一部分人在按某种系统性的反向理解在标(比如把"是否含广告"读成了"是否是好评")。第一件事是看混淆矩阵有没有明显的对角线错位,不是换人。
- 同意率 0.5175,无权 0.3964 / 线性 0.6747 / 二次加权 0.8510。必须加权是因为无权 Kappa 把「差一档」和「差四档」当成同样的错,而这份数据的分歧绝大多数只差 1 档 —— 对情感强度这类任务差 1 档基本无害。风险是二次加权容易把中等质量美化成优秀,所以报告必须写清用的是哪种权重,只写"Kappa=0.85"没有信息量。
- 训练集 ≥0.60,评测集 ≥0.80,高风险场景(医疗/风控/内容安全)≥0.80 且必须有仲裁;主观任务 0.5 可能就是天花板,该换加权 Kappa 或偏好对比。「人类一致性是天花板」意思是:两人只有 0.65 的一致性时,不可能得出可信的"模型准确率 95%"结论 —— 那里面必有一部分在拟合标注噪声。所以先看评测集一致性,再看模型分数。
- Kappa 悖论(prevalence effect):同样的 Po、同样的分歧绝对条数(都是 150 条),仅仅因为边际分布不同,Kappa 从 0.700(均衡
[[425,75],[75,425]])掉到 0.128(不均衡[[830,75],[75,20]])。所以极不平衡任务上低 Kappa 不一定意味着标注差,要把 Kappa + 正类专属一致率 + 分歧绝对条数三个一起报。 - Kappa = 1.00(完美一致),但按指南它们全都该标成广告。说明一致性高只说明指南被「一致地」理解了,不说明被「正确地」理解了 —— 集体误读的 Kappa 也是 1.0。所以一致性(双标重叠)和正确性(黄金集)是两套独立的质检,一个都不能省。
- 全部 139 条分歧里:投诉↔退款 88 条(63.3%)、咨询↔其他 51 条(36.7%)、其余 4 对 0 条。把投诉/退款合并后 Kappa 从 0.6477 变成 0.8624。合并不是解决方案是因为:Kappa 大涨说明的是在标注员眼里这两类本来就是一类,接下来只有两条路 —— 改指南把边界写死,或者真的合并;留着一对标注员分不开的类别,模型也一定分不开。
- 评测集 8,000 条单人标注、从没算过一致性(理由是"一个人标口径最统一")。💀 口径统一 ≠ 口径正确 —— 一个人标等于把一个人的偏好固化成了金标准。事后 3 人重标:整体 Fleiss 0.38,「擦边」类只有 0.19;那位标注员判违规的比例 11.2% vs 仲裁后 6.7%,系统性宽松 4.5pp;而模型 B 的训练数据也大量来自他,所以 B 只是"更像那个人"。用仲裁金标重测:A 0.781 vs B 0.729,结论完全翻转。离线看不出来是因为分子和分母用的是同一个人的口径。代价:误伤率 0.31%→0.94%,误伤 4.1 万条、申诉 1.7 万件,约 62 万元,上线推迟 9 周。
- 因为这种量级的差距必须做配对显著性检验。实测:8,000 条评测集、正样本 546 条时,单个模型 F1 的 bootstrap 95% 区间半宽就有 ±0.027,0.038 只比它略大。另外灰度只看拦截量也不行 —— 误伤和抓得准在拦截量这一个数字上长得一模一样,必须补申诉率、复审改判率这类人侧指标。
🛑 可以停在这里
⚡ 走神救援
⭐⭐一致性不是「两人标得一样的比例」,是「他们比按同样比例乱按好了多少」。 构造例子(1000 条,两人判是否投诉,矩阵
[[883,50],[50,17]]):同意率 Po = 0.900,看着很棒;但 Pe = 0.874978 —— 闭眼乱按都能有 87.5%,所以 Kappa = (0.900−0.874978)/(1−0.874978) = 0.2001。💀 那 90% 里 87.5 个百分点是白送的,靠水平挣的只有 2.5 个(最多能挣 12.5 个)。两人各标了 67 条投诉,共同认定的只有 17 条;正类专属一致率 0.2537 vs 负类 0.9464 —— 需要判断的那一半其实很差。类别越不平衡,同意率越没意义。 四个指标:Cohen(恰好 2 人、标同一批全部样本)、Fleiss(多人、每条固定 k 人、⭐不要求是同一批人,众包数据天然是这个形态)、Krippendorff α(⭐⭐任意人数、允许缺失和评分数不齐、支持有序/区间,最通用)、加权 Kappa(有序标签必须用);⚠️ 多人场景两两算 Cohen 再平均不等于 Fleiss,人数不齐时会系统性偏高。实跑对比:指南清楚 Fleiss 0.757,指南有歧义 −0.145(💀负数 = 比随机还差,多半是一部分人在系统性反向理解,先查混淆矩阵的对角线错位,别急着换人);同一份 1~5 分数据 无权 0.3964 / 线性 0.6747 / 二次 0.8510 —— ⚠️有序标签用无权 Kappa 会把好标注判成要返工(分歧几乎都只差 1 档),但二次加权又容易把中等美化成优秀,所以报告必须写清权重;Krippendorff α 实跑 0.7071 vs 乱标 −0.2247。阈值:训练集 ≥0.60、评测集 ≥0.80、高风险场景 ≥0.80 且必须仲裁,主观任务 0.5 可能就是天花板。⭐⭐人类一致性是模型性能的天花板 —— 两人只有 0.65 的一致,"模型准确率 95%"这个结论必然有一部分在拟合标注噪声,所以先报评测集一致性,再报模型分数。Kappa 三个坑:①Kappa 悖论 —— 实跑两个 Po 都是 0.850、分歧都是 150 条,均衡时 Kappa 0.700,不均衡时只有 0.128,所以不平衡任务要把 Kappa + 正类专属一致率 + 分歧绝对条数一起报;②bias effect,两人边际分布差很大时 Kappa 反而虚高,要同时报两人各自的类别分布;③💀Kappa 测「一致」不测「对」 —— 两人一致地把广告标成普通评价,Kappa = 1.0,所以一致性(双标重叠)和正确性(黄金集)是两套独立质检。定位靠分歧矩阵不靠总分:实跑 600 条 4 类,Kappa 0.6477,全部 139 条分歧里投诉↔退款 88 条(63.3%)、咨询↔其他 51 条(36.7%)、其余四对 0 条,合并投诉/退款后 Kappa 变 0.8624 —— ⭐中等的总分几乎总是少数几对类别拖的,修那两对比全员培训有用一百倍;⚠️但合并只是诊断不是解药,它说明这两类在标注员眼里本来就是一类,接下来要么改指南写死边界要么真合并,留着一对人分不开的类别,模型也一定分不开。💀事故:短视频审核模型选型,评测集 8,000 条单人标注、从没算过一致性("一个人标口径最统一");B 的 F1 0.812 vs A 0.774,差 0.038 就全量替换。五周后申诉量涨 2.8 倍才去测评测集:Fleiss 只有 0.38,「擦边」类 0.19;那位标注员判违规 11.2% vs 三人仲裁 6.7%,系统性宽松 4.5pp,而 B 的训练数据也大量来自他 —— ⭐⭐这场评测测的是"哪个模型更像那个人"。用仲裁金标重测 A 0.781 vs B 0.729,结论完全翻转。代价:误伤率 0.31%→0.94%、误伤 4.1 万条、申诉 1.7 万件、约 62 万元、上线推迟 9 周。三个盲区:"评测集不用算一致性"是流行误解(口径统一 ≠ 口径正确)、0.038 没做区间估计(实测 8,000 条、546 个正样本时单模型 F1 的 bootstrap 95% 半宽就有 ±0.027,必须做配对检验)、灰度只看拦截量(误伤和抓得准在这个数字上长得一模一样,要补申诉率和复审改判率)。最狠的一条改法:评测集的标注员必须和训练集不重叠,否则模型两头讨好同一个人,离线指标永远发现不了。
下一节 👉 11-标签噪声.md