机器学习入门——分类算法的评价
引言
我们在之前介绍的分类算法中,判断算法好坏经常使用的指标就是准确率。本文就来告诉大家准确率的陷阱以及如何避免。
- 上一篇:机器学习入门——图解逻辑回归
- 下一篇:机器学习入门——图解支持向量机
准确度的陷阱
如果只用分类准确度来评价分类算法的话,是存在一定的问题的。
比如,一个癌症预测系统,输入体检信息,可以判断是否患有癌症。
假设预测准确度达到99.99%,那么这个系统是好的系统吗?
如果癌症产生的概率只有0.1%,那么系统只要预测所有人都是健康的,即可达到99.9%的准确率。
因此仅使用分类准确度来衡量分类系统是远远不够的,这种情况常常发生在数据是极度偏斜的。
因此需要引入其他指标来衡量分类算法的好坏。
首先介绍混淆矩阵这一工具。
混淆矩阵
混淆矩阵(Confusion Matrix),首先对于二分类问题,混淆矩阵是一个2x2的矩阵(没有考虑表头):
图1
其中,蓝色部分是表头,灰色部分是相应的预测个数。在蓝色的表头中,通常列代表预测值,而行代表真实值,并且先写出0,再写出1。
其中用1(Postitive,正例)和0(Negative,负例)表示分类结果。
- 对于灰色2x2矩阵第一格,TN表示真实值是0,预测值也是0的个数,即正确(T)地预测为Negative →
TN
- FP表示真实值是0,预测值1的个数,即错误(F)地预测为Positive→
FP
- FN表示真实值是1,预测值0的个数,即错误(F)地预测为Negative→
FN
- TP表示真实值是1,预测值1的个数,即正确(T)地预测为Positive→
TP
下面以癌症预测为例,假设有10000个人,通过某个算法预测结果(这里1代表患有癌症,阳性;0代表阴性,不患癌症),得到的混淆矩阵如下:
图2
精确率和召回率
有了混淆矩阵,就可以引入两个指标:精准率(precision)和召回率(recall)。
精准率:
precision=TPTP+FPprecision = \frac{TP}{TP + FP} precision=TP+FPTP
图3
还是看刚才的例子,这里我们标出相应的TN,FP,FN,TP。然后就可以得到
精准率 = 8 / (8 + 12) = 40%
可以看到,精准率就是预测为正例(TP+FPTP + FPTP+FP)的那些数据里预测正确(TPTPTP)的数据比例。
为什么它叫精准率,因为通常在有偏的数据中,我们通常将关注的设为1(Positive)。在癌症预测中,40%的精准率代表,每做一次患病的预测,只有40%的概率是正确的。
从图中可以看到,精准率的分母是预测值为1的那一列。
召回率:
recall=TPTP+FNrecall = \frac{TP}{TP+FN} recall=TP+FNTP
在癌症预测例子中,召回率=8 / (8 + 2) = 80%
召回率就是真实为正例(FN+TPFN+TPFN+TP)的那些数据里预测正确(TPTPTP)的数据比例。
在癌症预测例子中,就是10000个人里面,共有10个癌症患者,其中成功预测出了8个。
从图中可以看到,召回率的分母是真实值为1的那一行。
现在,以预测癌症为例,假设有10000个人,其中有10个人患有癌症,我们的系统预测所有人都是健康的。根据这一情况,我们画出混淆矩阵。
图4
在这个系统中,准确率=9990 / (10000) = 99.9%
精准率 = 0 / (0+0) = 0 无意义,分母为0是无意义的,我们也认为这个预测结果也是无意义的,然后给个最低值0
召回率 = 0 / (10 + 0) = 0 ,说明患有癌症的一个都没预测出来。
这就是精准率和召回率在有偏的数据中的意义。
还是一样的,下面我们编程实现这些指标。
代码实现混淆矩阵、精准率和召回率
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegressiondigits = datasets.load_digits()
X = digits.data
y = digits.target.copy()# 产生偏斜数据
# 变成二分类问题,大多数偏向于0类别
y[digits.target==9] = 1
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)print(log_reg.score(X_test,y_test)) # 0.9755555555555555
下面我们用上面学到的精准率和召回率来进行评估看看。
首先得到预测结果,可以看到,是一个非0即1的数组。
根据numpy的数组运算,就可以写出下面几个函数:
def TN(y_true,y_predict):# 真实是0,预测也是0return np.sum((y_true == 0) & (y_predict == 0))def FP(y_true,y_predict):# 真实是0,预测是1return np.sum((y_true == 0) & (y_predict == 1))def FN(y_true,y_predict):# 真实是1,预测是0return np.sum((y_true == 1) & (y_predict == 0))def TP(y_true,y_predict):# 真实是1,预测也是1return np.sum((y_true == 1) & (y_predict == 1))# 混淆矩阵
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)],])
调用混淆矩阵函数,就可以得到混淆矩阵结果(去掉了表头)。
接下来计算精准率和召回率。
# 定义精准率函数
def precision_score(y_true, y_predict):tp = TP(y_true,y_predict)fp = FP(y_true,y_predict)return tp / (tp + fp) if tp + fp != 0 else 0# 定义召回率函数
def recall_score(y_true, y_predict):tp = TP(y_true,y_predict)fn = FN(y_true,y_predict)return tp / (tp + fn) if tp + fn != 0 else 0
这就是通过代码实现计算混淆矩阵、精准率和召回率的例子。
sklearn其实已经帮我们实现好了计算这些指标的函数,下面看一下是如何使用的:
from sklearn.metrics import confusion_matrixconfusion_matrix(y_test,y_log_predict)
from sklearn.metrics import precision_score,recall_scoreprint(precision_score(y_test,y_log_predict)) # 0.9473684210526315
print(recall_score(y_test,y_log_predict)) # 0.8
现在我们学会了两个指标,那么如何解读这两个指标呢。请继续阅读。
F1 Score
如何使用这两个指标呢,其实和使用的场景有关。
在不同的应用场景下,我们的关注点不同,例如,在预测股票的时候,我们更关心精准率,即我们预测上涨(Positive)的那些股票里,真的上涨了的有多少;
而在预测癌症的场景下,我们更关注召回率,即真的患癌的那些人里我们预测错了情况应该越少越好,因为真的患癌如果没有检测出来,结果是很严重的。
还有一些场景,可能需要同时关注精准率和召回率,此时引入一个新的指标:F1 Score,它就是兼顾精准率和召回率的指标。
F1 Score:
F1=2⋅precision⋅recallprecision+recallF1 = \frac{2 \cdot precision \cdot recall}{precision + recall} F1=precision+recall2⋅precision⋅recall
F1 Score 是精准率和召回率的调和平均值,把F1 Score取倒数,就得:
1F1=12(1precision+1recall)\frac{1}{F1} = \frac{1}{2} \left( \frac{1}{precision } + \frac{1}{recall} \right) F11=21(precision1+recall1)
调和平均值的性质就是,只有当精准率和召回率二者都非常高的时候,它们的调和平均才会高。如果其中之一很低,调和平均就会被拉得接近于那个很低的数。
下面我们通过代码实现以下这个F1 Score:
import numpy as npdef f1_score(precision,recall):try:return 2 * precision * recall /(precision + recall)except:# 如果分母为0return 0.0
如果精准率和召回率相等的话,那么F1 Score就返回相等的这个值。
可以看到,有一个较小,都会大大拉低F1 Score的值,如果是算术平均的话就是0.5了,这就是F1 Score的优势。
因此,精准率和召回率要同时比较好时,F1 Score才会比较高。
precision = precision_score(y_test,y_log_predict) # 0.9473684210526315recall = recall_score(y_test,y_log_predict) # 0.8print(f1_score(precision,recall)) # 0.8674698795180723
我们用上一节得到的精确率和召回率代入f1_score
函数中,得到具体的F1 Score为0.867。
精准率和召回率的平衡
那我们只要让精准率和召回率都变大,我们的F1 Score也会变得很大。但是,其实精准率和召回率有一定的矛盾关系。如果精准率变高,召回率就会下降;反之亦然。 因此,重点是如何找到这二者之间的平衡。
以逻辑回归为引子
逻辑回归计算出的p^\hat pp^如果大于等于0.5,分类为1;否则分类为0。 这样我们得到决策边界 θT⋅xb=0\theta^T \cdot x_b = 0θT⋅xb=0。
如果我们不是令决策边界等于0,而是定义一个阈值threshold
。
令
θT⋅xb=threshold\theta^T \cdot x_b = threshold θT⋅xb=threshold
此时如果θT⋅xb>threshold\theta^T \cdot x_b >thresholdθT⋅xb>threshold,就让它分类为1;否则分类为0。
也就是说,我们可以通过改变这个阈值,来影响分类结果,下面我们来看会如何影响分类结果。
我们以θT⋅xb\theta^T \cdot x_bθT⋅xb的值画一个轴,定义其为score。
图5
图5是以0为阈值时,分类的结果。其中轴上面的五角星和圆圈代表不同的样本,假设五角星是我们关注的样本,真实类别为1;而圆圈代表类别为0。
图6 此时,精准率为0.8,召回率为0.67。 怎么算的呢,大于阈值(所在垂线的右侧)分类为1,分类为1的是我们关注的样本。根据精确率的定义,预测为1的那些数据里,预测为正确的比例:4/5 = 0.8。
而召回率是,真实为1的数据里,预测正确的比例:4/6 = 0.67。
如果我们挪动阈值,那么精准率和召回率会怎样变化呢?
图7
这是我们将分类阈值变大之后的结果,如果变小呢?
图8
总结一下,看图8,随着阈值的增大,精准率变高,同时召回率变低。
从这可以看到精准率和召回率的矛盾。
如果我们思考一下,要想精确率变高,那么只有相当有把握的,我们才会分类为1,此时就有可能排除掉很多实际上为1的样本。
如果想要召回率升高,那么哪怕把握不高,我们也判断为1,这样就会包含很多实际为0的样本。
下面使用程序来看一下它们的关系。
# 逻辑回归默认以0为阈值进行判断的,我们无法这个阈值。
# 但是逻辑回归可以输出得到的分数
decision_scores = log_reg.decision_function(X_test)
decision_scores[:10]
print(np.min(decision_scores),np.max(decision_scores)) # -85.71086856567963 19.87471653041543# 我们可以利用这个分数来进行自定义判断
y_predict_2 = np.array(decision_scores >= 5, dtype='int')
confusion_matrix(y_test,y_predict_2)
我们把基于decision_scores >= 5
的结果转换为1和0的预测结果,然后得到混淆矩阵。
下面我们可以调低阈值,然而再次看结果:
精准率-召回率曲线
上一小节我们通过代码的方式,来看到了精确率和召回率之间的平衡关系。本小节,我们通过绘图的方式让大家更直观的看到这种关系。
from sklearn.metrics import precision_score,recall_score
import matplotlib.pyplot as pltprecisions = [] # 保存每个threshold的精准率
recalls = [] # 保存每个threshold的召回率# 以decision_scores的最小值为起点,最大值为终点,步长为0.1,定义一个数组
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()
图9 其中蓝色的曲线代表精确率,橙色的曲线代表召回率。
图10 如果说我们想让精确率保持90%,就可以在0.9的位置画一条横线,如图10所示的红线,再找到对应的阈值即可。
还有一种曲线叫Precisioni-Recall曲线,就是以精确率为横轴,召回率为纵轴画的曲线。
plt.plot(precisions, recalls)
plt.show()
图11 绘制这个曲线也很容易,该曲线的趋势也非常明显。该曲线是逐渐下降的,即随着精确率逐渐增大,召回率会逐渐减小。
图12 从 这个曲线中,我们可以看到过了某点之后,整个曲线就急剧下降,该点就是精确率和召回率较好的一个平衡点,如上图红点所示。
当然,sklearn也帮我们封装好了画精确率-召回率曲线的方法。
from sklearn.metrics import precision_recall_curve# sklearn会自动进行分隔,同样可以得到每个阈值对应的精确率和召回率
precisions,recalls,thresholds = precision_recall_curve(y_test,decision_scores)
plt.plot(thresholds, precisions[:-1]) # precisions会比thresholds多一个值
plt.plot(thresholds, recalls[:-1])
plt.show()
图13
plt.plot(precisions, recalls)
plt.show()
图14
根据这些图可以得出,Precision-Recall曲线大概是下面这样的一种趋势:
图15
图16 那么,假设有两个不同的模型,得到的曲线如上图。那么显然模型B比模型A要好一些,模型B的曲线在模型A的外面,几乎模型B的所有的精确率(precision)和召回率(recall)都比模型A的要大。
因此可以看出,该曲线也可以作为一个选择模型、选择算法、选择超参数的指标。
对于这个指标,外面说里外会有些抽象,因此可以用该曲线和x,y轴所包的面积(PR曲线面积)来看。面积越大,模型越好。
虽然这样可以,但是大多数情况下我们不用PR曲线面积来衡量模型的好坏。
而是用ROC曲线,请看下小节。
ROC曲线
描述的TPR(True Positive Rate)和FPR(False Positive Rate)之间的关系。
啥是TPR和FPR呢,TPR其实就等于召回率。
图17
TPR=TPTP+FNTPR = \frac{TP}{TP + FN} TPR=TP+FNTP
如图17所示,TPR就是用TP除以真实值为1的这一行。
图18 类似的,FPR就是用FP除以真实值为0的这一行。
FPR=FPTN+FPFPR = \frac{FP}{TN+ FP} FPR=TN+FPFP
简单来说,TPR就是预测为1,并且预测对了的数量占真实为1的比例;
FPR就是预测为1,但是预测错了的数量占真实为0的比例;
类似召回率和精确率,TPR和FPR之间也存在一定关系。我们来看下是什么关系。
图19
可以看到,随着阈值逐渐降低,FPR和TPR都是逐渐升高,它们有一种正相关关系。
而ROC曲线正是刻画它们之间这种关系的曲线。
下面通过代码来画ROC曲线:
def TPR(y_true, y_predict):tp = TP(y_true,y_predict)fn = FN(y_true,y_predict)try:return tp/(tp + fn)except:return 0.0def FPR(y_true, y_predict):fp = FP(y_true,y_predict)tn = TN(y_true,y_predict)try:return fp/(fp + tn)except:return 0.0import matplotlib.pyplot as pltfprs = []
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')tprs.append(TPR(y_test,y_predict))fprs.append(FPR(y_test,y_predict))plt.plot(fprs,tprs)
plt.show()
图20 图20就是ROC曲线,x轴是FPR,y轴是TPR。随着x的增大,y也相应的增大。
sklearn计算FPR和TPR也非常简单:
from sklearn.metrics import roc_curvefprs,tprs,thresholds = roc_curve(y_test,decision_scores)plt.plot(fprs,tprs)
plt.show()
图21
对于ROC曲线来说,我们通常关注的是曲线下面面积的大小。
面积越大,代表模型越好。
在x(FPR)越小的时候(犯False Positive错误越少的时候),如果曲线的值(TPR)越高(得到True Positive正确结果越多)的时候,曲线就会很高,也就是面积就会很大。
sklearn提供了一个方法来求这个面积:
from sklearn.metrics import roc_auc_scoreroc_auc_score(y_test,decision_scores)
计算的面积称为AUC (Area Under Curve),取值范围[0.5,1],这里得到的是0.98。
图22 AUC主要用来比较模型的好坏。
多分类问题中的混淆矩阵
上面我们看的都是基于二分类问题的,本小节来看下针对多分类问题,如何定义混淆矩阵。
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegressiondigits = datasets.load_digits()
X = digits.data
y = digits.targetX_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)
print(log_reg.score(X_test,y_test)) # 0.9408901251738526
首先还是以手写数字为例,不过这次完整的保留了10个类别。
from sklearn.metrics import precision_scorey_predict = log_reg.predict(X_test)
# 通过制定average='micro'来计算多分类的精确率
precision_score(y_test,y_predict,average='micro')
上面是如何计算多分类问题下的精确率。
我们重点来看下混淆矩阵:
from sklearn.metrics import confusion_matrixconfusion_matrix(y_test,y_predict)
可以看到,在10个类别的分类任务中,混淆矩阵是一个10x10的矩阵。
此时,对角线上的值都比较大,对角线代表预测正确的数量,说明预测正确的较多。
from sklearn.metrics import confusion_matrixcfm = confusion_matrix(y_test,y_predict)
plt.matshow(cfm, cmap=plt.cm.gray) #画矩阵
plt.show()
图23
在上图中,越亮的方块代表数值越大,越暗的方块代表数值越小。
不过我们关注预测正确的部分是没有意义的,我们真正关心的是预测错误的地方。
# 首先计算每行的样本数
row_numbers = np.sum(cfm,axis=1)
# 得到新的矩阵,用每行的数字除以每行的数字总和
err_matrix = cfm / row_numbers
# 我们不关心对角线上预测正确的数量,因此填充对角线上的值为0
np.fill_diagonal(err_matrix,0)
err_matrix
将对角线上的值设成0后,得到我们关注的错误矩阵。
下面我们按照之前的方式绘制这个矩阵:
plt.matshow(err_matrix, cmap=plt.cm.gray)
plt.show()
图24 这个结果图中,越亮的方块,代表犯错误越多的地方。可以看到右上角有一个很亮的白方块,代表将很多真值为1的样本错误的预测为9。
通过这样的一个图,我们就可以知道在哪里犯错比较多,同时还知道错在哪了。
参考
1.Python3入门机器学习
机器学习入门——分类算法的评价相关推荐
- [Python从零到壹] 十四.机器学习之分类算法五万字总结全网首发(决策树、KNN、SVM、分类对比实验)
欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...
- 机器学习分类算法_达观数据:5分钟带你理解机器学习及分类算法
1.本文介绍内容:什么是机器学习,机器学习有哪些分类算法,分类算法之k-近邻,决策树,随机森林2.本文适合人群:本文通过通俗易懂的语言和例子介绍核心思想,不拽高大上的名词,适合于不懂机器学习的小白3. ...
- 15 分钟带你入门 sklearn 与机器学习(分类算法篇)
众所周知,Scikit-learn(以前称为scikits.learn)是一个用于Python编程语言的免费软件机器学习库.它具有各种分类,回归和聚类算法,包括支持向量机,随机森林,梯度增强,k-me ...
- Sklearn 损失函数如何应用到_15 分钟带你入门 sklearn 与机器学习(分类算法篇)...
众所周知,Scikit-learn(以前称为scikits.learn)是一个用于Python编程语言的免费软件机器学习库.它具有各种分类,回归和聚类算法,包括支持向量机,随机森林,梯度增强,k-me ...
- 机器学习入门-kNN算法实现手写数字识别
实验环境 Python:3.7.0 Anconda:3-5.3.1 64位 操作系统:win10 开发工具:sublime text(非必要) 简介 本次实验中的重点为采用kNN算法进行手写数字识别, ...
- 【机器学习】总结了九种机器学习集成分类算法(原理+代码)
大家好,我是云朵君! 导读: 本文是分类分析(基于Python实现五大常用分类算法(原理+代码))第二部分,继续沿用第一部分的数据.会总结性介绍集成分类算法原理及应用,模型调参数将不在本次讨论范围内. ...
- 机器学习5—分类算法之随机森林(Random Forest)
随机森林(Random Forest) 前言 一.随机森林 1.什么是随机森林 2.随机森林的特点 3.随机森林的生成 二.随机森林的函数模型 三.随机森林算法实现 1.数据的读取 2.数据的清洗和填 ...
- 机器学习-决策树分类算法与应用
1. 决策树分类算法原理 1.1 概述 决策树(decision tree)--是一种被广泛使用的分类算法. 相比贝叶斯算法,决策树的优势在于构造过程不需要任何领域知识或参数设置 在实际应用中,对于探 ...
- 总结了九种机器学习集成分类算法(原理+代码)
公众号后台回复"图书",了解更多号主新书内容作者:云朵君来源: 数据STUDIO 导读: 本文是分类分析(基于Python实现五大常用分类算法(原理+代码))第二部分,继续沿用第一 ...
- 机器学习3—分类算法之K邻近算法(KNN)
K邻近算法(KNN) 一.算法思想 二.KNN类KNeighborsClassifier的使用 三.KNN分析红酒类型 3.1红酒数据集 3.2红酒数据的读取 3.3将红酒的数据集拆分为训练和测试集 ...
最新文章
- 原创 | 一文读懂正态分布与贝塔分布
- 代理工具Charles使用
- Java中Integer包装类之间的比较问题
- 二级(多级)指针,二级指针和二维数组的避坑,指针面试考题
- linux java的启动内存溢出,java - Java的R代码可在Linux上运行,但不能在Windows中运行 - 堆栈内存溢出...
- javaweb实现验证码功能
- c语言程序设计的几种循环,C语言程序设计教案 第六章 循环程序设计.doc
- 【Flink】 Flink 源码之 SQL 执行流程
- 深圳大学计算机就业报告,深圳大学2020届毕业生就业质量报告.pdf
- java最基础的小总结
- visio 科学图形包_【数据科学的python系列3】Python数据科学环境设置
- LeetCode 之 JavaScript 解答第98题 —— 验证二叉搜索树(Validate Binary Search Tree)
- python模块 - re模块使用示例
- c语言数据结构单链表输出链表操作,单链表一系列操作c语言实现(按照严蔚敏那本数据结构编写)...
- loongson PMON使用
- Active Learning through label error statistical methods(ALSE)
- cur前缀_常用词根前缀
- 计算机网络协议 | 只有程序员才能读懂的西游记 | 让我这样学习简直就是一种享受~
- c语言作业 身高预测,C语言的那些题(三) —— 编程计算身高问题
- VBA 为什么你redim() 动态二维数组总出错?因为 redim 动态数组不太适合和循环搭配
热门文章
- vue-awesome-swiper 的安装和使用
- 库表操作 - 存储引擎
- 限制textarea的输入字数
- PaddlePaddle(1)——让人拍案叫绝的创意都是如何诞生的
- JavaScript中值类型与引用类型
- 26.leetcode160_intersection_of_two_linked_lists
- 整合Spring Cloud微服务分布式云架构技术点
- SLF4J源码解析-LoggerFactory(二)
- 作用域public,private,protected,以及不写时的区别
- python源码中的学习笔记_第7章_字符串