📑 本页目录(点开跳转)
09 · 标注体系怎么设计
⏱ 70 分钟 | ⭐⭐ 标注指南就是产品文档 —— 写不清楚,标注员就替你定义了问题
🎯 一句话
模型学到的从来不是你脑子里的任务,是标注员对指南的理解。 指南里没写清楚的每一处,都会被十几个标注员各自补全成十几种不同的定义, 然后这些互相矛盾的定义被一起喂进模型, 最后你在评测集上看到一个漂亮的数字 —— 因为评测集继承了完全一样的歧义。 这一章讲怎么在花钱标之前,把问题定义清楚。
🧩 一、标签体系的三种形状,选错了后面全是补丁
| 形状 | 什么时候用 | 代价 | 💀 最容易出的事 |
|---|---|---|---|
| 互斥单标签 | 现实中真的只能是一种 | 最便宜、最好训、最好评 | 把本来多标签的问题硬压成单标签 ⭐⭐ |
| 多标签 | 一个样本可以同时属于多类 | 标注慢 30~50%;评测要换指标 | 标注员偷懒只勾最明显的一个 → 假阴性遍地 |
| 层级 | 类别天然有粗细两层 | 指南复杂度翻倍 | 上层标错,下层再准也白搭 |
为什么「硬压成互斥」是本节最重要的一条:
一条真实工单:
「我下单三天了还没发货,我要投诉,另外这单我不想要了要退款」
在【互斥 32 类】体系下,三个标注员给出三个答案:
标注员 A → 物流异常 (他抓的是"事实是什么")
标注员 B → 投诉 (他抓的是"情绪最强的诉求")
标注员 C → 退款申请 (他抓的是"最终要办的事")
⭐ 三个人都没错。错的是"必须选一个"这件事本身。
这条样本进训练集之后,它教给模型的是:这三个类可以互相替代。
💀 压成互斥的动机往往和任务无关: 「因为下游代码只支持 softmax」「因为多标签不好算准确率」「因为老板要一张饼图」。 这三个理由都是工程便利,不是任务性质。 用工程便利去改任务定义,代价会在标注一致性上原样还回来。
判断「是不是真互斥」的三个测试(花 30 分钟,能省掉整个项目的返工):
① 自己先标 50 条真实样本
有几条你想勾两个?>5% → 它不是互斥问题
② 能不能造出"两者都是"的合法例子?
造得出来 → 不是互斥。造不出来才是。
⚠️ 别用想象的例子,从真实数据里翻
③ 看「其他」类的占比
>10% → 体系没盖住现实,缺类别
>20% → 体系需要重做,不是加一个类的问题 ⭐
层级体系有一条特殊纪律:允许只标到能确定的那一层。 强迫标注员在信息不足时硬标到叶子节点,得到的是随机叶子标签, 而「粗类正确 + 细类留空」的数据是可用的(可以训两级模型,也可以做粗粒度评测)。
def taxonomy_smell_test(df, label_col, multi_col=None, other_label="其他"):
"""三个测试的量化版:跑在试标注的前 200 条上"""
n = len(df)
other_rate = float((df[label_col] == other_label).mean())
r = {"其他类占比": round(other_rate, 4),
"结论_其他类": "OK" if other_rate <= 0.10 else ("缺类别" if other_rate <= 0.20 else "体系需重做")}
if multi_col is not None: # multi_col: 标注员"想勾多个"的标记列
want_multi = float(df[multi_col].fillna(False).astype(bool).mean())
r["想勾多个的比例"] = round(want_multi, 4)
# ⭐ 超过 5% 就不该做成互斥单标签
r["结论_互斥性"] = "可以互斥" if want_multi <= 0.05 else "应改为多标签"
r["样本量"] = n
return r
✍️ 二、指南写什么:六个必需块
一份能用的标注指南,必须有且只需要这六块:
| 块 | 内容 | 为什么必需 |
|---|---|---|
| ① 任务一句话 + 下游用途 | 「这批标注用来训练自动分派模型,分错会让工单进错队列」 | ⭐ 标注员知道用途之后,判断质量立刻上升 —— 他能自己推理边界 |
| ② 每个标签的定义 | 正面定义 + 明确的排除项 | 只有正面定义的标签一定会溢出 |
| ③ 正例 / 反例 | 每类至少 3 + 3,从真实数据里挑,不许编 | 编的例子永远太典型,覆盖不到真实的模糊地带 |
| ④ 边界样例库 ⭐⭐ | 按「类别对」组织,见第三节 | 这才是指南的正文 |
| ⑤ 优先级 / 仲裁规则 | 多个标签都沾边时按什么顺序取舍 | 没有它,每个标注员会自建一套优先级 |
| ⑥ 不确定怎么办 | 必须有一个合法出口(跳过 / 标"存疑"+ 写理由) | 没有出口,不确定就变成了随机猜 💀 |
②「正面定义 + 排除项」的差距有多大:
| ❌ 坏定义 | ✅ 好定义 |
|---|---|
| 投诉:用户表达不满的工单 | 投诉:用户明确指出某次服务或商品存在问题,并要求平台介入处理。仅表达情绪、没有具体诉求的 → 标「咨询-情绪」;只要求退钱、没有指责服务问题的 → 标「退款申请」 |
| 广告:推销性质的内容 | 广告:包含可联系的商业主体(店铺名/微信号/链接/电话)且在推荐购买行为。只夸产品好但无联系方式 → 「普通评价」 |
| 紧急:需要尽快处理的 | 紧急:满足任一:涉及人身安全 / 已产生资金损失 / 用户已提到「监管」「315」「起诉」。主观着急程度不作为判据 |
⭐ 三个好定义的共同点:它们都写了"不是什么"。 一个只说"是什么"的定义,在标注员遇到模糊样本时提供不了任何信息 —— 而标注员遇到的绝大多数需要思考的样本,都是模糊样本。 明确的样本根本不需要指南,他一秒就标完了。
⚠️ 第 ⑥ 块「不确定怎么办」最常被漏掉,后果最直接。 没有合法出口时,标注员面对拿不准的样本只有两个选择:随便选一个,或者选「其他」。 前者在你的训练集里制造纯噪声,后者把「其他」变成垃圾桶。 给一个"存疑"按钮 + 要求写一行理由,这些样本就从噪声变成了下一版指南的原材料。 ⭐
🧭 三、边界样例:指南真正的正文
⭐ 指南里最没用的一段永远是「标签定义」,最有用的永远是「边界样例」。 定义是给写指南的人看的,边界样例是给标注员用的。
边界样例的三个来源(都不需要额外花钱,都是现成的):
① 试标注里的分歧样本 ← 最主要来源,见第四节
② 线上模型的低置信样本 ← 模型犹豫的地方,人也在犹豫
③ 质检打回的样本 ← 每一条都自带"为什么错"
每条边界样例必须有四个字段:
| 字段 | 例子 |
|---|---|
| 原文 | 「东西还行吧,就是快递太慢了,等了五天」 |
| 正确标签 | 物流异常 |
| 为什么 ⭐⭐ | 主要诉求指向配送时效;「东西还行」不构成商品问题,不算投诉 |
| 对照反例 | 「东西不行,快递也慢」→ 商品质量(有明确商品否定时,商品问题优先) |
⭐ 「为什么」这一栏是整份指南里投入产出比最高的文字。 没有它,边界样例只是一堆答案,标注员只能死记; 有了它,标注员能把这条规则外推到没见过的样本上 —— 这正是你花钱雇人而不是写正则的原因。
组织方式:按「类别对」而不是按「类别」。 分歧永远发生在两个类之间,不会均匀撒在 32 个类上。
❌ 按类别组织 ✅ 按类别对组织 ⭐
──────────────────── ──────────────────────────
投诉:边界样例 12 条 投诉 ↔ 退款申请:8 条
退款申请:边界样例 9 条 投诉 ↔ 咨询-情绪:5 条
咨询:边界样例 7 条 物流异常 ↔ 投诉:6 条
...(标注员不知道去哪找) 其他类别对:无分歧,不用写
两条数量经验:每个高频类别对至少 5 条边界样例; 整份指南里边界样例的字数应该是标签定义字数的 3 倍以上 —— 低于这个比例,说明指南还停留在"定义阶段"。
💀 但指南越长越没人读,这是真的。 解决办法不是砍内容,是换载体: 定义部分 1 页封顶(打印出来贴在旁边),边界样例做成可检索的表格, 由标注工具在当前样本旁边自动弹出最相似的 3 条。 让标注员"查得到",而不是"记得住"。
import json
BOUNDARY_SCHEMA = {
"pair": ["投诉", "退款申请"], # ⭐ 按类别对索引,不按单类别
"text": "我不想要了,退钱吧",
"label": "退款申请",
"why": "只有退款诉求,未指出服务/商品问题,也未要求平台介入",
"contrast": {"text": "发错货了,退钱并且给我个说法", "label": "投诉",
"why": "指出了商品问题 + 要求平台介入"},
"source": "pilot_r2_dispute", # 来源:试标注R2的分歧样本
"added_in": "guideline_v1.3", # ⭐ 每条边界样例都要知道是哪版加的
}
def lint_guideline(defs: dict, boundaries: list):
"""指南体检:定义字数 vs 边界样例字数、类别对覆盖"""
def_chars = sum(len(v) for v in defs.values())
bd_chars = sum(len(b["text"]) + len(b["why"]) for b in boundaries)
pairs = {tuple(sorted(b["pair"])) for b in boundaries}
thin = [p for p in pairs if sum(1 for b in boundaries if tuple(sorted(b["pair"])) == p) < 5]
return {"定义字数": def_chars, "边界样例字数": bd_chars,
# ⭐ 低于 3 倍,说明指南还停在"定义阶段"
"倍数": round(bd_chars / max(def_chars, 1), 2),
"覆盖的类别对": len(pairs), "样例不足5条的类别对": thin}
print(json.dumps(lint_guideline({"投诉": "用户明确指出问题并要求平台介入"},
[BOUNDARY_SCHEMA]), ensure_ascii=False))
🔁 四、试标注—修订循环
没有试标注就开量,是本章事故的起点。 正确的节奏是这样:
R1 抽 100~200 条 → 3~5 人独立标 → 算一致性
↓
分歧会:⭐ 不讨论"谁对",只讨论"指南哪一句没说清"
↓
改指南(新增边界样例 / 补排除项 / 拆类别 / 合并类别)
↓
R2 ⚠️ 换一批新样本 再来一遍
↓
R3 ... 直到收敛
一组典型数值(客服工单意图,5 名标注员):
| 轮次 | Fleiss' Kappa | 相比上轮提升 | 新增争议条目 | 这一轮改了什么 |
|---|---|---|---|---|
| R1 | 0.41 | — | 37 | 发现「投诉↔退款」「咨询↔其他」两对是主要战场 |
| R2 | 0.63 | +0.22 | 12 | 给两对各加 8 条边界样例 + 明确优先级规则 |
| R3 | 0.71 | +0.08 | 3 | ⚠️ 争议只剩 3 条,但提升还有 0.08 —— 不算收敛 |
| R4 | 0.74 | +0.03 | 1 | 只补了 1 条样例;两条判据都满足,收敛 ✅ |
⚠️ R3 是最容易被误判为「收敛」的那一轮:争议条目已经从 37 掉到 3, 会上大家都觉得"差不多了、可以开量了"。 但 Kappa 还在以每轮 0.08 的速度上涨 —— 还在涨,就说明指南还在变清楚。 这一轮省下的一周,会在开量之后以十倍的价钱还回来。
收敛判据两条,必须同时满足:
① Kappa 相比上一轮提升 < 0.05
② 新增争议条目 < 3 条 / 轮
⭐ 只看 ① 会被"标注员记住了上一批答案"骗到
⭐ 只看 ② 会在类别本身太难时假收敛
⚠️ 每一轮必须换一批新样本,这条纪律最常被违反。 重复用同一批样本时,Kappa 上升的原因可能只是标注员记住了讨论结果, 而不是指南变清楚了。前者不会外推到新数据上,后者才会。 分辨方法:R3 之后再随机抽一批完全没讨论过的样本做一次验证轮,Kappa 掉超过 0.08 就是记答案。
from typing import Sequence
def pilot_converged(kappas: Sequence[float], new_disputes: Sequence[int],
k_gain: float = 0.05, dispute_max: int = 3):
"""试标注收敛判据:⭐ 两条必须同时满足"""
if len(kappas) < 2:
return False, "至少要两轮"
gain = kappas[-1] - kappas[-2]
ok = (gain < k_gain) and (new_disputes[-1] < dispute_max)
return ok, {"最新Kappa": kappas[-1], "本轮提升": round(gain, 3),
"新增争议": new_disputes[-1],
# ⚠️ Kappa 还很低却"收敛"了 = 任务本身定义有问题,不是标注员问题
"警告": "Kappa<0.6 却收敛,说明类别体系需要重做" if ok and kappas[-1] < 0.6 else ""}
print(pilot_converged([0.41, 0.63, 0.71], [37, 12, 3]))
# (False, {'最新Kappa': 0.71, '本轮提升': 0.08, '新增争议': 3, '警告': ''})
# ⭐ 争议条目已经降到 3 条,但提升还有 0.08 ≥ 0.05 —— 判定【未收敛】,该再来一轮 R4
⭐ 分歧会的开法决定了这套循环有没有用: 会上只准问一句话 —— 「指南里的哪一句话,让你们做出了不同的判断?」 一旦变成「你这条为什么标错了」,标注员就会开始猜出题人的意图而不是读指南, 一致性会短暂上升,然后在换人的时候原样崩掉。
🛑 读到这里可以停 —— 前半章讲完了(约 30 分钟)。 后半章还有:什么时候该改指南,而不是怪标注员 · 质检:抽多少条才够 · 事故复盘:那 41 万块钱的「投诉」和「退款」 · 把指南当代码管 回来的时候不用重读,直接从下一节接着看就行。
🔧 五、什么时候该改指南,而不是怪标注员
一致性低的时候,先跑这三个诊断,再决定骂谁:
① 分歧是【集中】在少数几对类别,还是【均匀】撒开?
集中在 1~3 对 → 指南问题(那几对的定义重叠了) ⭐
均匀撒开 → 可能是任务太难 / 培训不足
② 多数标注员是不是【朝同一个方向】错?
是 → 一定是指南问题(系统性偏移,大家都按同一种误读在标)
否 → 才可能是个体问题
③ 换一批全新标注员重标,分歧模式一样吗?
一样 → 100% 是指南问题 ⭐⭐ 这是最硬的判据
不一样 → 前一批人的培训或态度有问题
⭐ 一个应该记住的比例:真实项目里,七到八成的低一致性来自指南歧义,而不是标注员能力。 「换人重标,分歧模式一模一样」这个现象一旦出现,就不用再讨论了 —— 是你的问题定义有洞,不是他们不认真。
确实该归到人身上的情况,也要用数据说话,不是靠印象:
| 现象 | 怎么查 | 处置 |
|---|---|---|
| 某人和所有人都不一样 | 逐人对比:他 vs 其他人的两两 Kappa 明显低一档 | 复训;仍不行则换人 |
| 抢时间乱标 | 单条耗时分布:出现大量 < 2 秒的样本 | 设最短耗时门槛 |
| 批量点同一个键 | 连续相同标签的最长游程异常长 | 打乱样本顺序 |
| 不读长文本 | catch trial:混入答案唯一的检验题 ⭐ | 通过率 < 90% 的数据整批复核 |
import pandas as pd
def annotator_health(df, ann="annotator", label="label", secs="seconds", gold="gold_label"):
"""按标注员拆开看:⭐ 总体质检指标会把个别问题平均掉"""
out = []
for a, g in df.groupby(ann):
runs, cur, best = 1, g[label].iloc[0], 1 # 最长连续相同标签游程
for v in g[label].iloc[1:]:
runs = runs + 1 if v == cur else 1
cur, best = v, max(best, runs)
row = {"标注员": a, "条数": len(g),
"中位耗时秒": round(float(g[secs].median()), 1),
"秒杀比例(<2s)": round(float((g[secs] < 2).mean()), 3),
"最长同标签游程": best}
if gold in g.columns: # ⭐ catch trial 通过率
m = g[gold].notna()
row["检验题通过率"] = round(float((g.loc[m, label] == g.loc[m, gold]).mean()), 3) if m.any() else None
out.append(row)
return pd.DataFrame(out)
🎯 六、质检:抽多少条才够
三种质检手段,作用完全不同,不能互相替代:
| 手段 | 怎么做 | 能发现什么 | 成本 |
|---|---|---|---|
| 黄金集(gold / catch trial) | 3~5% 已知答案的题伪装成普通任务混进去 | 态度问题、乱标、不读题 | 低 ⭐ |
| 抽样复核 | 交付后随机抽 N 条由资深标注员重判 | 整体错误率、错误类型分布 | 中 |
| 双标重叠 | 一部分样本由 2 人各标一遍 | 指南歧义(第 10 章的一致性指标就靠它) | 高 |
⚠️ 黄金集必须伪装。 一旦标注员能认出哪些是检验题, 它测的就是"他认不认得出检验题",而不是"他平时怎么标"。
抽多少条:用公式算,不要拍比例。
要以置信度 C 至少发现一条缺陷率为 p 的错误,需要 n = ln(1−C) / ln(1−p):
| 想发现的缺陷率 | 90% 置信 | 95% 置信 | 99% 置信 |
|---|---|---|---|
| 10% | 22 条 | 29 条 | 44 条 |
| 5% | 45 条 | 59 条 | 90 条 |
| 2% | 114 条 | 149 条 | 228 条 |
| 1% | 230 条 | 299 条 | 459 条 |
反过来看更有用 —— 抽 N 条全对,能说多狠的话:
| 抽样量 | 全部正确时,95% 置信下的错误率上限 |
|---|---|
| 50 条 | ≤ 5.82% |
| 100 条 | ≤ 2.95% |
| 200 条 | ≤ 1.49% |
| 500 条 | ≤ 0.60% |
⭐ 「抽 5%」这种比例式抽样是错的。 12 万条抽 6,000 条是浪费,1,000 条抽 50 条又什么都保证不了。 该定的是绝对条数,由你想发现多小的错误率决定 —— 和批次大小几乎无关。
💀 另一个必须记住的:抽 200 条全对,你能说的是「95% 置信下错误率 ≤ 1.49%」, 不是「错误率 0%」。在 12 万条的批次上,1.49% 就是 1,788 条错标。 验收报告上写「抽检 100% 正确」而不写置信区间,是在制造虚假的安全感。
最后,质检指标必须按「标注员 × 类别」拆开看:整体准确率 96% 完全可能是 「30 个类里有 2 个类的准确率是 61%」—— 而那 2 个类恰好是你最在乎的低频类。
import math
def sample_size(defect_rate, conf=0.95):
"""⭐ 抽多少条才能以 conf 的把握至少发现一条缺陷率为 defect_rate 的错误"""
return math.ceil(math.log(1 - conf) / math.log(1 - defect_rate))
def error_upper_bound(n, conf=0.95):
"""抽 n 条全对时,真实错误率的单侧上限(规则三的一般形式)"""
return 1 - (1 - conf) ** (1 / n)
print(sample_size(0.05), sample_size(0.02), sample_size(0.01)) # 59 149 299
print(round(error_upper_bound(200) * 100, 2), "%") # 1.49 %
💀 七、事故复盘:那 41 万块钱的「投诉」和「退款」
发生了什么
系统:客服工单自动分派模型
体系:32 个意图,互斥单标签
指南:4 页,只有标签定义和各 2 条例子,0 条边界样例
流程:⚠️ 没有试标注,直接外包 8 人标 12 万条,3 周交付
指南里那两条定义,单独看都很通顺:
「投诉」 :用户对服务或商品表达不满的工单
「退款申请」:用户要求退回已支付款项的工单
⭐ 问题在于:"我不想要了,退钱" → 两条都不完全命中
"发错货了,退钱并给个说法" → 两条都命中
而指南里没有一个字说明这时候该怎么办。
为什么没被发现(三条,第三条最要命)
① 没做试标注
第一次计算标注一致性,是在事故发生【之后】的复盘会上
💀 这个项目在花完 12 万条的钱之前,从来没量过一次一致性
② 验收只看"完成率"和"抽检准确率",抽检准确率 96%
但抽检的判定依据【也是那份有歧义的指南】
质检员和标注员碰巧理解一致 → 判"对"
💀 用同一把歪尺子量自己,量出来永远是直的 ⭐
③ 离线评测:意图=投诉 的 F1 = 0.88,很好看
因为评测集和训练集【由同一批标注员、按同一份有歧义的指南】标注
模型完美学会了这批人的随机习惯,评测集也按同样的随机习惯打分
⭐⭐ 评测集继承了同样的歧义,所以它永远测不出这个问题
代价
事后抽 500 条由 2 名资深标注员 + 1 名仲裁重标:
「投诉 ↔ 退款申请」这一对的原始标注一致率只有 51%
这两类合计占全部工单的 21%
上线之后:
真实投诉的线上召回 0.42 (离线 F1 是 0.88)
投诉工单没进人工优先队列
投诉平均首响时长 8 分钟 → 3.7 小时
客诉升级(二次投诉 + 监管投诉) 月均 62 件 → 340 件
直接成本:
重标 2.6 万条 18 万元
3 人 × 2 周 重做指南 + 重训 23 万元
⭐ 合计 41 万元,上线延期 5 周
💀 这个事故的本质: 指南没定义的那部分,被 8 个标注员各自定义了一遍,然后一起喂进了模型。 更糟的是 —— 同一份歧义同时污染了训练集和评测集, 于是所有离线指标都在自洽地告诉你「一切正常」。 离线指标测不出标注歧义,因为它用的是同一批标注。
该补什么
| 缺失 | 补上之后 |
|---|---|
| 没有试标注 | 开量前必须跑 3 轮试标注,Kappa 不到 0.6 不许开量 ⭐⭐(第 10 章给阈值) |
| 指南 0 条边界样例 | 「投诉↔退款」这类高频类别对至少各 5 条,带「为什么」 |
| 定义只有正面描述 | 每条定义必须写排除项("只有退款诉求、未指出问题 → 退款申请") |
| 抽检用同一份指南、同一批人 | 质检要用独立仲裁;一致性要靠双标重叠测,不能靠抽检准确率 ⭐ |
| 评测集和训练集同源 | 评测集单独标、多人标、带仲裁,和训练集分开做(第 13 章) ⭐⭐ |
| 32 类互斥 | 跑一遍互斥性测试;「投诉」实际上是正交维度,该做成独立的二元标签 |
⭐ 最后一条其实是根因:「投诉」根本不是一个和「退款申请」并列的意图, 它是一个正交的属性 —— 任何意图的工单都可以同时是投诉。 把一个正交维度塞进互斥类别表里,无论指南写得多好,都不可能标一致。
📋 八、把指南当代码管
guideline/
v1.0.md 初版
v1.1.md + 投诉↔退款 8 条边界样例 影响:已标 0 条
v1.2.md 「紧急」定义加入排除项 影响:已标 3.1 万条 ⚠️ 需重标 4,200 条
v1.3.md 拆分「物流异常」为两类 影响:已标 12 万条 💀 需全量重标该类
CHANGELOG.md 每次改动 + 受影响范围 + 是否重标
四条纪律:
| 纪律 | 为什么 |
|---|---|
指南有版本号,每条标注结果存 guideline_version |
⭐ 否则半年后你无法解释为什么两批数据的类别分布不一样 |
| 每次改动写清影响范围:哪些已标数据的正确性发生了变化 | 决定要不要重标的唯一依据 |
| 改了定义就重跑一次小规模试标注(50~100 条) | 改指南本身也可能引入新歧义 |
| 重标的判据:受影响类别占比 × 定义变化幅度 | 只改措辞不重标;改了判定边界必须重标该类 |
⭐
guideline_version这个字段的价值在半年后才会显现: 当你发现模型在某个类上突然变差,第一件事就是查 「这批训练数据是按哪一版指南标的」。 没有这个字段,标注体系的演进就是一段无法复现的历史(见第 17 章数据版本与血缘)。
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 第 10 章 · 标注一致性怎么量 | 本章反复出现的 Kappa 怎么算、阈值定多少、为什么不能用同意率 |
| 第 3 章 · 数据契约 | 指南和契约是一回事的两面:一个约束机器产出,一个约束人产出 |
| 第 7 章 · 异常值是错还是真 | 「不确定怎么办」的同款问题:默认动作选错,信息就永久丢了 |
| 大模型全景导论 12 · 对齐与AI安全 | RLHF 的偏好标注是同一个问题的放大版:偏好指南没写清,对齐就对到标注员的口味上 |
| 大模型全景导论 11 · 评测体系 | 「评测集继承了同样的歧义」在大模型评测里是重灾区 |
| 上线之后 19 · 合规审计与模型卡 | 指南版本、标注方 、一致性数值都要写进模型卡 |
✅ 检查点
- 标签体系的三种形状是什么?把多标签问题硬压成互斥单标签的常见动机是什么,为什么这些动机站不住?
- 判断「是不是真互斥」的三个测试是什么?「其他」类占比超过多少说明体系要重做?
- 指南的六个必需块是哪些?哪一块最常被漏掉、漏掉之后会发生什么?
- 好的标签定义和坏的标签定义,最本质的差别是什么?为什么这个差别如此重要?
- 为什么说「指南里最没用的是标签定义,最有用的是边界样例」?边界样例的四个字段里哪个最值钱,为什么?
- 边界样例为什么要按「类别对」而不是按「类别」组织?
- 试标注的收敛判据是哪两条?为什么必须两条同时满足?为什么每轮必须换新样本?
- 判断「该改指南还是该怪标注员」的三个诊断是什么?哪一个是最硬的判据?大约多大比例的低一致性来自指南?
- 三种质检手段各自能发现什么?黄金集为什么必须伪装?
- 要以 95% 把握发现 2% 的缺陷率,需要抽多少条?抽 200 条全对能说的准确结论是什么?
- 那个 41 万的事故里,离线 F1 有 0.88,为什么完全没测出问题?抽检准确率 96% 又为什么没用?
- 那个事故的根因(最后一条改法)是什么?
👀 答案
- 互斥单标签 / 多标签 / 层级。常见动机是「下游只支持 softmax」「多标签不好算准确率」「老板要一张饼图」—— 这三个都是工程便利,不是任务性质。用工程便利改任务定义,代价会在一致性上原样还回来(那条「未发货+投诉+退款」的工单,三个标注员给出三个都不算错的答案)。
- ①自己先标 50 条真实样本,想勾两个的 >5% 就不是互斥;②能不能从真实数据里找出「两者都是」的合法例子,找得到就不是互斥;③看「其他」类占比:>10% 是缺类别,>20% 是体系需要重做。
- ①任务一句话 + 下游用途 ②定义(正面 + 排除项)③真实正例反例各 3 ④边界样例库 ⑤优先级/仲裁规则 ⑥不确定怎么办。最常漏第 ⑥ 块:没有合法出口时,标注员只能随便选(制造纯噪声)或选「其他」(把它变成垃圾桶)。给一个"存疑 + 写一行理由"的出口,这些样本就变成下一版指南的原材料。
- 好定义写了「不是什么」(排除项)。因为标注员需要思考的样本几乎全是模糊样本 —— 明确的样本一秒就标完了,根本不看指南;只说"是什么"的定义在模糊处提供不了任何信息。
- 因为定义是给写指南的人看的,边界样例才是给标注员用的。四个字段里 「为什么」 最值钱:没有它标注员只能死记答案,有了它才能外推到没见过的样本 —— 而这正是你雇人而不是写正则的原因。
- 因为分歧永远发生在两个类之间,不会均匀撒在 32 个类上。按类别组织时标注员不知道该去哪找;按类别对组织,「投诉↔退款申请:8 条」直接命中他正在纠结的地方。
- ①Kappa 相比上一轮提升 < 0.05 ②新增争议条目 < 3 条/轮。只看①会被「标注员记住上一批答案」骗到,只看②会在类别本身太难时假收敛。换新样本是因为重复用同一批时 Kappa 上升可能只是记住了讨论结果 —— 那不会外推到新数据上。验证办法:收敛后再抽一批完全没讨论过的样本,Kappa 掉超过 0.08 就是记答案。
- ①分歧是集中在少数几对类别还是均匀撒开(集中 → 指南问题)②多数人是不是朝同一方向错(是 → 系统性误读,指南问题)③换一批全新标注员重标,分歧模式是否一样(一样 → 100% 是指南问题)。第三条是最硬的判据。七到八成的低一致性来自指南歧义,不是标注员能力。
- 黄金集发现态度问题/乱标/不读题(成本低);抽样复核给整体错误率和错误类型分布;双标重叠才能发现指南歧义(第 10 章的一致性指标靠它)。黄金集必须伪装,否则它测的是「他认不认得出检验题」而不是「他平时怎么标」。
- 149 条(95% 置信、缺陷率 2%)。抽 200 条全对只能说 「95% 置信下错误率 ≤ 1.49%」,不是 0% —— 在 12 万条的批次上 1.49% 就是 1,788 条错标。另外「抽 5%」这种比例式抽样是错的,该定绝对条数。
- 因为评测集和训练集由同一批标注员、按同一份有歧义的指南标注 —— 模型学会了这批人的随机习惯,评测集也按同样的随机习惯打分,同一份歧义同时污染了两边,所以离线指标自洽地说"一切正常",而线上真实投诉召回只有 0.42。抽检准确率 96% 没用是因为抽检的判定依据也是那份有歧义的指南,质检员和标注员碰巧理解一致就判"对" —— 用同一把歪尺子量自己,量出来永远是直的。事后重标发现「投诉↔退款」原始一致率只有 51%,这两类占 21% 的工单;代价是首响 8 分钟→3.7 小时、客诉升级 62→340 件/月、41 万元 + 延期 5 周。
- 「投诉」根本不是一个和「退款申请」并列的意图,它是一个正交属性 —— 任何意图的工单都可以同时是投诉。把正交维度塞进互斥类别表,无论指南写得多好都不可能标一致,正确做法是把它做成独立的二元标签。
🛑 可以停在这里
⚡ 走神救援
⭐⭐模型学到的不是你脑子里的任务,是标注员对指南的理解 —— 指南没写清楚的每一处,都会被十几个人各自补全成十几种定义,然后一起喂进模型。 标签体系三种形状:互斥单标签 / 多标签 / 层级;💀最常见的错是把多标签硬压成互斥,动机往往是「下游只支持 softmax」「不好算准确率」「老板要饼图」——全是工程便利,不是任务性质。判互斥的三个测试:自己标 50 条想勾两个的 >5% 就不是互斥、能否从真实数据找出「两者都是」的例子、「其他」类 >10% 是缺类别 >20% 是体系要重做。层级体系要允许只标到能确定的那一层(粗类对+细类空是可用数据,硬标叶子得到的是随机标签)。指南六块:任务一句话+下游用途(标注员知道用途,判断质量立刻上升)、定义(⭐正面定义 + 排除项,好定义都写了"不是什么",因为需要思考的样本几乎全是模糊样本)、真实正反例各 3、边界样例库、优先级规则、⚠️「不确定怎么办」必须有合法出口(没有出口,不确定就变成随机猜,或者把「其他」变成垃圾桶;给个"存疑+写理由"按钮,噪声就变成了下一版指南的原材料)。⭐指南里最没用的是标签定义,最有用的是边界样例 —— 每条要有原文/正确标签/为什么/对照反例,「为什么」最值钱因为它让标注员能外推到没见过的样本;边界样例按「类别对」组织而不是按类别(分歧永远发生在两个类之间);每个高频类别对至少 5 条,边界样例字数应是定义字数的 3 倍以上;指南太长没人读的解法不是砍内容而是换载体 —— 定义 1 页封顶,样例做成标注工具里自动弹出的可检索表格,让人查得到而不是记得住。试标注循环:每轮 100~200 条、3~5 人独立标、算一致性、分歧会上只准问「指南里哪一句让你们做出了不同判断」(一变成「你为什么标错」,标注员就开始猜出题人意图,一致性短暂上升然后在换人时崩掉)。典型四轮 Kappa 0.41→0.63→0.71→0.74,争议条目 37→12→3→1(⚠️ R3 最容易被误判成收敛 —— 争议只剩 3 条了,但提升还有 0.08,还在涨就说明指南还在变清楚);收敛判据两条必须同时满足:提升 <0.05 且新增争议 <3 条;⚠️每轮必须换新样本,否则涨的是"记住了答案"。诊断该改指南还是怪人:分歧集中在少数类别对→指南;多数人朝同一方向错→指南;⭐⭐换一批全新标注员重标、分歧模式一样 → 100% 是指南问题(最硬的判据)。七到八成的低一致性来自指南歧义,不是标注员能力。质检三件套:黄金集(3~5%,⚠️必须伪装)、抽样复核、双标重叠(唯一能测指南歧义的)。抽样量用公式不用比例:95% 置信下发现 5% 缺陷需 59 条、2% 需 149 条、1% 需 299 条;反过来抽 200 条全对只能说「错误率 ≤1.49%」不是 0% —— 12 万条里那就是 1,788 条错标。💀事故:32 类互斥的工单意图,指南 4 页、0 条边界样例、没做试标注就外包 8 人标 12 万条。「投诉」和「退款申请」定义重叠,"我不想要了退钱"两条都不完全命中。没被发现的三个原因:第一次算一致性是在事故之后;抽检准确率 96% 但抽检依据也是那份有歧义的指南 —— 用同一把歪尺子量自己;⭐⭐离线 F1 0.88 是因为评测集和训练集同一批人同一份指南标的,同一份歧义同时污染了两边。事后重标 500 条发现「投诉↔退款」原始一致率只有 51%,这两类占 21% 工单;线上真实投诉召回 0.42,首响 8 分钟→3.7 小时,客诉升级 62→340 件/月,重标 2.6 万条 + 重做指南重训共 41 万元、延期 5 周。根因:⭐「投诉」不是和「退款申请」并列的意图,是一个正交属性 —— 正交维度塞进互斥类别表,指南写得再好也不可能标一致。最后,把指南当代码管:版本号 + CHANGELOG + 每次改动写清影响范围,每条标注结果存
guideline_version,改了定义就重跑一次 50~100 条的小试标注。
下一节 👉 10-标注一致性怎么量.md