评估指标

本节定位
训练完模型后,怎么判断模型好不好?准确率 95% 一定好吗?不一定!选错了指标,可能做出完全错误的决策。本节帮你掌握各种场景下应该关注什么指标。
学习目标
- 掌握分类指标:准确率、精确率、召回率、F1-score、混淆矩阵
- 理解 ROC 曲线与 AUC
- 掌握回归指标:MSE、RMSE、MAE、R²
- 理解多分类评估(macro、micro、weighted)
先说一个很重要的学习预期
这一节最容易让新人学乱的地方,不是公式本身,而是:
- 指标很多
- 每个看起来都有道理
- 但第一次做项目时根本不知道先看哪个
所以这节更适合新人的第一目标不是“把所有指标背全”,而是先建立一个判断框架:
先问任务类型,再问错误代价,再选主指标。
只要这条线立住了,后面的 Accuracy、Recall、AUC、RMSE 才不会变成一堆散碎名词。
先建立一张地图
新人第一次学评估指标,最常见的问题不是“公式不会写”,而是:
- 知道很多指标名,但不知道什么时候该看哪个
- 知道模型分数高低,但不知道这和业务风险有什么关系
更稳的学习顺序应该是:
也就是说,指标不是“ 训练结束后顺手看的分数”,而是模型设计的一部分。
一、为什么准确率不够?
1.1 不平衡数据的陷阱
import numpy as np
# 假设:1000 封邮件中有 10 封是垃圾邮件
y_true = np.array([0] * 990 + [1] * 10)
# "聪明"的模型:全部预测为正常
y_pred = np.zeros(1000)
accuracy = np.mean(y_true == y_pred)
print(f"准确率: {accuracy:.1%}")
# 准确率 99%!但一封垃圾邮件都没抓到!
准确率的陷阱
在不平衡数据中,永远预测多数类就能获得很高的准确率。但这样的模型毫无用处。我们需要更精细的指标。
1.2 先别急着背指标,先问错判代价
Andrew Ng 那种机器学习课最值得借鉴的一点是:
先问错误会造成什么后果,再决定怎么评估模型。
比如:
- 癌症筛查里,漏掉病人通常比误报更危险,所以先看召回率
- 垃圾邮件过滤里,把正常邮件错判成垃圾邮件很烦,所以要盯精确率
- 欺诈检测里,两种代价都高,就要同时看召回率、精确率和阈值曲线
所以评估指标不是抽象数学题,而是在帮你回答:
- 模型到底错在哪
- 这种错法我能不能接受
二、混淆矩阵——一切分类指标的基础
2.1 四个基本量
| 预测为正(Positive) | 预测为负(Negative) | |
|---|---|---|
| 实际为正 | TP(真正例) | FN(假负例/漏报) |
| 实际为负 | FP(假正例/误报) | TN(真负例) |
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt
# 乳腺癌数据集
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, test_size=0.2, random_state=42
)
model = LogisticRegression(max_iter=10000, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
# 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(cm)
fig, ax = plt.subplots(figsize=(6, 5))
disp = ConfusionMatrixDisplay(cm, display_labels=['恶性', '良性'])
disp.plot(ax=ax, cmap='Blues')
ax.set_title('乳腺癌分类混淆矩阵')
plt.tight_layout()
plt.show()
2.2 从混淆矩阵推导指标
2.3 一个更适合新人的读法
很多人第一次看混淆矩阵,会把它当成一个要死记的表。
其实更简单的读法是:
- 先只看“真实为正”的那一行或那一列
- 再问模型到底漏掉了多少
- 然后再看“模型判为正”的那一行或那一列
- 再问这些正例里有多少是误报
这样你会自然得到两种最重要的问题:
- 漏掉了多少?这对应召回率
- 抓到的这些里有多少是真的?这对应精确率
三、分类指标详解
3.1 精确率(Precision)
Precision = TP / (TP + FP)
"模型说是正例的里面,有多少真的是正例?"
关注场景:误报代价高——如推荐系统(推错了用户体验差)、垃圾邮件检测(误判正常邮件很烦)。
3.2 召回率(Recall / Sensitivity)
Recall = TP / (TP + FN)
"真正的正例里面,模型抓到了多少?"
关注场景:漏报代价高——如疾病筛查(漏诊很危险)、欺诈检测(漏过欺诈损失大)。
3.3 F1-Score
F1 = 2 × Precision × Recall / (Precision + Recall)
精确率和召回率的调和平均。
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
print(f"准确率 (Accuracy): {accuracy_score(y_test, y_pred):.4f}")
print(f"精确率 (Precision): {precision_score(y_test, y_pred):.4f}")
print(f"召回率 (Recall): {recall_score(y_test, y_pred):.4f}")
print(f"F1-Score: {f1_score(y_test, y_pred):.4f}")
3.4 精确率 vs 召回率的权衡
from sklearn.metrics import precision_recall_curve
# 获取不同阈值下的精确率和召回率
y_proba = model.predict_proba(X_test)[:, 1]
precisions, recalls, thresholds = precision_recall_curve(y_test, y_proba)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# PR 曲线
axes[0].plot(recalls, precisions, 'b-', linewidth=2)
axes[0].set_xlabel('召回率 (Recall)')
axes[0].set_ylabel('精确率 (Precision)')
axes[0].set_title('Precision-Recall 曲线')
axes[0].grid(True, alpha=0.3)
# 阈值的影响
axes[1].plot(thresholds, precisions[:-1], 'b-', label='精确率')
axes[1].plot(thresholds, recalls[:-1], 'r-', label='召回率')
axes[1].set_xlabel('分类阈值')
axes[1].set_ylabel('分数')
axes[1].set_title('阈值对精确率/召回率的影响')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
如何选择?
- 宁可误报也不漏报(如疾病筛查)→ 优先召回率,降低阈值
- 宁可漏报也不误报(如垃圾邮件)→ 优先精确率,提高阈值
- 两者都重要 → 看 F1-Score