第4章 F1分数:调和平均的意义
讲完了精确率和召回率,你可能会问:有没有一个指标能同时照顾两边?
有,就是F1分数。
我个人习惯管它叫“调和平均下的综合分”。
调和平均到底调和了什么?
先看一个简单例子。有两个数:0.9和0.1。
- 算术平均:(0.9+0.1)/2 = 0.5
- 几何平均:√(0.9×0.1) ≈ 0.3
- 调和平均:2/(1/0.9+1/0.1) ≈ 0.18
看到了吗?调和平均被0.1那个小值严重拉低。这正是我们想要的——如果精确率很高但召回率很低,或者反过来,这个模型其实不可用。调和平均会狠狠地惩罚这种偏科。
公式很简洁:
F1 = 2 × (Precision × Recall) / (Precision + Recall)
本质上就是精确率和召回率的调和平均。
我在项目里见过不少团队只看精确率,结果召回率掉到0.2,业务方直接炸毛。用F1就能有效避免这种自欺欺人。
F1的一个小陷阱
嗯,注意,如果精确率和召回率都是0,公式分母是0,代码里会报除零错误。sklearn的处理方式是填0。但如果你自己手写,记得加个epsilon。
def f1_score(p, r):
if p + r == 0:
return 0.0
return 2 * p * r / (p + r)
Fβ:给精确率和召回率加权重
有时候我们更在意其中一方。比如癌症筛查,宁可误报也不能漏掉(更看重召回率)。反之,垃圾邮件过滤,宁可放过几封垃圾邮件,也别误删正常邮件(更看重精确率)。
这时候就轮到Fβ上场了:
Fβ = (1 + β²) × (Precision × Recall) / (β² × Precision + Recall)
β控制召回率的权重:
- β=1 → F1,两者平衡
- β<1 → 更看重精确率(比如β=0.5)
- β>1 → 更看重召回率(比如β=2)
多分类下的麻烦:宏平均 vs 微平均
二分类问题只有一个F1。但多分类呢?每个类别都有一个精确率和召回率,怎么合成一个数字?
两种主流做法:宏平均(Macro)和微平均(Micro)。
| 对比项 | 宏平均(Macro) | 微平均(Micro) |
|---|---|---|
| 计算方法 | 对每个类别算F1,然后算术平均 | 把所有类别的TP/FP/FN加起来,统一算一个F1 |
| 对类别数量的敏感性 | 每个类别权重相同,小类和大类平等 | 大类主导,小类很容易被淹没 |
| 适用场景 | 类别均衡,希望每个类都被同等重视 | 样本极不平衡时,更关心整体表现 |
| 风险 | 小类别性能差但平均后还是高,可能虚高 | 大类别做得好就掩盖了小类的糟糕 |
我举个实际例子。假设3个类别:A有900个样本,B有90个,C有10个。模型在A上F1=0.95,在B上F1=0.5,在C上F1=0.0。
Macro F1 = (0.95 + 0.50 + 0.00) / 3 = 0.483
Micro F1 = 先把混淆矩阵合计
全局TP = A的TP + B的TP + C的TP
全局FP = A的FP + B的FP + C的FP
全局FN = A的FN + B的FN + C的FN
然后带入公式 2TP/(2TP+FP+FN) = 0.92 (假设A表现很好)
你看,Micro F1高达0.92,但C类一个都没识别对。这个指标就失真了。反过来,Macro F1只有0.483,更诚实地反映了对少数类的失败。
动手算一算:sklearn里的用法
其实sklearn一行搞定:
from sklearn.metrics import f1_score, fbeta_score
y_true = [0, 1, 2, 0, 1, 2]
y_pred = [0, 2, 1, 0, 0, 1]
# F1 宏平均
print(f1_score(y_true, y_pred, average='macro'))
# F1 微平均
print(f1_score(y_true, y_pred, average='micro'))
# F2 宏平均(β=2)
print(fbeta_score(y_true, y_pred, beta=2, average='macro'))
记得传入标签一定要从0开始连续索引,否则参数得补labels。
一张图理清关系
说到这里,其实核心就一句话:F1是精确率和召回率的调和平均,调和平均对低值很敏感,所以F1会诚实地反映两者中的短板。Fβ让你能调节偏向哪一边;多分类里宏平均与微平均的选择取决于你更关心每个类还是整体。
下次别人问你模型好不好,别只丢一个F1。先把精确率和召回率亮了,再给F1,最后说明是宏平均还是微平均——这样才显得专业。