利用adaboost元算法提高分类性能

元算法:对于其他算法进行组合的一种方式。主要关注boosting方法机器代表分类器:adaboost。

bagging法:自举汇聚法,从原始数据集选择S次后得到S个数据集的一种技术,新的数据集和原始数据集大小相等。每个数据及都是通过在原始数据集中随机选择一个样本进行替换得到(又放回)。这一性质允许新的数据集中可以有重复的值,而某些值在新集合中将不出现。将某个学习算法分别作用于每个数据集就得到了S个分类器(S个测试数据集针对同一算法训练出S个分类器),当要对新数据进行分类时,应用这S个分类器进行分类。得到结果采取投票的方式,选取最多类别作为最后的结果。还有一些更加先进的bagging方法,如随即森林:下面截取http://www.cnblogs.com/wentingtu/archive/2011/12/13/2286212.html的一段对于随机森林的介绍:

*************************************

随机森林顾名思义,是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一棵决策树之间是没有关联的。在得到森林之 后,当有一个新的输 入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本 为那一类。

在建立每一棵决策树的过程中,有两点需要注意 - 采样与完全分裂。首先是两个随机采样的过程,random forest对输入的数据要进行行、列的采样。对于行采样,采用有放回的方式,也就是在采样得到的样本集合中,可能有重复的样本。假设输入样本为N个,那 么采样的样本也为N个。这样使得在训练的时候,每一棵树的输入样本都不是全部的样本,使得相对不容易出现over-fitting。然后进行列采样,从M 个feature中,选择m个(m << M)。之后就是对采样之后的数据使用完全分裂的方式建立出决策树,这样决策树的某一个叶子节点要么是无法继续分裂的,要么里面的所有样本的都是指向的同一 个分类。一般很多的决策树算法都一个重要的步骤 - 剪枝,但是这里不这样干,由于之前的两个随机采样的过程保证了随机性,所以就算不剪枝,也不会出现over-fitting。

按这种算法得到的随机森林中的每一棵都是很弱的,但是大家组合起来就很厉害了。我觉得可以这样比喻随机森林算法:每一棵决策树就是一个 精通于某一个窄领域 的专家(因为我们从M个feature中选择m让每一棵决策树进行学习),这样在随机森林中就有了很多个精通不同领域的专家,对一个新的问题(新的输入数 据),可以用不同的角度去看待它,最终由各个专家,投票得到结果。

随机森林的过程请参考Mahout的random forest 。

****************************************

下面重点学习一个与bagging类似的集成分类器方法boosting:

不论是在boosting还是bagging中,所使用的多个分类器的类型都是一致的。但是区别:boosting的不同分类器是串行训练得到的,每个新的分类器都根据已训练出的分类器的性能来训练(boosting集中关注被已有分类器错分的那些数据来获取新的分类器,主要通过调整样本数据的权重来实现关注)

boosting分类的结果是基于所有分类器的加权求和结果的,所以boosting与bagging不太一样:bagging中的分类器权重是相等的,boosting中的分类器权重不等。每个权重代表其对应分类器在上一轮迭代中的成功度。

boosting方法有许多版本,在《机器学习实战》中只关注其中最流行的一个版本:adaboost(adaptive boosting).自适应boosting

adaboost运行过程如下:训练数据中的每个样本,并赋予权重(初始权重全部一样,权重总和为1),这些权重构成向量D。在训练数据上训练出一个弱分类器,并计算错误率,利用错误率得到一个alpha值,adaboost为每个弱分类器都分配一个权重值alpha。利用该alpha值以及某个样本在本次分类中是否正确分类,更改样本的权重值,得到新的D,这些D进入到下一轮的迭代中。如何得到总分类器给出的最后分类结果?因为分类的结果为1,-1,每个弱分类器的alpha值与给出分类相乘,每次迭代中求出加权结果(浮点值),将该结果代入sign()函数中,该函数返回自变量的正负(自变量为正值时,返回1;自变量为负值时,返回-1),作为分类器本次迭代给出的分类,如果错误率为0或迭代次数达到用户需求,则停止迭代。首先创建构造单层决策树分类器的函数:

单层决策树:是一种简单的决策树,仅仅基于单个特征来做决策(可以理解为某个方面的专家)。(数据中的类别是1和-1,不是0和1!!!)

创建一个简单的数据集:

def loadSimpleData():
	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

 生成的数据集只有两个特征,所以可以在二维平面中进行绘制,利用如下代码将上述数据点绘制出来:

def plot(datMat,classLabels):
    datarr=array(datMat)    
    xcord1=[];ycord1=[]
    xcord2=[];ycord2=[]
    fig=plt.figure()
    ax=fig.add_subplot(111)
    for i in range(len(classLabels)):
        if classLabels[i]==1.0:
            xcord1.append(datarr[i,0]);ycord1.append(datarr[i,1])
        else:
            xcord2.append(datarr[i,0]);ycord2.append(datarr[i,1])
    ax.scatter(xcord1,ycord1,s=30,c='red',marker='s')        
    ax.scatter(xcord2,ycord2,s=30,c='green')
    plt.show()

构建单层决策树的目的在于:在x轴或y轴画一条直线,可以将数据分类开来(对于多维特征则是在多维空间中选取一个特征,画一条直线,分隔开来,遍历所有特征,并尝试画线,得到错误率最低的特征以及直线。想象一下。。。)

以下为实现的函数:

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):#对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)#调用stump函数,得出在当前特征和阈值条件下的分类
                errArr=mat(ones((m,1)))
                errArr[predictedVals==labelMat]=0#如果predictedVal与labelMat对应相等,将errArr中设置为0,如果不等,则为1,这个用法很好,在之后的stmpClassfy函数中也有相似的应用
                weightedError=D.T*errArr#如果errArr中全部为0(表明全部预测正确),那么weightedError得出也为0
                print "split:dim %d,thresh %.2f,thresh inequal: %s,the weightedError is %.3f" %(i,threshVal,inequal,weightedError)
                if weightedError<minError:
                    minError=weightedError
                    bestClasEst=predictedVals.copy()
                    bestStump['dim']=i
                    bestStump['thresh']=threshVal
                    bestStump['ineq']=inequal
    return bestStump,minError,bestClasEst
                

 

以下函数为给定特征和阈值后进行分类的函数,也就是上面函数中调用的stumpClassify函数,函数返回对于每个样本的分类:

'''
dataMatrix:数据集
dimen:选中的特征对应的下标
threshVal:选中特征上设定的阈值
threshInreq:可能值:'lt','gt'。在给定阈值后,分别对大于阈值时为-1,以及小于阈值时为-1,做分别的操作
'''
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):#返回在给定特征(dimen),给定阈值,给定大于还是小于
    retArray=ones((shape(dataMatrix)[0],1))#retArray:在给定条件下返回的分类数组
    if threshIneq=='lt':#当给定的threshIneq为lt时:(将在给定特征下值为小于等于阈值的样本标记为-1类)
        retArray[dataMatrix[:,dimen]<=threshVal]=-1.0#dataMatrix[:,dimen]给出所有样本在给定特征下的值,对于小于阈值的,在retArray数组中标记为-1类,有趣的用法
    else:
        retArray[dataMatrix[:,dimen]>threshVal]=-1.0
    return retArray
   
ps:


对于巧妙用法的一个小补充,没有系统学过python和numpy,所以看到这样的用法记录一下。实现了在dataMat第二(下标为1)列中,如果值大于1,那么将ret数组中对应位置的值赋为0

Adaboost算法伪代码:

对每次迭代:

  利用buildStump()函数找到最佳单层决策树,

  将最佳单层决策树加入单层决策树数组,

  计算alpha

  计算新的权重向量

  更新累计类别估计值

  如果错误率等于0.0 退出循环

以下为基于单层决策树的adaboost训练过程:

def adaBoostTrainDS(dataArr,classLabels,numIt=40):
    weakClassArr=[]#弱分类器组
    m=shape(dataArr)[0]#样例数
    D=mat(ones((m,1))/m)#样例权重向量,每个样例的权重都初始化为1/m.D为概率分布向量,总和为1.0
    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)))#利用错误率计算当前所得分类器的alpha值。防止error=0出现除0状况
        bestStump['alpha']=alpha#将分类器的alpha值添加到分类器的信息字典中
        weakClassArr.append(bestStump)#将该分类器添加到弱分类器数组
        print "classEst: ",classEst.T
        expon=multiply(-1*alpha*mat(classLabels).T,classEst)#从此处开始以下三行为计算下一次迭代的D向量值,有一个公式
        D=multiply(D,exp(expon))
        D=D/D.sum()
        aggClassEst+=alpha*classEst#加权求预测的分类(将当前分类器的预测与之前分类器的结果加权求和得到一个浮点值数组,相当于投出当前专家的一票)
        print "aggClassEst: ",aggClassEst
        aggErros=multiply(sign(aggClassEst)!=mat(classLabels).T,ones((m,1)))#sign函数给出浮点值的正负性,与真实类型比对,通过与一个全1数组的对应值相乘,得到最终分类数组(巧妙!!)对于multiply函数理解可能有偏差,此处前调算法内容,对于python代码不做过多研究
        errorRate=aggErros.sum()/m#计算错误率,此处用sum函数,因为数组中只有0,1,调用sum函数得到的就是预测错误的样本个数
        print "total error: ",errorRate,"\n"
        if errorRate==0.0:break
    return weakClassArr,aggClassEst
    

对于loadSimpleData中的数据集,结果如下:

可以看到D向量的不断变化,迭代三次后错误率成0,迭代结束。图中下方为三个弱分类器的相关信息。同样,可以读文件数据,进行分类。

 

posted @ 2016-03-12 18:40  sweetxy  阅读(1409)  评论(0编辑  收藏  举报