一直陪伴你的,是那个了不起的自己。
全文字数:3598字
阅读时间:15分钟
前言
本系列是《玩转机器学习教程》一个整理的视频笔记。本小节首先通过具体的编程实现混淆矩阵进而计算精准率和召回率两个指标,最后使用sklearn中封装的库函数实现混淆矩阵、精准率以及召回率。
a
实现混淆矩阵、精准率&召回率
上一小节详细介绍了什么是混淆矩阵,并且基于混淆矩阵这个小工具介绍了两个新的指标精准率和召回率。这一小节就来通过具体的编程来实现混淆矩阵、精准率和召回率。
这一小节使用digits手写数字识别数据集,不过混淆矩阵、精准率和召回率是应对极度偏斜的数据集提出来的分类指标,而digits手写数字识别数据集的10个类别整体并没有太大的偏斜,为了试验效果我们需要对digits数据集进行改造,手动的让digits数据集产生比较大的偏斜。
具体的改造方法也比较简单,在介绍将多分类转换为二分类的时候提高过One to Rest,也就是将其中一个类别的样本看作是一个类别,而剩下的所有类别的样本看成是另外一个类别的样本。这里将digits数据集中标签值为9的类别看作一个类别(即y = 1),而将digits数据集中所有标签值不为9的类别看作是另外一个类别(即y = 0),其实就相当于将10个类别的digits数据集的十分类问题转换成了2个类别的二分类问题。这里需要注意,由于我们此时关注的是那些类别值等于9的样本,所以将其转换为二分类的时候,这些样本标签为y = 1。这样就将一个样本分布比较均衡的数据集改造成了极度偏斜的数据集,因为所有不等于9的样本数量肯定要比等于9的样本数量多得多,由于原始的digits数据集样本分布均衡,所以y = 1的样本数量大概是y = 0样本数量的1 / 9左右。
还需要注意一点,如果改造的时候直接使用y = digits.target的话,下面直接修改y的时候相对应的原始的digits.target也会跟着被修改,因为"="本质是将y和digits.target指向了同一个引用,并没有进行数据的拷贝,所以需要使用y = digits.target.copy()将数据拷贝,这样修改y就不会影响到digits原始数据集了。
有了极度偏斜的数据集,接下来就可以使用二分类算法进行分类了。这里二分类算法使用上一章介绍的逻辑回归算法。
先使用train_test_split将数据集划分为训练集和测试集,如果设置相同的随机种子后面得到的所有结果都是一样的。之后实例化LogisticRegression,fit训练拟合训练集,最后通过score计算算法在测试集上的分类准确度为97.5%。由于此时的数据集是极度偏斜的数据集,所以即使算法将所有的测试样本都预测为不为9,也就是将所有的测试样本都预测为y = 0,相应的准确率也能够达到90%左右,所以在极度偏斜的数据集上,准确率并不能准确的衡量算法的好坏。
由于准确率在处理极度偏斜数据集时候不能准确的衡量分类算法的性能,所以我们需要使用一些其它的性能指标。
首先使用训练好的逻辑回归计算在测试集X_test上的预测结果y_log_predict,之后就可以根据真实值和预测值来计算混淆矩阵中的TN、FP、FN以及TP四个值。
- TN(True Negative)表示样本的真实值为0(Negative),并且算法正确预测样本为0(Negative)
这里构造一个名为TN的函数,函数有两个参数,其中一个参数是数据集对应的真值y_true,另一个参数是数据集对应的预测值y_predict。
在TN函数中首先进行断言assert,确定传入的两个代表分类结果的向量y_true和y_predict的长度是否相等,即"len(y_true) == len(y_predict)"。之后就可以计算True Negative的值了,所谓的True Negative就是样本对应的真值y_true = 0,与此同时算法正确预测了样本的y_predict = 0,所有符合条件的样本数量。布尔运算符"&"返回的是一个布尔向量,而这里我们需要得到的是符合上面条件的样本个数,所以使用np.sum计算布尔向量中True的个数,即为最终TN的值。
- FP(False Positive)表示样本的真实值为0(Negative),但是算法错误预测样本为1(Positive)
有了前面计算TN的经验,相应的后面的3个值也就非常简单了。下面来求一下FP,False Positive的值。
所谓的False Positive就是样本真实值y_true = 0,但是算法错误预测样本为y_predict = 1,所有符合条件的样本数量。
- FN(False Negative)表示样本的真实值为1(Positive),但是算法错误预测样本为0(Negative)
所谓的False Negative就是样本真实值y_true = 1,但是算法错误预测样本为y_predict = 0,所有符合条件的样本数量。
- TP(True Positive)表示样本的真实值为1(Positive),并且算法正确预测样本为1(Positive)
所谓的True Positive就是样本真实值y_true = 1,与此同时算法正确预测了样本的y_predict = 1,所有符合条件的样本数量。
有了TN、FP、FN以及TP的值,接下来就可以计算出相应的混淆矩阵了。
构造一个名为confusion_matrix函数,函数的参数依然是样本的真实值y_true和样本在算法上的预测值y_predict,函数的返回值是一个(2, 2)的ndarray数组。
对于混淆矩阵的第一行是TN和FP,第二行是FN和TP。由于我们需要在测试集上评估算法的性能,所以将测试集的真实y_true和算法在测试集上的预测值y_log_predict传入confusion_matrix混淆矩阵的函数中,结果即为算法对应的混淆矩阵。
有了算法的混淆矩阵,相应的就可以计算出算法的精准率以及召回率两个指标。首先来看一下如何来求出精准率。
构造一个名为precision_score的函数,函数的参数依然是y_true以及y_predict这两个结果向量。精准率的计算公式为TP / (TP FP),所以只需要计算相应的TP和FP的值即可。
这里需要注意,通过上一小节的学习知道了(TP FP)的值可能为0,而0作为分母程序会抛出异常,因此我们需要通过try来捕获异常。当分母为0的时候,直接返回精准率的值为0.0。
有了计算精准度的函数,只需要将测试集的真实值y_true以及算法在测试集上的预测值y_log_predict传入函数中,最终算法的精准率为94.73%。
有了计算精准率的经验,召回率也是同样的套路。
构造一个名为recall_score的函数,函数参数同样是y_true和y_predict。召回率的计算公式为TP / (TP FN),所以只需要计算相应的TP和FN的值即可。
这里同样需要注意,(TP FN)的值可能为0,而0作为分母程序会抛出异常,因此我们需要通过try来捕获异常。当分母为0的时候,直接返回召回率的值为0.0。
有了计算召回率的函数,只需要将测试集的真实值y_true以及算法在测试集上的预测值y_log_predict传入函数中,最终算法的召回率为80%左右。
至此就将前面两个小节介绍的混淆矩阵以及对应的精准率和召回率两个指标通过自己编写的代码实现了。
b
调用Sklearn中的库函数实现
最后来看一下如何调用sklearn中的库函数来实现相应的混淆矩阵、精准率和召回率这些指标。
无论是混淆矩阵还是精准率和召回率都属于指标的范畴,所以都在sklearn的metrics包下。sklearn封装的混淆矩阵函数和前面我们自己实现的混淆矩阵的函数名是一样的都是confusion_matrix,类似的只需要将测试集的真实值y_ture以及在算法上的预测值y_log_predict传入函数中,最终的结果就是对应算法的混淆矩阵。使用sklearn计算的混淆矩阵和我们自己编写函数实现的混淆矩阵的结果是一样的。
使用sklearn封装的precision_score函数计算算法的精准率。
同样从sklearn中的metrics包下导入precision_score函数,这和之前我们自己实现的函数名一样,类似的只需要将测试集的真实值y_ture以及在算法上的预测值y_log_predict传入函数中,最终的结果就是对应算法的精准率。使用sklearn计算的精准率和我们自己编写函数计算的精准率的结果是一样的都是94.73%。
使用sklearn封装的recall_score函数计算算法的召回率。
同样的从sklearn中的metrics包下导入recall_score函数,这和之前我们自己实现的函数名一样,类似的只需要将测试集的真实值y_ture以及在算法上的预测值y_log_predict传入函数中,最终的结果就是对应算法的召回率。使用sklearn计算的召回率和我们自己编写函数计算的召回率的结果是一样的都是80%。
这一小节我们自己手动编程实现了TN、FP、FN以及TP这4个混淆矩阵中的值,其次实现了用这4个量来组合成一个混淆矩阵,之后又计算出了算法相应精准率和召回率。与此同时,学习了在sklearn中如何调用相应的库函数来计算这些指标。这里主要引入了精准率和召回率这两个新的指标,像混淆矩阵小工具是为了计算这两个指标而服务的。
不过虽然计算出了算法的精准率以及召回率,但是对这两个指标的解读并没有介绍,如果对于两个算法或者同一个算法的两组超参数,这两个指标出现了差异。比如对于一个算法来说精准率高但是召回率低,而另一种算法精准率低但是召回率高,对于这两种算法要如何进行取舍呢?这些将在下一个小节中介绍。