机器学习算法笔记(二十七):混淆矩阵、精准率与召回率、F1 Score

对于回归问题来说,评论算法的好坏我们讨论过 MSE、MAE、RMSE、R Squared。但对于分类算法的评价,我们在前面始终使用“分类准确度”这一个指标。实际上分配准确度在评价分类算法的时候是存在问题的,这时我们就要引入混淆矩阵、精准率与召回率的概念。

一、分类准确度的局限性

如果我们要编写一个癌症预测系统,输入一个人体检的信息指标,可以判断此人是否有癌症,思路当然是收集大量的数据,训练机器学习算法模型,进而完成癌症预测系统。假如最终我们以分类准确度来评价这个算法的好坏,就可以假设以下两种情况:

● 如果该种癌症在人群中产生的概率只有 0.1%,那么即使随便一个系统,预测所有人都是健康,该系统也可达到 99.9% 的准确率;也就是说,即使该系统什么都不做,也可以达到 99.9% 的准确率。
● 如果该种癌症在人群中产生的概率只有 0.01%,此时即使系统什么都不做,其预测准确率也能达到 99.99%,则该机器学习算法的模型是失败的。

由上面的假设可以看到,对于极度偏斜(Skewed Data)的数据,只使用分类准确度是远远不够的(比如上面癌症患者和健康人的人数之比是非常小的)。面对这种极度偏斜的数据,虽然分类准确度可能非常的高,其实算法是不够好的,甚至有些情况下非常烂的算法也能得到非常高的准确度。这时我们就要引入其他指标,在极度偏斜的情况下也能很好反映分类的算法的好坏。

二、混淆矩阵

首先我们引入一个非常基础的工具——混淆矩阵(Confusion Matrix)。

对于二分类问题,有以上混淆矩阵。

● 混淆矩阵中矩阵的最上一行代表预测值,最左列为真实值。
● 0 代表负例(Negative),1 代表正例(Positive)。
TN、FP、FN、TP:表示预测结果的样本数量
 TN(True Negative):实际值为 Negative,预测值为 Negative,预测 negative 正确。
FP(False Positive):实际值为 Negative,预测值为 Positive,预测 Positive 错误。
FN(False Negative):实际值为 Positive,预测值为 Negative,预测 Negative 错误。
TP(True Positive):实际值为 Positive,预测值为 Positive,预测 Positive 正确。

下面我们继续举预测癌症的例子来具体说明混淆矩阵:

● 9978:9978 个人本身没有患癌症,同时算法预测他们也没有患癌症。
● 12:12个人本身没有患癌症,但算法预测他们患有癌症。
● 2:2个人本身患有癌症,但算法预测他们没有患癌症。
8:8个人本身患有癌症,同时算法预测他们也患有癌症。

三、精准率和召回率

我们回到上一节癌症的例子:

接下来我们就要基于上面的例子来讨论两个新的概念——精准率(precision)召回率(recall)

1、精准率

● 定义:预测所关注的事件的结果中,预测正确的概率(共预测了 20 次,8 次正确,12 次错误)。
● 公式:

以预测癌症为例,预测精准率 = TP / (TP + FP) = 8 / (8 + 12) = 40%,代表每做 100个患病的预测(预测值为 1),平均会有 40个是正确的。

简而言之就是在所有的正预测结果中预测对的概率

2、召回率(查全率)

● 定义:对所有所关注的类型,将其预测出的概率(共 10 个癌症患者,预测出 8 个)。
● 公式:

以预测癌症为例,召回率 = TP / (TP + FN) = 8 / (8 + 2) = 80%,代表每 100个癌症患者中,通过该预测系统,能够成功的找出 80个癌症患者。

简而言之就是某事件真实发生了并成功预测出的概率,可以理解为查全率。

结合精准率和召回率的概念,我们再来看一下一个预测所有人都健康的预测癌症算法,它的混淆矩阵和准确率、召回率的情况:

可以看到虽然准确率很高,但在精准率和召回率的指标下,这个算法是完全没有用的。

从而我们看出,在极度偏斜的数据中,我们不看准确率,使用精准率和召回率才能更加准确评价分类系统的好坏。

四、编程实现计算准确率和召回率

新建一个工程,创建一个main.py文件,实现如下代码:

import numpy as np
from sklearn import datasets

digits = datasets.load_digits()
X = digits.data
y = digits.target.copy() #y是标记的拷贝,避免下面对y修改后digits.target也变化的情况

y[digits.target==9] = 1
y[digits.target!=9] = 0

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)
print(log_reg.score(X_test, y_test)) #prints: 0.9755555555555555

y_log_predict = log_reg.predict(X_test)

def TN(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 0) & (y_predict == 0))

def FP(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 0) & (y_predict == 1))

def FN(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 1) & (y_predict == 0))

def TP(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 1) & (y_predict == 1))

#生成混淆矩阵
def confusion_matrix(y_true, y_predict):
    return np.array([
        [TN(y_true, y_predict), FP(y_true, y_predict)],
        [FN(y_true, y_predict), TP(y_true, y_predict)]
    ])

print(confusion_matrix(y_test, y_log_predict))
"""prints: [[403   2]
            [  9  36]]"""

#计算准确率
def precision_score(y_true, y_predict):
    tp = TP(y_true, y_predict)
    fp = FP(y_true, y_predict)
    try:
        return tp / (tp + fp) #防止分母为零的状况
    except:
        return 0.0

print(precision_score(y_test, y_log_predict)) #prints: 0.9473684210526315

#计算召回率
def recall_score(y_true, y_predict):
    tp = TP(y_true, y_predict)
    fn = FN(y_true, y_predict)
    try:
        return tp / (tp + fn) #防止分母为零的状况
    except:
        return 0.0

print(recall_score(y_test, y_log_predict)) #prints: 0.8

在sklearn中,也为我们封装好了相应的接口,调用如下:

from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test, y_log_predict))

from sklearn.metrics import precision_score
print(precision_score(y_test, y_log_predict))

from sklearn.metrics import recall_score
print(recall_score(y_test, y_log_predict))

打印结果应该与自己实现的函数相同。

五、F1 Score

从上文中我们看到,精准率和召回率是两个指标,具体使用算法时我们怎么通过精准率和召回率判断算法优劣?其实这和机器学习领域中大多数关于取舍问题的答案是一样的,我们要根据具体使用场景而定。

● 在预测未来该股票是涨还是跌的情况下,我们要求更精准的找到能够上涨的股票,若出现误判(FP的错误)会造成实实在在金钱损失。此情况下,模型精准率越高越好,即使召回率低一些也没关系——即使我们落下了一些股票的上升周期也并没有关系,我们并不会有金钱上的损失。但如果我们错误判断一个股票会上涨(实际上是下跌)从而投资,那我们就会有实际的损失。在这种情况下,精准率比召回率更重要。

● 在诊断一个人是否患病的情况下,我们要求更全面的找出所有患病的病人,而且尽量不漏掉一个患者;甚至说即使将正常人员判断为病人也没关系,只要不将病人判断成健康人员就好。此情况下,模型召回率越高越好。

还有一些其他情况,我们不用特别关心精准率也不用特别关心召回率,我们希望同时关注这两种指标,这种情况下,我们就使用一种新的指标——F1 Score。F1 Score 实际上是精准率和召回率的调和平均值,用公式表示就是:

如果 1/a = (1/b + 1/c) / 2,则称 a 是 b 和 c 的调和平均值。调和平均值的特点为:|b – c| 越大,a 越小;当 b – c = 0 时,a = b = c,a 达到最大值。具体到精准率和召回率,只有当二者大小均衡时,F1 指标才高。

在 sklearn 中,为我们封装好了计算 F1 Score 的函数(虽然自己实现也很简单),我们利用上文手写数据集的数据,调用如下:

from sklearn.metrics import f1_score
print(f1_score(y_test, y_log_predict)) #prints: 0.8674698795180723

对于这个有偏的数据,算法运行后的精准率和召回率都比准确率低一些,在这里精准率和召回率更能反映算法的结果。对于有偏的数据,使用逻辑回归进行预测,它的召回率是相对比较低的,所以 F1 Score 被比较低的召回率拉低了,最终的结果只有86.7%。相比准确度的 97.5%,我们倾向于认为 86.7% 这个指标更能反映这个算法的好坏。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注