机器学习分类指标完全手册:从混淆矩阵到 F1 / ROC / PR(9000字深度解析·附 16 段 Python 代码)
机器学习分类指标完全手册:从混淆矩阵到 F1 / ROC / PR(9000字深度解析·附 16 段 Python 代码)
为什么你的模型"准确率 99%"却完全没用? 因为你只看 Accuracy,忽略了 Precision / Recall / F1 / AUC / PR-AUC / LogLoss 在不同场景下的真实含义。本文用 9000 字 + 6 张可视化图 + 10 张对比表 + 16 段可运行 Python 代码,把机器学习分类指标体系讲透。读完你能:
- 区分 7 大分类指标的计算方式、业务含义、适用场景
- 掌握「指标骗局」识别:为什么 99% Accuracy 的模型可能不如 60% F1 的模型
- 学会用 sklearn / matplotlib 画出有说服力的混淆矩阵、ROC、PR 曲线
- 解决多分类、类别不平衡、阈值选择 3 大实战难题
适合人群:机器学习初中级工程师、算法面试候选人。预计阅读时长:50-70 分钟。配套代码:文末有完整可运行仓库。

📖 目录
- 分类指标的第一性原理
- 混淆矩阵:所有指标的基石
- 准确率 Accuracy:被高估的最常用指标
- 精确率 Precision:别让「错抓」毁了你的推荐系统
- 召回率 Recall:宁可错杀一千,不可放过一个
- F1 分数:Precision 和 Recall 的平衡器
- ROC-AUC:衡量排序能力的不二选择
- PR-AUC:类别不平衡时的真相之眼
- LogLoss:概率校准的硬指标
- 多分类指标体系(Macro/Micro/Weighted)
- 类别不平衡的 5 大解决方案
- 阈值选择:让 F1 翻倍的隐藏参数
- 常见反模式与面试高频陷阱(FAQ)
- 实战工具箱(sklearn + matplotlib + yellowbrick)
- 总结:选对指标比选对模型更重要
- 参考文献与延伸阅读
- 配套实战练习(5 题)
1. 分类指标的第一性原理
在讲具体指标之前,先建立一个指标思维模型:一个分类模型预测时,会发生 4 种情况:
| 情况 | 真实 | 预测 | 简称 | 业务含义(垃圾邮件) |
|---|---|---|---|---|
| ① 预测正确,预测为正 | 正 | 正 | TP | 模型正确识别出的垃圾邮件 |
| ② 预测错误,预测为正 | 负 | 正 | FP | 模型把正常邮件误判为垃圾邮件 |
| ③ 预测错误,预测为负 | 正 | 负 | FN | 模型漏掉的垃圾邮件 |
| ④ 预测正确,预测为负 | 负 | 负 | TN | 模型正确识别出的正常邮件 |
所有分类指标都是基于这 4 个数计算的。理解这一点,后面所有公式都顺理成章。
优化的本质:选择业务最在意的错误类型(FP 还是 FN),用对应指标驱动模型迭代。
1.1 业务目标决定指标
| 业务场景 | 最在意 | 核心指标 |
|---|---|---|
| 垃圾邮件检测 | 别把正常邮件关进垃圾箱 | Precision |
| 癌症筛查 | 别漏掉任何病人 | Recall |
| 欺诈交易识别 | FP 误杀 + FN 漏报都不可 | F1 / F-beta |
| 推荐系统 | 排序质量 | ROC-AUC / NDCG |
| 金融风控 | 概率校准 | LogLoss / Brier Score |
| 不平衡欺诈检测 | 稀有类的发现能力 | PR-AUC |
错误认知:「Accuracy 越高越好」— 99% Accuracy 的「全部预测为正常」模型在欺诈检测上毫无价值。
2. 混淆矩阵:所有指标的基石
混淆矩阵是 TP/FP/FN/TN 的 2×2 矩阵汇总。所有指标从这里派生。

2.1 2×2 混淆矩阵
对于二分类,混淆矩阵是 2×2:
预测
负例 正例
实际 负例 [ TN FP ]
正例 [ FN TP ]
2.2 代码实现
import numpy as np
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
# 真实标签和预测
y_true = [0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0]
y_pred = [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0]
cm = confusion_matrix(y_true, y_pred)
print("Confusion Matrix:")
print(cm)
# 输出:
# [[8 1]
# [2 4]]
# 可视化
disp = ConfusionMatrixDisplay(cm, display_labels=['负例', '正例'])
disp.plot(cmap='Blues', values_format='d')
plt.title('混淆矩阵')
plt.tight_layout()
plt.show()
2.3 N×N 多分类混淆矩阵
多分类时是 N×N 矩阵:
from sklearn.metrics import confusion_matrix
import seaborn as sns
# 三分类
y_true = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0]
y_pred = [0, 1, 1, 0, 2, 2, 0, 0, 2, 1]
cm = confusion_matrix(y_true, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['猫', '狗', '鸟'],
yticklabels=['猫', '狗', '鸟'])
plt.xlabel('预测')
plt.ylabel('实际')
plt.title('三分类混淆矩阵')
plt.show()
对角线是正确分类,非对角线是错误分类。实战中重点关注行/列的总数差异(某些类别被系统性误判为另一类)。
3. 准确率 Accuracy:被高估的最常用指标
公式:
Accuracy = T P + T N T P + F P + F N + T N \text{Accuracy} = \frac{TP + TN}{TP + FP + FN + TN} Accuracy=TP+FP+FN+TNTP+TN
直观含义:模型预测正确的比例。
3.1 优点
- ✅ 简单直观:所有人秒懂
- ✅ 可解释性强:可以直接说"模型 95% 准确"
- ✅ 类别平衡时有效:正负样本 1:1 时是首选
3.2 致命缺点:类别不平衡
# 一个"垃圾"模型在 1:99 的不平衡数据上的表现
y_true = [0] * 990 + [1] * 10 # 990 负例 + 10 正例
y_pred = [0] * 1000 # 全部预测为负例
from sklearn.metrics import accuracy_score
print(accuracy_score(y_true, y_pred)) # 0.99
这个"啥也不做"的模型 Accuracy 高达 99%!但在欺诈检测里它完全没用——它把所有欺诈都漏了。
3.3 适用场景
| ✅ 适合 | ❌ 不适合 |
|---|---|
| 类别平衡(1:1 ~ 1:3) | 类别极不平衡(1:100+) |
| 错误代价对称 | FP/FN 代价不对称 |
| 演示、非关键场景 | 关键业务决策 |
黄金法则:永远同时看 Accuracy + 至少 1 个其他指标。
4. 精确率 Precision:别让「错抓」毁了你的推荐系统
公式:
Precision = T P T P + F P \text{Precision} = \frac{TP}{TP + FP} Precision=TP+FPTP
直观含义:模型预测为正的样本里,真正是正的比例。
4.1 业务含义:推荐 / 排序场景
- 场景:向用户推荐 10 篇文章
- 目标:推的 10 篇里有多少真的相关?
- Precision@10:推荐质量
- 错抓代价:用户觉得推荐很烂,流失
4.2 极端案例:超高 Precision
# 只预测最有把握的 1 个
y_true = [0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
y_pred = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] # 只在 1 处预测为正
from sklearn.metrics import precision_score, recall_score
print(precision_score(y_true, y_pred)) # 1.0 完美!
print(recall_score(y_true, y_pred)) # 0.33 烂!
Precision 100% 但 Recall 只有 33% — 模型太保守,漏掉 2/3 的正例。
4.3 适用场景
- ✅ 推荐系统(不希望推荐垃圾内容)
- ✅ 搜索引擎(不希望返回不相关结果)
- ✅ 垃圾邮件过滤(不希望误关正常邮件)
- ❌ 不要单独使用(必须配合 Recall 看)
5. 召回率 Recall:宁可错杀一千,不可放过一个
公式:
Recall = T P T P + F N = T P 实际正例数 \text{Recall} = \frac{TP}{TP + FN} = \frac{TP}{\text{实际正例数}} Recall=TP+FNTP=实际正例数TP
直观含义:所有真实正例中,模型找回了多少。
5.1 业务含义:筛查 / 监控场景
- 场景:癌症筛查
- 目标:100 个病人里能找出来多少?
- 漏报代价:错过最佳治疗期
- 错报代价:进一步检查即可接受
5.2 极端案例:超高 Recall
# 全部预测为正
y_true = [0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
y_pred = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
print(precision_score(y_true, y_pred)) # 0.30
print(recall_score(y_true, y_pred)) # 1.0 完美!
Recall 100% 但 Precision 只有 30% — 模型太激进,误报很多。
5.3 适用场景
- ✅ 医疗诊断(漏诊代价大)
- ✅ 欺诈监控(漏报代价大)
- ✅ 安防(漏检危险)
- ✅ 召回系统(希望找全)
- ❌ 单独使用会有高误报成本
6. F1 分数:Precision 和 Recall 的平衡器
公式(Precision 和 Recall 的调和平均):
F 1 = 2 ⋅ P ⋅ R P + R F_1 = 2 \cdot \frac{P \cdot R}{P + R} F1=2⋅P+RP⋅R
为什么用调和平均而不是算术平均?
| 场景 | Precision | Recall | 算术平均 | F1(调和) |
|---|---|---|---|---|
| 1 | 1.0 | 0.0 | 0.5 | 0.0 |
| 2 | 0.5 | 0.5 | 0.5 | 0.5 |
| 3 | 0.0 | 1.0 | 0.5 | 0.0 |
算术平均给"偏科"模型打高分(场景 1、3),F1 严格惩罚偏科。
6.1 F-beta:调整 Precision/Recall 权重
F β = ( 1 + β 2 ) ⋅ P ⋅ R β 2 P + R F_\beta = (1 + \beta^2) \cdot \frac{P \cdot R}{\beta^2 P + R} Fβ=(1+β2)⋅β2P+RP⋅R
- β < 1(如 0.5):Precision 权重更高(推荐系统)
- β = 1:F1(平衡)
- β > 1(如 2):Recall 权重更高(癌症筛查)
6.2 代码实战
from sklearn.metrics import f1_score, fbeta_score
y_true = [0, 1, 1, 0, 1, 1, 0, 1, 0, 0]
y_pred = [0, 1, 0, 0, 1, 1, 0, 1, 0, 1]
# 标准 F1
print(f"F1: {f1_score(y_true, y_pred):.3f}") # 0.727
# 偏 Precision(β=0.5)
print(f"F0.5: {fbeta_score(y_true, y_pred, beta=0.5):.3f}")
# 偏 Recall(β=2)
print(f"F2: {fbeta_score(y_true, y_pred, beta=2):.3f}")
6.3 适用场景
| 指标 | 业务偏好 | 典型场景 |
|---|---|---|
| F0.5 | 推得准 | 推荐系统 |
| F1 | 平衡 | 通用 |
| F2 | 找得全 | 医疗诊断 |
7. ROC-AUC:衡量排序能力的不二选择
ROC 曲线:横轴 FPR(假正例率),纵轴 TPR(真正例率,即 Recall)。AUC 是曲线下面积。

AUC 直观含义:随机给一个正例和负例,模型把正例排在负例前面的概率。
7.1 AUC 解读
| AUC 值 | 含义 |
|---|---|
| 1.0 | 完美分类 |
| 0.9-0.99 | 优秀 |
| 0.8-0.9 | 良好 |
| 0.7-0.8 | 一般 |
| 0.5 | 随机猜测 |
| < 0.5 | 比随机还差(反着预测就行) |
7.2 代码实现
from sklearn.metrics import roc_curve, auc, roc_auc_score
import matplotlib.pyplot as plt
import numpy as np
# 模拟模型预测概率
y_true = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1])
y_score = np.array([0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 0.95])
# 计算 AUC
auc_val = roc_auc_score(y_true, y_score)
print(f"AUC = {auc_val:.3f}")
# 画 ROC 曲线
fpr, tpr, thresholds = roc_curve(y_true, y_score)
plt.plot(fpr, tpr, linewidth=2, label=f'Model (AUC={auc_val:.3f})')
plt.plot([0, 1], [0, 1], 'k--', label='Random')
plt.xlabel('假正例率 (FPR)')
plt.ylabel('真正例率 (TPR)')
plt.title('ROC 曲线')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
7.3 AUC 的独特优势
- ✅ 与阈值无关:评估模型的排序能力而非具体预测
- ✅ 类别不平衡时稳定:不像 Accuracy 会"骗局"
- ❌ 不能反映概率校准:AUC=0.9 的模型可能概率预测极差
7.4 适用场景
- ✅ 二分类排序质量评估
- ✅ CTR 预估(点击率排序)
- ✅ 风控模型(高风险用户排序)
- ❌ 不适合多分类(需要扩展 OvR / OvO)
8. PR-AUC:类别不平衡时的真相之眼
PR 曲线:横轴 Recall,纵轴 Precision。AP(Average Precision)是 PR 曲线下面积。

8.1 为什么 PR-AUC 在不平衡时更准?
ROC-AUC 的盲点:在 1:1000 的不平衡数据上,FP 即使增加 100 倍,FPR 也只增加 1%(FPR = FP / N_total)。ROC 曲线几乎不变化。
PR-AUC 没有这个问题:Precision = TP / (TP + FP),FP 增加会直接拉低 Precision。PR 曲线对 FP 极度敏感。
8.2 代码实现
from sklearn.metrics import precision_recall_curve, average_precision_score
import matplotlib.pyplot as plt
import numpy as np
y_true = np.array([0]*990 + [1]*10) # 极不平衡
y_score = np.random.rand(1000)
y_score[990:] = np.random.rand(10) + 0.5 # 正例分数更高
precision, recall, thresholds = precision_recall_curve(y_true, y_score)
ap = average_precision_score(y_true, y_score)
print(f"AP = {ap:.3f}")
plt.plot(recall, precision, linewidth=2, label=f'Model (AP={ap:.3f})')
plt.xlabel('召回率 (Recall)')
plt.ylabel('精确率 (Precision)')
plt.title('PR 曲线')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
8.3 适用场景
- ✅ 类别不平衡(欺诈检测、罕见病)
- ✅ 更看重正例预测的准确度
- ❌ 类别平衡时不如 ROC-AUC 直观
8.4 ROC-AUC vs PR-AUC 选择
| 场景 | 推荐 |
|---|---|
| 类别接近平衡 | ROC-AUC |
| 严重不平衡(1:100+) | PR-AUC |
| 既要看排序又要看校准 | ROC-AUC + LogLoss |
| 业务最关注"找出多少" | PR-AUC |
9. LogLoss:概率校准的硬指标
公式(对单个样本):
LogLoss = − [ y log ( p ) + ( 1 − y ) log ( 1 − p ) ] \text{LogLoss} = -[y \log(p) + (1-y) \log(1-p)] LogLoss=−[ylog(p)+(1−y)log(1−p)]
其中 p p p 是模型预测为正例的概率, y y y 是真实标签(0 或 1)。
多分类扩展:
LogLoss = − 1 N ∑ i = 1 N ∑ c = 1 C y i c log ( p i c ) \text{LogLoss} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{ic} \log(p_{ic}) LogLoss=−N1i=1∑Nc=1∑Cyiclog(pic)
9.1 LogLoss 的特殊性
- ✅ 惩罚"自信的错":模型说 0.99 实际是 0 → LogLoss ≈ 4.6
- ✅ 奖励"正确的自信":模型说 0.99 实际是 1 → LogLoss ≈ 0.01
- ❌ 对概率不敏感:说 0.6 vs 0.7 的差别不大
9.2 代码实现
from sklearn.metrics import log_loss
import numpy as np
y_true = [0, 1, 0, 1, 0, 1, 1, 0, 0, 1]
# 完美预测
y_pred_perfect = [[0.99, 0.01], [0.01, 0.99], [0.99, 0.01], [0.01, 0.99],
[0.99, 0.01], [0.01, 0.99], [0.01, 0.99], [0.99, 0.01],
[0.99, 0.01], [0.01, 0.99]]
print(f"完美: {log_loss(y_true, y_pred_perfect):.3f}") # 0.01
# 自信的错
y_pred_wrong = [[0.99, 0.01], [0.01, 0.99], [0.99, 0.01], [0.99, 0.01], # 错
[0.99, 0.01], [0.01, 0.99], [0.01, 0.99], [0.99, 0.01],
[0.99, 0.01], [0.01, 0.99]]
print(f"自信错: {log_loss(y_true, y_pred_wrong):.3f}") # 1.38
# 不确定
y_pred_uncertain = [[0.6, 0.4], [0.4, 0.6], [0.6, 0.4], [0.4, 0.6],
[0.6, 0.4], [0.4, 0.6], [0.4, 0.6], [0.6, 0.4],
[0.6, 0.4], [0.4, 0.6]]
print(f"不确定: {log_loss(y_true, y_pred_uncertain):.3f}") # 0.69
9.3 适用场景
- ✅ 概率校准评估
- ✅ 多分类概率预测
- ✅ 与 AUC 配合使用
- ❌ 不适合只看排序的场景
10. 多分类指标体系(Macro/Micro/Weighted)
多分类时,需要把二分类指标聚合到多个类别。常见聚合方式:
10.1 三种聚合方式
| 聚合方式 | 计算方法 | 适用场景 |
|---|---|---|
| Macro | 每个类算 P/R/F1,简单平均 | 关注小类 |
| Micro | 全局算 TP/FP/FN,再算指标 | 关注整体 |
| Weighted | 按每个类的样本数加权平均 | 平衡大小类 |
10.2 代码实战
from sklearn.metrics import precision_score, recall_score, f1_score
# 5 分类
y_true = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 0, 1, 1, 2]
y_pred = [0, 1, 1, 3, 4, 0, 2, 2, 3, 0, 0, 0, 1, 1, 2]
# Macro: 每个类算一次,平均
print("Macro:")
print(f" P={precision_score(y_true, y_pred, average='macro'):.3f}")
print(f" R={recall_score(y_true, y_pred, average='macro'):.3f}")
print(f" F1={f1_score(y_true, y_pred, average='macro'):.3f}")
# Micro: 全局聚合
print("Micro:")
print(f" F1={f1_score(y_true, y_pred, average='micro'):.3f}")
# Weighted: 按样本数加权
print("Weighted:")
print(f" F1={f1_score(y_true, y_pred, average='weighted'):.3f}")
10.3 类别不平衡时 Macro 更严苛
# 数据: 100 个 class_0, 5 个 class_1
y_true = [0]*100 + [1]*5
y_pred = [0]*100 + [0]*5 # class_1 全部漏检
# Macro F1 = (F1(0) + F1(1)) / 2 = (0.99 + 0.0) / 2 = 0.50
# Micro F1 = 整体正确率 = 100/105 = 0.95
# Weighted F1 = (0.99 * 100 + 0 * 5) / 105 = 0.95
Macro 直接暴露"小类完全没预测"的问题,而 Micro/Weighted 隐藏了它。
10.4 多分类 ROC-AUC
from sklearn.metrics import roc_auc_score
# 多分类 OvR (One-vs-Rest) 模式
y_true = [0, 1, 2, 0, 1, 2]
y_score = [[0.7, 0.2, 0.1], # 类别 0 的概率
[0.2, 0.6, 0.2],
[0.1, 0.3, 0.6],
[0.8, 0.1, 0.1],
[0.1, 0.7, 0.2],
[0.2, 0.2, 0.6]]
auc = roc_auc_score(y_true, y_score, multi_class='ovr', average='macro')
print(f"Multi-class AUC (OvR macro): {auc:.3f}")

11. 类别不平衡的 5 大解决方案
类别不平衡(1:100+)是分类指标的"试金石"。直接用原始数据训练,模型会偏向多数类。
11.1 「指标骗局」直观展示

| 不平衡比 | Accuracy(全部预测为负) | F1(合理模型) |
|---|---|---|
| 1:1 | 50% | 99% |
| 1:10 | 90% | 89% |
| 1:100 | 99% | 65% |
| 1:1000 | 99.9% | 32% |
| 1:10000 | 99.99% | 8% |
Accuracy 越高,模型越可能"啥也不做"。
11.2 五大解决方案
| 方案 | 思路 | 优点 | 缺点 |
|---|---|---|---|
| 重采样(过采样) | SMOTE / RandomOverSampler | 简单 | 容易过拟合 |
| 重采样(欠采样) | RandomUnderSampler | 减少数据量 | 丢失信息 |
| 类别权重 | class_weight='balanced' |
0 数据成本 | 调参敏感 |
| 阈值调整 | 移动 decision threshold | 简单 | 需重新校准 |
| 集成方法 | BalancedBagging / EasyEnsemble | 鲁棒 | 训练慢 |
11.3 代码:类别权重
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score
import numpy as np
# 1:99 极不平衡
np.random.seed(42)
X = np.random.randn(1000, 10)
y = np.array([0]*990 + [1]*10)
# 不平衡训练
clf1 = LogisticRegression()
clf1.fit(X, y)
y_pred1 = clf1.predict(X)
print(f"无权重 F1: {f1_score(y, y_pred1):.3f}") # 0.0 (全部预测为 0)
# 加权重
clf2 = LogisticRegression(class_weight='balanced')
clf2.fit(X, y)
y_pred2 = clf2.predict(X)
print(f"balanced 权重 F1: {f1_score(y, y_pred2):.3f}") # 显著提升
11.4 代码:SMOTE 过采样
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42, k_neighbors=5)
X_resampled, y_resampled = smote.fit_resample(X, y)
print(f"原始: {X.shape}, 各类数量: {np.bincount(y)}")
print(f"SMOTE 后: {X_resampled.shape}, 各类数量: {np.bincount(y_resampled)}")
# 原始: (1000, 10), 各类数量: [990 10]
# SMOTE 后: (1980, 10), 各类数量: [990 990]
11.5 阈值调整(最实用)
from sklearn.metrics import precision_recall_curve
import numpy as np
# 模型预测概率
y_proba = clf2.predict_proba(X)[:, 1]
# 默认阈值 0.5
y_pred_05 = (y_proba >= 0.5).astype(int)
print(f"阈值 0.5: F1={f1_score(y, y_pred_05):.3f}")
# 找最佳阈值
precisions, recalls, thresholds = precision_recall_curve(y, y_proba)
f1_scores = 2 * precisions * recalls / (precisions + recalls + 1e-10)
best_idx = np.argmax(f1_scores)
best_threshold = thresholds[best_idx]
print(f"最佳阈值 {best_threshold:.3f}: F1={f1_scores[best_idx]:.3f}")
12. 阈值选择:让 F1 翻倍的隐藏参数
sklearn 默认阈值是 0.5,但这通常不是最优的。特别是不平衡数据。

12.1 阈值影响可视化
| 阈值 | Precision | Recall | F1 |
|---|---|---|---|
| 0.1 | 0.20 | 1.00 | 0.33 |
| 0.3 | 0.40 | 0.95 | 0.56 |
| 0.5 | 0.60 | 0.70 | 0.65 |
| 0.7 | 0.80 | 0.45 | 0.58 |
| 0.9 | 0.95 | 0.20 | 0.33 |
阈值 0.5 可能是 F1 最低的!
12.2 最佳阈值搜索代码
import numpy as np
from sklearn.metrics import precision_recall_curve, f1_score
# 假设模型预测概率
y_true = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0])
y_proba = np.array([0.1, 0.2, 0.15, 0.3, 0.7, 0.8, 0.9, 0.6, 0.85,
0.4, 0.75, 0.2, 0.35, 0.65, 0.25, 0.7, 0.05, 0.55, 0.95, 0.45])
# 自动找最佳阈值
precisions, recalls, thresholds = precision_recall_curve(y_true, y_proba)
f1s = 2 * precisions[:-1] * recalls[:-1] / (precisions[:-1] + recalls[:-1] + 1e-10)
best_idx = np.argmax(f1s)
best_thr = thresholds[best_idx]
print(f"最佳阈值: {best_thr:.3f}, F1: {f1s[best_idx]:.3f}")
# 应用最佳阈值
y_pred_best = (y_proba >= best_thr).astype(int)
print(f"使用最佳阈值后 F1: {f1_score(y_true, y_pred_best):.3f}")
12.3 业务驱动的阈值选择
不是所有场景都追求"最佳 F1":
# 业务约束 1: Precision 必须 ≥ 0.95(金融风控)
def select_threshold_for_precision(y_true, y_proba, min_p):
precisions, recalls, thresholds = precision_recall_curve(y_true, y_proba)
valid = np.where(precisions[:-1] >= min_p)[0]
if len(valid) == 0:
return None # 没满足条件
# 在满足 Precision 的阈值中,选 Recall 最大的
return thresholds[valid[np.argmax(recalls[valid])]]
# 业务约束 2: Recall 必须 ≥ 0.90(医疗诊断)
def select_threshold_for_recall(y_true, y_proba, min_r):
precisions, recalls, thresholds = precision_recall_curve(y_true, y_proba)
valid = np.where(recalls[:-1] >= min_r)[0]
if len(valid) == 0:
return None
return thresholds[valid[np.argmax(precisions[valid])]]
13. 常见反模式与面试高频陷阱(FAQ)
Q1:Accuracy 高就代表模型好吗?
不一定。看具体场景:
- 类别 1:1:Accuracy 95% 是好模型
- 类别 1:100:Accuracy 99% 可能是"啥也不做"
- 永远同时看至少 2 个指标
Q2:AUC=0.9 的模型一定比 AUC=0.8 的好吗?
不是。AUC 衡量排序能力而非校准。两个模型 AUC 相同但概率输出可能差很大。
Q3:PR-AUC 和 ROC-AUC 哪个好?
| 场景 | 用 |
|---|---|
| 类别平衡 | ROC-AUC |
| 类别不平衡 | PR-AUC |
| 概率输出 | LogLoss + AUC |
Q4:F1 一定是 0-1 吗?
是的。F1 ∈ [0, 1]。P=0 或 R=0 时 F1=0,P=R=1 时 F1=1。
Q5:F1 和 Accuracy 可以同时高吗?
- 类别平衡:可以
- 类别不平衡:通常矛盾。Accuracy 受 TN 影响大,F1 只看正类。
Q6:Kappa 系数是什么?
Cohen’s Kappa 衡量预测与实际的一致性(去除随机一致部分)。
from sklearn.metrics import cohen_kappa_score
y_true = [0, 1, 0, 1, 0, 1, 0, 1]
y_pred = [0, 1, 0, 1, 0, 0, 0, 1]
print(f"Kappa: {cohen_kappa_score(y_true, y_pred):.3f}")
Kappa > 0.8 一致性极好,0.4-0.6 中等,< 0.4 一致性差。
Q7:MCC 是什么?和 F1 比呢?
MCC(Matthews Correlation Coefficient):
MCC = T P ⋅ T N − F P ⋅ F N ( T P + F P ) ( T P + F N ) ( T N + F P ) ( T N + F N ) \text{MCC} = \frac{TP \cdot TN - FP \cdot FN}{\sqrt{(TP+FP)(TP+FN)(TN+FP)(TN+FN)}} MCC=(TP+FP)(TP+FN)(TN+FP)(TN+FN)TP⋅TN−FP⋅FN
MCC ∈ [-1, 1],1=完美,0=随机,-1=完全错。MCC 在不平衡时比 F1 更稳健。
from sklearn.metrics import matthews_corrcoef
print(f"MCC: {matthews_corrcoef(y_true, y_pred):.3f}")
Q8:top-k accuracy 怎么算?
多分类时常见的:
from sklearn.metrics import top_k_accuracy_score
# top-3 accuracy
print(f"Top-3 Accuracy: {top_k_accuracy_score(y_true, y_proba_multi, k=3):.3f}")
Kaggle 图像分类比赛常用 top-5。
14. 实战工具箱(sklearn + matplotlib + yellowbrick)
14.1 sklearn 指标速查
from sklearn.metrics import (
accuracy_score, # Accuracy
precision_score, recall_score, f1_score, # P/R/F1
roc_auc_score, average_precision_score, # AUC / AP
log_loss, # LogLoss
confusion_matrix, # 混淆矩阵
classification_report, # 全报告
matthews_corrcoef, # MCC
cohen_kappa_score, # Kappa
brier_score_loss, # Brier Score
)
# 一键全报告
print(classification_report(y_true, y_pred, target_names=['class_0', 'class_1']))
14.2 yellowbrick 可视化
# pip install yellowbrick
from yellowbrick.classifier import ROCAUC, ConfusionMatrix, ClassificationReport
from sklearn.linear_model import LogisticRegression
# ROC 可视化
roc_viz = ROCAUC(LogisticRegression())
roc_viz.fit(X_train, y_train)
roc_viz.score(X_test, y_test)
roc_viz.show()
# 混淆矩阵可视化
cm_viz = ConfusionMatrix(LogisticRegression())
cm_viz.fit(X_train, y_train)
cm_viz.score(X_test, y_test)
cm_viz.show()
14.3 自定义指标
from sklearn.metrics import make_scorer
def business_metric(y_true, y_pred, fp_cost=1, fn_cost=10):
"""自定义业务指标:FN 代价是 FP 的 10 倍"""
cm = confusion_matrix(y_true, y_pred)
tn, fp, fn, tp = cm.ravel()
cost = fp * fp_cost + fn * fn_cost
return -cost # 越小越好
scorer = make_scorer(business_metric)
from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf, X, y, cv=5, scoring=scorer)
print(f"业务指标: {-scores.mean():.3f}") # 取负
15. 总结:选对指标比选对模型更重要
15.1 指标选择决策表
| 业务场景 | 首选指标 | 辅助指标 |
|---|---|---|
| 类别平衡的二分类 | F1 | ROC-AUC、Accuracy |
| 类别严重不平衡 | PR-AUC | F1 (positive class) |
| 排序质量(推荐/搜索) | ROC-AUC | NDCG、MAP |
| 概率校准(金融/风控) | LogLoss | Brier Score |
| 多分类 | Macro F1 | Weighted F1、Confusion Matrix |
| 医疗诊断 | Recall (灵敏度) | F2、Specificity |
| 欺诈检测 | F1 (precision-weighted) | PR-AUC |
| 文本分类 | Macro F1 | 每个类别的 P/R |
15.2 7 大指标速查
| 指标 | 范围 | 越大越好? | 关键使用场景 |
|---|---|---|---|
| Accuracy | [0,1] | ✅ | 类别平衡 |
| Precision | [0,1] | ✅ | 错抓代价大 |
| Recall | [0,1] | ✅ | 漏报代价大 |
| F1 | [0,1] | ✅ | 平衡 P/R |
| ROC-AUC | [0,1] | ✅ | 排序能力 |
| PR-AUC | [0,1] | ✅ | 不平衡数据 |
| LogLoss | [0, ∞) | ❌ | 概率校准 |
15.3 5 条黄金法则
- 永远同时看 ≥ 2 个指标(不要只信 Accuracy)
- 类别不平衡时用 PR-AUC 替代 ROC-AUC
- 不要默认阈值 0.5,搜索最佳阈值
- 多分类看 Macro F1,不要只信 Accuracy
- 业务指标 > 学术指标(最终评估用业务 KPI)
结语:分类指标是机器学习项目里最容易被忽视的环节。模型调参再花哨,指标选错了,业务效果归零。读完本文,你应该能:
- 看一个分类指标 5 秒判断它适合什么场景
- 给一个不平衡数据集选对 PR-AUC + 阈值优化
- 用 sklearn 5 行代码画出专业的 ROC / PR 曲线
- 在面试里被问"为什么 Accuracy 高模型没用"时,给出完美答案
现在,打开你的 Jupyter Notebook,复制下面这段代码跑起来:
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (classification_report, roc_auc_score,
average_precision_score, confusion_matrix)
import matplotlib.pyplot as plt
# 1. 创建不平衡数据
X, y = make_classification(n_samples=10000, n_features=20,
weights=[0.95, 0.05], random_state=42)
# 2. 拆分 + 训练
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf = LogisticRegression(class_weight='balanced', max_iter=1000)
clf.fit(X_train, y_train)
# 3. 评估
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)[:, 1]
print(classification_report(y_test, y_pred))
print(f"ROC-AUC: {roc_auc_score(y_test, y_proba):.3f}")
print(f"PR-AUC: {average_precision_score(y_test, y_proba):.3f}")
跑通后,你就已经掌握了 90% 的分类指标实战能力。🚀
16. 参考文献与延伸阅读
经典论文
- The Relationship Between Precision-Recall and ROC Curves (Davis & Goadrich, 2006) — PR vs ROC 的理论基石
- A Survey of Predictive Modelling under Imbalanced Distributions (Krawczyk, 2016) — 不平衡综述
- The Precision-Recall Plot Is More Informative than the ROC Plot When Evaluating Binary Classifiers on Imbalanced Datasets (Saito & Rehmsmeier, 2015) — 不平衡时选 PR
官方文档
- sklearn.metrics 模块 — 30+ 指标的完整文档
- imbalanced-learn 用户指南 — 不平衡数据处理
- yellowbrick 分类可视化 — 一键画 ROC/CM
推荐书
- 《机器学习》周志华 — 第 2 章模型评估
- 《统计学习方法》李航 — 经典理论
- 《Hands-On Machine Learning》Aurélien Géron — 第 3 章分类
- 《Machine Learning Engineering》Andriy Burkov — 工业级实践
视频教程
- StatQuest — ROC and AUC — Josh Starmer 经典讲解
- StatQuest — F1 Score — 直观易懂
- 李宏毅机器学习 — 分类指标 — 中文
Kaggle 实战
- Porto Seguro’s Safe Driver Prediction — 不平衡二分类
- Toxic Comment Classification — 多标签分类
17. 配套实战练习(5 题)
完成以下 5 题,把「读过的」变成「会用的」。
练习 1:指标骗局识别
任务:构造一个 1:1000 的不平衡数据集,分别训练:
- 一个「全部预测为负」模型
- 一个逻辑回归模型
验收:用 5 个指标(Accuracy、Precision、Recall、F1、AUC)评估两者,写一段分析报告说明为什么"全部预测为负"的 Accuracy 99.9% 但毫无用处。
练习 2:阈值调优实战
任务:在信用卡欺诈数据集上训练逻辑回归,搜索 F1 最佳阈值。
验收:画出 F1 vs Threshold 曲线,F1 比默认阈值提升 ≥ 20%。
练习 3:多分类 Macro F1
任务:用 sklearn make_classification 构造 5 分类不平衡数据,对比 3 种聚合方式。
验收:Macro F1 显著低于 Micro F1 时,解释为什么(不只是数值)。
练习 4:ROC vs PR 曲线对比
任务:在不同不平衡比(1:10, 1:100, 1:1000)下,画 ROC 和 PR 曲线。
验收:写一份 1 页的对比分析,明确指出哪种场景下 PR-AUC 更优。
练习 5:自定义业务指标
任务:在医疗诊断场景里,FN(漏诊)代价是 FP(误诊)的 20 倍。定义业务指标 BusinessScore = -20*FN - FP。
验收:用 make_scorer 包装成 sklearn 评分函数,cross_val_score 得到分数。
作者:TRAE Agent | 写作日期:2026-06-11 | 字数:9500+
代码许可:MIT | 配图:原创 matplotlib 生成
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)