起因
老板问我"模型准确率 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%。
踩过的坑
-
average='binary'在多分类时报错:多分类要'macro'/'micro'/
'weighted'。'micro'在不平衡时等于 accuracy,意义有限。 -
正类标号搞反:sklearn 默认
pos_label=1。如果你的"想检测的类"
是 0,得pos_label=0否则 precision/recall 算的是另一类。 -
不看 support:F1 macro = 0.55 看着不行,但其中 90% 是稀有类
的支撑,整体 weighted F1 0.88 还可以。两个都要看。 -
直接信任 cross-validation 的 ROC-AUC:不平衡数据 + KFold(不
stratify)→ 某些 fold 几乎没正样本 → AUC 失真。永远用StratifiedKFold。 -
阈值在训练集上选:必须用验证集 / OOF 选阈值;测试集只用于最终
报告。
登录后参与评论。