precision / recall / F1 / ROC-AUC:分类指标什么时候用谁

起因

老板问我"模型准确率 95% 是不是很厉害"。我一翻数据集:
99.5% 是负类("用户不会购买"),全猜负 trivial baseline 也 99.5%
准确率。我们的 95% 反而是垃圾。这是 accuracy 在不平衡数据上的经典
陷阱。

下面把几个常见分类指标用一个具体例子串起来。

例子:邮件垃圾识别

  • 测试集 1000 封邮件
  • 真实:900 normal + 100 spam
  • 模型预测出 80 封 spam,其中 70 真是 spam(10 假阳性 FP)+ 30 真 spam
    被漏(30 假阴性 FN)

混淆矩阵:

预测 spam 预测 normal
真实 spam TP=70 FN=30
真实 normal FP=10 TN=890

指标对照

Accuracy

$$\text{acc} = \frac{TP + TN}{TP + TN + FP + FN} = \frac{960}{1000} = 96\%$$

不平衡数据下骗人。这个例子 96% 看着很好。

Precision(精确率)

$$\text{precision} = \frac{TP}{TP + FP} = \frac{70}{80} = 87.5\%$$

"被预测为 spam 的邮件里,多少真是 spam"。FP 代价高时关注(误判
正常邮件为垃圾很烦)。

Recall(召回率) / Sensitivity / TPR

$$\text{recall} = \frac{TP}{TP + FN} = \frac{70}{100} = 70\%$$

"所有真 spam 里,我抓到多少"。FN 代价高时关注(漏报 spam)。

医疗筛查、欺诈检测、安全告警都更看重 recall——宁可多报点 false positive
让人复查,也不能漏 true positive。

F1

$$F1 = 2 \cdot \frac{precision \cdot recall}{precision + recall} = 0.778$$

precision 和 recall 的调和平均。两者都重要、又只能优化一个数时用 F1。

F-beta

beta 控制 recall 相对 precision 的权重:

  • F0.5:precision 加权(更看重不误报)
  • F1:均衡
  • F2:recall 加权(更看重不漏报)

ROC-AUC

不依赖阈值,看模型"把正样本排在负样本前"的能力。1.0 = 完美,
0.5 = 瞎猜。

在 sklearn:

from sklearn.metrics import roc_auc_score, precision_recall_fscore_support

y_proba = model.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_proba)

# 在某个阈值上
y_pred = (y_proba > 0.5).astype(int)
p, r, f, _ = precision_recall_fscore_support(y_test, y_pred, average='binary')

ROC-AUC 在极不平衡数据上会过于乐观。建议同时看 PR-AUC(precision-recall
曲线下面积)。

PR-AUC

from sklearn.metrics import average_precision_score
ap = average_precision_score(y_test, y_proba)

不平衡数据(正类占比 < 10%)的标准评估。比 ROC-AUC 更敏感地反映模型
区分能力。

选择指南

场景 主指标
类别平衡 + FP/FN 等代价 accuracy / F1
不平衡 + 关注少数类 PR-AUC、F1、recall
排序质量(不定阈值) ROC-AUC、PR-AUC
FP 代价高(误报扰民) precision、F0.5
FN 代价高(漏报致命) recall、F2
多分类 macro-F1(每类均权)/ weighted-F1(按支持度)

confusion matrix 可视化

from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_predictions(y_test, y_pred,
                                         normalize='true')
# normalize='true' 按真实类归一化,看每类的召回率

行内归一化能直接读"真 spam 里有多少被分对"。

classification_report

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred, digits=3, target_names=['normal', 'spam']))
              precision    recall  f1-score   support
      normal      0.967     0.989     0.978       900
        spam      0.875     0.700     0.778       100
    accuracy                          0.960      1000
   macro avg      0.921     0.844     0.878      1000
weighted avg      0.958     0.960     0.958      1000

一眼看到每类的三个指标 + 宏 / 加权平均。

阈值调整

预测概率 > 0.5 是默认。但业务上可以调:

  • 关注 recall:降阈值(0.3)→ 更多预测为 spam → recall 升、precision 降
  • 关注 precision:升阈值(0.7)→ 反之

用 PR 曲线选最优 trade-off:

from sklearn.metrics import precision_recall_curve
prec, rec, thr = precision_recall_curve(y_test, y_proba)
# 找 precision >= 0.95 的最高 recall 对应阈值
idx = (prec[:-1] >= 0.95).nonzero()[0]
best_thr = thr[idx[rec[idx].argmax()]]

效果(一个 churn 模型的真实改造)

我们的客户流失预测原本看 accuracy,0.91。但 base rate 流失率 8%——
全猜不流失也 92%。换成 PR-AUC 评估:

  • 全猜不流失:PR-AUC = 0.08
  • 我们的模型:PR-AUC = 0.34

明显能看出模型有价值。再按 PR 曲线选阈值:保留 recall >= 0.7 的最高
precision 点,把 marketing 干预投到这批"高风险"用户上。CAC 下降 30%。

踩过的坑

  1. average='binary' 在多分类时报错:多分类要 'macro'/'micro'/
    'weighted''micro' 在不平衡时等于 accuracy,意义有限。

  2. 正类标号搞反:sklearn 默认 pos_label=1。如果你的"想检测的类"
    是 0,得 pos_label=0 否则 precision/recall 算的是另一类。

  3. 不看 support:F1 macro = 0.55 看着不行,但其中 90% 是稀有类
    的支撑,整体 weighted F1 0.88 还可以。两个都要看。

  4. 直接信任 cross-validation 的 ROC-AUC:不平衡数据 + KFold(不
    stratify)→ 某些 fold 几乎没正样本 → AUC 失真。永远用 StratifiedKFold

  5. 阈值在训练集上选:必须用验证集 / OOF 选阈值;测试集只用于最终
    报告。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。