混淆矩阵 -- 分类模型评判指标






概述


混淆矩阵是 ROC 曲线绘制的基础, 同时它也是衡量分类模型准确度中最基本、最直观、计算最简单的方法.


一句话解释就是: 混淆矩阵就是分别统计分类模型归错类、归对类的观测值个数, 然后把结果放在一个表里展示出来. 这个表就是混淆矩阵.



定义

混淆矩阵也称误差矩阵,是表示精度评价的一种标准格式,用 n 行 n 列的矩阵形式来表示。


以分类模型中最简单的二分类为例,对于这种问题,我们的模型最终需要判断样本的结果是 0 还是 1,或者说是 positive 还是 negative。

我们通过样本的采集,能够直接知道真实情况下,哪些数据结果是 positive,哪些结果是 negative。同时,我们通过用样本数据跑出分类器模型的结果,也可以知道模型认为这些数据哪些是 positive,哪些是 negative。

因此,我们就能得到这样四个基础指标:

其中,

  • 把正例正确地分类为正例,表示为 TP(true positive)
  • 把正例错误地分类为负例,表示为 FN(false negative)
  • 把负例正确地分类为负例,表示为 TN(true negative)
  • 把负例错误地分类为正例,表示为 FP(false positive)


将这四个指标一起呈现在表格中,就能得到如下这样一个矩阵,我们称它为混淆矩阵(Confusion Matrix):







混淆矩阵(Confusion Matrix):

混淆矩阵的每一列代表了预测类别,每一列的总数表示预测为该类别的数据的数目;每一行代表了数据的真实归属类别,每一行的数据总数表示该类别的数据实例的数目。每一列中的数值表示真实数据被预测为该类的数目:第一行第一列中的 43 表示有 43 个实际归属第一类的实例被预测为第一类,同理,第一行第二列的 2 表示有 2 个实际归属为第一类的实例被错误预测为第二类。

如有 150 个样本数据,预测为 1, 2, 3 类各为 50 个。分类结束后得到的混淆矩阵为:

每一行之和表示该类别的真实样本数量,每一列之和表示被预测为该类别的样本数量,

第一行说明有 43 个属于第一类的样本被正确预测为了第一类,有两个属于第一类的样本被错误预测为了第二类.







混淆矩阵的指标 (一级指标)


预测性分类模型, 肯定是希望越准越好. 那么, 对应到混淆矩阵, 那肯定是希望 TP 和 TN 的数量大, 而 FP 和 FN 的数量小. 所以当我们得到了模型的混淆矩阵后, 就需要去看有多少观测值在第二、四象限对应的位置, 这里的数值越多越好; 反之, 在第一、三象限对应位置出现的观测值肯定是越少越好.


基于混淆矩阵的其他指标 (二级指标)


混淆矩阵里面统计的是个数, 有时候面对大量的数据, 光凭算个数, 很难衡量模型的优劣. 因此混淆矩阵在基本的统计结果上又延伸了如下 4 个指标:

  • 准确率 (Accuracy) -- 针对整个模型
    • 分类模型所有判断正确的结果占总观测值的比重
  • 精确率 (Precision) 
    • 在模型预测是 Positive 的所有结果中, 模型预测对的比重
  • 灵敏度 (Sensitivity) -- 就是召回率
    • 在真实值是 Positive 的所有结果中, 模型预测对的比重
  • 特异度 (Specificity)
    • 在真实值是 Negative 的所有结果中, 模型预测对的比重



三级指标


通过上面的四个二级指标, 可以将混淆矩阵中数量的结果转换为 0-1 之间的比率. 便于进行标准化的衡量.


在这四个指标的基础上进行拓展, 回产生另外一个三级指标, 这个指标叫做 F1 Score. 他的计算公式是:


  • F1-Score 指标综合了 Precision 与 Recall 的结果.
  • F1-Score 的取值范围从 0 到 1, 1 代表模型的输出最好, 0 代表模型的输出结果最差.


sklearn 实现



根据预测标签和真实标签计算混淆矩阵


from sklearn.metrics import confusion_matrix

confusion_matrix(真实标签向量, 预测标签向量)

注意:如果是二分类,计算出的矩阵,左上角是 TN (真负类),右下角是 TR (真正类)

In [1]: from sklearn.metrics import confusion_matrix

In [2]: confusion_matrix([1,2,3,2,1], [2,1,3,2,1])
Out[2]:
array([[1, 1, 0],
       [1, 1, 0],
       [0, 0, 1]])

In [4]: confusion_matrix([2,1,3,2,1], [2,1,3,2,1])
Out[4]:
array([[2, 0, 0],
       [0, 2, 0],
       [0, 0, 1]])


根据预测标签和真实标签计算精确率和召回率


精确率 precision = TP / (TP + FP)

召回率 recall = TP / (TP + FN)


from sklearn.metrics import precision_score, recall_score

precision_score(真实标签向量,预测标签向量)

recall_score(真实标签向量,预测标签向量)


In [5]: from sklearn.metrics import precision_score, recall_score   

In [10]: precision_score([1,2,3,2,1], [2,1,3,2,1], average='weighted')
Out[10]: 0.6

In [11]: precision_score([1,2,3,2,1], [2,1,3,2,1], average='micro')
Out[11]: 0.6

In [12]: precision_score([1,2,3,2,1], [2,1,3,2,1], average='macro')
Out[12]: 0.6666666666666666

In [13]: precision_score([2,1,3,2,1], [2,1,3,2,1], average='weighted')
Out[13]: 1.0

In [14]: precision_score([2,1,3,2,1], [2,1,3,2,1], average='micro')
Out[14]: 1.0

In [15]: precision_score([2,1,3,2,1], [2,1,3,2,1], average='macro')
Out[15]: 1.0

In [17]: recall_score([1,2,3,2,1], [2,1,3,2,1], average='weighted')
Out[17]: 0.6

In [18]: recall_score([1,2,3,2,1], [2,1,3,2,1], average='micro')
Out[18]: 0.6

In [19]: recall_score([1,2,3,2,1], [2,1,3,2,1], average='macro')
Out[19]: 0.6666666666666666

In [20]: recall_score([2,1,3,2,1], [2,1,3,2,1], average='weighted')
Out[20]: 1.0

In [21]: recall_score([2,1,3,2,1], [2,1,3,2,1], average='micro')
Out[21]: 1.0

In [22]: recall_score([2,1,3,2,1], [2,1,3,2,1], average='macro')
Out[22]: 1.0


根据预测标签和真实标签计算 F1 分数


F1 

= 2 / (1 / precision + 1 / recall) 

= 2 * ((precision * recall) / (precision + recall)) 

= TP / (TP + (FN + FP) / 2)


from sklearn.metrics import f1_score

f1_score(真实标签向量,预测标签向量)


F1-Score 指标综合了 Precision 与 Recall 的产出的结果。F1-Score 的取值范围从 0 到 1 的,1 代表模型的输出最好,0 代表模型的输出结果最差。

In [28]: from sklearn.metrics import f1_score

In [29]: f1_score([2,1,3,2,1], [2,1,3,2,1], average='weighted')
Out[29]: 1.0

In [30]: f1_score([2,1,3,2,1], [2,1,3,2,1], average='micro')
Out[30]: 1.0

In [31]: f1_score([2,1,3,2,1], [2,1,3,2,1], average='macro')
Out[31]: 1.0


精度和召回率随阈值变化的曲线 (PR 曲线)


调节阈值可以得到不同的精度和召回率,通过调用决策函数计算出每个实例的分数,然后根据这些分数确定决定阈值


1. cross_val_predict() 函数获取训练集中所有实例的分数

from sklearn.model_selection import cross_val_predict
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function") # 第一个参数是代表我们使用的算法

注: cross_val_predict 返回的预测 y 值,是由分片的 test y 组合起来的,而这样 y 值的各个部分来源于不同的输入的学习器。故 cross_val_predict 的返回值不能直接用于计算得分评价!



2. 有了这些分数,可以使用 precision_recall_curve() 函数来计算所有可能的阈值的精度和召回率。

from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

提高阈值确实可以降低召回率, 提高精度


3. 使用 Matplotlib 绘制精度和召回率相对于阈值的函数图

def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
      plt.plot(thresholds, precisions[:-1], "b--", label="Precision")
      plt.plot(thresholds, recalls[:-1], "g-", label="Recall")
      plt.xlabel("Threshold")
      plt.legend(loc="upper left")
      plt.ylim([0, 1])


当你提高阈值时,精度有时也有可能会下降(尽管总体趋势是上升的),但是召回率只会下降,所以图中的精度曲线有点崎岖,召回率曲线看起来更平滑


精度/召回率的函数图 (PR 曲线)


def plot_precision_recall(precisions, recalls):
    plt.plot(recalls[:-1],precisions[:-1],"g-")
    plt.xlabel("Recall")
    plt.ylabel("Precision")
    plt.ylim([0,1])
    plt.xlim([0,1])
plot_precision_recall(precisions, recalls)
plt.show()



ROC 曲线 (受试者工作特征曲线)

ROC: receiver operating characteristic curve


首先要清楚两个指标定义:

  • TPR = TP / (TP+FN): 召回率的另一种名称, 真阳率
  • FPR = FP / (FP + TN):  1-特异度, 假阳率

ROC 曲线绘制的是灵敏度和(1-特异度)的关系。


1. 首先需要使用 roc_curve() 函数计算多种阈值的 TPR 和 FPR

 from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)


2. 使用 Matplotlib 绘制 FPR 对 TPR 的曲线

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')
   plt.ylabel('True Positive Rate')
   plot_roc_curve(fpr, tpr)
   plt.show()


同样这里再次面临一个折中权衡:召回率(TPR)越高,分类器产生的假正类率(FPR)就越多。虚线表示纯随机分类器的 ROC 曲线;一个优秀的分类器应该离这条线越远越好(向左上角)



AUC


有一种比较分类器的方法是测量 ROC 曲线下面积(AUC)。完美的分类器的 ROC,AUC 等于 1,而纯随机分类器的ROC,AUC 等于 0.5。Scikit-Learn 提供计算 ROC 曲线的 AUC 的函数

from sklearn.metrics import roc_auc_score

roc_auc_score(y_train_5, y_scores)

由于 ROC 曲线与精度/召回率(或 PR)曲线非常相似,因此你可能会问如何决定使用哪种曲线。有一个经验法则是,当正类非常少见或者你更关注假正类而不是假负类时,你应该选择 PR 曲线,反之则是 ROC 曲线。 


补充


如果训练的是一个随机森林的分类器,是没有决策函数的,需要用到 predict_proba() 方法得出一个数组,其中每行代表一个实例,每列代表一个类别,数组的每一个元素代表某个给定实例属于某个给定类别的概率。

from sklearn.ensemble import RandomForestClassifier

forest_clf = RandomForestClassifier(random_state=42)

y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3, method="predict_proba")

y_scores_forest = y_probas_forest[:, 1] 
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5,y_scores_forest)

直接使用实例属于正类的概率值作为分数值




reference

https://baike.baidu.com/item/%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5/10087822

https://zhuanlan.zhihu.com/p/111274912#:~:text=%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5%E6%98%AFROC%E6%9B%B2%E7%BA%BF%E7%BB%98%E5%88%B6%E7%9A%84%E5%9F%BA%E7%A1%80%EF%BC%8C%E5%90%8C%E6%97%B6%E5%AE%83%E4%B9%9F%E6%98%AF%E8%A1%A1%E9%87%8F%E5%88%86%E7%B1%BB%E5%9E%8B%E6%A8%A1%E5%9E%8B%E5%87%86%E7%A1%AE%E5%BA%A6%E4%B8%AD%E6%9C%80%E5%9F%BA%E6%9C%AC%EF%BC%8C%E6%9C%80%E7%9B%B4%E8%A7%82%EF%BC%8C%E8%AE%A1%E7%AE%97%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84%E6%96%B9%E6%B3%95%E3%80%82%20%E4%B8%80%E5%8F%A5%E8%AF%9D%E8%A7%A3%E9%87%8A%E5%B0%B1%E6%98%AF%20%EF%BC%9A%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5%E5%B0%B1%E6%98%AF%E5%88%86%E5%88%AB%E7%BB%9F%E8%AE%A1%E5%88%86%E7%B1%BB%E6%A8%A1%E5%9E%8B%E5%BD%92%E9%94%99%E7%B1%BB%EF%BC%8C%E5%BD%92%E5%AF%B9%E7%B1%BB%E7%9A%84%E8%A7%82%E6%B5%8B%E5%80%BC%E4%B8%AA%E6%95%B0%EF%BC%8C%E7%84%B6%E5%90%8E%E6%8A%8A%E7%BB%93%E6%9E%9C%E6%94%BE%E5%9C%A8%E4%B8%80%E4%B8%AA%E8%A1%A8%E9%87%8C%E5%B1%95%E7%A4%BA%E5%87%BA%E6%9D%A5%E3%80%82%20%E8%BF%99%E4%B8%AA%E8%A1%A8%E5%B0%B1%E6%98%AF%E6%B7%B7%E6%B7%86%E7%9F%A9%E9%98%B5%E3%80%82,%E5%AE%9A%E4%B9%89%20%E4%BB%A5%E5%88%86%E7%B1%BB%E6%A8%A1%E5%9E%8B%E4%B8%AD%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84%E4%BA%8C%E5%88%86%E7%B1%BB%E4%B8%BA%E4%BE%8B%EF%BC%8C%E5%AF%B9%E4%BA%8E%E8%BF%99%E7%A7%8D%E9%97%AE%E9%A2%98%EF%BC%8C%E6%88%91%E4%BB%AC%E7%9A%84%E6%A8%A1%E5%9E%8B%E6%9C%80%E7%BB%88%E9%9C%80%E8%A6%81%E5%88%A4%E6%96%AD%E6%A0%B7%E6%9C%AC%E7%9A%84%E7%BB%93%E6%9E%9C%E6%98%AF0%E8%BF%98%E6%98%AF1%EF%BC%8C%E6%88%96%E8%80%85%E8%AF%B4%E6%98%AFpositive%E8%BF%98%E6%98%AFnegative%E3%80%82%20%E6%88%91%E4%BB%AC%E9%80%9A%E8%BF%87%E6%A0%B7%E6%9C%AC%E7%9A%84%E9%87%87%E9%9B%86%EF%BC%8C%E8%83%BD%E5%A4%9F%E7%9B%B4%E6%8E%A5%E7%9F%A5%E9%81%93%E7%9C%9F%E5%AE%9E%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8C%E5%93%AA%E4%BA%9B%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%9C%E6%98%AFpositive%EF%BC%8C%E5%93%AA%E4%BA%9B%E7%BB%93%E6%9E%9C%E6%98%AFnegative%E3%80%82%20%E5%90%8C%E6%97%B6%EF%BC%8C%E6%88%91%E4%BB%AC%E9%80%9A%E8%BF%87%E7%94%A8%E6%A0%B7%E6%9C%AC%E6%95%B0%E6%8D%AE%E8%B7%91%E5%87%BA%E5%88%86%E7%B1%BB%E5%99%A8%E6%A8%A1%E5%9E%8B%E7%9A%84%E7%BB%93%E6%9E%9C%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E7%9F%A5%E9%81%93%E6%A8%A1%E5%9E%8B%E8%AE%A4%E4%B8%BA%E8%BF%99%E4%BA%9B%E6%95%B0%E6%8D%AE%E5%93%AA%E4%BA%9B%E6%98%AFpositive%EF%BC%8C%E5%93%AA%E4%BA%9B%E6%98%AFnegative%E3%80%82