训练机器学习模型后,必须回答一个问题:模型到底好不好?很多人第一反应是看准确率,但准确率并不总是可靠。尤其在类别不均衡、漏判成本高或误判成本高的业务中,只看准确率可能得到完全错误的结论。

本文介绍分类任务中常用指标:准确率、精确率、召回率、F1、混淆矩阵,并结合 Scikit-learn 给出计算方式。

混淆矩阵

以二分类为例,模型预测结果可以分为四类:

  • TP:真实为正,预测为正。
  • TN:真实为负,预测为负。
  • FP:真实为负,预测为正。
  • FN:真实为正,预测为负。

例如垃圾邮件识别中,垃圾邮件是正类。把垃圾邮件识别为垃圾邮件是 TP;把正常邮件误判为垃圾邮件是 FP;把垃圾邮件漏掉是 FN。

Scikit-learn 可以直接计算混淆矩阵:

1
2
3
4
5
6
7
from sklearn.metrics import confusion_matrix

y_true = [1, 0, 1, 1, 0, 0, 1]
y_pred = [1, 0, 0, 1, 0, 1, 1]

matrix = confusion_matrix(y_true, y_pred)
print(matrix)

输出中的行通常表示真实类别,列表示预测类别。

准确率

准确率表示预测正确的样本占全部样本的比例:

1
Accuracy = (TP + TN) / (TP + TN + FP + FN)

代码:

1
2
3
4
from sklearn.metrics import accuracy_score

acc = accuracy_score(y_true, y_pred)
print(acc)

准确率适合类别分布比较均衡、各类错误成本差不多的场景。

问题在于类别不均衡时准确率会误导判断。例如 10000 条样本中只有 100 条欺诈交易,模型全部预测为正常,也有 99% 准确率,但它没有识别出任何欺诈。

精确率

精确率关注“预测为正的样本中,有多少是真的正类”:

1
Precision = TP / (TP + FP)

代码:

1
2
3
4
from sklearn.metrics import precision_score

precision = precision_score(y_true, y_pred)
print(precision)

精确率适合误判成本高的场景。例如风控系统中,如果把正常用户误判为高风险,会影响用户体验;此时希望模型预测为风险时尽量准确。

召回率

召回率关注“真实正类中,有多少被模型找出来”:

1
Recall = TP / (TP + FN)

代码:

1
2
3
4
from sklearn.metrics import recall_score

recall = recall_score(y_true, y_pred)
print(recall)

召回率适合漏判成本高的场景。例如疾病筛查、欺诈检测、安全告警等任务中,漏掉真正风险的代价通常很高。

召回率提高后,精确率可能下降。因为模型更积极地预测正类,会找出更多真实正类,也可能带来更多误报。

F1 分数

F1 是精确率和召回率的调和平均:

1
F1 = 2 * Precision * Recall / (Precision + Recall)

代码:

1
2
3
4
from sklearn.metrics import f1_score

f1 = f1_score(y_true, y_pred)
print(f1)

F1 适合需要兼顾精确率和召回率的场景。它比简单平均更重视较低的一方,如果精确率很高但召回率很低,F1 不会太高。

classification_report

Scikit-learn 提供了综合报告:

1
2
3
from sklearn.metrics import classification_report

print(classification_report(y_true, y_pred))

它会输出每个类别的 precision、recall、f1-score 和 support。support 表示该类别的真实样本数量。

多分类任务中,报告会包含 macro avg 和 weighted avg:

  • macro avg:对每个类别指标简单平均,每个类别权重相同。
  • weighted avg:按每个类别样本数量加权平均。

如果少数类很重要,应重点看每个类别自己的指标,而不是只看总体平均。

阈值对指标的影响

很多分类模型输出的是概率,而不是固定类别。例如:

1
2
proba = model.predict_proba(X_test)[:, 1]
y_pred = (proba >= 0.5).astype(int)

默认阈值通常是 0.5,但业务上可以调整。降低阈值会让模型更容易预测为正类,通常提高召回率、降低精确率。提高阈值则相反。

可以遍历不同阈值,观察指标变化:

1
2
3
4
5
6
7
8
for threshold in [0.2, 0.4, 0.6, 0.8]:
y_pred = (proba >= threshold).astype(int)
print(
threshold,
precision_score(y_test, y_pred),
recall_score(y_test, y_pred),
f1_score(y_test, y_pred),
)

业务指标往往比模型默认阈值更重要。比如审核系统可以接受更多误报,让人工复核;但支付拦截系统可能更关注误伤率。

类别不均衡时的建议

遇到类别不均衡,可以采用以下方法:

  • 使用 stratified split 保持训练集和测试集类别比例。
  • 关注少数类的 precision、recall 和 F1。
  • 尝试调整分类阈值。
  • 使用 class_weight 或重采样方法。
  • 使用 PR 曲线而不仅是 ROC 曲线。

划分数据集时可以使用:

1
2
3
4
5
6
7
8
9
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
stratify=y,
random_state=42,
)

stratify=y 可以让训练集和测试集保持类似类别分布。

小结

模型评估不能只看准确率。准确率适合类别均衡场景,精确率关注误报,召回率关注漏报,F1 用于平衡二者。实际项目中应先明确业务代价,再选择核心指标。对于类别不均衡任务,要重点观察少数类表现,并根据业务目标调整阈值。