《机器学习实战》之Adaboost

来源:互联网 发布:php 斗牛算法 编辑:程序博客网 时间:2024/05/16 02:09
首先抛出问题,如何解决不均衡分类问题?这个问题我觉得应该从原理上和实际调参两个方面来回答,原理部分从adboost入手,实践部分则是sklearn相关参数(这个后面遇到了再补充)。
什么是boosting?通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提高分类性能。那么问题又来了,对于提升方法来说,有两个问题需要回答:
(1)每一轮如和改变训练样本的权值和概率分布?
(对于adaboost,提高那些被前一轮弱分类器错误分类样本的权值,降低被争取分类的样本权值)
(2)如何将弱分类器组合成一个强分类器?
(对于adaboost,采用加权多数表决,具体地加大分类误差率小的弱分类器权重,反之亦然)
一、Adaboost优缺点:
优点:泛化错误率低,易编码,可以应用在大部分分类器上,无参数调整。
缺点:对离群点敏感
适用数据类型:数值型和标称型数据
二、算法(伪代码)以及算法解析
这一段取自《统计学习方法》,讲解的很详细,适合反复看。根据以下的推导,可以总结一下迭代过程中的几个关键的更新值,alpha值是弱分类器的权重,是由该轮迭代中的分类错误率决定的,而分类错误率又和样本的权重有关,下一轮的样本权重又和这一轮的分类器权重alpha有关,具体推导式如下。




三、代码实现
《机器学习实战》上的python代码,一开始看不懂所以对每一行进行详细注释。
1. 基于单层决策树构建弱分类器
单层决策树可以看做是由一个根节点直接连接两个叶结点的简单决策树,比如x>v或x<v,就可以看做是一个简单决策树。以下代码实际上是对每一列特征进行划分正负样本,对于某一阈值k,可以认为x>k则为正样本,或者认为x<k为正样本。对于每一特征的每一阈值,考虑这两种情况,从所有特征、所有阈值、所有大于/小于分类的情况中,选出分类错误权重中最小的那种情况,最为最佳的单层决策树输出。
#获取数据集def loadSimpData():    datMat = matrix([[ 1. ,  2.1],        [ 2. ,  1.1],        [ 1.3,  1. ],        [ 1. ,  1. ],        [ 2. ,  1. ]])    classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]    return datMat,classLabels
通过上述数据集来寻找最佳的单层决策树,最佳单层决策树是具有最低分类错误率的单层决策树,伪代码如下:
#将最小错误率minError设为+∞#对数据集中的每个特征(第一层循环):    #对每个步长(第二层循环):        #对每个不等号(第三层循环):            #建立一颗单层决策树并利用加权数据集对它进行测试            #如果错误率低于minError,则将当前单层决策树设为最佳单层决策树#返回最佳单层决策树
#单层决策树的阈值过滤函数def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):    #对数据集每一列的各个特征进行阈值过滤    retArray=ones((shape(dataMatrix)[0],1))   #初始化特征归类为1    #阈值的模式,将小于某一阈值的特征归类为-1    if threshIneq=='lt':        retArray[dataMatrix[:,dimen]<=threshVal]=-1.0    #将大于某一阈值的特征归类为-1    else:        retArray[dataMatrix[:,dimen]>threshVal]=-1.0def buildStump(dataArr,classLabels,D):     #将数据集和label列转为矩阵形式    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']:            #阈值计算公式:该特征的最小值+j(-1<=j<=numSteps+1)*步长            threshVal=(rangeMin+float(j)*stepSize)            #选定阈值后,对该特征,调用阈值过滤函数分类预测            predictedVals=stumpClassify(dataMatrix,i,threshVal,'inequal')            #初始化错误向量为1,向量长度为样本个数m            errArr=mat(ones((m,1)))            #将错误向量中分类正确项置0            errArr[predictedVals==labelMat]=0            #计算"加权"的错误率  #D为权重向量            weigthedError=D.T*errArr   #这里两个向量乘积出来为一个数,也就是整体的错误率            #打印相关信息            #print("split: dim %d, thresh %.2f,thresh inequal: %s, the weighted error is %.3f", %(i,threshVal,inequal,weigthedError))            #如果当前错误率小于当前最小错误率,将当前错误率作为最小错误率            #存储相关信息            if weigthedError<minError:                minError=weigthedError                bestClasEst=predictedVals.copy()                bestStump['dim']=i                bestStump['thresh']='threshVal'                bestStump['ineq']=inequal    #返回最佳单层决策树相关信息的字典,最小错误率,决策树预测输出结果    return bestStump,minError,bestClasEst
上面的代码包含两个函数,第一个函数是分类器的阈值过滤函数,即设定某一阈值,凡是超过/小于该阈值的结果被归为一类,小于/超过阈值的结果都被分为另外一类,采用+1和-1作为类别。
第二个函数,就是建立单层决策树的具体代码,基于样本值的各个特征及特征值的大小,设定合适的步长,获得不同的阈值,然后以此阈值作为根结点,对数据集样本进行分类,并计算错误率,需要指出的是,这里的错误率计算是基于样本权重的,所有分错的样本乘以其对应的权重,然后进行累加得到分类器的错误率。错误率得到之后,根据错误率的大小,跟当前存储的最小错误率的分类器进行比较,选择出错误率最小的特征训练出来的分类器,作为最佳单层决策树输出,并通过字典类型保存其相关重要的信息。
上面代码最终生成一个弱分类器。那么就要用多个弱分类器构建Adaboos代码。
2.完整AdaBoost算法实现
整个AdaBoost的伪代码:
#对每次迭代:    #利用buildStump()函数找到最佳的单层决策树    #将最佳单层决策树加入到单层决策树数组    #计算alpha    #计算新的权重向量D    #更新累计类别估计值    #如果错误率为等于0.0,退出循环
具体实现代码:
#@dataArr:数据矩阵#@classLabels:标签向量#@numIt:迭代次数    def adaBoostTrainDS(dataArr,classLabels,numIt=40):    #弱分类器相关信息列表    weakClassArr=[]    #获取数据集行数    m=shape(dataArr)[0]    #初始化权重向量的每一项值相等    D=mat(ones((m,1))/m)  #D为样本权重    #累计估计值向量    aggClassEst=mat((m,1))    #循环迭代次数    for i in range(numIt):        #根据当前数据集、标签及权重建立最佳单层决策树  #分类方式、误差、分类结果        bestStump,error,classEst=buildStump(dataArr,classLabels,D)        #打印权重向量        print("D:",D.T)        #求单层决策树的系数alpha        alpha=float(0.5*log((1.0-error)/(max(error,1e-16))))        #存储决策树的系数alpha到字典        bestStump['alpha']=alpha  # bestStump里面存储的是“大于”“小于”,也就是单层决策树的决策方法        #将该决策树存入弱分类器列表        weakClassArr.append(bestStump)        #打印决策树的预测结果        print("classEst:",classEst.T)        #预测正确为exp(-alpha),预测错误为exp(alpha)        #即增大分类错误样本的权重,减少分类正确的数据点权重        expon=multiply(-1*alpha*mat(classLabels).T,classEst)   #numpy函数,数组相乘就是对应元素相乘;这里得到单个弱分类器的权重值        #更新权值向量        D=multiply(D,exp(expon))     #数组乘积        D=D/D.sum()    #D为样本权重,更新样本权重        #累加当前单层决策树的加权预测值        aggClassEst+=alpha*classEst    #每次迭代之后,不断把当前分类器乘以分类器权重累加到之前的分类器上        print("aggClassEst",aggClassEst.T)        #求出分类错的样本个数   #这个地方没问题??分类错误率应该考虑上样本权重 啊!??        aggErrors=multiply(sign(aggClassEst)!=\                    mat(classLabels).T,ones((m,1)))   #预测值不等于真实label值则为1        #计算错误率        errorRate=aggErrors.sum()/m   #错误分类率        print("total error:",errorRate,"\n")        #错误率为0.0退出循环        if errorRate==0.0:break    #返回弱分类器的组合列表    return weakClassArr
对于上面的代码,需要说明的有几点:
(1)上面的输入除了数据集和标签之外,还有用户自己指定的迭代次数,用户可以根据自己的成本需要和实际情况,设定合适的迭代次数,构建出需要的弱分类器数量。
(2)权重向量D包含了当前单层决策树分类器下,各个数据集样本的权重,一开始它们的值都相等。但是,经过分类器分类之后,会根据分类的权重加权错误率对这些权重进行修改,修改的方向为,提高分类错误样本的权重,减少分类正确的样本的权重。
(3)分类器系数alpha,是另外一个非常重要的参数,它在最终的分类器组合决策分类结果的过程中,起到了非常重要的作用,如果某个弱分类器的分类错误率更低,那么根据错误率计算出来的分类器系数将更高,这样,这些分类错误率更低的分类器在最终的分类决策中,会起到更加重要的作用。
(4)上述代码的训练过程是以达到迭代的用户指定的迭代次数或者训练错误率达到要求而跳出循环。而最终的分类器决策结果,会通过sign函数,将结果指定为+1或者-1。
3.测试算法
那么有了训练好的分类器,需要在分类器未知的数据上进行测试,看看分类效果。上面的训练代码会保存每个弱分类器的重要信息,比如分类器系数,分类器的最优特征,特征阈值等。有了这些重要的信息,我们拿到之后,就可以对测试数据进行预测分类了。
#测试adaBoost,adaBoost分类函数#@datToClass:测试数据点#@classifierArr:构建好的最终分类器def adaClassify(datToClass,classifierArr):    #构建数据向量或矩阵    dataMatrix=mat(datToClass)    #获取矩阵行数    m=shape(dataMatrix)[0]    #初始化最终分类器    aggClassEst=mat(zeros((m,1)))    #初始分类器为全为1的向量,存储分分类结果    #遍历分类器列表中的每一个弱分类器    for i in range(len(classifierArr)):        #每一个弱分类器对测试数据进行预测分类        classEst=stumpClassify(dataMat,classifierArr[i]['dim'],\                                classifierArr[i]['thresh'],                                classifierArr[i]['ineq'])  #分类器由最优特征、特征阈值、分类方式(大于、小于)决定        #对各个分类器的预测结果进行加权累加        aggClassEst+=classifierArr[i]['alpha']*classEst        print('aggClassEst',aggClassEst)    #通过sign函数根据结果大于或小于0预测出+1或-1    return sign(aggClassEst)
上面的adaBoost分类器训练和测试代码,随着分类器数目的增加,adaBoost分类器的训练错误率不断的减少,而测试错误率则是经历先减少到最小值,再逐渐增大的过程。显然,这就是所说的过拟合。因此,对于这种情况,我们应该采取相应的措施,比如采取交叉验证的方法,在训练分类器时,设定一个验证集合,不断测试验证集的分类错误率,当发现训练集错误率减少的同时,验证集的错误率较之上一次结果上升了,就停止训练。或者其他比较实用的模拟退火方法,基因遗传方法等。

回到最开始的问题,如何解决不均衡分类问题?
1.Adaboost是基于代价函数来调整错误权重向量D,给错误率较小的分类更多权重。
2.另外一种针对非均衡问题调节分类器的方法,就是对分类器的训练数据进行改造。通过欠抽样(删除样例)、过抽样(复制样例)实现。例如对于信用卡欺诈中,正例属于罕见类别,应该保留所有正例,而对反例进行欠抽样或者删除处理,或者使用反例类被的欠抽样和正例样本的过抽样的方法。