机器学习分类指标完全手册:从混淆矩阵到 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 分钟。配套代码:文末有完整可运行仓库。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


📖 目录

  1. 分类指标的第一性原理
  2. 混淆矩阵:所有指标的基石
  3. 准确率 Accuracy:被高估的最常用指标
  4. 精确率 Precision:别让「错抓」毁了你的推荐系统
  5. 召回率 Recall:宁可错杀一千,不可放过一个
  6. F1 分数:Precision 和 Recall 的平衡器
  7. ROC-AUC:衡量排序能力的不二选择
  8. PR-AUC:类别不平衡时的真相之眼
  9. LogLoss:概率校准的硬指标
  10. 多分类指标体系(Macro/Micro/Weighted)
  11. 类别不平衡的 5 大解决方案
  12. 阈值选择:让 F1 翻倍的隐藏参数
  13. 常见反模式与面试高频陷阱(FAQ)
  14. 实战工具箱(sklearn + matplotlib + yellowbrick)
  15. 总结:选对指标比选对模型更重要
  16. 参考文献与延伸阅读
  17. 配套实战练习(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=2P+RPR

为什么用调和平均而不是算术平均?

场景PrecisionRecall算术平均F1(调和)
11.00.00.50.0
20.50.50.50.5
30.01.00.50.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+RPR

  • β < 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)+(1y)log(1p)]

其中 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=1Nc=1Cyiclog(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:150%99%
1:1090%89%
1:10099%65%
1:100099.9%32%
1:1000099.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 阈值影响可视化

阈值PrecisionRecallF1
0.10.201.000.33
0.30.400.950.56
0.50.600.700.65
0.70.800.450.58
0.90.950.200.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) TPTNFPFN

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 指标选择决策表

业务场景首选指标辅助指标
类别平衡的二分类F1ROC-AUC、Accuracy
类别严重不平衡PR-AUCF1 (positive class)
排序质量(推荐/搜索)ROC-AUCNDCG、MAP
概率校准(金融/风控)LogLossBrier Score
多分类Macro F1Weighted 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 条黄金法则

  1. 永远同时看 ≥ 2 个指标(不要只信 Accuracy)
  2. 类别不平衡时用 PR-AUC 替代 ROC-AUC
  3. 不要默认阈值 0.5,搜索最佳阈值
  4. 多分类看 Macro F1,不要只信 Accuracy
  5. 业务指标 > 学术指标(最终评估用业务 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. 参考文献与延伸阅读

经典论文

官方文档

推荐书

视频教程

Kaggle 实战


17. 配套实战练习(5 题)

完成以下 5 题,把「读过的」变成「会用的」。

练习 1:指标骗局识别

任务:构造一个 1:1000 的不平衡数据集,分别训练:

  1. 一个「全部预测为负」模型
  2. 一个逻辑回归模型

验收:用 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 生成

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐