精确率与召回率的权衡:PR曲线与F1的关系
做分类模型时,大部分人最早接触的指标是准确率。但一旦遇到数据不平衡——比如欺诈检测、罕见病诊断——准确率立刻变成谎言制造机。这时候我们不得不面对精确率和召回率这对冤家。
为什么说它们是冤家?很简单:你越想抓回所有正例(高召回),就越容易误伤负例,精确率就往下掉;你越想精打细算只抓最确定的(高精确),就越容易漏掉真正例,召回率就上不去。说白了,这就是一个跷跷板。
我以前在做一个信用卡盗刷检测的项目时,老板要求“一个都不能漏”。好,我调了阈值,召回率拉到 98%,结果所有交易被拦截,正常用户连买咖啡都刷不了。客服投诉电话打爆了。嗯,从那以后我明白了——你必须在这两个指标之间找到一句“啥算够用”的界限。
PR曲线到底在画什么?
PR曲线就是精确率和召回率在不同阈值下的“舞步记录”。横轴是召回率(Recall),纵轴是精确率(Precision)。每改变一次阈值,就得到一对(Recall, Precision),连起来就是曲线。
你可以这样理解:
- 阈值很低时,模型几乎把所有样本都判成正类 → 召回率非常高(趋近1),但精确率极低(因为掺了大量负例)。
- 阈值很高时,模型只敢把最像正类的判成正类 → 精确率高,但召回率很低(漏了很多正例)。
PR曲线从右上角(高精确高召回)向左下角(低精确低召回)逐渐下降。曲线越靠近右上角,模型越好。如果曲线完全是一条从 (0,0) 到 (1,1) 的直线,那模型就跟随机猜差不多。
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强迫我们寻找平衡点,而不是极端优化某一个指标。
如何用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.10 | 0.12 | 0.95 | 0.21 | 全杀模式,误伤严重 |
| 0.30 | 0.35 | 0.78 | 0.48 | 稍微平衡 |
| 0.45 | 0.52 | 0.60 | 0.56 | F1较高 |
| 0.55 | 0.61 | 0.52 | 0.56 | 与上一条F1相近,但精更高 |
| 0.70 | 0.70 | 0.35 | 0.47 | 漏判过多 |
| 0.90 | 0.85 | 0.12 | 0.21 | 几乎找不到正例 |
你可以看到,阈值从0.45到0.55,F1都维持在0.56附近。但精确率从0.52升到0.61,召回率从0.60降到0.52。到底选哪个?取决于业务。如果你的业务是“宁可错杀一千不放过一个”,选0.45;如果“抓到人必须坐实证据”,选0.55。F1只是告诉你“这两个候选都挺平衡”,具体选哪个要结合成本。这就是为什么我从来不会只给一个F1值就交差——我总会附上PR曲线和业务建议阈值。
一图看透PR曲线与F1的关系
下面对整个流程做个可视化总结。这张图展示了PR曲线、不同阈值对应的点,以及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是平衡度的数字体现。P、R、F1三者联动,缺一不可。我希望你下次再做分类模型时,能记得画一张PR曲线,找一下F1最大的点,再跟业务方坐下来聊聊——这个点是不是真的要选。