三、召回率与F1分数:平衡的艺术

前面我们聊了精确率,也就是模型预测为正例的那些样本里,到底有多少是真正正确的。但光看精确率够吗?不够。我举个例子你就明白了。

假设我们做一个癌症筛查模型。如果模型只把最有把握的那一个样本判为阳性,其他全判阴性,精确率可能是100%。但这模型有用吗?没用。因为它漏掉了几乎所有真正的病人。

所以,我们需要另一个指标——召回率。

3.1 召回率的深度解析

召回率衡量的是:所有真正的正例中,模型找出来了多少。

公式很简单:

Recall = TP / (TP + FN)

TP是正确找出来的正例,FN是漏掉的正例。说白了,召回率就是模型对正例的「抓全率」。

核心理解:

  • 精确率:模型说「是」的,有多少真「是」?—— 关注预测的准确性
  • 召回率:真正的「是」里,模型找出了多少?—— 关注正例的覆盖率

我在项目中遇到过这样一个场景:做金融欺诈检测时,业务方要求「宁可错杀一千,不可放过一个」。为什么?因为漏掉一笔欺诈交易,损失可能是几十万。这时候,召回率就是第一优先级。

但召回率也不是越高越好。你想想看,如果模型把所有样本都判为正例,召回率就是100%。但精确率会跌到惨不忍睹。这就是精确率和召回率之间的矛盾关系。

场景 优先指标 原因
癌症筛查 召回率 漏诊后果严重,宁可误诊也要找全
垃圾邮件过滤 精确率 误删正常邮件比漏掉垃圾邮件更糟糕
商品推荐 两者平衡 既要推荐准,又不能漏掉用户可能喜欢的

避坑指南:

我曾经在一个二分类项目中只看精确率,结果模型在测试集上精确率95%,上线后业务反馈「怎么漏了这么多?」。一查才发现,正例只占5%,模型学成了「保守派」——只预测最有把握的少数样本。从那以后,我养成了同时看精确率和召回率的习惯。

3.2 F1分数:调和平均的智慧

既然精确率和召回率各有侧重,能不能用一个数字综合衡量?这就是F1分数的由来。

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

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

为什么用调和平均,而不是算术平均?我解释一下。

假设精确率=1.0,召回率=0.0。算术平均是0.5,看起来还行。但调和平均呢?

F1 = 2 * (1.0 * 0.0) / (1.0 + 0.0) = 0.0

F1直接归零。这才是真实情况——召回率为0的模型,根本不能用。

调和平均的特点:

  • 对极端值更敏感:一个指标差,F1就低
  • 鼓励两者均衡:只有精确率和召回率都高,F1才高
  • 比算术平均更「诚实」

我的经验:

做模型对比时,我习惯先看F1。如果两个模型F1差不多,再分别看精确率和召回率。F1就像一个「体检总评分」,帮你快速筛掉明显不合格的模型。

3.3 Precision-Recall曲线:动态视角

前面我们讨论的都是固定阈值下的指标。但实际应用中,阈值是可以调整的。比如逻辑回归输出的是概率,默认阈值0.5。调高到0.7,精确率可能上升,召回率下降。调低到0.3,情况反过来。

怎么全面评估模型在不同阈值下的表现?Precision-Recall曲线(PR曲线)就是干这个的。

PR曲线的横轴是召回率,纵轴是精确率。每个点对应一个阈值下的(Recall, Precision)对。把所有点连起来,就是PR曲线。

我画了一张图,帮你直观理解:

Precision-Recall 曲线示意图 召回率 (Recall) 精确率 (Precision) 0 0.5 1.0 0 0.5 1.0 阈值=0.9 阈值=0.5 阈值=0.3 阈值=0.1 曲线越靠近右上角 模型性能越好

从图上可以看到:

  • 阈值高(如0.9)时,精确率很高,但召回率很低
  • 阈值低(如0.1)时,召回率很高,但精确率很低
  • 曲线越靠近右上角,说明模型在不同阈值下都能保持较高的精确率和召回率

PR曲线还有一个重要用途:比较不同模型。如果模型A的PR曲线完全包住模型B的曲线,那A肯定优于B。如果曲线有交叉,就需要结合具体业务场景来选。

PR曲线 vs ROC曲线:

很多人问我,为什么不用ROC曲线?我的回答是:当正负样本严重不平衡时(比如正例只占1%),PR曲线比ROC曲线更敏感。ROC曲线可能会给你一种「模型还不错」的错觉,但PR曲线会如实反映模型在少数类上的表现。所以,处理不平衡数据集时,我优先看PR曲线。

3.4 实战:用Python绘制PR曲线

光说不练假把式。下面我给出一个完整的PR曲线绘制代码,你可以直接拿去用。

from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt

# 生成不平衡数据集
X, y = make_classification(n_samples=1000, n_classes=2, 
                           weights=[0.9, 0.1], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42)

# 训练模型
model = LogisticRegression()
model.fit(X_train, y_train)

# 预测概率
y_scores = model.predict_proba(X_test)[:, 1]

# 计算PR曲线
precisions, recalls, thresholds = precision_recall_curve(
    y_test, y_scores)

# 计算平均精确率(AP)
ap = average_precision_score(y_test, y_scores)

# 绘制PR曲线
plt.figure(figsize=(8, 6))
plt.plot(recalls, precisions, 'b-', linewidth=2, 
         label=f'PR曲线 (AP = {ap:.3f})')
plt.xlabel('召回率 (Recall)')
plt.ylabel('精确率 (Precision)')
plt.title('Precision-Recall 曲线')
plt.legend(loc='lower left')
plt.grid(True, alpha=0.3)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.show()

# 查看不同阈值下的指标
print("阈值\t精确率\t召回率")
for i in range(0, len(thresholds), 50):
    print(f"{thresholds[i]:.3f}\t{precisions[i]:.3f}\t{recalls[i]:.3f}")

实用技巧:

代码中的average_precision_score返回的是AP值,也就是PR曲线下的面积。AP值越接近1,模型越好。我一般用AP作为模型选择的参考指标之一。

3.5 本章小结

好了,这一章的内容就这些。我们聊了三个核心概念:

  • 召回率:衡量模型找全正例的能力,和精确率是一对「冤家」
  • F1分数:用调和平均综合精确率和召回率,比算术平均更合理
  • PR曲线:展示模型在不同阈值下的表现,特别适合不平衡数据集

记住一句话:没有完美的指标,只有适合业务的指标。选哪个,取决于你更在意「误报」还是「漏报」。