机器学习实战(五)——基于单层决策树(dicision-stump)的adaBoosting

  • Post author:
  • Post category:其他


一、元算法

元算法(meta-algorithm)是一类将多个分类器分类结果进行整合的算法,元算法一般包括一下几种:bagging(boostrap aggregating),random-forest,jackknife,boosting

遇到罕见病例时,医院会组织专家团进行临床会诊共同分析病例以判定结果。如同专家团临床会诊一样,重大决定汇总多个人的意见往往胜过一个人的决定。机器学习中也吸取了‘三个臭皮匠顶个诸葛亮’(实质上是由三个裨将顶个诸葛亮口误演化而来)的思想,这就是元算法的思想。元算法(meta-algorithm)也叫集成方法(ensemble method),通过将其他算法进行组合而形成更优的算法,组合方式包括:不同算法的集成,数据集不同部分采用不同算法分类后的集成或者同一算法在不同设置下的集成。

有了元算法的思想,PAC((Probably Approximately Correct)学习模型中就有了弱学习算法和强学习算法的等价性问题–即组合任意给定的弱学习算法 ,是否可以将其提升为强学习算法 ? 如果二者等价 ,那么只需将弱学习算法提升为强学习算法就行,而不必寻找很难获得的强学习算法。理论证明,实际上只要弱分类器个数趋向于无穷个时,其组合而成的强分类器的错误率将趋向于零。

弱学习算法—识别错误率小于1/2(即准确率仅比随机猜测略高的学习算法)

强学习算法—识别准确率很高并能在可接受时间内完成的学习算法

介绍几种比较重要的将多个分类器整合为一个分类器的方法–boostrapping方法、bagging方法和Boosting算法。

1)Bootstrapping:

i)重复地从一个样本集合D中采样n个样本,新样本中可能存在重复的值或者丢失原样本集合的一些值。

ii)针对每次采样的子样本集,进行统计学习,获得假设H i

iii)将若干个假设进行组合,形成最终的假设H final

iv)将最终的假设用于具体的分类任务

2)Bagging方法

i)训练分类器-从整体样本集合中,抽样n * < N个样本, 针对抽样的集合训练分类器C i, 抽取方法有很多种

ii)分类器进行投票,最终的结果是分类器投票的优胜结果,每个分类器权重是相等的

3)Boosting

Boosting是一种与Bagging很类似的技术,两者所使用的多个分类器的类型都是一致的。但是在前者当中,不同的分类器是通过串行训练而获得的,每个新分类器都在已训练出的分类器的性能基础上再进行训练,通过集中关注被已有分类器错分的些数据来获得新的分类器。Boosting分类的结果是基于所有分类器的加权求和结果的,分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。Boosting算法有很多种,AdaBoost(Adaptive Boost)就是其中最流行的,与SVM分类并称机器学习中最强大的学习算法。

AdaBoost 是一种迭代算法,其核心思想是针对同一个训练集训练M个弱分类器,每个弱分类器赋予不同的权重,然后把这些弱分类器集合起来而构造一个更强的最终分类器,本文就详解AdaBoost算法的详细过程。

这里提到的分类器=样本(包括样本数据和样本权值)+分类算法

二、具体理解adaboosting

boosting :主要是adaBoosting,样本数据不变,样本的权重变化,权重变化与分类器的错误率有关,错误率同时影响样本权重和分类器的权重,具体公式如下:








ϵ


=






















































































































































加权后的








ϵ











:








ϵ


=











































































































































































分类器的权值与该分类器的错误率有关:








α


=





1






2













l


n


(






1





ϵ







ϵ













)











公式可以看出,当








ϵ


>


0.5











时,








α


>


0











;当








0


<


ϵ


<


0.5











时,








α


<


0












对于每个分类器分类完成后根据错误率计算分类器的权值,进而计算下一个分类器中各个样本的样本权重,样本权重D是一个向量,在上一个分类器中被正确分类的样本,其在下一个分类器中的样本权重变为:
















D






i















(


t


+


1


)









=














D






i















(


t


)












e











α















S




u


m


(


D


)























在上一个分类器中被错误分类的样本,其在下一个分类器中的样本权重变为:
















D






i















(


t


+


1


)









=














D






i















(


t


)












e






α













S




u


m


(


D


)























综合起来:
















D






i















(


t


+


1


)









=














D






i















(


t


)












e











p


r


e


d




i


c


t





l


a


b


e


l





α















S




u


m


(


D


)























其中predict是上一个分类器得到的分类结果,label是该样本的实际类别

根据公式可以看出,下一个分类器中的样本权重,与其是否被错分和上一个分类器的权值都有关系:具体来说,当样本错分,且








α


>


0











(错误率低,分类结果可信)时,增大其权重,若








α


<


0











(错误率高,分类结果不可信),则减少权重;当样本正确分类,且








α


>


0











(错误率低,分类结果可信)时,减少其权重,若








α


<


0











(错误率高,分类结果不可信),则增加权重。

这种原则实际上就是,样本

确实

被错分的(这里的确实表示错误率小于0.5的分类器中认为正确分类的样本和错误率大于0.5的分类器中认为错误分类的样本),增加其权重,反之减少权重。


注意:1.adaboosting最大的特点在于,每次迭代生成的新的分类器与上一个分类器是有联系的,联系在于上一个分类器的错误率可以通过其权值对下一个分类器中的样本权值进行影响,这种影响可以使得下一个分类器向着更低错误率的方向演变。2.理解样本错分的意义,要

结合分类器的错误率

,对于错误率高于0.5的分类器,我们认为被他正确分类的样本其结果是错误的,实际上该样本是被错误分类了。


目前实现的代码adaboosting仍然是对于二分问题的,对于多分类问题的解法还没有想过,值得考虑。

代码是基于单层决策树的adaboosting,对马疝气症状的诊断,判断马匹是否能存活。stumpTree.py:

# -*- coding: utf-8 -*-
##############
#adaBoosting
####################
from numpy import *

def stumpClassify(dataMat,dimen,threshVal,ineq):
    retArray=ones([shape(dataMat)[0],1])
    if ineq=='lt':
        retArray[dataMat[:,dimen]<=threshVal]=-1.0
    else:
        retArray[dataMat[:,dimen]>threshVal]=-1.0
    return retArray



#build a tree stump
def buildStump(dataSet,labels,D):
    dataMat=mat(dataSet)
    classLabels=mat(labels).T
    m,n =shape(dataMat)
    numStep=10.0
    bestStump={}
    bestClassEst=mat(zeros([m,1]))
    minErr=inf
    #之前放在了循环内造成错误,理解原因:算法的这一部分实际上是在
    #给定的样本权值D的条件下,寻找错误率最低的划分方法(包括特征编号,阈值,alpha值),这中划分方法实际上
    #就是一个分类器
    #当结束一次循环之后,根据分类器的错误率得到了这个分类器的权值alpha,以及各个样本的新权值D(新权值D与
    #旧权值D相关),在新样本权值D的条件下进行下一次循环,与第一次循环相比,只有D发生了变化,且D的变化可以
    #保证错误率减少;此时如果将minErr放在循环内部,实际上最终记录的是最后一个特征的最优划分方法,而这种方
    #法不是对算法分类器整体的分类错误率最有利的;放在循环外不必担心下次循环无法得到更小的minErr,因为算法
    #的设计是使err向变小的方向变化的。
    for i in xrange(n):
        rangeMin=dataMat[:,i].min()
        rangeMax=dataMat[:,i].max()
        threshStep=(rangeMax-rangeMin)/float(numStep)
        #minErr=inf 之前放在此处造成了错误
        weightedErr=0.0
        for j in xrange(-1,int(numStep)+1):
            threshVal=rangeMin+j*threshStep
            for ineq in ['lt','gt']:
                errArr=mat(ones([m,1]))
                predictVal=stumpClassify(dataMat,i,threshVal,ineq)
                errArr[predictVal==classLabels]=0
                weightedErr=float((mat(D).T*mat(errArr)))
                #print weightedErr
                #print type(weightedErr)
                #print errArr
                #print D
                if (weightedErr<minErr):
                    #print weightedErr
                    minErr=weightedErr
                    #bestStump['D']=D
                    bestStump['thresh']=threshVal
                    bestStump['dim']=i
                    bestStump['ineq']=ineq
                    bestClasEst=predictVal.copy()
                else:pass
    return bestStump,minErr,bestClasEst


def adaBoostTrainDS(dataArr,classLabels,numIter=40):
    m,n =shape(dataArr)
    D=mat(ones([m,1])/m)
    #print D
    weakClassEst=[]
    aggErrRate=0.0
    aggClassEst=mat(zeros([m,1]))
    for i in xrange(numIter):
        aggErr=zeros([m,1])
        bestStump,err,bestClassEst=buildStump(dataArr,classLabels,D)
        alpha=float(log((1.0-err)/max(float(err),1e-16))/2.0)
        #alpha=float(log((1.0-err)/float(err))/2)
        #print alpha
        bestStump['alpha']=alpha
        weakClassEst.append(bestStump)
        expon=multiply(-1.0*alpha*mat(classLabels).T,bestClassEst)
        #print shape(expon)
        D=multiply(D,exp(expon))
        D=D/D.sum()
        #print 'the sum of D :',D.sum()
        #print 'the D:',D
        #print shape(D)
        aggClassEst+=alpha*bestClassEst
        #print 'the best Class Est',bestClassEst
        #print 'the aggClassEst',aggClassEst
        #aggErr[sign(aggClassEst)!=mat(classLabels).T]=1
        aggErr=multiply(sign(aggClassEst)!=mat(classLabels).T,ones([m,1]))
        aggErrRate=aggErr.sum()/m
        #print 'the aggErr',aggErr
        #print 'total Error:',aggErrRate
        if aggErrRate==0:break
    print 'total Error:',aggErrRate
    return weakClassEst

def loadData():
    dataMat=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 dataMat,classLabels


def file2Matrix(fileName):
    fileHorse=open(fileName)
    arrayOLines=fileHorse.readlines()
    numberOfLines=len(arrayOLines)
    dataArray=zeros([numberOfLines,21])
    labels=[]
    index=0
    for line in arrayOLines:
        line=line.strip()
        line=line.split('\t')
        dataArray[index,:]=line[0:21]
        labels.append(float(line[-1]))
        index+=1
    return dataArray,labels


def adaBoostTest(dataMatrix,labels,weakClassify):
    dataMat=mat(dataMatrix)
    l=len(weakClassify)
    m,n=shape(dataMat)
    aggErr=mat(zeros([m,1]))
    aggErrRate=0.0
    aggClassify=mat(zeros([m,1]))
    for i in xrange(l):
        predict=stumpClassify(dataMat,weakClassify[i]['dim'],weakClassify[i]['thresh'],\
                      weakClassify[i]['ineq'])
        weightedPredict=predict*float(weakClassify[i]['alpha'])
        aggClassify+=weightedPredict
        aggErr=multiply(sign(aggClassify)!=mat(labels).T,ones([m,1]))
        aggErrRate=aggErr.sum()/m
    print 'the error rate:',aggErrRate

testStumpTree.py:

import stumpTree
#dataMat,classLabels=stumpTree.loadData()
#stumpTree.adaBoostTrainDS(dataMat,classLabels)

dataMat,classLabels=stumpTree.file2Matrix('/home/lvsolo/python/adaBoosting/horseColicTraining2.txt')
weakClassify=stumpTree.adaBoostTrainDS(dataMat,classLabels,50)
dataTest,testLabels=stumpTree.file2Matrix('/home/lvsolo/python/adaBoosting/horseColicTest2.txt')
stumpTree.adaBoostTest(dataTest,testLabels,weakClassify)

三、转载:关于集中元算法的联系

以下为转载内容:boosting,booststrap,bagging,random forest之间的联系区别

http://blog.csdn.net/jlei_apple/article/details/8168856


这两天在看关于boosting算法时,看到一篇不错的文章讲bootstrap, jackknife, bagging, boosting, random forest 都有介绍,以下是搜索得到的原文,没找到博客作者的地址,

在这里致谢作者的研究。

一并列出一些找到的介绍boosting算法的资源:

(1)视频讲义,介绍boosting算法,主要介绍AdaBoosing



(2) 在这个网站的资源项里列出了对于boosting算法来源介绍的几篇文章,可以下载:

http://www.boosting.org/tutorials


(3) 一个博客介绍了许多视觉中常用算法,作者的实验和理解,这里附录的链接是关于使用opencv进行人脸检测的过程和代码,可以帮助理解训练过程是如何完成的:


http://www.cnblogs.com/tornadomeet/archive/2012/03/28/2420936.html


(4)这里是一个台湾的电子期刊上关于AdaBoost的介绍:

http://140.113.87.114/cvrc/edm/vol_6/tech1.htm


( 一 )

Jackknife,Bootstraping, bagging, boosting, AdaBoosting, Rand forest 和 gradient boosting

这些术语,我经常搞混淆,现在把它们放在一起,以示区别。(部分文字来自网络,由于是之前记的笔记,忘记来源了,特此向作者抱歉)

Bootstraping: 名字来自成语“pull up by your own bootstraps”,意思是依靠你自己的资源,称为自助法,它是一种有放回的抽样方法,它是非参数统计中一种重要的估计统计量方差进而进行区间估计的统计方法。其核心思想和基本步骤如下:

(1) 采用重抽样技术从原始样本中抽取一定数量(自己给定)的样本,此过程允许重复抽样。

(2) 根据抽出的样本计算给定的统计量T。

(3) 重复上述N次(一般大于1000),得到N个统计量T。

(4) 计算上述N个统计量T的样本方差,得到统计量的方差。

应该说Bootstrap是现代统计学较为流行的一种统计方法,在小样本时效果很好。通过方差的估计可以构造置信区间等,其运用范围得到进一步延伸。

Jackknife: 和上面要介绍的Bootstrap功能类似,只是有一点细节不一样,即每次从样本中抽样时候只是去除几个样本(而不是抽样),就像小刀一样割去一部分。

下列方法都是上述Bootstraping思想的一种应用。

bagging:bootstrap aggregating的缩写。让该学习算法训练多轮,每轮的训练集由从初始的训练集中随机取出的n个训练样本组成,某个初始训练样本在某轮训练集中可以出现多次或根本不出现,训练之后可得到一个预测函数序列h_1,⋯ ⋯h_n ,最终的预测函数H对分类问题采用投票方式,对回归问题采用简单平均方法对新示例进行判别。

[训练R个分类器f_i,分类器之间其他相同就是参数不同。其中f_i是通过从训练集合中(N篇文档)随机取(取后放回)N次文档构成的训练集合训练得到的。对于新文档d,用这R个分类器去分类,得到的最多的那个类别作为d的最终类别。]

boosting: 其中主要的是AdaBoost(Adaptive Boosting)。初始化时对每一个训练例赋相等的权重1/n,然后用该学算法对训练集训练t轮,每次训练后,对训练失败的训练例赋以较大的权重,也就是让学习算法在后续的学习中集中对比较难的训练例进行学习,从而得到一个预测函数序列h_1,⋯, h_m , 其中h_i也有一定的权重,预测效果好的预测函数权重较大,反之较小。最终的预测函数H对分类问题采用有权重的投票方式,对回归问题采用加权平均的方法对新示例进行判别。

(类似Bagging方法,但是训练是串行进行的,第k个分类器训练时关注对前k-1分类器中错分的文档,即不是随机取,而是加大取这些文档的概率。)

(pku, sewm,shinningmonster.)

Bagging与Boosting的区别:二者的主要区别是取样方式不同。Bagging采用均匀取样,而Boosting根据错误率来取样,因此Boosting的分类精度要优于Bagging。Bagging的训练集的选择是随机的,各轮训练集之间相互独立,而Boostlng的各轮训练集的选择与前面各轮的学习结果有关;Bagging的各个预测函数没有权重,而Boosting是有权重的;Bagging的各个预测函数可以并行生成,而Boosting的各个预测函数只能顺序生成。对于象神经网络这样极为耗时的学习方法。Bagging可通过并行训练节省大量时间开销。

bagging和boosting都可以有效地提高分类的准确性。在大多数数据集中,boosting的准确性比bagging高。在有些数据集中,boosting会引起退化— Overfit。

Boosting思想的一种改进型AdaBoost方法在邮件过滤、文本分类方面都有很好的性能。

gradient boosting(又叫Mart, Treenet):Boosting是一种思想,Gradient Boosting是一种实现Boosting的方法,它主要的思想是,每一次建立模型是在之前建立模型损失函数的梯度下降方向。损失函数(loss function)描述的是模型的不靠谱程度,损失函数越大,则说明模型越容易出错。如果我们的模型能够让损失函数持续的下降,则说明我们的模型在不停的改进,而最好的方式就是让损失函数在其梯度(Gradient)的方向上下降。

Rand forest: 随机森林,顾名思义,是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一棵决策树之间是没有关联的。在得到森林之后,当有一个新的输入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本为那一类。 在建立每一棵决策树的过程中,有两点需要注意 - 采样与完全分裂。首先是两个随机采样的过程,random forest对输入的数据要进行行、列的采样。对于行采样,采用有放回的方式,也就是在采样得到的样本集合中,可能有重复的样本。假设输入样本为N个,那么采样的样本也为N个。这样使得在训练的时候,每一棵树的输入样本都不是全部的样本,使得相对不容易出现over-fitting。然后进行列采样,从M个feature中,选择m个(m << M)。之后就是对采样之后的数据使用完全分裂的方式建立出决策树,这样决策树的某一个叶子节点要么是无法继续分裂的,要么里面的所有样本的都是指向的同一个分类。一般很多的决策树算法都一个重要的步骤 - 剪枝,但是这里不这样干,由于之前的两个随机采样的过程保证了随机性,所以就算不剪枝,也不会出现over-fitting。 按这种算法得到的随机森林中的每一棵都是很弱的,但是大家组合起来就很厉害了。可以这样比喻随机森林算法:每一棵决策树就是一个精通于某一个窄领域的专家(因为我们从M个feature中选择m让每一棵决策树进行学习),这样在随机森林中就有了很多个精通不同领域的专家,对一个新的问题(新的输入数据),可以用不同的角度去看待它,最终由各个专家,投票得到结果。

Rand forest与bagging的区别:1). Rand forest是选与输入样本的数目相同多的次数(可能一个样本会被选取多次,同时也会造成一些样本不会被选取到),而bagging一般选取比输入样本的数目少的样本;2). bagging是用全部特征来得到分类器,而rand forest是需要从全部特征中选取其中的一部分来训练得到分类器; 一般Rand forest效果比bagging效果好!