一、概述
当做重要决定时,大家可能都会考虑吸取多个专家而不只是一个人的意见。机器学习处理问题时又何尝不是如此?这就是元算法(meta-algorithm)背后的思路。元算法是对其他算法进行组合的一种方式。接下来我们将集中关注一个称作AdaBoost的最流行的元算法。由于某些人认为AdaBoost是最好的监督学习的方法,所以该方法是机器学习工具箱中最强有力的工具之一。
本章首先讨论不同分类器的集成方法,然后主要关注boosting方法及其代表分类器Adaboost。再接下来,我们就会建立一个单层决策树(decision stump)分类器。实际上,它是一个单节点的决策树。AdaBoost算法将应用在上述单层决策树分类器之上。我们将在一个难数据集上应用AdaBoost分类器,以了解该算法是如何迅速超越其他分类器的。
最后,在结束分类话题之前,我们将讨论所有分类器都会遇到的一个通用问题:非均衡分类问题。当我们试图对样例数目不均衡的数据进行分类时,就会遇到这个问题。信用卡使用中的欺诈检测就是非均衡问题中的一个极好的例子,此时我们可能会对每一个正例样本都有1000个反例样本。在这种情况下,分类器将如何工作?读者将会了解到,可能需要利用修改后的指标来评价分类器的性能。而就这个问题而言,并非AdaBoost所独用,只是因为这是分类的最后一章,因此到了讨论这个问题的最佳时机。
二、优缺点
优点:泛化错误率低,易编码,可以应用在大部分分类器上,无参数调整。
缺点:对离群点敏感。
适用数据类型:数值型和标称型数据。
三、数学公式
四、基于数据集多重抽样的分类器
前面已经介绍了五种不同的分类算法,它们各有优缺点。我们自然可以将不同的分类器组合起来,而这种组合结果则被称为集成方法(ensemble method)或者元算法(meta-algorithm)。使用集成方法时会有多种形式:可以是不同算法的集成,也可以是同一算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。接下来,我们将介绍基于同一种分类器多个不同实例的两种计算方法。在这些方法当中,数据集也会不断变化,而后应用于不同的实例分类器上。最后,我们会讨论如何利用机器学习问题的通用框架来应用AdaBoost算法。
1、bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法(bootstrap aggregating),也称为bagging方法,是在从原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到的。这里的替换就意味着可以多次地选择同一样本。这一性质就允许新数据集中可以有重复的值,而原始数据集的某些值在新集合中则不再出现。在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们要对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果。当然,还有一些更先进的bagging方法,比如随机森林(random forest)。接下来我们将注意力转向一个与bagging类似的集成分类器方法boosting。
2、boosting
boosting是一种与bagging很类似的技术。不论是在boost-ing还是bagging当中,所使用的多个分类器的类型都是一致的。但是在前者当中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能来进行训练。boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。由于boosting分类的结果是基于所有分类器的加权求和结果的,因此boosting与bagging不太一样。bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。boosting方法拥有多个版本,本章将只关注其中一个最流行的版本AdaBoost。
AdaBoost的一般流程
(1)收集数据:可以使用任意方法。
(2)准备数据:依赖于所使用的弱分类器类型,本章使用的是单层决策树,这种分类器可以处理任何数据类型。当然也可以使用任意分类器作为弱分类器,第2章到第6章中的任一分类器都可以充当弱分类器。作为弱分类器,简单分类器的效果更好。
(3)分析数据:可以使用任意方法。
(4)训练算法:AdaBoost的大部分时间都用在训练上,分类器将多次在同一数据集上训练弱分类器。
(5)测试算法:计算分类的错误率。
(6)使用算法:同SVM一样,AdaBoost预测两个类别中的一个。如果想把它应用到多个类别的场合,那么就要像多类SVM中的做法一样对AdaBoost进行修改。
五、训练算法:基于错误提升分类器的性能
能否使用弱分类器和多个实例来构建一个强分类器?这是一个非常有趣的理论问题。这里的“弱”意味着分类器的性能比随机猜测要略好,但是也不会好太多。这就是说,在二分类情况下弱分类器的错误率会高于50%,而“强”分类器的错误率将会低很多。AdaBoost算法即脱胎于上述理论问题。
AdaBoost是adaptive boosting(自适应boosting)的缩写,其运行过程如下:
训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化成相等值。首先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练当中,将会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重将会提高。为了从所有弱分类器中得到最终的分类结果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。其中,错误率ε的定义为:
而alpha的计算公式如下:
AdaBoost算法的流程如图7-1所示。
图7-1 AdaBoost算法的示意图。左边是数据集,其中直方图的不同宽度表示每个样例上的不同权重。在经过一个分类器之后,
加权的预测结果会通过三角形中的alpha值进行加权。每个三角形中输出的加权结果在圆形中求和,从而得到最终的输出结果
计算出alpha值之后,可以对权重向量D进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法如下。
如果某个样本被正确分类,那么该样本的权重更改为:
而如果某个样本被错分,那么该样本的权重更改为:
在计算出D之后,AdaBoost又开始进入下一轮迭代。Ad-aBoost算法会不断地重复训练和调整权重的过程,直到训练错误率为0或者弱分类器的数目达到用户的指定值为止。
六、基于单层决策树构建弱分类
单层决策树(decision stump,也称决策树桩)是一种简单的决策树。前面我们已经介绍了决策树的工作原理,接下来将构建一个单层决策树,而它仅基于单个特征来做决策。由于这棵树只有一次分裂过程,因此它实际上就是一个树桩。
在构造AdaBoost的代码时,我们将首先通过一个简单数据集来确保在算法实现上一切就绪。然后,建立一个叫adaboost.py的新文件并加入如下代码:
1 def loadSimpData(): 2 datMat = matrix([[ 1. , 2.1], 3 [ 2. , 1.1], 4 [ 1.3, 1. ], 5 [ 1. , 1. ], 6 [ 2. , 1. ]]) 7 classLabels = [1.0, 1.0, -1.0, -1.0, 1.0] #注意 这里的分类 为 1 和 -1 8 return datMat,classLabels
图7-2给出了上述数据集的示意图。如果想要试着从某个坐标轴上选择一个值(即选择一条与坐标轴平行的直线)来将所有的圆形点和方形点分开,这显然是不可能的。这就是单层决策树难以处理的一个著名问题。通过使用多棵单层决策树,我们就可以构建出一个能够对该数据集完全正确分类的分类器。
图7-2 用于检测AdaBoost构建函数的简单数据。这不可能仅仅通过在某个坐标轴上选择某个阈值来将圆形点和方形点分开。AdaBoost需要将多个单层决策树组合起来才能对该数据集进行正确分类
有了数据,接下来就可以通过构建多个函数来建立单层决策树。
第一个函数将用于测试是否有某个值小于或者大于我们正在测试的阈值。第二个函数则更加复杂一些,它会在一个加权数据集中循环,并找到具有最低错误率的单层决策树。这个程序的伪代码看起来大致如下:
将最小错误率minError设为+∞
对数据集中的每一个特征(第一层循环):
对每个步长(第二层循环):
对每个不等号(第三层循环):
建立一棵单层决策树并利用加权数据集对它进行测试
如果错误率低于minError,则将当前单层决策树设为最佳单层决策树
返回最佳单层决策树
程序清单7-1 单层决策树生成函数
# 是通过阈值比较对数据进行分类的。所有在阈值一边的数据会分到类别-1,而在另外一边的数据分到类别+1。该函数可以通过数组过滤来实现,首先将返回数组的全部元素设置为1,然后将所有不满足不等式要求的元素设置为-1。 #可以基于数据集中的任一元素进行比较,同时也可以将不等号在大于、小于之间切换。1 def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):# 返回一个列向量-------- 分正确的为1, 分错的为-1 , 2 retArray = ones((shape(dataMatrix)[0],1)) 3 if threshIneq == ‘lt‘: 4 retArray[dataMatrix[:,dimen] <= threshVal] = -1.0 5 else: 6 retArray[dataMatrix[:,dimen] > threshVal] = -1.0 7 #print (retArray) 8 return retArray 9 10 #遍历stumpClassify()函数所有的可能输入值,并找到数据集上最佳的单层决策树。这里的“最佳”是基于数据的权重向量D来定义的 11 def buildStump(dataArr,classLabels,D): #权重向量D classLabels=[1.0, 1.0, -1.0, -1.0, 1.0] 12 dataMatrix = mat(dataArr); labelMat = mat(classLabels).T #labelMat = 列矩阵 13 m,n = shape(dataMatrix) 14 numSteps = 10.0; bestStump = {}; bestClasEst = mat(zeros((m,1))) 15 minError = inf #init error sum, to +infinity 16 for i in range(n):#loop over all dimensions 17 rangeMin = dataMatrix[:,i].min(); rangeMax = dataMatrix[:,i].max(); 18 stepSize = (rangeMax-rangeMin)/numSteps 19 for j in range(-1,int(numSteps)+1):#loop over all range in current dimension 20 for inequal in [‘lt‘, ‘gt‘]: #go over less than and greater than 21 threshVal = (rangeMin + float(j) * stepSize) 22 predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)#列向量 23 #print (predictedVals) 24 errArr = mat(ones((m,1))) #构建一个列向量errArr,如果predict-edVals中的值不等于labelMat中的真正类别标签值,那么er-rArr的相应位置为1。 25 errArr[predictedVals == labelMat] = 0 26 print(errArr) 27 weightedError = D.T*errArr #将错误向量errArr和权重向量D的相应元素相乘并求和 -----分类错误率 28 #print "split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" % (i, threshVal, inequal, weightedError) 29 if weightedError < minError: 30 minError = weightedError 31 bestClasEst = predictedVals.copy() 32 bestStump[‘dim‘] = i 33 bestStump[‘thresh‘] = threshVal 34 bestStump[‘ineq‘] = inequal 35 return bestStump,minError,bestClasEst #返回分类的最小错误率
为了解实际运行过程,在Python提示符下输入如下命令:
1 >>> import adaboost 2 >>> from numpy import * 3 >>> D=mat(ones((5,1))/5) 4 >>> datMat,classLabels=adaboost.loadSimpData() 5 >>> adaboost.buildStump(datMat,classLabels,D) 6 split: dim 0, thresh 0.90, thresh ineqal: lt, the weighted error is 0.400 7 split: dim 0, thresh 0.90, thresh ineqal: gt, the weighted error is 0.600 8 split: dim 0, thresh 1.00, thresh ineqal: lt, the weighted error is 0.400 9 split: dim 0, thresh 1.00, thresh ineqal: gt, the weighted error is 0.600 10 split: dim 0, thresh 1.10, thresh ineqal: lt, the weighted error is 0.400 11 split: dim 0, thresh 1.10, thresh ineqal: gt, the weighted error is 0.600 12 split: dim 0, thresh 1.20, thresh ineqal: lt, the weighted error is 0.400 13 split: dim 0, thresh 1.20, thresh ineqal: gt, the weighted error is 0.600 14 split: dim 0, thresh 1.30, thresh ineqal: lt, the weighted error is 0.200 15 split: dim 0, thresh 1.30, thresh ineqal: gt, the weighted error is 0.800 16 split: dim 0, thresh 1.40, thresh ineqal: lt, the weighted error is 0.200 17 split: dim 0, thresh 1.40, thresh ineqal: gt, the weighted error is 0.800 18 split: dim 0, thresh 1.50, thresh ineqal: lt, the weighted error is 0.200 19 split: dim 0, thresh 1.50, thresh ineqal: gt, the weighted error is 0.800 20 split: dim 0, thresh 1.60, thresh ineqal: lt, the weighted error is 0.200 21 split: dim 0, thresh 1.60, thresh ineqal: gt, the weighted error is 0.800 22 split: dim 0, thresh 1.70, thresh ineqal: lt, the weighted error is 0.200 23 split: dim 0, thresh 1.70, thresh ineqal: gt, the weighted error is 0.800 24 split: dim 0, thresh 1.80, thresh ineqal: lt, the weighted error is 0.200 25 split: dim 0, thresh 1.80, thresh ineqal: gt, the weighted error is 0.800 26 split: dim 0, thresh 1.90, thresh ineqal: lt, the weighted error is 0.200 27 split: dim 0, thresh 1.90, thresh ineqal: gt, the weighted error is 0.800 28 split: dim 0, thresh 2.00, thresh ineqal: lt, the weighted error is 0.600 29 split: dim 0, thresh 2.00, thresh ineqal: gt, the weighted error is 0.400 30 split: dim 1, thresh 0.89, thresh ineqal: lt, the weighted error is 0.400 31 split: dim 1, thresh 0.89, thresh ineqal: gt, the weighted error is 0.600 32 split: dim 1, thresh 1.00, thresh ineqal: lt, the weighted error is 0.200 33 split: dim 1, thresh 1.00, thresh ineqal: gt, the weighted error is 0.800 34 split: dim 1, thresh 1.11, thresh ineqal: lt, the weighted error is 0.400 35 split: dim 1, thresh 1.11, thresh ineqal: gt, the weighted error is 0.600 36 split: dim 1, thresh 1.22, thresh ineqal: lt, the weighted error is 0.400 37 split: dim 1, thresh 1.22, thresh ineqal: gt, the weighted error is 0.600 38 split: dim 1, thresh 1.33, thresh ineqal: lt, the weighted error is 0.400 39 split: dim 1, thresh 1.33, thresh ineqal: gt, the weighted error is 0.600 40 split: dim 1, thresh 1.44, thresh ineqal: lt, the weighted error is 0.400 41 split: dim 1, thresh 1.44, thresh ineqal: gt, the weighted error is 0.600 42 split: dim 1, thresh 1.55, thresh ineqal: lt, the weighted error is 0.400 43 split: dim 1, thresh 1.55, thresh ineqal: gt, the weighted error is 0.600 44 split: dim 1, thresh 1.66, thresh ineqal: lt, the weighted error is 0.400 45 split: dim 1, thresh 1.66, thresh ineqal: gt, the weighted error is 0.600 46 split: dim 1, thresh 1.77, thresh ineqal: lt, the weighted error is 0.400 47 split: dim 1, thresh 1.77, thresh ineqal: gt, the weighted error is 0.600 48 split: dim 1, thresh 1.88, thresh ineqal: lt, the weighted error is 0.400 49 split: dim 1, thresh 1.88, thresh ineqal: gt, the weighted error is 0.600 50 split: dim 1, thresh 1.99, thresh ineqal: lt, the weighted error is 0.400 51 split: dim 1, thresh 1.99, thresh ineqal: gt, the weighted error is 0.600 52 split: dim 1, thresh 2.10, thresh ineqal: lt, the weighted error is 0.600 53 split: dim 1, thresh 2.10, thresh ineqal: gt, the weighted error is 0.400 54 ({‘ineq‘: ‘lt‘, ‘thresh‘: 1.3, ‘dim‘: 0}, matrix([[ 0.2]]), array([[-1.], 55 [ 1.], 56 [-1.], 57 [-1.], 58 [ 1.]])) 59 >>>
上述单层决策树的生成函数是决策树的一个简化版本。它就是所谓的弱学习器,即弱分类算法。到现在为止,我们已经构建了单层决策树,并生成了程序,做好了过渡到完整AdaBoost算法的准备。在下一节当中,我们将使用多个弱分类器来构建Ad-aBoost代码。
七、完整的AdaBoost算法
整个实现的伪代码如下:
对每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳单层决策树加入到单层决策树数组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率等于0.0,则退出循环
程序清单7-2 基于单层决策树的AdaBoost训练过程
1 def adaBoostTrainDS(dataArr,classLabels,numIt=40): 2 weakClassArr = [] 3 m = shape(dataArr)[0] 4 D = mat(ones((m,1))/m) #向量D非常重要,它包含了每个数据点的权重 5 aggClassEst = mat(zeros((m,1))) #列向量aggClassEst,记录每个数据点的类别估计累计值。 6 for i in range(numIt): 7 bestStump,error,classEst = buildStump(dataArr,classLabels,D)#bestStump=字典,error=分类错误率,classEst=列向量,预测之后的分类列表 8 #print "D:",D.T 9 alpha = float(0.5*log((1.0-error)/max(error,1e-16)))#求alpha 值 上面的数学公式, throw in max(error,eps) to account for error=0 10 bestStump[‘alpha‘] = alpha 11 weakClassArr.append(bestStump) #store Stump Params in Array 12 #print "classEst: ",classEst.T // ①(以下两行)为下一次迭代计算D 13 expon = multiply(-1*alpha*mat(classLabels).T,classEst) # 样本被正确分类的话 expon 为负,错误分类的话 为正 其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重将会提高 14 D = multiply(D,exp(expon)) #迭代计算公式 15 D = D/D.sum() 16 # ②(以下五行)错误率累加计算 17 aggClassEst += alpha*classEst 18 #print "aggClassEst: ",aggClassEst.T 19 aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1))) 20 errorRate = aggErrors.sum()/m 21 print ("total error: ",errorRate) 22 if errorRate == 0.0: break 23 return weakClassArr,aggClassEst
测试代码:
1 >>> imp.reload(adaboost) 2 <module ‘adaboost‘ from ‘F:\\99999_算法\\《机器学习实战》源代码\\machinelearninginaction\\Ch07\\adaboost.py‘> 3 >>> classifierArray,aggClassEst=adaboost.adaBoostTrainDS(datMat,classLabels,9) 4 D: [[ 0.2 0.2 0.2 0.2 0.2]] 5 alpha: 0.6931471805599453 6 classEst: [[-1. 1. -1. -1. 1.]] 7 aggClassEst: [[-0.69314718 0.69314718 -0.69314718 -0.69314718 0.69314718]] 8 aggErrors : [[ 1. 0. 0. 0. 0.]] 9 total error: 0.2 10 D: [[ 0.5 0.125 0.125 0.125 0.125]] 11 alpha: 0.9729550745276565 12 classEst: [[ 1. 1. -1. -1. -1.]] 13 aggClassEst: [[ 0.27980789 1.66610226 -1.66610226 -1.66610226 -0.27980789]] 14 aggErrors : [[ 0. 0. 0. 0. 1.]] 15 total error: 0.2 16 D: [[ 0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]] 17 alpha: 0.8958797346140273 18 classEst: [[ 1. 1. 1. 1. 1.]] 19 aggClassEst: [[ 1.17568763 2.56198199 -0.77022252 -0.77022252 0.61607184]] 20 aggErrors : [[ 0. 0. 0. 0. 0.]] 21 total error: 0.0 22 >>> classifierArray 23 [{‘ineq‘: ‘lt‘, ‘thresh‘: 1.3, ‘alpha‘: 0.6931471805599453, ‘dim‘: 0}, {‘ineq‘: ‘lt‘, ‘thresh‘: 1.0, ‘alpha‘: 0.9729550745276565, ‘dim‘: 1}, {‘ineq‘: ‘lt‘, ‘thresh‘: 0.90000000000000002, ‘alpha‘: 0.8958797346140273, ‘dim‘: 0}] 24 >>> aggClassEst 25 matrix([[ 1.17568763], 26 [ 2.56198199], 27 [-0.77022252], 28 [-0.77022252], 29 [ 0.61607184]]) 30 >>>
AdaBoost算法的输入参数包括数据集、类别标签以及迭代次数numIt,其中numIt是在整个AdaBoost算法中唯一需要用户指定的参数。
我们假定迭代次数设为9,如果算法在第三次迭代之后错误率为0,那么就会退出迭代过程,因此,此时就不需要执行所有的9次迭代过程。每次迭代的中间结果都会通过print语句进行输出。后面,读者可以把print输出语句注释掉,但是现在可以通过中间结果来了解AdaBoost算法的内部运行过程。
向量D非常重要,它包含了每个数据点的权重。一开始,这些权重都赋予了相等的值。在后续的迭代中,AdaBoost算法会在增加错分数据的权重的同时,降低正确分类数据的权重。D是一个概率分布向量,因此其所有的元素之和为1.0。为了满足此要求,一开始的所有元素都会被初始化成1/m。同时,程序还会建立另一个列向量aggClassEst,记录每个数据点的类别估计累计值。
AdaBoost算法的核心在于for循环,该循环运行numIt次或者直到训练错误率为0为止。循环中的第一件事就是利用前面介绍的buildStump()函数建立一个单层决策树。该函数的输入为权重向量D,返回的则是利用D而得到的具有最小错误率的单层决策树,同时返回的还有最小的错误率以及估计的类别向量。
接下来,需要计算的则是alpha值。该值会告诉总分类器本次单层决策树输出结果的权重。其中的语句max(error,1e-16)用于确保在没有错误时不会发生除零溢出。而后,alpha值加入到bestStump字典中,该字典又添加到列表中。该字典包括了分类所需要的所有信息。
接下来的三行①则用于计算下一次迭代中的新权重向量D。
在训练错误率为0时,就要提前结束for循环。此时程序是通过aggClassEst变量保持一个运行时的类别估计值来实现的②。该值只是一个浮点数,为了得到二值分类结果还需要调用sign()函数。如果总错误率为0,则由break语句中止for循环。
classifierArray:该数组包含三部词典,其中包含了分类所需要的所有信息。
此时,一个分类器已经构建成功,而且只要我们愿意,随时都可以将训练错误率降到0。那么测试错误率会如何呢?为了观察测试错误率,我们需要编写分类的一些代码。下一节我们将讨论分类。
八、测试算法:基于AdaBoost的分类
一旦拥有了多个弱分类器以及其对应的alpha值,进行测试就变得相当容易了。在程序清单7-2的adaBoostTrainDS()中,我们实际已经写完了大部分的代码。现在,需要做的就只是将弱分类器的训练过程从程序中抽出来,然后应用到某个具体的实例上去。每个弱分类器的结果以其对应的alpha值作为权重。所有这些弱分类器的结果加权求和就得到了最后的结果。在程序清单7-3中列出了实现这一过程的所有代码。然后,将下列代码添加到adaboost.py中,就可以利用它基于adaboostTrainDS()中的弱分类器对数据进行分类。
程序清单7-3 AdaBoost分类函数
1 def adaClassify(datToClass,classifierArr): 2 dataMatrix = mat(datToClass)#do stuff similar to last aggClassEst in adaBoostTrainDS 3 m = shape(dataMatrix)[0] 4 aggClassEst = mat(zeros((m,1))) 5 for i in range(len(classifierArr)): 6 classEst = stumpClassify(dataMatrix,classifierArr[i][‘dim‘], 7 classifierArr[i][‘thresh‘], 8 classifierArr[i][‘ineq‘])#call stump classify 9 aggClassEst += classifierArr[i][‘alpha‘]*classEst 10 print (aggClassEst) 11 return sign(aggClassEst) #返回-1 0 1
上述的adaClassify()函数就是利用训练出的多个弱分类器进行分类的函数。该函数的输入是由一个或者多个待分类样例datToClass以及多个弱分类器组成的数组classifierArr。程序返回aggClassEst的符号,即如果aggClassEst大于0则返回+1,而如果小于0则返回-1。
我们再看看实际中的运行效果。加入程序清单7-3中的代码之后,在Python提示符下输入:
1 >>> adaboost.adaClassify([0,0],classifierArray) 2 [[-0.69314718]] 3 [[-1.66610226]] 4 [[-2.56198199]] 5 matrix([[-1.]]) 6 >>>
可以发现,随着迭代的进行,数据点[0,0]的分类结果越来越强。
九、示例:在一个难数据集上应用AdaBoost
本节我们将在第4章给出的马疝病数据集上应用AdaBoost分类器。在第4章,我们曾经利用Logistic回归来预测患有疝病的马是否能够存活。而在本节,我们则想要知道如果利用多个单层决策树和AdaBoost能不能预测得更准。
示例:在一个难数据集上的AdaBoost应用
(1)收集数据:提供的文本文件。
(2)准备数据:确保类别标签是+1和-1而非1和0。
(3)分析数据:手工检查数据。
(4)训练算法:在数据上,利用adaBoostTrainDS()函数训练出一系列的分类器。
(5)测试算法:我们拥有两个数据集。在不采用随机抽样的方法下,我们就会对AdaBoost和Logistic回归的结果进行完全对等的比较。
(6)使用算法:观察该例子上的错误率。不过,也可以构建一个Web网站,让驯马师输入马的症状然后预测马是否会死去。
程序清单7-4 自适应数据加载函数
1 def loadDataSet(fileName): #general function to parse tab -delimited floats 2 numFeat = len(open(fileName).readline().split(‘\t‘)) #get number of fields 3 dataMat = []; labelMat = [] 4 fr = open(fileName) 5 for line in fr.readlines(): 6 lineArr =[] 7 curLine = line.strip().split(‘\t‘) 8 for i in range(numFeat-1): 9 lineArr.append(float(curLine[i])) 10 dataMat.append(lineArr) 11 labelMat.append(float(curLine[-1])) 12 return dataMat,labelMat
将上述代码添加到adaboost.py文件中并且将其保存之后,就可以输入如下命令来使用上述函数:
1 >>> datArr,labelArr=adaboost.loadDataSet(‘horseColicTraining2.txt‘) 2 >>> classifierArray,b=adaboost.adaBoostTrainDS(datArr,labelArr,10) 3 total error:0.284280936455 4 total error:0.284280936455 5 . 6 . 7 total error:0.230769230769 8 >>> testArr,testLabelArr=adaboost.loadDataSet(‘horseColicTest2.txt‘) 9 >>> prediction10=adaboost.adaClassify(testArr,classifierArray) 10 To get the number of misclassified examples type in: 11 >>> errArr=mat(ones((67,1))) 12 >>>errArr[prediction10!=mat(testLabelArr).T].sum() 13 16.0
要得到错误率,只需将上述错分样例的个数除以67即可。
将弱分类器的数目设定为1到10000之间的几个不同数字,并运行上述过程。这时,得到的结果就会如表7-1所示。在该数据集上得到的错误率相当低。如果没忘的话,在第5章中,我们在同一数据集上采用Logistic回归得到的平均错误率为0.35。而采用AdaBoost,得到的错误率就永远不会那么高了。从表中可以看出,我们仅仅使用50个弱分类器,就达到了较高的性能。
表7-1 不同弱分类器数目情况下的AdaBoost测试和分类错误率。该数据集是个难数据集。通常情况下,AdaBoost会达到一个稳定的测试错误率,而并不会随分类器数目的增多而提高
观察表7-1中的测试错误率一栏,就会发现测试错误率在达到了一个最小值之后又开始上升了。这类现象称之为过拟合(overfitting,也称过学习)。有文献声称,对于表现好的数据集,AdaBoost的测试错误率就会达到一个稳定值,并不会随着分类器的增多而上升。或许在本例子中的数据集也称不上“表现好”。该数据集一开始有30%的缺失值,对于Logistic回归而言,这些缺失值的假设就是有效的,而对于决策树却可能并不合适。如果回到数据集,将所有的0值替换成其他值,或者给定类别的平均值,那么能否得到更好的性能?
很多人都认为,AdaBoost和SVM是监督机器学习中最强大的两种方法。实际上,这两者之间拥有不少相似之处。我们可以把弱分类器想象成SVM中的一个核函数,也可以按照最大化某个最小间隔的方式重写AdaBoost算法。而它们的不同就在于其所定义的间隔计算方式有所不同,因此导致的结果也不同。特别是在高维空间下,这两者之间的差异就会更加明显。
在下一节中,我们不再讨论AdaBoost,而是转而关注所有分类器中的一个普遍问题。
十、非均衡分类的问题
在我们结束分类这个主题之前,还必须讨论一个问题。在前面六章的所有分类介绍中,我们都假设所有类别的分类代价是一样的。例如在第5章,我们构建了一个用于检测患疝病的马匹是否存活的系统。在那里,我们构建了分类器,但是并没有对分类后的情形加以讨论。假如某人给我们牵来一匹马,他希望我们能预测这匹马能否生存。我们说马会死,那么他们就可能会对马实施安乐死,而不是通过给马喂药来延缓其不可避免的死亡过程。我们的预测也许是错误的,马本来是可以继续活着的。毕竟,我们的分类器只有80%的精确率(accuracy)。如果我们预测错误,那么我们将会错杀了一个如此昂贵的动物,更不要说人对马还存在情感上的依恋。
如何过滤垃圾邮件呢?如果收件箱中会出现某些垃圾邮件,但合法邮件永远不会扔进垃圾邮件夹中,那么人们是否会满意呢?癌症检测又如何呢?只要患病的人不会得不到治疗,那么再找一个医生来看看会不会更好呢(即情愿误判也不漏判)?
还可以举出很多很多这样的例子,坦白地说,在大多数情况下不同类别的分类代价并不相等。在本节中,我们将会考察一种新的分类器性能度量方法,并通过图像技术来对在上述非均衡问题下不同分类器的性能进行可视化处理。然后,我们考察这两种分类器的变换算法,它们能够将不同决策的代价考虑在内。
1、其他分类性能度量指标:正确率、召回率及ROC曲线
2、基于代价函数的分类器决策控制
3、处理非均衡问题的数据抽样方法