报出"准确率 96%"而没有标签分布,这句话等于没说。指标的争吵通常不在数学,在口径:同一个 F1,micro 与 macro 能差出二十个点;同一个 AUC,换个人算就是另一个数。下面把口径逐个拆开。
1. 混淆矩阵:所有指标的母体
预测正 预测负
实际正 TP(命中) FN(漏报)
实际负 FP(误报) TN(正确拒绝)
| 指标 | 公式 | 成立前提 |
|---|---|---|
| Precision | TP/(TP+FP) | "预测为正"稀缺才有意义,否则被稀释 |
| Recall | TP/(TP+FN) | 必须有完整标注才能确定正样本全集 |
| Specificity | TN/(TN+FP) | 负样本远多于正样本时最敏感 |
| F_beta | (1+β²)PR/(β²P+R) | β 必须固定,否则跨版本不可比 |
| Accuracy | (TP+TN)/全体 | 只有类别平衡才有解释力 |
2. P 与 R 为什么必须成对
两者方向相反:阈值调低 → FP 增、R 升、P 降。所以单指标存在退化最优解——全判为正则 Recall = 1,只判一个样本则 Precision = 1。单看任一项都能被平凡拉满,这就是必须成对的数学原因;第二层原因是代价不对称。
| 场景 | 更贵的错 | 取向 | 阈值 |
|---|---|---|---|
| 垃圾邮件拦截 | FP 误杀正常邮件 | 偏 P | 偏高 |
| 疾病初筛、风险预警 | FN 漏掉高代价样本 | 偏 R | 偏低 |
| 交易风控 | 两者都贵 | F-beta 按赔付比定 | 按金额分层 |
| 内容审核 | FP 伤创作者、FN 伤合规 | 双阈值分流 | 高置信自动、低置信人审 |
互换的代价不是线性的:Recall 从 0.90 拉到 0.95,常要拿 10 个点以上的 Precision 去换。
3. F1 为什么是调和平均
算术平均对小数不敏感,1.0 与 0.0 的平均是 0.5,看起来"还行";调和平均把小数拖到底,正确表达"一头废了整体就废"。
方案 A:P=0.90, R=0.20
算术平均 = (0.90+0.20)/2 = 0.55
F1 = 2·0.90·0.20/(0.90+0.20) ≈ 0.33
方案 B:P=0.60, R=0.54
算术平均 = 0.57
F1 = 2·0.60·0.54/(0.60+0.54) ≈ 0.57
算术平均认为 A 与 B 打平,F1 判 B 明显胜出。F1 的偏好是两头都要及格。数字仅为公式演算,不是实测。
| 平均 | 性质 | 何时误导 |
|---|---|---|
| 算术 (P+R)/2 | 线性 | 一头近 0 仍给高分 |
| 几何 √(PR) | 乘法,更严格 | 报告习惯少见 |
| 调和 F1 | 只对较低项敏感 | P、R 都 < 0.5 时仍中等 |
| F-beta | β>1 偏 R | β 一变等于换尺子 |
4. micro / macro / weighted
三类样本量:A 950、B 40、C 10。
from sklearn.metrics import precision_recall_fscore_support as prf
macro = prf(y_true, y_pred, average="macro") # 各类等权
micro = prf(y_true, y_pred, average="micro") # 全局累计 TP/FP/FN
weighted = prf(y_true, y_pred, average="weighted") # 按支持度加权
| 平均 | 算法 | 在 A950/B40/C10 下的表现 |
|---|---|---|
| micro | 先累计再算 | 被 950 条大类支配,接近 accuracy |
| macro | 每类算完等权平均 | 10 条的 C 与 950 条的 A 同权 |
| weighted | 按类样本数加权 | 小类权重被压得极低 |
macro 惩罚小类可直接演算:F1 分别为 0.96 / 0.50 / 0.30。
macro-F1 = (0.96+0.50+0.30)/3 = 0.587
weighted-F1 = (0.96·950+0.50·40+0.30·10)/1000 = 0.935
同一份预测,报 0.94 还是 0.59,只取决于 average 参数。整体吞吐看 micro;长尾业务看 macro;对齐线上分布看 weighted。
5. 多分类与多标签
| 维度 | 多分类 | 多标签 |
|---|---|---|
| 标签数 | 恰好 1 | 0 到 N |
| 混淆矩阵 | 一个 K×K 装下 | 逐标签 2×2 |
| micro 含义 | 与 accuracy 高度重合 | 标签级整体正确性 |
| 关键指标 | macro-F1、top-k | subset accuracy、Hamming loss、逐标签 F1 |
多标签的 subset accuracy 要求整条样本全部标签都判对,比逐标签平均严格得多。打标系统常出现逐标签 F1 好看、subset accuracy 只有三成的局面。
6. AUC:定义与乐观偏差
AUC 的含义是随机取一个正样本和一个负样本,模型给正样本打分更高的概率,等价于 Mann-Whitney 统计量,只看排序、与阈值无关。
AUC = Σ_{正}Σ_{负} ( 1[s_pos > s_neg] + 0.5·1[s_pos = s_neg] ) / (n_pos · n_neg)
| 正:负 | AUC 可读性 | 原因 |
|---|---|---|
| 1:1 | 与直觉一致 | 两轴样本量对等 |
| 1:10 | 尚可 | 轻微稀释 |
| 1:10000 | 明显乐观 | FPR 轴前段被单个 FP 撑住 |
演算:10,000 个负样本里只 1 个 FP 时 FPR = 0.0001,曲线横轴几乎不动,而 TPR 已可到 0.8。AUC 0.95 的模型在 1% 告警预算下可能一文不值,原因就在这里。
from sklearn.metrics import roc_auc_score, average_precision_score
auc = roc_auc_score(y_true, y_score)
ap = average_precision_score(y_true, y_score) # AUC-PR 的阶梯积分近似
top = y_score.argsort()[::-1][:200]
precision_at_k = y_true[top].mean() # 业务只处理前 K 条
7. PR 曲线为什么更适合不平衡
PR 的分母不含 TN。TN 在不平衡场景里是巨大且无信息量的数字,把它删掉,指标才不会被海量负样本稀释。
| 对比 | ROC | PR |
|---|---|---|
| 轴 | TPR vs FPR | Precision vs Recall |
| 受 TN 影响 | 强 | 无 |
| 合理基线 | 0.5 | 正样本比例 |
| 回答的问题 | 排序能力 | 告警质量 |
注意误读:正样本占 2% 时,AP = 0.10 可能已可用,拿 0.5 当及格线会误判。
8. 阈值选择的两个依据
阈值不是模型属性,是部署决策。
threshold_policy:
basis: cost_ratio # 或 target_metric
cost: {fp: 2.0, fn: 60.0}
expected_cost: "fp*FP(th) + fn*FN(th)"
target_metric: {metric: recall, floor: 0.92, tie_break: precision}
procedure:
- 在验证集上扫阈值,不在训练集
- bootstrap 给指标加置信区间
- 记录阈值随漂移的复核周期
| 依据 | 输入 | 失效情形 |
|---|---|---|
| 业务成本比 | FP/FN 单次成本 | 成本靠拍脑袋,阈值不可信 |
| 目标指标 | 硬约束如 Recall ≥ 0.92 | 约束过高只能靠人审兜底 |
| 双阈值 | 高置信自动 + 低置信人审 | 没算人工队列容量会爆 |
9. 生成式任务的指标
| 任务 | 指标 | 硬伤 |
|---|---|---|
| 分类/抽取 | P、R、F1 | 需标注,粒度固定 |
| 翻译 | BLEU | 认可改写;与人工相关性视任务 |
| 摘要 | ROUGE-N/L | 只看词面,不看事实 |
| 语义相似 | BERTScore | 对"数字错了""说法反了"不敏感 |
| 可用性 | 人工评分 | 贵、方差大,需评分卡 |
结论只有一句:生成任务必须至少保留一列人工或强判定结果。BLEU 涨而人工跌,常见原因是模型学会抄原文片段拉 n-gram 重叠。
10. 指标涨了但效果变差:四种成因
| 成因 | 机制 | 识别 |
|---|---|---|
| 数据泄漏 | 特征混入标签派生量、时间穿越 | 查管道;做时间切分 |
| 测试集污染 | 训练语料含测试原文 | n-gram/嵌入近邻去重 |
| 指标与目标错位 | 优化 F1,业务要低漏报 | 单独埋业务指标 |
| 指标被优化 | 反复对着测试集调参 | 看实验次数 vs 集大小 |
第四种最隐蔽:500 条测试集上试 50 组配置取最优,等于在测试集上训练,提升是过拟合测试集的必然结果。
11. 评测集建设
| 要点 | 做法 | 不做的后果 |
|---|---|---|
| 分层抽样 | 按类别/来源/时段分层,小类保底配额 | 少数类个位数,方差巨大 |
| 标注一致性 | 双标 + Cohen's κ 抽样核算 | 标注漂移被当成模型波动 |
| 留出集 | 只跑最终候选 | 全量集被优化污染 |
| 线上抽样 | 定期回流真实分布样本 | 集与线上逐渐脱节 |
def stratified(df, groups, n_total, min_per_group=30, seed=90):
parts = []
for key, g in df.groupby(groups):
n = max(min_per_group, int(round(n_total * len(g) / len(df))))
parts.append(g.sample(min(n, len(g)), random_state=seed))
return pd.concat(parts).sample(frac=1, random_state=seed).reset_index(drop=True)
留出集防的是人(研发过程污染),线上抽样防的是世界变了(分布漂移),两者不能互相替代。
12. 排错:六种指标异常
| 现象 | 原因 | 处理 |
|---|---|---|
| accuracy 很高但业务无效 | 正样本极少 | 补报 P/R 与基准率 |
| 两版报告差二十个点 | macro 与 micro 混用 | 报告头固定口径 |
| 离线涨线上无感 | 阈值在训练集上调 | 换验证集或时间后段 |
| 换 seed 就波动 | 单次随机种子 | 多种子取均值与方差 |
| 逐标签 F1 好、整条样本差 | 多标签只看标签级 | 补 subset accuracy |
| PR 面积 0.5 被当及格 | 基线误用 0.5 | 基线改为正样本比例 |
13. 边界:这些场景指标本就该换
| 场景 | 分类指标不够用的原因 | 该补什么 |
|---|---|---|
| 排序/推荐 | 不看位置 | NDCG@k、Recall@k、MRR |
| 长尾多类 | macro 仍被多数类噪声影响 | 每类曲线 + 混淆对分析 |
| 自由文本生成 | 词面指标不看事实 | 判定模型 + 人工抽样 |
| 强成本约束 | 指标不体现钱 | 期望成本曲线 |
| 在线系统 | 离线集覆盖不到分布 | 影子流量、A/B |
指标是尺子,口径是刻度。尺子可以换,刻度必须写死在报告抬头。
14. 自测
- 正样本占 2% 时全判为负,accuracy 是多少?为什么没有解释力?
- P=0.8/R=0.2 与 P=0.5/R=0.5,算两种平均,说明 F1 的偏好。
- 三类 950/40/10,F1 为 0.96/0.50/0.30,macro 与 weighted 各是多少?
- "随机正负样本对排序正确概率"这一定义,为什么在不平衡下会乐观?
- AUC-PR 的及格基线为什么是正样本比例?
- 500 条集上试 50 组配置取最优,指标提升说明了什么?