(九)评价分类结果-爱代码爱编程
一,准确度的陷阱和混淆矩阵
分类准确度的问题
引入:
假设一个癌症检测系统,输入体检信息,可以判断是否有癌症,预测的准确度是99.9%
是否可以判断这个系统非常的好呢?
不可以!如果癌症的产生率只有0.1%,系统只要预测所有人都是健康的,就可以达到99.9%的准确率。如果癌症产生率是0.01%,系统预测所有人都是健康的,准确率变成了99.99%。
对于数据极度偏斜(Skewed Data)的数据(上述),只使用分类准确度是远远不够的
使用混淆矩阵可以做进一步分析
混淆矩阵(Confusion Matrix)
对于二分类问题:
行占据第一个维度,列占据第二个维度,并且每个维度的值的排列也是从小到大的顺序
假设有10000个人的体检信息进行了真实与预测的对比
之后就可以根据混淆矩阵创建新的判断标准
二,精准率和召回率
新的俩个指标
精准率:正确预测的癌症患者/预测癌症患者数量
为什么取预测值为1的一列为精准率呢?
因为在实际有偏的数据中,我们通常把1作为重点关注的对象,在预测癌症中就是把1作为患者。放到金融预测中,我们把1作为有风险的人,把他们作为关注的对象。
召回率:正确预测的癌症患者/真实癌症患者的数量
当有10000个人,我们预测所有人都是健康的:
精准率和召回率就是把偏的数据样本基础大的忽略掉使用近重点关注一小部分
三,实现混淆矩阵,精准率和召回率
自己构造混淆矩阵,精准率和召回率
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
构造比较偏斜的数据:
digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
y[digits.target==9] = 1 #仅关注=9的数字
y[digits.target!=9] = 0
使用线性回归:
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
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) #得到预测结果的向量形式
定义TN的函数:
def TN(y_true, y_predict):
assert len(y_true) == len(y_predict) #确保向量长度相等
return np.sum((y_true == 0) & (y_predict == 0)) #返回真实和预测都是0的个数
TN(y_test, y_log_predict) #TN=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 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 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 confusion_matrix(y_true, y_predict): #定义混淆矩阵
return np.array([ #是一个2×2的矩阵
[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: #异常检测,防止分母为0
return tp / (tp + fp)
except:
return 0.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中的混淆矩阵,精准率和召回率
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
confusion_matrix(y_test, y_log_predict)
precision_score(y_test, y_log_predict)
recall_score(y_test, y_log_predict)
以上和自己编写的结果一样
如何解决精准率和召回率差异较大的问题?
四,F1 Score
如何取舍精准率与召回率?
具体问题具体分析!
有时候我们注重精准率。比如股票预测,把股票上升作为1,我们做的所有分类为1的预测中,有多少是预测准确的,对应股票预测就是所有分类中,预测的股票上升的情况里,有多少是真的上升。如果根据股票的上涨来买入,假如有些时候股票也上升了,只是没有预测出来,对我们来说也没有多大的损失,而如果预测股票上升了,但实际下降的,那么我们就会亏损很多。
同理,有时候更加注重精准率,比如医疗领域对病人的诊断。如果召回率低,则表示如果有病人得病了,但是我们并没有预测出来,那么患者就会面临风险;而如果我们预测病人是患病了,但实际没有患病,那么其实对于病人也没有什么影响,就是虚惊一场。其实就是宁可错杀不能漏杀的意思。因此我们希望召回率更高一些。
有些情况下,俩个指标是都可以的,这时如果要同时用到这俩个指标,就需要对这俩个指标进行一个平衡:F1 Score
F1 Score原理
俩个指标,二者都兼顾
此时的F1表示精准率和召回率的调和平均值
不同于算数平均值的是:如果俩个值一个很高,一个很低,用调和平均就会变得比较低,而用算数平均值可能结果还是很高。
实现F1 Score
手动实现:
import numpy as np
def f1_score(precision, recall):
try:
return 2 * precision * recall / (precision + recall)
except:
return 0.0
sklearn中的F1 Score
对上一节的预测结果进行计算
from sklearn.metrics import f1_score
f1_score(y_test, y_predict)
五,精准率和召回率的平衡
平衡关系与理解
实际上,精准率和召回率是俩个互相矛盾的指标,当我们着手提高了精准率,召回率会跟着下降。因此我们在优化模型的时候,需要对这俩个评价指标进行平衡。
用逻辑回归来说,我们用以下的式子表示决策边界,对于逻辑回归的阈值时0,当我们改变这个阈值就可以有不同的精准率和召回率
下图中,五角星表示重点关注对象,分配其值为1;圆形分配值为0。阈值的左侧表示预测是0,右侧表示预测为1。
当阈值划分到右侧些,精准率提高了,召回率下降;当阈值划分到左侧些,精准率下降,召回率上升。
可以这样理解。在金融系统预测中,我们想要把特别有把握上升的对象判断为1,也就是想提高精准率,那么就可以提高这个阈值,把门槛抬高;只有当上升可能性确实很高时候(比如90%可能性上升)才可以预测判定为1。同理,在医院的患者诊断系统中,我们如果希望,即使患者有10%的患癌风险,我们也将其判定为患癌,那么就可以调低阈值,这样可以看到比较高的召回率,但精准率就会下降。
召回:某批次的零件中,如果降低判断标准,那么更多批次的零件就会被召回;医院诊断中,如果降低判断阈值,那么就会有更多的病人被召回重新检查。
精准:在股票市场中,如果提高判断阈值,就更有把握盈利。
在程序中调节阈值
同样使用上面的样本进行处理
sklearn中没有可以传入阈值的参数,逻辑回归中自带的predict函数阈值默认是0
可以使用逻辑回归中的decision_function函数可以得到每一个样本的score值是多少进而设定阈值
log_reg.decision_function(X_test)[:10] #可以看前十个score
log_reg.predict(X_test)[:10] #对应前十个的预测值
score<0就判定预测为0
decision_scores = log_reg.decision_function(X_test) #将预测的score值存起来
看看score里的最大和最小值
y_predict_2 = np.array(decision_scores >= 5, dtype='int')
#判断条件,布尔型转换为整型,将>=5的score变为1,否则为0
confusion_matrix(y_test, y_predict_2) #查看混淆矩阵,发生了变化
precision_score(y_test, y_predict_2)
recall_score(y_test, y_predict_2)
精准率与召回率均变化,精准率上升,召回率下降
当改变阈值为-5,可以看到精准率下降,召回率上升
那么怎么选取阈值呢?
六,精准率-召回率曲线
数据同上,同样进行逻辑回归操作,然后我们对通过改变阈值来绘制精准率与召回率的曲线。
import matplotlib.pyplot as plt
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), 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()
绘制精确率-召回率曲线
plt.plot(precisions, recalls)
plt.show()
大约在0.75的位置,曲线急剧下降,对应召回率不怎么改变,精确率急剧下降,暗示这个点可能对应要找的平衡的最佳阈值
scikit-learn中的精确率,召回率曲线
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores)
需要注意的是,对应精准率为1,召回率为0的点没有阈值,因此向量中,阈值给的长度比精准数和召回数的长度要小1个元素。
plt.plot(thresholds, precisions[:-1]) #注意按上述说法,要去掉一个值
plt.plot(thresholds, recalls[:-1])
plt.show()
当使用不同的算法得到不同的P-R曲线,外边的曲线要优于里边的曲线
七,ROC曲线 (Receiver Operation Characteristic Curve)
描述TPR(TP-rate)和FPR之间的关系
绘制ROC
数据同上,使用逻辑回归拟合
from sklearn.metrics import roc_curve
fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
plt.plot(fprs, tprs)
plt.show()
通常关注曲线下的面积,曲线下的面积越大,表示效果越好
from sklearn.metrics import roc_auc_score #求面积
roc_auc_score(y_test, decision_scores)
通过ROC曲线下的面积大小就可以判断算法模型的好坏
PR曲线与ROC曲线的选择
首先需要明确一点:在机器学习领域,对于指标,很多时候不是选择谁的问题,而是在可能的情况下,所有的指标都应该看一看,以确定训练的模型是否有问题。这就好比在医院检查身体,不是先确定要看哪个指标,然后就只看这个指标;而是尽可能去看所有指标。因为任何一个指标存在问题,都可能意味着你的身体的某个机能存在问题。
所以,我们的目的不是“找到”单一的“最好”的指标;而是了解所有的指标背后在反映什么,在看到这个指标出现问题的时候,能够判断问题可能出现在哪里,进而改进我们的模型。虽然我们的改进方向可能是单一的。这就好比在医院看病,我们主要症状可能是发烧,此时,我们的主要异常指标是“温度”,所以我们主要尝试使用可以“降温”的治疗手段,但这不代表我们在治疗的过程中对其他指标不管不顾,只要把温度降到正常水平就可以了。在尝试“降温”的过程中,如果我们发现血压,心跳,白血球,红血球,任何一个指标出现异常,我们都需要马上做出相应的反应。
具体到PR曲线和ROC曲线,他们的核心区别在TN。可以看出来,PR曲线其实不反应TN。所以,如果你的应用场景中,如果TN并不重要,那么PR曲线是一个很好的指标(事实上,Precision和Recall就是通过抹去TN,来去除极度的偏斜数据带来的影响,进而放大FP, FN和TP三者的关系的)。
而ROC曲线则综合了TN, FP, FN和TP。虽然它对TN极度多的情况下,FP,FN和TP的变化不敏感。所以在TN没有那么多(数据没有那么偏斜),或者TN是一种很重要的需要考虑的情况下,ROC能反映出PR不能反映的问题。
八,多分类问题中的混淆矩阵
导入数据,不对数据进行偏斜处理,这样解决的是10分类问题
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score
from sklearn.metrics import confusion_matrix
digits = datasets.load_digits()
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.8, random_state=666)
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
log_reg.score(X_test, y_test)
y_predict = log_reg.predict(X_test)
precision_score(y_test, y_predict, average="micro") #默认解决二分类问题,需要加参数
混淆矩阵:
confusion_matrix(y_test, y_predict) #10×10矩阵
解读方式一样,对角线表示预测成功
cfm = confusion_matrix(y_test, y_predict)
plt.matshow(cfm, cmap=plt.cm.gray) #绘制混淆矩阵,灰度图,越亮值越大
plt.show()
row_sums = np.sum(cfm, axis=1) #在列的方向求和,压缩第二个维度即求出每一行的和
err_matrix = cfm / row_sums
np.fill_diagonal(err_matrix, 0) #把对角线填0,不关心对角线,关心犯得错误
plt.matshow(err_matrix, cmap=plt.cm.gray)
plt.show()
越亮的地方表示犯错越多的地方,其中最亮那个表示真值为1的样本被预测为了9,可能不是算法的问题,而是数据的问题,需要剔除数据