9.1 准确度的陷阱和混淆矩阵
对于这样一个系统,我们什么都没有做,就可以达到99.9%的准确率
image.png如果我们真的进行了一个机器学习算法进行训练,而准确度是99.9%的话,那么说明我们的机器学习算法是失败的,因为他比纯粹的预测一个人是监控的准确率更低。
image.png对于极度偏斜的数据(Skeked Data),只使用分类准确度是远远不够的
混淆矩阵 Confusion Matrix
对于二分类问题来说,混淆矩阵是一个2*2的矩阵。每一行代表的是对于预测的问题来说真实值是多少,相应的每一列是分类算法进行预测的预测值是多少。
-
行相当于二维数组的第一个维度,列相当于二维数组的第二个维度,相对来将,真实值(第一个维度)要在预测值(第二个维度)的前面
-
每个维度都是按照顺序排列的。
image.png
9.2 精准率和召回率
-
精准率:预测数据为1,预测对了的概率
在有偏的数据中,我们将分类1作为我们关注的对象,例如在医疗中,这个精准率就是指我们预测癌症预测的成功率
精准率=40%:我们没做100次患病的预测,其中会有40次是对的
image.png-
召回率:我们关注的那个事件,真实的发生了的数据(分母)中,我们成功的预测了多少。
在10000个癌症患者中,一共有10个癌症患者,我们成功的预测的预测出了8个
image.png image.png
我们通过精准率和召回率,判断出了这样做的预测算法是完全没有用的。
在及其有偏的数据中,我们不看准确率,而看精准率和召回率,才能分析出算法的好坏
image.png
9.3 实现混淆矩阵,精准率和召回率
import numpy as np
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
# 手动将手写数据集变成及其偏斜的数据。不是9的y=0,是9的y=1
y[digits.target==9] = 1
y[digits.target!=9] = 0
digits.target
array([0, 1, 2, ..., 8, 9, 8])
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)
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))
TN(y_test,y_log_predict)
403
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)
2
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)
36
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)
9
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)
array([[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
precision_score(y_test, y_log_predict)
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
recall_score(y_test, y_log_predict)
0.8
scikit-learn中的混淆矩阵,精准率和召回率
from sklearn.metrics import confusion_matrix, precision_score, recall_score
confusion_matrix(y_test,y_log_predict)
array([[403, 2],
[ 9, 36]])
precision_score(y_test, y_log_predict)
0.9473684210526315
recall_score(y_test, y_log_predict)
0.8
9.4 F1 Score
1.F1 Score的含义
精准率和召回率是两个指标,有的时候精准率高一些,有的时候召回率高一些,在我们使用的时候,我们应该怎么解读这个精准率和召回率呢?
这个问题的答案,和机器学习大多数的取舍是一样的,应该视情况而定。
-
有的时候我们注重精准率,如股票预测。
我们希望这个比例越高越好。如果我们预测股票升了,我们就要购买这个股票,如果我们犯了FP的错误(实际上股票将下来了,而我们预测升上来了),那么我们就就亏钱了。 但是对于这个应用来说,很有可能我们对召回率不是特别关注。可能有很多上升周期,但是我们落掉了一些上升的周期(本来为1,我们错误的判断为0),这对我们没有太多的损失,因为我们漏掉了他,也不会投钱进去。
-
有的时候我们注重召回率,如病人诊断。
召回率低意味着:本来一个病人得病了,但是我们没有把他预测出来,这就意味着这个病人的病情会继续恶化下去。所以召回率更加重要,我们希望把所有有病的患者都预测出来。但是精准率却不是特别重要,因为本来一个人没病,我们预测他有病,这时候让他去做进一步的检查,进行确诊就好了。我们犯了FP的错误,只是让他多做了一次检查而已。这个时候召回率比精准率重要。
-
有的时候我们希望同时关注精准率和召回率,这个时候我们可以使用F1 Score,让我们兼顾精准率和召回率
调和平均值的特点:如果一个值特别高,一个值特别低,那么我们得到的F1 Score 也将特别的低,只有二者都非常高,我们得到的值才会特别高
image.png2.F1 Score 的实现
import numpy as np
def f1_score(precision, recall):
return 2 * (precision * recall) / (precision + recall)
precision = 0.5
recall = 0.5
f1_score(precision, recall)
# 当二者相同,得到的就是这个相同的值
0.5
precision = 0.1
recall = 0.9
f1_score(precision, recall)
# 有一个非常小,整体就非常小
0.18000000000000002
precision = 0.9
recall = 0
f1_score(precision, recall)
0.0
precision = 0.9
recall = 0.95
f1_score(precision, recall)
# 只有都非常大,结果才会打
0.9243243243243242
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
# 手动将手写数据集变成及其偏斜的数据。不是9的y=0,是9的y=1
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)
log_reg.score(X_test,y_test)
y_log_predict = log_reg.predict(X_test)
from sklearn.metrics import f1_score
f1_score(y_test, y_log_predict)
0.8674698795180723
0.8674698795180723 显然没有精准率和召回率高,这是因为首先我们的数据是有偏的,精准率和召回率都比准确率要低一些,在这里精准率和召回率能够更好的反应我们的结果。其次使用逻辑回归进行预测,明显召回率比较低,所以f1_score 被召回率拉低了,这个时候对于这个有偏的数据来说,我们更倾向认为f1_score 能更好的反应算法的水平
9.5 Precision-Recall的平衡
精准率和召回率这两个指标是互相矛盾的。我们要找到的是这两个指标直接的一个平衡。
1.理解为什么二者是矛盾的
回忆我们上一章学习的逻辑回归,通过一系列的推导,我们得出了决策边界:θT·Xb = 0
image.png如果我们设置任意一个常量-threshold。可不可以让这个threshold作为决策边界呢
即θT·Xb = threshold。这样,相当于我们为我们的逻辑回归算法添加了一个新的超参数threshold,通过指定threshold,相对于可以平移决策边界的直线,进而影响逻辑回归的结果。
由上图可知,不同的threshold对应的精准率和召回率的关系。随着threshold增大,精准率在不断的变高,而召回率在不断的降低。由此说明精准率和召回率是相互矛盾的
2.直观的理解
如果想让精准率变高的话,相应的其实就是我们只能讲那些特别有把握的数据分类为1。在这种情况下,实际上我们做的事情是让我们的算法做的事让其判断样本的概率是百分之90甚至百分之99的时候,我们才预测他为1,在这样的情况,很显然有很多真实为1的样本就被排除在了外面,所以召回率就会变低。
如果要让召回率升高,我们就要降低算法判断的概率的threshold,这时却是召回率提高了,但是精准率下降了
3.编码观察精准率和召回率的平衡
# 结果即为对于每一个样本来说,在逻辑回归算法中对应的score值是什么
log_reg.decision_function(X_test)[:10]
array([-22.05701166, -33.02939187, -16.21331661, -80.37908046,
-48.25125463, -24.54003391, -44.39167886, -25.04286766,
-0.97831023, -19.7173878 ])
# 因为对于前10个样本来说,分数值都是小于0 的,所以预测值都是0
log_reg.predict(X_test)[:10]
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
dscision_scores = log_reg.decision_function(X_test)
print(np.min(dscision_scores))
print(np.max(dscision_scores))
-85.68606536672523
19.889528301315465
# 通过构建新的y_predict,来实现修改threshold为5
y_predict2 = np.array(dscision_scores >= 5, dtype='int')
confusion_matrix(y_test, y_predict2)
array([[404, 1],
[ 21, 24]])
# 升高threshold后,精准率升高,召回率降低
print(precision_score(y_test, y_predict2))
print(recall_score(y_test, y_predict2))
0.96
0.5333333333333333
9.6 精准率-召回率曲线
召回率曲线
import numpy as np
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
# 手动将手写数据集变成及其偏斜的数据。不是9的y=0,是9的y=1
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)
decision_scores = log_reg.decision_function(X_test)
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
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))
from matplotlib import pyplot as plt
plt.plot(thresholds, precisions)
plt.plot(thresholds, recalls)
plt.show()
image.png
Precision-Recall 曲线
plt.plot(precisions, recalls)
[<matplotlib.lines.Line2D at 0x1a132c5b38>]
image.png通过曲线再次印证了,精准率和召回率是相互制约的,这个曲线急剧下降的一个点,可能就是精准率和召回率平衡最好的一个位置
scikit-learn中的Precision-Recall 曲线
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores)
# sklearn中会根据我们顶一个的decision_scores的值来取最合适的步长
precisions.shape
(145,)
recalls.shape
(145,)
# The last precision and recall values are 1. and 0. respectively and do not have a corresponding threshold.
# This ensures that the graph starts on the x axis.
thresholds.shape
(144,)
plt.plot(thresholds, precisions[:-1])
plt.plot(thresholds, recalls[:-1])
# 没有从最小值开始取,在sklearn的封装中,他自动寻找了他认为最重要的数据
plt.show()
image.png
plt.plot(precisions, recalls)
[<matplotlib.lines.Line2D at 0x1a173457f0>]
image.png image.png我们用不同的超参数训练不同的模型,每得到一个新的模型,就可以得到一根不同的P-R曲线,如果这个P-R曲线更靠外(也就是与X轴,Y轴所包围的面积越大),那么说明这根曲线对应的模型越好,因为对应这个曲线上的每一个点,他的precision_score和recall_score都比另一个曲线要大
9.7 ROC曲线
1.什么是ROC 曲线
-
ROC:Receiver Operation Characteristic Curve,描述TPR 和 FPR之间的关系
-
TPR - True Positive Rate,FPR - False Positive Rate
TPR:预测为1,并且预测对了的数量占真实值为1的百分比是多少
FPR:预测为1,但是预测错了的数量占真实值为0的百分比是多少
-
TPR和FPR之间的关系
随着threshold逐渐降低,TPR 和 FPR都是逐渐升高的,换句话说,TPR和FPR之间是存在着相一致的关系的
为了提高TPR,我们就要将threshold拉低,但是在拉低的过程中,犯FP的错误的概率也会增高
编程实现ROC曲线
ROC曲线
from machine_learning.metrics import TPR,FPR
fprs = []
tprs = []
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')
fprs.append(FPR(y_test, y_predict))
tprs.append(TPR(y_test, y_predict))
# 绘制ROC曲线
plt.plot(fprs,tprs)
[<matplotlib.lines.Line2D at 0x1a1795af98>]
image.pngscikit-learn中的ROC曲线
from sklearn.metrics import roc_curve
fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
plt.plot(fprs, tprs)
[<matplotlib.lines.Line2D at 0x1a179b9160>]
image.png对于ROC曲线,我们通常关注的是这根曲线下面的面积的大小,面积越大,代表分类算法效果越好。
在fpr越低的时候(犯fp错误的次数越少的时候),相应的FPR越高的时候,这根曲线整体就会被抬的越高,
他的面积相应的也会越大,这种情况下分类算法的效果就更好
# 计算ROC曲线的面积
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, decision_scores)
0.9830452674897119
对于ROC曲线来说,他对于有偏的数据并不是那么敏感,所以在有偏的数据集里,看一下精准率和召回率是非常有必要的
而ROC的应用场景是比较两个模型的优劣
9.8 多分类问题中的混淆矩阵
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.8)
from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
# sklearn 的 逻辑回归,如果我们传进来的数据集有多个分类,他讲使用OVR的方式来解决多分类的问题
log_reg.fit(X_train, y_train)
log_reg.score(X_test, y_test)
0.9304589707927677
y_predict = log_reg.predict(X_test)
from sklearn.metrics import precision_score
# 通过传入average参数可以让precision_score处理多分类问题
precision_score(y_test, y_predict, average="micro")
0.9304589707927677
from sklearn.metrics import confusion_matrix
# sklearn 的混淆矩阵天然支持多分类问题
# 第i行第j列的数值代表 真值为i而预测为j的样本数量有多少
confusion_matrix(y_test, y_predict)
array([[147, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[ 0, 130, 2, 0, 0, 2, 0, 0, 7, 6],
[ 0, 0, 139, 2, 0, 0, 0, 0, 1, 0],
[ 0, 0, 0, 145, 0, 0, 0, 0, 3, 1],
[ 0, 3, 0, 1, 135, 0, 0, 0, 0, 4],
[ 1, 1, 1, 0, 0, 147, 0, 0, 0, 4],
[ 0, 0, 0, 0, 2, 0, 133, 0, 0, 0],
[ 0, 0, 0, 3, 5, 0, 0, 141, 1, 2],
[ 1, 11, 3, 6, 1, 2, 1, 1, 105, 6],
[ 1, 2, 0, 3, 3, 1, 0, 0, 5, 116]])
cfm = confusion_matrix(y_test, y_predict)
# 绘制混淆矩阵,越亮的地方说明数值越大
plt.matshow(cfm, cmap=plt.cm.gray)
<matplotlib.image.AxesImage at 0x117a6ca58>
image.png# 每一行有多少样本,在列方向上求和
row_sums = np.sum(cfm, axis=1)
# 矩阵中的每一行的数字都会除以这一行的数字和得到的一个百分比矩阵
err_matrix = cfm / row_sums
# 将对角线上的数字全都填成是0,剩下的其他的格子就是犯错误的百分比
np.fill_diagonal(err_matrix, 0)
err_matrix
array([[0. , 0. , 0. , 0. , 0.00699301,
- , 0. , 0. , 0. , 0. ],
[0. , 0. , 0.01408451, 0. , 0. ,
0.01298701, 0. , 0. , 0.05109489, 0.04580153],
[0. , 0. , 0. , 0.01342282, 0. , - , 0. , 0. , 0.00729927, 0. ],
[0. , 0. , 0. , 0. , 0. , - , 0. , 0. , 0.02189781, 0.00763359],
[0. , 0.02040816, 0. , 0.00671141, 0. , - , 0. , 0. , 0. , 0.03053435],
[0.00675676, 0.00680272, 0.00704225, 0. , 0. , - , 0. , 0. , 0. , 0.03053435],
[0. , 0. , 0. , 0. , 0.01398601, - , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0.02013423, 0.03496503, - , 0. , 0. , 0.00729927, 0.01526718],
[0.00675676, 0.07482993, 0.02112676, 0.04026846, 0.00699301,
0.01298701, 0.00740741, 0.00657895, 0. , 0.04580153],
[0.00675676, 0.01360544, 0. , 0.02013423, 0.02097902,
0.00649351, 0. , 0. , 0.03649635, 0. ]])
# 多分类问题中越亮的地方代表的就是犯错误越多的地方,并且通过横纵坐标可以看出具体的错误
plt.matshow(err_matrix, cmap=plt.cm.gray)
<matplotlib.image.AxesImage at 0x1a19bc8630>
image.png通过这样一个矩阵,我们就可以很清晰的发现分类的错误,并且更重要的是,可以看出具体的错误类型,比如有很多的8我们把他规约为了1,有很多1我们规约成了8,有了这些提示,我们就可以进一步改进我们的算法了。
我们可以看到,这个分类的结果,我们又把他规约成了一个二分类的问题,我们现在的分类的结果很容易混淆1和9以及1和8,相应的我们可以微调1和9和1和8分类问题 中的阈值,来相应的调整多分类问题的准确度
网友评论