混淆矩阵、精确率、召回率、F1Score和ROC曲线的总结(ML with sklearn)

混淆矩阵、精确率、召回率、F1Score和ROC曲线的总结(ML with sklearn)

前言:这是我第一次在知乎上写文章,最近一直在看关于Machine Learning的书,发现在评估分类器的时候,涉及了很多概念与性能指标,有时候容易忘记,为了是自己有一个更深的理解,通过代码演练一遍效果会好很多。接下来,我们一步步通过代码,揭开这些概念的神秘的面纱。

一、训练一个分类器

首先,我们导入MNIST数据集,看看这个数据集的特征。

from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
mnist
X,y= mnist["data"],mnist["target"]
print(X.shape)
print(y.shape)

打印的结果为(70000,784),(70000,)

共有七万张图片,每张图片有784个特征,即每张图有28 \times 28个像素点。接下来,我们来通过Matplotlib的imshow()函数来显示某张图片:

%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
somedigit= X[23000]
some_digit_img= somedigit.reshape(28,28)
plt.imshow(some_digit_img,cmap =matplotlib.cm.binary,interpolation="nearest")
plt.axis("off")
plt.show()

看到这里,我们大致可以猜出来,这个数字是3,通过查看标签值y在索引23000处的值发现,我们的猜测没错。在深入探讨分类器之前,我们先将数据进行划分,将测试集放到一边。MNIST数据集已经将其分好,前60000万张图像为训练集,后10000张图像为测试集。这里我们只需要对数据训练集数据进行shuffle操作即可:

import numpy as np
X_train,X_test ,y_train, y_test= X[:60000] ,X[60000:],y[:60000],y[60000:]
shuffle_index=np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index],y_train[shuffle_index]

在这里,我们将问题简化:生成一个只识别数字“3”和“非3”的二元分类器。挑选sklearn中的SGD分类器,这个分类器的好处就是收敛速度快,非常适合大数据量的训练。训练完成后,我们顺便看下它的预测结果。

from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
y_train_3 =(y_train ==3)
y_test_3 =(y_test==3)
sgd_clf.fit(X_train,y_train_3)
sgd_clf.predict([somedigit])

结果表明,这个分类器的预测是对的,那么怎么评价这个分类器的优劣呢?下一小节,将会是重点。

二、性能指标

现在使用,sklearn中的交叉验证方法,调用cross_ val_ score()函数来评估这个模型。其中参数cv选用10个折叠数。

结果表明准确率大多数超过了95%,在高兴之余,我们不妨思考一个问题。如果对于任意的一个图片,我们都将其划分为“非3”,那么准确率也可以达到90%!这就说明了, 准确率不能成为评价分类器的首要性能指标,特别是在处理偏斜数据集(skewed dataset)时。

a.混淆矩阵

评估分类器的更好的办法就是通过混淆矩阵统计各个类别被错误分成其他类别的情况。比如,要想知道在数字3和数字5混淆了多少次。可以查看矩阵的第3行和第五列。

获取混淆矩阵:

要计算混淆矩阵,首先要生成一组预测结果,这里需要调用sklearn中的cross_val_predict()函数,它与上面的cross_val_score()类似不过它返回的是每个折叠的预测。

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix
y_train_pred =cross_val_predict(sgd_clf,X_train,y_train_3,cv=10)
confusion_matrix(y_train_3,y_train_pred)

需要说明的是,在Sklearn中混淆矩阵的表示方法,与很多书籍与网上博客的方法不同,具体表现在行和列的排列上。混淆矩阵图如下图所示:


在上例中,矩阵第一行表示有52309张图片被正确地分为“非3”类(真负类),1560张图片被错误地分为“3”类(假正类),第二行表示有1250张图片被错误地分为“非3”类(假负类),4782张图片被正确地分为“3”类(真正类)。可以看出,一个完美的分类器,其混淆矩阵的只有在对角线上才会出现非0值。

b.精确度、召回率和F1 score

公式如下:

精确度= TP/(TP+FP)

召回率=TP/(TP+FN)

精确度用来评估在分类器所得正例结果中,真正正确的正例占该结果的多少。召回率表示分类器正确找出来的正例,占样本实际正例的多少。说起来,这两个指标很绕。接下来回到我们的例子中,看看我们训练的模型的精确率和召回率是多少。

得出我们模型的精确度和召回率分别为0.753和0.780,引入了精确度和召回率这两个指标后,现在我们发现了这个模型其实并没有它看起来那么光鲜。在实际应用中,精确率和召回率可以用一个简单的指标衡量,即是F1 score,F1 score是精确率和召回率的调和平均,公式如下:

\frac{1}{F1 }=\frac{2}{\frac{1}{精确度}+\frac{1}{召回率}}

不幸的是,精确度和召唤率是鱼和熊掌不可兼得的关系,不能既提高精确度又提高召回率。

在不同的应用场景下,对精确度和召回率的要求不同,比如说在罪犯人脸识别系统中,我们可以接受精确度只有30%的,但是召回率必须在99%以上,有一点“宁可错杀一千,不可放过一个”的意思。而在筛选未成年的电影节目中,我们对精度要求更高,即筛选出来的结果,必须是安全的。

c.阀值

SGD分类器的决策函数是基于一个阀值来进行分类的,这个阀值对精确度和召回率有不同的影响。使用cross_valpredict()获取训练集中所有实例的分数,然后用precision_recall_curve()计算所有阀值的精度和召回率,最后绘制图像。

y_scores= cross_val_predict(sgd_clf,X_train,y_train_3,cv=10,method="decision_function")
y_scores = y_scores[:, 1]
from sklearn.metrics import precision_recall_curve
precisions,recalls,thresholds =precision_recall_curve(y_train_3,y_scores)
def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
    plt.plot(thresholds, precisions[:-1], "b--", label="Precision", linewidth=2)
    plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
    plt.xlabel("Threshold", fontsize=16)
    plt.legend(loc="upper left", fontsize=16)
    plt.ylim([0, 1])
plt.figure(figsize=(8, 4))
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.xlim([-700000, 700000])
plt.show()

需要说明的是,精确度的曲线要比召回率的曲线“崎岖”一些,当你提高阀值时,精确的有可能下降,尽管总体时上升的。当阀值上升时,召回率只会下降。

一种更简单的绘制方法,就是直接绘制召回率和精度的关系。

def plot_precision_vs_recall(precisions, recalls):
    plt.plot(recalls, precisions, "b-", linewidth=2)
    plt.xlabel("Recall", fontsize=16)
    plt.ylabel("Precision", fontsize=16)
    plt.axis([0, 1, 0, 1])
plt.figure(figsize=(8, 6))
plot_precision_vs_recall(precisions, recalls)
save_fig("precision_vs_recall_plot")
plt.show()

d.ROC曲线

还有一个评价分类器的指标叫做ROC(受试者工作特征)曲线,它反映的是真正类率(TPR)和假正类率(FPR)的关系。评价两个分类器的优劣,需要看它们的ROC曲线,如过其中前者分类器的ROC曲线完全“包裹”住后一个分类器,则可以说前者分类器的性能优于后者,如果没有完全“包裹”则需要通过积分求面积比较,两个曲线所围成的面积大小即AUC。下面再引入一个随机森林的分类器,与我们之前的分类器做比较。

from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_3, y_scores)
def plot_roc_curve(fpr, tpr, label=None):
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1], [0, 1], 'k--')
    plt.axis([0, 1, 0, 1])
    plt.xlabel('False Positive Rate', fontsize=16)
    plt.ylabel('True Positive Rate', fontsize=16)
plot_roc_curve(fpr, tpr)
from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_3, cv=10,
                                    method="predict_proba")
y_scores_forest = y_probas_forest[:, 1] 
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_3,y_scores_forest)