评价指标——F1 Score与Precision-Recall的平衡

本篇主要内容:F1 Score,Precision-Recall 的平衡,P-R 曲线

F1 Score

上篇我们提到,精准率和召回率这两个指标有时精准率低一些有时召回率低一些,有时可能都低。那么实际中用哪个指标比较好呢?这一般和应用场景有关,对于有些场景,我们更注重精准率,比如股票预测,假设预测的是一个二分类问题:股票会升还是降,显然为了利润我们关注的是升(即上升为类 1),为什么这种情况下精准率指标更好呢?因为精准率是所有分类为 1 的预测中有多少是正确的,对本例也就是预测未来股票上升有多少是对的,这更复合我们的利润最大决策。而召回率是实际上升的股票中我们预测对了多少,基于风险投资理念,有很多股票会上升的时刻,我们就算落掉一些也是没有关系的,没有投资进去也就没有损失,更重要的是我们的决策中有多少能赚钱,所以在这种场景下,精准率更好。

而如果在医疗领域,则是召回率更加重要,也就是要能在实际得病的人中尽量预测的更加准确,我们不想漏掉任何真正患病的人,这样才更有可能挽回一些人的生命,而精准率低些(没病的被预测为有病)并不会导致特别严重的后果,只是进行了一些过度医疗。
不过并非所有场景都如上面两个例子般极端,只关注精准率或只关注召回率。更多的我们希望得到它们之间的一种平衡,即同时关注精准率和召回率。这种情况下我们有一个新的指标:F1 Score,它的计算公式为:

如果两个都为 0,则定义 F1=0。本质上 F1 是精准率和召回率的调和平均,调和平均一个很重要的特性是如果两个数极度不平衡(一个很大一个很小),最终的的结果会很小,只有两个数都比较高时,调和平均才会比较高,这样便达到了平衡精准率和召回率的目的。下面编程实现一下 F1 Score:

1
2
3
4
5
6
7
import numpy as np

def f1_score(precision,recall):
try:
return 2*precision*recall/(precision+recall)
except:
return 0.0

输入几组值进行测试:

调和平均测试

下面在真实数据集上来使用 F1 Score,分类算法使用逻辑回归,数据集和上篇文章相同,是人工处理为 2 类的手写数字数据集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()

'''人工加入偏斜'''
y[digits.target==9]=1
y[digits.target!=9]=0
'''10分类变为2分类,用Logistic分类'''
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train,y_train)
log_reg.score(X_test,y_test)

准确率:

准确率

精准率和召回率:

精准率和召回率

1
2
from sklearn.metrics import f1_score
f1_score(y_test,y_predict)

F1 Score:

F1_Score

可以看到 F1 的值是综合了精准率和召回率的,用它来衡量模型性能是比准确率要好的。


Precision-Recall 的平衡

精准率和召回率是相互制约的,如果想要精准率提高,召回率则会下降,如果要召回率提高,精准率则会下降,我们需要找到二者之间的一个平衡。
下面通过调整决策边界导致的分类结果变化来观察一下这个现象,不过 Logistic 回归中并没有直接能调整决策边界的函数,可以通过 decision_score 来间接调节:

1
log_reg.decision_function(X_test)[:10]

decision_score 的前 10 个值展示:

d_c

其值就是样本特征带入决策边界函数得到的结果,对于其中小于 0 的就被判别为类别 0 了,大于 0 的则是类别 1。默认的决策边界函数是,此时如果需要调节边界比如要调节为,只需在计算时让再与 0 比较即可:

1
2
3
4
'''如果阀值为5'''
y_predict_2 = np.array(decision_scores >= 5,dtype=int)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,y_predict_2)

此时的混淆矩阵为:

混淆矩阵

精准率和召回率:

精准率和召回率

F1 Score:

F1 Score

再调节决策边界为:

1
2
3
4
'''如果阀值为-5'''
y_predict_3 = np.array(decision_scores >= -5,dtype=int)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,y_predict_3)

精准率、召回率和 F1 Score:

精准率召回率和 F1

从上面的结果可以观察到当精准率升高时召回率相应下降,召回率升高时精准率相应下降,我们希望模型尽量有比较平横的精准率和召回率,为了更好地观察它们之间的关系,可以绘制精准率召回率曲线(P-R 曲线):

1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt
precisions = []
recalls = []
thresholds = np.arange(np.min(decision_scores),np.max(decision_scores))

for threshold in thresholds:
y_predict = np.array(decision_scores >= threshold,dtype=int)
precisions.append(precision_score(y_test,y_predict))
recalls.append(recall_score(y_test,y_predict))
plt.plot(thresholds,precisions)
plt.plot(thresholds,recalls)
plt.show()

P、R 随阀值变化曲线

P-R 曲线

从 P-R 曲线看出精准率和召回率是相互制约的,召回率的升高会带来精准率的下降。同样可以用 sklearn 中自带的 P-R 曲线绘制函数来绘制:

1
2
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_test,decision_scores)
1
2
3
4
plt.plot(thresholds,precisions[:-1])
plt.plot(thresholds,recalls[:-1])
plt.show()
'''sklearn自动寻找最优部分的数据'''

P、R 随阀值变化曲线

P-R 曲线

在 P-R 曲线中,突然下降的位置很可能就是二者平衡的一个位置。
整体的,如果将两个模型的 P-R 曲线绘制到一个图中,则靠外的那个模型显然更优,也就是与两个轴围成的面积大的那个模型更好。