AdaBoost | 算法实现

4263204-6289bb2beb2d480d.png

这篇文章中,我们一起学习了AdaBoost算法的原理,今天我们在python3的环境下,根据原理,自己写一段代码来实现AdaBoost算法。

01 构造单层决策树

  • 逻辑:
    • 遍历数据集的每个特征:
      • 遍历特征的每个步长:
        • 遍历步长的每个阈值对比方式(less than/greater than):
          • 计算每次迭代的weightedError
    • 认为weightedError最小的点(特征,阈值,方式)是最佳决策点,以此构建一棵决策树桩(stump)

根据这样的逻辑,写出如下单层决策树生成函数。

#通过阈值对数据分类+1 -1
#dimen为dataMat的列索引值,即特征位置;threshIneq为阈值对比方式,大于或小于
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
    retArray=ones((shape(dataMatrix)[0],1))#注意,有两个()
    #阈值的模式,将小于某一阈值的特征归类为-1
    if threshIneq=='lt':#less than
        retArray[dataMatrix[:,dimen]<=threshVal]=-1.0
    #将大于某一阈值的特征归类为-1
    else:#greater than
        retArray[dataMatrix[:,dimen]>threshVal]=-1.0
    return retArray

#单层决策树生成函数
#D为各样本权重,shape=(m,1);label为样本标签,shape=(1,m)
def buildStump(dataArr,classLabels,D):
#将数据集和标签列表转为矩阵形式
    dataMatrix=mat(dataArr);labelMat=mat(classLabels).T
    m,n=shape(dataMatrix)
    #步长或区间总数 最优决策树信息 最优单层决策树预测结果
    numSteps=10.0;bestStump={};bestClasEst=mat(zeros((m,1))) #注意,有两个()
    #最小错误率初始化为+∞
    minError=inf
    
    #遍历数据集的每个特征:遍历特征的每个步长:遍历步长的每个阈值对比方式
    for i in range(n):
        #找出列中特征值的最小值和最大值
        rangeMin=dataMatrix[:,i].min();rangeMax=dataMatrix[:,i].max()
        #求取步长大小或者说区间间隔
        stepSize=(rangeMax-rangeMin)/numSteps
        #遍历各个步长区间
        for j in range(-1,int(numSteps)+1):
            #两种阈值过滤模式
            for inequal in ['lt','gt']:
                threshVal=rangeMin+float(j)*stepSize
                #选定阈值后,调用阈值过滤函数分类预测
                predictedVals=\
                    stumpClassify(dataMatrix,i,threshVal,inequal)
                #初始化错误向量
                errArr=mat(ones((m,1)))
                #将错误向量中分类正确项置0
                errArr[predictedVals==labelMat]=0
                #计算"加权"的错误率
                weigthedError=D.T*errArr
                #print ("分割特征为第{0}个,分割阈值为{1},分割方式为{2},weight error为{3}"\
                #      .format(i+1,threshVal,threshIneq,weightedErr))
                if weigthedError<minError:
                    minError=weigthedError
                    bestClasEst=predictedVals.copy()
                    bestStump['dim']=i
                    bestStump['thresh']=threshVal
                    bestStump['ineq']=inequal
    #返回最佳单层决策树相关信息的字典,最小错误率,决策树预测输出结果
    return bestStump,minError,bestClasEst

测试一下

data=mat([[ 1. ,  2.1],[ 2. ,  1.1],[ 1.3,  1. ],[ 1. ,  1. ],[ 2. ,  1. ]])
label=[1.0,1.0,-1.0,-1.0,1.0];D=mat(ones((5,1))/5)
buildStump(array(data),array(label),D)

得到如下结果,表示最佳分类特征为第1个特征,分割阈值为1.3,分割方式为"将<阈值的样本归为-1类":
4263204-364880de89b821f3.png

02 利用AdaBoost提升单层决策树效果

  • 逻辑
    • 样本初始权重D=1/m
    • 进入循环:(设置循环次数上限or错误率e=0为止)
      • 将D带入buildStump,训练最佳决策树,得到本次迭代的最佳分类bestClass
      • 计算汇集的分类结果aggClass(即,将每次最佳决策树分类结果bestClass*alpha相加)
      • 根据当前的汇集分类错误率e,计算alpha,并更新样本权值D
      • 直到达到循环次数上限或错误率e=0为止
    • 输出弱分类器集合(bestStump的集合)

根据这样的逻辑,给出如下函数。

#label为样本标签,shape=(1,m)
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)
        alpha = float(0.5*log((1.0-error)/max(error,1e-16))) #max(error,1e-16)防止0误差的计算溢出
        bestStump["alpha"]=alpha
        weakClassArr.append(bestStump)
        #print ("D:",D.T,"\n","predClass:",classEst.T)
        
        #为下一次迭代更新D(很关键,矩阵运算容易写错!)
        expon=multiply(-1*alpha*mat(classLabels).T,classEst) #shape(5,1)*shape(5,1)对应元素相乘,得到各样本的-alpha*yi*G(xi)
        D=multiply(D,exp(expon)) #shape(5,1)*shape(5,1)对应元素相乘,
        D=D/D.sum() #得到各样本更新后的wi
        
        #计算汇集的分类结果aggClassEst(即,将每次最佳决策树分类结果bestClass*alpha相加)
        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)
        if errorRate==0.0:
            break
    return weakClassArr,errorRate,aggClassEst

测试一下

data=mat([[ 1. ,  2.1],[ 2. ,  1.1],[ 1.3,  1. ],[ 1. ,  1. ],[ 2. ,  1. ]])
label=[1.0,1.0,-1.0,-1.0,1.0];D=mat(ones((5,1))/5)
adaBoostTrainDS(array(data),array(label))

输出结果如下,当叠加到第三个弱分类器时,分类错误率降为了0:
4263204-1a7951b2176080c1.png

03 使用adaboost+单层决策树模型分类数据

刚才我们写好了模型,可以利用训练集结合我们的代码来训练若干个弱分类器了,当得到弱分类器集合之后,我们就可以利用这些弱分类器对测试集数据进行分类了。

分类原理是,将测试集特征带入各个弱分类器中,得到一系列预测值,然后将这些 预测值 X 对应弱分类器的alpha,再求和,就可以得到这些弱分类器对测试集样本的累加预测值了

def adaClassify(datToClass,classifierArr):
    dataMatrix=mat(datToClass)
    m=shape(dataMatrix)[0]
    aggClassEst=mat(zeros((m,1))) #初始化最终分类器
    for i in range(len(classifierArr)):#遍历各个弱分类器
        #每一个弱分类器对测试数据进行预测分类
        classEst=stumpClassify(dataMatrix,classifierArr[i]['dim'],\
                                classifierArr[i]['thresh'],
                                classifierArr[i]['ineq'])
        #对各个分类器的预测结果进行加权累加
        aggClassEst+=classifierArr[i]['alpha']*classEst
        #print('aggClassEst',aggClassEst)
    #通过sign函数根据结果大于或小于0预测出+1或-1
    return sign(aggClassEst)

04 使用自定义AdaBoost预测疝病马是否存活

  • 步骤:
    1. 读取数据、处理数据
    2. 使用adaBoostTrainDS训练弱分类器
    3. 使用adaClassify预测疝病马是否存活
def horseColic(numWeakClassifier):
    #读取训练集、测试集
    dataArr,labelArr=loadDataSet(r'D:\DM\python\data\MLiA_SourceCode\machinelearninginaction\Ch07\horseColicTraining2.txt')
    dataTest,labelTest=loadDataSet(r'D:\DM\python\data\MLiA_SourceCode\machinelearninginaction\Ch07\horseColicTest2.txt')
    #训练弱分类器
    classifierArr,trainErrorRate=adaBoostTrainDS(dataArr,labelArr,numWeakClassifier)
    #用训练好的弱分类器分类测试样本
    predictLabels=adaClassify(dataTest,classifierArr)
    #计算分类准确率
    errArr=mat(ones((len(labelTest),1)))
    errArr[predictLabels==mat(labelTest).T]=0
    testErrorRate=errArr.sum()/len(labelTest)
    return numWeakClassifier,trainErrorRate,testErrorRate

测试一下,下面设置了训练不同个数的弱分类器

for i in [1,10,100,500,1000]:
    numWeakClassifier,trainErrorRate,testErrorRate=horseColic(i)
    print("=".center(70,"="))
    print("弱分类器个数为{0}时,训练集分类错误率为{1}%,测试集分类错误率为{2}%"\
          .format(i,round(trainErrorRate*100,2),round(testErrorRate*100,2)))
print("=".center(70,"="))

我们来看看效果,
4263204-176322b0e3c51496.png

可以看到,并不是弱分类器越多越好,太多容易过拟合,使得测试集准确率较低,太少容易欠拟合,使得训练集准确率较低,因此adaboost的弱分类器个数是一个需要斟酌的参数

ROC曲线

最后,我们来看一下本次预测的ROC曲线,看看我们训练的这个分类器是不是一个“好医生”。
4263204-e42e313d13dd9b0e.png

可以看到,本次训练的模型,AUC达到了0.895,图中表现为红色曲线,可以说这个模型是一个比较好的医生了,诊断结果还是比较靠谱的,只要选取合适的阈值,如A点,可以实现较低的漏诊率(1-y轴)和误诊率(x轴)。


05 总结

本文根据AdaBoost原理,给出基于python3的自定义函数实现,然后用一组疝病马的数据测试了算法。

06 参考

  • 《机器学习实战》 Peter Harrington Chapter7
  • 《统计学习方法》 李航 Chapter8

猜你喜欢

转载自blog.csdn.net/weixin_33739541/article/details/87230031
今日推荐