精确率与召回率的权衡:PR曲线与F1的关系

做分类模型时,大部分人最早接触的指标是准确率。但一旦遇到数据不平衡——比如欺诈检测、罕见病诊断——准确率立刻变成谎言制造机。这时候我们不得不面对精确率和召回率这对冤家。

为什么说它们是冤家?很简单:你越想抓回所有正例(高召回),就越容易误伤负例,精确率就往下掉;你越想精打细算只抓最确定的(高精确),就越容易漏掉真正例,召回率就上不去。说白了,这就是一个跷跷板。

我以前在做一个信用卡盗刷检测的项目时,老板要求“一个都不能漏”。好,我调了阈值,召回率拉到 98%,结果所有交易被拦截,正常用户连买咖啡都刷不了。客服投诉电话打爆了。嗯,从那以后我明白了——你必须在这两个指标之间找到一句“啥算够用”的界限。

PR曲线到底在画什么?

PR曲线就是精确率和召回率在不同阈值下的“舞步记录”。横轴是召回率(Recall),纵轴是精确率(Precision)。每改变一次阈值,就得到一对(Recall, Precision),连起来就是曲线。

你可以这样理解:
- 阈值很低时,模型几乎把所有样本都判成正类 → 召回率非常高(趋近1),但精确率极低(因为掺了大量负例)。
- 阈值很高时,模型只敢把最像正类的判成正类 → 精确率高,但召回率很低(漏了很多正例)。

PR曲线从右上角(高精确高召回)向左下角(低精确低召回)逐渐下降。曲线越靠近右上角,模型越好。如果曲线完全是一条从 (0,0) 到 (1,1) 的直线,那模型就跟随机猜差不多。

实战技巧: 当正负样本比例严重失衡时(比如正类只有1%),别用ROC曲线——它会过度乐观。PR曲线才是你的真实镜子。我每次做长尾识别时,第一张图一定是PR曲线,先看模型到底会不会“假努力”。

F1:把两个指标塞进一个数

精确率和召回率都是单侧指标,很难同时评估。于是F1出现了——它是精确率和召回率的调和平均数。调和平均的特性是:只有当两者都高时,F1才高;一个极低,另一个再高也没用。

公式很简单:

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

举个例子:你有一个模型,精确率0.9,召回率0.2。F1是多少?

2 × (0.9 × 0.2) / (0.9 + 0.2) = 0.36 / 1.1 ≈ 0.327

只有0.327,惨不忍睹。但如果你把精确率换成0.5,召回率0.5,虽然精确率降了,但F1直接升到0.5。这说明F1强迫我们寻找平衡点,而不是极端优化某一个指标。

我个人习惯: 做二分类调参时,会先看F1的整体趋势。如果F1一直在涨,说明阈值调整方向是对的。如果F1开始下降,即使召回率还在涨,也要停下来想想。

如何用PR曲线选择最佳阈值?

PR曲线上每个点都对应一个阈值。哪个阈值最好?常见的做法是找曲线上“膝盖”位置——即F1最大的点。因为F1最大的时候,精确率和召回率达到了相对最优的折中。

我们可以在对数轴或线性轴上遍历阈值,计算每个阈值下的精确率、召回率、F1,然后选出F1最大的那个阈值。下面是我常用的代码片段:

from sklearn.metrics import precision_recall_curve
import numpy as np

# 假设 y_true 是真实标签,y_scores 是模型输出概率
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)

# 注意 sklearn 返回的 thresholds 长度比 precisions 少1
# 计算每个阈值的 F1
f1_scores = 2 * (precisions[:-1] * recalls[:-1]) / (precisions[:-1] + recalls[:-1] + 1e-9)

# 找到最大 F1 对应的索引
best_idx = np.argmax(f1_scores)
best_threshold = thresholds[best_idx]
best_f1 = f1_scores[best_idx]

print(f"最佳阈值: {best_threshold:.3f}, F1: {best_f1:.3f}")
print(f"对应精确率: {precisions[best_idx]:.3f}, 召回率: {recalls[best_idx]:.3f}")

这段代码我几乎每个分类项目都会用。有一次在医疗诊断模型中,最佳阈值刚好卡在0.35,而不是默认的0.5。如果我没做这个搜索,直接拍0.5,F1会从0.81掉到0.65。差了16个百分点。

一张表看清楚不同阈值下的权衡

下面我用一个真实项目中的例子,展示不同阈值下P、R、F1的变化。数据来自一个点击率预测模型,正类比例约5%。

阈值精确率召回率F1备注
0.100.120.950.21全杀模式,误伤严重
0.300.350.780.48稍微平衡
0.450.520.600.56F1较高
0.550.610.520.56与上一条F1相近,但精更高
0.700.700.350.47漏判过多
0.900.850.120.21几乎找不到正例

你可以看到,阈值从0.45到0.55,F1都维持在0.56附近。但精确率从0.52升到0.61,召回率从0.60降到0.52。到底选哪个?取决于业务。如果你的业务是“宁可错杀一千不放过一个”,选0.45;如果“抓到人必须坐实证据”,选0.55。F1只是告诉你“这两个候选都挺平衡”,具体选哪个要结合成本。这就是为什么我从来不会只给一个F1值就交差——我总会附上PR曲线和业务建议阈值。

我曾经踩过一个坑: 用默认0.5阈值跑线上A/B实验,F1 0.78看起来不错。结果业务方反馈说“误报太多”。我回头画了PR曲线才发现,真正对业务友好的阈值在0.65,F1虽然降到了0.73,但误报率降了一半。记住:F1是统计指标,不是业务指标。一定要让业务方参与阈值选择。

一图看透PR曲线与F1的关系

下面对整个流程做个可视化总结。这张图展示了PR曲线、不同阈值对应的点,以及F1等高线如何帮我们定位最佳阈值。

PR曲线与F1最佳阈值选择 召回率 (Recall) 精确率 (Precision) 阈值0.10 阈值0.30 最佳阈值 (F1=0.56) 阈值0.55 F1=0.5 F1=0.4 PR曲线越靠近右上角越好 绿色高亮点为F1最大阈值 紫色虚线为不同F1等高线
读图要点:
  • PR曲线从左上(高精确率高召回)向右下(低精确率低召回)延伸。
  • 绿色高亮点是我们通过遍历阈值找到的F1最大点,它通常位于曲线“膝盖”处。
  • 紫色虚线代表F1的等值线(简化示意)。理论上F1最大的点就是等值线与PR曲线的切点。
  • 实际项目中,我会在多组阈值附近都观察一下,因为PR曲线可能有一段平坦区域,F1变化不大但业务代价不同。

避坑指南:别让F1变成你的盲点

F1虽然好用,但滥用也会出问题。我总结几个常见陷阱:

  • 多分类别搞平均坑:F1可以微平均、宏平均、加权平均。做多分类时,宏平均对每个类一视同仁,容易受小类影响;微平均则被大类主导。我一般先看每个类的F1,再决定用哪种平均方式。
  • 极端类别比例不要只用F1:当正类占比不到0.1%时,模型只要把全部判负,精确率会变成 undefined(因为分母为0),召回率直接为0。此时F1也是0。但别以为“F1=0就是差”——其实模型可能只是没找到任何正例。我建议在这种场景下先看召回率,再结合精确率的有效区间。
  • 业务成本不对称时别迷信F1:比如欺诈检测,漏一次放行可能损失一万,误拦一次只损失用户体验。此时召回率权重应该高于精确率,F1的等权假设就不成立了。我习惯的做法是引入Fbeta——召回率的权重设为beta倍。beta=2表示召回率权重是精确率的两倍。
小技巧: 如果你们团队有成本矩阵,可以直接把精确率和召回率的收益量化成一个目标函数,然后用PR曲线作为可视化工具来辅助决策,而不要死磕F1。F1只是工具,不是真理。

总结一下这一节的核心:PR曲线是阈值选择的利器,F1是平衡度的数字体现。P、R、F1三者联动,缺一不可。我希望你下次再做分类模型时,能记得画一张PR曲线,找一下F1最大的点,再跟业务方坐下来聊聊——这个点是不是真的要选。