机器学习分类指标完全手册:从混淆矩阵到 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

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

场景 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+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: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) 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 指标选择决策表

业务场景 首选指标 辅助指标
类别平衡的二分类 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 条黄金法则

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

更多推荐