7.4 完整AdaBoost算法的实现

在上一节,我们构建了一个基于加权输入值进行决策的分类器。现在,我们拥有了实现一个完整AdaBoost算法所需要的所有信息。我们将利用7.3节构建的单层决策树来实现7.2节中给出提纲的算法。

整个实现的伪代码如下:

对每次迭代:
    利用buildStump函数找到最佳的单层决策树   
    将最佳单层决策树加入到单层决策树数组 
    计算alpha   
    计算新的权重向量D 
    更新累计类别估计值  
    如果错误率等于0.0,则退出循环       
  

为了将该函数放入Python中,打开adaboost.py文件并将程序清单7-2的代码加入其中。

程序清单7-2 基于单层决策树的AdaBoost训练过程

def adaBoostTrainDS(dataArr,classLabels,numIt=40):
    weakClassArr = 
    m = shape(dataArr)[0]
    D = mat(ones((m,1))/m)
    aggClassEst = mat(zeros((m,1)))
    for i in range(numIt):
        bestStump,error,classEst = buildStump(dataArr,classLabels,D)
        print \"D:\",D.T
        alpha = float(0.5*log((1.0-error)/max(error,1e-16)))
        bestStump[\'alpha\'] = alpha
        weakClassArr.append(bestStump)
        print \"classEst: \",classEst.T
        #❶(以下两行)为下一次迭代计算*D*
        expon = multiply(-1*alpha*mat(classLabels).T,classEst)
        D = multiply(D,exp(expon))
        D = D/D.sum
        #❷(以下五行)错误率累加计算
        aggClassEst += alpha*classEst
        print \"aggClassEst: \",aggClassEst.T
        aggErrors = multiply(sign(aggClassEst) !=mat(classLabels).T,ones((m,1)))
        errorRate = aggErrors.sum/m
        print \"total error: \",errorRate,\"n\"
        if errorRate == 0.0: break
    return weakClassArr
>>> classifierArray = adaboost.adaBoostTrainDS(datMat,classLabels,9)
D: [[ 0.2 0.2 0.2 0.2 0.2]]
classEst: [[-1. 1. -1. -1. 1.]]
aggClassEst: [[-0.69314718 0.69314718 -0.69314718 -0.69314718 0.69314718]]
total error: 0.2
D: [[ 0.5 0.125 0.125 0.125 0.125]]
classEst: [[ 1. 1. -1. -1. -1.]]
aggClassEst: [[ 0.27980789 1.66610226 -1.66610226 -1.66610226 -0.27980789]]
total error: 0.2
D: [[ 0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]]
classEst: [[ 1. 1. 1. 1. 1.]]
aggClassEst: [[ 1.17568763 2.56198199 -0.77022252 -0.77022252 0.61607184]]
total error: 0.0
  

AdaBoost算法的输入参数包括数据集、类别标签以及迭代次数numIt,其中numIt是在整个AdaBoost算法中唯一需要用户指定的参数。

我们假定迭代次数设为9,如果算法在第三次迭代之后错误率为0,那么就会退出迭代过程,因此,此时就不需要执行所有的9次迭代过程。每次迭代的中间结果都会通过print语句进行输出。后面,读者可以把print输出语句注释掉,但是现在可以通过中间结果来了解AdaBoost算法的内部运行过程。

函数名称尾部的DS代表的就是单层决策树(decision stump),它是AdaBoost中最流行的弱分类器,当然并非唯一可用的弱分类器。上述函数确实是建立于单层决策树之上的,但是我们也可以很容易对此进行修改以引入其他基分类器。实际上,任意分类器都可以作为基分类器,本书前面讲到的任何一个算法都行。上述算法会输出一个单层决策树的数组,因此首先需要建立一个新的Python表来对其进行存储。然后,得到数据集中的数据点的数目m,并建立一个列向量D

向量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循环。

接下来我们观察一下中间的运行结果。还记得吗,数据的类别标签为[1.0, 1.0, -1.0, -1.0, 1.0]。在第一轮迭代中,D中的所有值都相等。于是,只有第一个数据点被错分了。因此在第二轮迭代中,D向量给第一个数据点0.5的权重。这就可以通过变量aggClassEst的符号来了解总的类别。第二次迭代之后,我们就会发现第一个数据点已经正确分类了,但此时最后一个数据点却是错分了。D向量中的最后一个元素变成0.5,而D向量中的其他值都变得非常小。最后,第三次迭代之后aggClassEst所有值的符号和真实类别标签都完全吻合,那么训练错误率为0,程序就此退出。

为了观察classifierArray的值,键入:

>>> classifierArray
[{\'dim\': 0, \'ineq\': \'lt\', \'thresh\': 1.3, \'alpha\': 0.69314718055994529},
     {\'dim\': 1, \'ineq\': \'lt\', \'thresh\': 1.0, \'alpha\': 0.9729550745276565},
     {\'dim\': 0,\'ineq\': \'lt\', \'thresh\': 0.90000000000000002, \'alpha\':0.89587973461402726}]
  

该数组包含三部词典,其中包含了分类所需要的所有信息。此时,一个分类器已经构建成功,而且只要我们愿意,随时都可以将训练错误率降到0。那么测试错误率会如何呢?为了观察测试错误率,我们需要编写分类的一些代码。下一节我们将讨论分类。

《机器学习实战》