在大多数情况下不同类比的分类代价并不相等。
分类性能度量指标:正确率、召回率及ROC曲线
通常情况下,我们可以基于错误率来衡量分类器任务的成功程度。错误率指的是在所有测试样例中错分的样例比例 。实际上,这样的度量错误掩盖了样例如何被分错的事实。在机器学习中,有一个普遍适用的称为混淆矩阵的工具,它可以帮助人们更高地了解分类中的错误。有这样一个关于子啊房子周围可能发现的动物类型的预测,混淆矩阵如图:
利用混淆矩阵就可以更好地理解分类中的错误了。如果矩阵中的非对角元素均为0,就得到了一个完美的分类器。
接下来,是一个简单的二类问题的混淆矩阵:
在这个二类问题中,如果一个正例判为正例,那么就可以认为产生了一个真正例 (TP、真阳);如果一个反例正确地判为反例,则认为产生了一个真反例 (TN、真阴)。相应的,另外两种情况则分别被称为伪反例 (FP、假阴)和伪正例(FN假阴)。
在分类中,当某个类别的重要性高于其他类别时,我们就可以利用上述定义来定义出多个比错误率更好的指标。第一个指标是正确率,它等于TP/(TP+FP),给出的是预测为正例的样本中真正正例的比例。第二个指标是召回率,它等于TP/(TP+FN),给出的是预测为正例的真实正例占所有真实正例的比例。在召回率很大的分类器中,真正判错的正例的数目并不多。
我们可以很容易构造出一个高正确率或者高召回率的分类器,但是很难同时保证两者成立。如果任何样本都判为正例,那么召回率达到100%但争取率很低。构造一个同时使争取率和召回率最大的分类器是具有挑战性的。
另一个用于度量分类中的非均衡性的工具是ROC曲线,ROC代表接收者操作特征。
ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。左下角的点对应的是将所有样例判为反例的情况,右上角的点对应的是所有样例判为正例的情况。
ROC曲线不但可以用于比较分类器,还可以基于成本效益分析来做出决策。由于在不同阈值下,不同的分类器的表现情况可能各不相同,因此以某种方式将它们组合起来或许会更有意义。
在理想的情况下,最佳的分类器应该尽可能处于左上角,这就意味着分类器在假阳率很低的同时获得了很高的真阳率。例如在垃圾邮件的过滤中,这就相当于过滤了所有的垃圾邮件,但没有将任何合法邮件误识为垃圾邮件。
对不同的ROC曲线进行比较的一个指标是曲线下的面积(AUC)。AUC给出的是分类器的平均性能值,当然它并不能完全代替对整条曲线的观察。一个完美分类器的AUC为1.0,而随机猜测的AUC约为0.5。
为了画出ROC,分类器必须提供每个样例被判为阳性或者阴性的可信程度值。尽管大多数分类器都能做到这一点,但通常情况下,这些值会在最后输出离散分类标签之前被清除。朴素贝叶斯能够提供一个可能值,而在Logistic回归中输入到sigmoid函数中的是一个数值。在AdaBoost和SVM中,都会计算出一个数值然后输入到sign()中。所有的这些值都可以用于衡量给定分类器的预测强度。为了创建ROC曲线,首先要将分类样例按照其预测强度排序。先从排名最低的样例开始,所有排名更低的样例都被判为反例,而所有排名更高的样例都被判为正例。该情况就对应点<1.0,1.0>。然后,将其一刀排名次低的样例中去,如果该样例属于正例,那么对真阳例进行修改;如果该样例属于反例,那么对假阴率进行修改。
代码实现:
python
from numpy import *
def plotROC(predStrengths,classLabels):
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
cur=(1.0,1.0)
ySum=0.0
numPosClass=sum(array(classLabels)==1.0)
yStep=1/float(numPosClass)
xStep=1/float(len(classLabels)-numPosClass)
#获取排好序的索引
sortedIndiecies=predStrengths.argsort()
fig=plt.figure()
plt.clf()
ax=plt.subplot(111)
for index in list(sortedIndiecies.tolist)[0]:
if classLabels[index]==1.0:
delX=0
delY=yStep
else:
delX=xStep
delY=0
ySum=ySum+cur[1]
ax.plot([cur[0],cur[0]-delX],[cur[1],cur[1]-delY],c='b')
cur=(cur[0]-delX,cur[1]-delY)
ax.plot([0,1],[0,1],'b--')
plt.xlabel('假阳性率')
plt.ylabel('真阳性率')
plt.title('ROC曲线')
plt.show()
函数中有两个输入参数,第一个参数是一个NumPy数组或者一个行向量组成的矩阵。该参数代表的则是分类器的预测强度。在分类器和训练函数将这些数值应用到sign()函数之前,它们就已经产生了。第二个输入参数是标签列表。
我们首先导入pyplot,然后构建一个浮点数二元组,并将它初始化为(1.0,1.0)。该元组保留的是绘制光标的位置,变量ySum则用于计算AUC的值。接下来,通过数组过滤方式计算正例的数目,并将该值赋给numPosClas。该值先是确定了在y坐标轴上的步进数目,接着在x轴和y轴的0.0到1.0区间上绘点,因此y轴上的步长是1.0/numPosClas。类似的就可以得到x轴的步长了。
接下来,我们得到了排序索引,但是这些索引是按照最小到最大的顺序排列的,因此我们需要从点<1.0,1.0>开始绘制,一直到<0.0,0.0>。跟着的三行代码则是用于构建画笔,并在所有排序值手机上进行循环。这些值在一个NumPy数组或者矩阵中进行排序,Python则需要一个表来进行迭代循环,因此我们需要将它转为list。当遍历表时,每得到一个标签为1.0的类,则要沿着y轴的方向下降一个步长,即不断降低真阳率。类似的,对于其他每个类别的标签,则是在x轴方向上倒退了一个步长(假阴率方向)。上述代码只关注1这个类别标签,因此就无所谓是采用1/0标签还是+1/-1标签。
为了计算AUC,我们需要对多个小矩形的面积进行累加,这些小矩形的宽度是xStep,因此可以先对所有巨星的高度进行累加,最后再乘以xStep得到其总面积。所有高度的和(ySum)随着x轴的每次移动而渐次增加。一旦决定了是在x轴还是y轴方向上进行移动的,我们就可以在当前点和新店之间画出一条线段。然后,更新当前点cur。最后,我们就会得到一个像样的绘图并将AUC打印到终端输出。
这事后,我们就得到了一条ROC曲线。
基于代价函数的分类器决策控制
除了调节分类器的阈值之外,我们还有一些其他可以用于处理非均衡分类的代价的方法,其中的一种称为代价敏感的学习。
上图中,第一张表给出的是目前为止分类器的代价矩阵(代价不是0就是1),我们可以基于该代价矩阵计算其总代价:TP*0+FN*1+FP*1+TN*0。第二张表中,基于该代价矩阵的分类代价的计算公式为TP*(-5)+FN*1+FP*50+TN*0。采用第二张表作为代价矩阵时,两种分类错误的代价是不一样的。类似的,这两种正确分类所得到的收益也不一样。如果构建分类器时,知道了这些代价值,那么就可以选择付出最小代价的分类器。
在分类算法中,我们有很多方法可以用来引入代价信息。在AdaBoost中,可以基于代价函数来调整错误权重向量D。在朴素贝叶斯中,可以选择具有最小期望代价而不是最大概率的类别作为最后的结果。在SVM中,可以在代价函数中对于不同的类别选择不同的参数C。上述做法就会给较小类更多的权重,即在训练时,小类当中只允许更少的错误。
处理非均衡问题的数据抽样方法
另外一种针对非均衡问题调节分类器的方法,就是对分类器的训练数据进行改造。这可以通过欠抽样 或者过抽样来实现。过抽样意味着复制样例,而欠抽样意味着删除样例。不管采用哪种方式,数据都会从原始数据改造为新形式。抽样过程则可以通过随机方式或者某个预定方式来实现。
通常也会存在某个罕见的类别需要我们来识别。如前所述,正例类别属于函件类别。我们希望对于这种函件类别能尽可能保留更多的信息,因此,我们应该保留正例类别中的所有样例,而反例类别进行欠抽样或者样例删除处理。这种方法的一个缺点就在于要确定哪些样例需要进行剔除。但是,在选择剔除的样例中可能携带了剩余样例中并不包含的有价值信息。
上述问题的一种解决办法,就是选择那些离决策边界较远的样例进行删除。
要对正例离别进行过抽样,我们可以复制已有样例或者假如与已有样例相似的点。