混淆矩阵(Confusion Matrix)
在机器学习领域和统计分类问题中,混淆矩阵(confusion matrix)是可视化工具,特别用于监督学习,在无监督学习一般叫做匹配矩阵。矩阵的每一列代表一个类的实例预测,而每一行表示一个实际的类的实例。之所以如此命名,是因为通过这个矩阵可以方便地看出机器是否将两个不同的类混淆了(比如说把一个类错当成了另一个)。1
对于二分类问题,混淆矩阵为一个 2*2 的表,行代表真实值,列代表预测值,见下表:
真实 \ 预测 |
0 |
1 |
0 |
预测 negative 正确(TN) |
预测 positive 错误(FP) |
1 |
预测 negative 错误(FN) |
预测 positive 正确(TP) |
以「患癌症问题」举例,上表中 0
代表未得癌症,1
代表得了癌症,行代表患癌症的真实值,列代表患癌症的预测值,那么,TN
就代表着真实情况没有得癌症且预测没有得癌症正确,同样 FN
代表真实情况得了癌症但预测其未得癌症。
精准率和召回率
精准率(precision):在所有预测值为 1
的情况下,实际也正确的概率。例如在癌症问题中表示预测患癌症成功的概率。公式如下:
召回率(recall):在所有真实值为 1
的情况下,预测正确的概率。例如在癌症问题中表示患癌症的人群中成功预测的概率。公式如下:
下面代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| 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
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
from sklearn.linear_model import LogisticRegression log_reg = LogisticRegression() log_reg.fit(X_train, y_train) log_reg.score(X_test, y_test)
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)) TN(y_test, y_log_predict)
def FP(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 0) & (y_predict == 1)) FP(y_test, y_log_predict)
def FN(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 1) & (y_predict == 0)) FN(y_test, y_log_predict)
def TP(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 1) & (y_predict == 1)) TP(y_test, y_log_predict)
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)] ]) confusion_matrix(y_test, y_log_predict)
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 precision_score(y_test, y_log_predict)
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 recall_score(y_test, y_log_predict)
|
在 Scikit-learn
中实现混淆矩阵、精准率和召回率
1 2 3 4 5 6 7 8
| from sklearn.metrics import confusion_matrix confusion_matrix(y_test, y_log_predict)
from sklearn.metrics import precision_score precision_score(y_test, y_log_predict)
from sklearn.metrics import recall_score recall_score(y_test, y_log_predict)
|
F1 Score
对于精准率和召回率,在不同的场景有不同的侧重点。
在股票预测问题中,设股票增长为 1
,我们更加关注精准率,即我们预测股票增长情况下预测正确的概率,而对于回归率我们并不太关心,因为召回率代表着实际会增长的股票我们预测到会增长的股票的概率,而增长的股票有很多,我们只是漏掉了部分会增长的股票而已,我们并没有什么损失;在病人诊断问题中,我们就更加关注召回率,即病人已经得病了能够诊断出其患病的概率,显然这时候召回率越高越好,能够不漏掉任何一个患病的病人,而精准率低一些并没有关系,即有一些人没有病被预测为有病,再继续做检查确诊就行了,不会造成巨大危害。
如果要同时关注这两个指标,就需要引入一个新的指标——F1 Score
F1 Score 是精准率和召回率的调和平均值
下面尝试不同的精准率和召回率下,F1 Score 的值变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def f1_score(precision, recall): try: return 2 * precision * recall / (precision + recall) except: return 0.0
precision = 0.5 recall = 0.5 f1_score(precision, recall)
precision = 0.1 recall = 0.9 f1_score(precision, recall)
precision = 0.0 recall = 1.0 f1_score(precision, recall)
|
在前面的例子中我们测得了精准率和召回率,现在使用 Scikit-learn
计算 f1_score:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from sklearn.metrics import confusion_matrix confusion_matrix(y_test, y_log_predict)
from sklearn.metrics import precision_score precision_score(y_test, y_log_predict)
from sklearn.metrics import recall_score recall_score(y_test, y_log_predict)
from sklearn.metrics import f1_score f1_score(y_test, y_log_predict)
|
F1 Score 对那些具有相近的精准率和召回率的分类器更为有利。
Precision-recall 的平衡
下图中用竖线代表阈值,划分左右两边分别为预测为 0 和 1,五角星代表实际值为 1,圆代表 0。
当阈值为 0 ,小于 0,大于 0 时分别不同的精准率和召回率,由此可知鱼与熊掌不可兼得。
Scikit-learn
不允许直接设置阈值,但可以访问它用于预测的决策分数。不是调用分类器的 predict() 方法,而是调用 decision_function() 方法,这个方法返回每个实例的分数,然后就可以根据这些分数,使用任意阈值进行预测了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| decision_scores = log_reg.decision_function(X_test)
np.min(decision_scores)
np.max(decision_scores)
y_predict_2 = np.array(decision_scores >= 5, dtype='int') confusion_matrix(y_test, y_predict_2)
precision_score(y_test, y_predict_2)
recall_score(y_test, y_predict_2)
y_predict_3 = np.array(decision_scores >= -5, dtype='int') confusion_matrix(y_test, y_predict_3)
precision_score(y_test, y_predict_3)
recall_score(y_test, y_predict_3)
|
下面用 for 循环拿到所有的阈值,绘制成图:
1 2 3 4 5 6 7 8 9 10 11
| precisions = [] recalls = [] thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1) 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()
|
Precision-Recall 曲线
将 Precision 和 Recall 分别放在坐标轴的 x 和 y 轴上,可以清晰的观察到两者的关系:
1 2
| plt.plot(precisions, recalls) plt.show()
|
在 Scikit-learn
中可以直接调用 precision_recall_curve
方法来得到相应参数:
1 2
| from sklearn.metrics import precision_recall_curve precisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores)
|
返回了三个参数,分别是 precisions、recalls 和 thresholds(阈值),下面看看这几个参数的元素个数:
1 2 3 4 5 6
| precisions.shape
recalls.shape
thresholds.shape
|
翻阅官方文档2可以知道最后一个精准率或回归率的值默认为 1 或 0,且没有 threshold。
最后用 matplotlib
绘制即可:
1 2 3
| plt.plot(thresholds, precisons[:-1]) plt.plot(thresholds, recalls[:-1]) plt.show()
|
若绘制前面那种 precison 与 recall 的坐标系:
1 2
| plt.plot(precisions, recalls) plt.show()
|
对比前面的图会发现这里只是前面的中间部分,是因为 precision_recall_curve 选择了它认为最重要的数据。
ROC 曲线
ROC 曲线经常与二元分类器一起使用,它与 precison-recall 曲线非常相似,但绘制的不是精准率和召回率,而是真正类率(TPR)与假正类率(FPR)。
回到前面的混淆矩阵二分类问题上,其实 TPR = recall,二者是一样的含义,而 FPR 表示在真实值为 0 的情况下,预测 为 1 的概率,公式:
TPR 和 FPR 二者的关系如下图,同大同小:
使用 Scikit-learn
中的 roc_curve
方法可以得到想要的参数:
1 2
| from sklearn.metrics import roc_curve fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
|
1 2
| plt.plot(fprs, tprs) plt.show()
|
绘制结果:
ROC 曲线一般用来比较两个模型孰优孰劣,其曲线下面积(AUC)是非常重要的参数,完美的 ROC AUC 等于 1,而纯随机分类器的 ROC AUC 等于 0.5。
下面是 Scikit-learn
中提供的方法:
1 2 3
| from sklearn.metrics import roc_auc_score roc_auc_score(y_test, decision_scores)
|
完整代码:Classification-Performance-Measures.ipynb