机器学习算法原理与实践(四)、AdaBoost算法详解与实战

来源:互联网 发布:mac如何使用外置光驱 编辑:程序博客网 时间:2024/05/06 00:51


【原创】Liu_LongPo 转载请注明出处
【CSDN】http://blog.csdn.net/llp1992

AdaBoost算法是基于单层决策树等弱分类算法的强学习分类算法。单层决策树算法也是一种分类算法,但是其分类效果较差,只根据一个特征进行数据划分,因此单层决策树算法被称为弱分类算法;而AdaBoost算法通过将多个弱分类算法串行训练而成,属于强分类算法。

AdaBoost算法是boosting算法的一种,它所串联的弱分类器一般都是一致的,而且它训练是的关注点在于被之前分类器分错的数据,通过调整每个样本数据的权重 D 以及每个分类器的权重 a 来进行强分类器的训练。

优缺点

优点:泛化错误率低,可应用在大部分分类器上,无需调整参数
缺点:对离群点敏感

算法原理

AdaBoost是adaptive boosting的缩写,运行过程如下:

1.训练数据中的每个样本刚开始被赋予一个相等的权重,这些权重构成向量 D
2.在训练集上训练出一个弱分类器并计算该分类器的错误率 ϵ
3.根据分类器的错误率可以计算出该分类器的权重 a=12ln(1ϵϵ)
4.根据上一次分类器的权重 a 调整每个训练样本的权重,分对的样本权重降低,分错的样本权重升高,然后在同一训练集上再次训练弱分类器
5.训练直到错误率为0或者弱分类器的数目达到用户指定值为止

上述过程中错误率 ϵ 的计算方法如下:

ϵ=

分类器的权重的计算方法如下:

a=12ln(1ϵϵ)

正确分类样本权重更新方法如下:

D(t+1)i=D(t)ieaSum(D)

错误分类样本权重更新方法如下:
D(t+1)i=D(t)ieaSum(D)

算法流程图


如上图:左边第一列是样本数据,直方图的不同长度代表不同样本的权重D,三角形中的数字就是每个分类器的权重a
当我们训练得到模型后,直接将数据乘以权重代入分类器,然后再将分类器加权求和进行分类,就得到我们的预测结果

关于样本权重以及分类器权重:

上面我们说到,AdaBoost算法关注点在于被已有分类器分错的那些数据,而数据的权重的更新方法是:分对的数据的权重降低,分错的数据权重升高。这是因为我们算法主要就是找到 误差最小的分类器,分错的数据显然是比较难区分的点,加大权重进行训练有利于我们找到性能更好的分类器对其进行划分,从而减少总的误差。

而分类效果好的分类器,由上面的权重a的更新公式可以看出, a 跟误差 ϵ 是负相关关系,也就是说,分类误差 ϵ 越小的分类器权重越高,这也很容易理解,就是为了让所有分类器求和之后得到的分类效果更好。

实例详解

该实例的图片来自百度某大牛,链接好像失效了。文字描述是自己对算法的理解。


如上图,假如我们现在有数据集,+号和-号分别代表两种不同的数据集,总共10个数据点。样本的权重 D1 此时是相等的,为 0.1 ,也就是 1/10 。运用AdaBoost算法时,我们首先使用水平或者垂直的直线作为分类器(默认使用决策树算法作为弱分类器)来对数据进行划分。

第一次迭代,也就是第一个分类器:


显然,有3个样本被分错了,根据上面的误差公式,我们可以得到误差 ϵ=310=0.3
同样的,根据分类器的权重公式可以得到 a1=0.5×ln(10.30.3)=0.42
同样的,此时每个样本的权重 D也可以根据公式计算,由于数据比较多,这里就不计算啦。样本权重更新之后如下:


图中被分错的3个数据点被标大了,表示它们的权重被加大了。

第二次迭代,也就是第二个分类器:


由上图可以知道,有3个点分错了,同样的,样本权重ϵ2和分类器权重a2可以由公式计算得到。这次迭代之后的样本权重更新如下:


第三次迭代,也就是第三个分类器:


每一次迭代之后,分类器的预测结果都是由前面所有的分类器的分类结果与分类器的权重 a 相乘累加得到,如果累加得到的分类结果误差为0,或者是分类器的数目达到用户指定的值,那么循环退出。

这里假设我们3个分类器就可以达到分类误差为0的效果。整合所有的分类器,也就是加权求和,最终得到的结果如下:


由图可以知道,此时的分类效果较好。

Python代码实现

接下来用Python代码实现AdaBoost算法并进行测试。

首先加载数据集,如下:

def loadSimpData():    dataMat = array([[1.,2.1],[1.5,1.6],[1.3,1.],[1.,1.],[2.,1.],[1.2,1.1],\    [1.1,0.4],[0.9,1.3],[0.86,1.2],[1.8,1.8],[1.7,1.5],[1.9,1.8]])    classLabels = array([1.0,1.0,-1.0,-1.0,1.0,-1.0,-1.0,-1.0,\    -1.0,1.0,1.0,1.0])    return dataMat,classLabels

如图:


我们要做的就是将这两个不同的数据集分开。

首先我们建立单层决策树分类器,代码如下:

from numpy import *# 该函数根据某个最好的特征的最好划分点对数据进行分类# demen就是特征,threshVal就是划分点,retArray就是返回的分类结果def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):    retArray = ones((shape(dataMatrix)[0],1))    if threshIneq == 'lt':        retArray[dataMatrix[:,dimen]<=threshVal] = -1.0    else:        retArray[dataMatrix[:,dimen]>threshVal] = -1.0    return retArray

然后建立树桩,用于从数据中得到最好的分类特征和该特征最好的划分点

# 创建树桩,返回最好的树桩和该树桩的分类最小误差以及分类结果# 该函数用于从数据集中找到最好的划分特征以及该特征的最好划分点def buildStump(dataArr,classLabels,D):    dataMatrix = mat(dataArr);    labelMat = mat(classLabels).T    # m 是行,也就是每个样本,n是列,也就是每个特征    m,n = shape(dataMatrix)    # 步数的设置,也就是在每个特征的最大值和最小值中分几次设置阈值进行数据划分    # 也就是获取这个特征的最佳划分点,步数越高,特征点寻找得越精细,但耗时更多    # 用字典来存储bestStump的数据    numSteps = 10.0;bestStump = {};bestClassEst = 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)))                errArr[predictedVals == labelMat] = 0                # 样本权重乘以样本误差                weightedError = D.T * errArr                print "split:dim %d,thresh %.2f, thresh ineqal: %s,the weighted eror is %.3f" %\                (i,threshVal,inequal,weightedError)                if weightedError < minError:                    minError = weightedError                    # 最好的分类结果                    bestClassEst = predictedVals.copy()                    bestStump['dim'] = i                    bestStump['thresh'] = threshVal                    bestStump['ineq'] = inequal    return bestStump,minError,bestClassEst

然后再编写AdaBoost训练函数,该函数返回值为弱分类器的组合

def adaBoostTrainDS(dataArr,classLabels,numIt = 40):    weakClassArr = []    m = shape(dataArr)[0]    D = mat(ones((m,1))/m)    # 创建矩阵 mat    aggClassEst = mat(zeros((m,1)))    for i in range(numIt):        bestStump,error,classEst = buildStump(dataArr,classLabels,D)        print 'D:',D.T        #  a = 0.5*ln((1-e)/e)        alpha = float(0.5*log((1.0-error)/max(error,1e-16)))        bestStump['alpha'] = alpha        # 将当前的最好的树桩添加到弱分类其数组中        weakClassArr.append(bestStump)        print 'classEst:',classEst.T        # D权重的更新公式,利用原本的类别classLabels与划分的类别classEst做乘积        # 用来同时计算正确划分和错误划分的公式,也就是自动确定正负号        expon = multiply(-1*alpha*mat(classLabels).T,classEst)        # 更新权重D        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

最后一个函数是用训练得到的多个分类器进行级联数据分类

    # 利用学习得到的多个级联弱分类器进行数据分类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    return sign(aggClassEst)

测试代码

# -*- coding: utf-8 -*-"""Created on Thu Jun 11 12:57:27 2015AdaBoost@author: LiuLongpo"""import adaboostimport matplotlibimport matplotlib.pyplot as pltfrom numpy import *dataMat,classLabels = adaboost.loadSimpData()# D是样本的权重矩阵D = mat(ones((5,1))/5)#adaboost.buildStump(dataMat,classLabels,D)print 'data train...'classifierArr = adaboost.adaBoostTrainDS(dataMat,classLabels,30)print 'getClassifier:',classifierArrprint 'data predict...'# 学习得到3个分类器,predict时,每一个分类器级联分类得到的预测累加值 # aggClassEst越来越远离0,也就是正越大或负越大,也就是分类结果越来越强adaboost.adaClassify([[1,0.8],[1.8,2]],classifierArr)# 0,lt,1.3   1,lt,1.0   0,lt,0.9plt.figure()I = nonzero(classLabels>0)[0]plt.scatter(dataMat[I,0],dataMat[I,1],s=60,c=u'r',marker=u'o')I = nonzero(classLabels<0)[0]plt.scatter(dataMat[I,0],dataMat[I,1],s=60,c=u'b',marker=u'o')plt.plot([1.32,1.32],[0.5,2.5])plt.plot([0.5,2.5],[1.42,1.42])plt.plot([0.97,0.97],[0.5,2.5])

训练完数据后,我们得到的分类器总数为3,如下:

[{‘dim’: 0, ‘ineq’: ‘lt’, ‘thresh’: 1.3160000000000001, ‘alpha’: 1.1989476363991853}, {‘dim’: 1, ‘ineq’: ‘lt’, ‘thresh’: 1.4199999999999999, ‘alpha’: 1.5222612188617115}, {‘dim’: 0, ‘ineq’: ‘lt’, ‘thresh’: 0.97399999999999998, ‘alpha’: 1.1256458993032477}]

第一个分类器是对0维,也就是x轴的特征进行划分,划分的点为:1.3160,分类器的阈值 a 为:1.1989,不等号是:lt,小于
第二个分类器是对1维,也就是y轴的特征进行划分,划分的点为:1.4199,分类器的阈值 a 为:1.5222,不等号是:lt,小于
第三个分类器是对0维,也就是x轴的特征进行划分,划分的点为:0.9739,分类器的阈值 a 为:1.1256,不等号是:lt,小于

画出来如下图:


这三个分类器串联起来训练时误差为0,接下来我们来分析上图,看看它是怎么个分类法。

回到这个函数 stumpClassify(). 中间代码如下:

 retArray = ones((shape(dataMatrix)[0],1)) if threshIneq == 'lt':     retArray[dataMatrix[:,dimen]<=threshVal] = -1.0 else:     retArray[dataMatrix[:,dimen]>threshVal] = -1.0

以及这个函数:buildStump(),中间有代码如下:

          for inequal in ['lt','gt']:                threshVal = (rangeMin + float(j) * stepSize)                # 预测值                predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal)                errArr = mat(ones((m,1)))                errArr[predictedVals == labelMat] = 0                # 样本权重乘以样本误差                weightedError = D.T * errArr                print "split:dim %d,thresh %.2f, thresh ineqal: %s,the weighted eror is %.3f" %\                (i,threshVal,inequal,weightedError)                if weightedError < minError:                    minError = weightedError                    # 最好的分类结果                    bestClassEst = predictedVals.copy()                    bestStump['dim'] = i                    bestStump['thresh'] = threshVal                    bestStump['ineq'] = inequal

我们在代码中对于有不等号 lt 和 gt ,这两个的作用就是用来对划分的阈值后进行分类的,我们初始化数据时是分为+1和-1两类,所以在stumpClassify()函数中,首先让返回矩阵初始化为全1矩阵,如果当前是lt,也就是小于号,则让所有当前维的特征中小于等于阈值的数据样本全部划分为-1的类,如果是gt,也就是大于号,则让当前维的特征中大于阈值的所有数据点全部划分为-1的类。这就完成了根据某个特征的某一个划分点对数据进行分类的功能。

这个函数buildStump()中就是不断在每个特征的每个划分点的大于号和小于号中寻找分类误差最小的划分方法。

由上面知道,我们的3个分类器都是lt,我们看第一个分类器,也就四对应图上的蓝色直线(数据集中红色点为+1类,蓝色点为-1类),lt表示小于,也就是说,蓝色线左边的点被划分-1类,我们看到,有一个红色点被分错了,该分类器的阈值为:1.1989。可以看到,此时如果单独由这个分类器来划分数据是有误差的。

接着看第二个分类器,是对y轴进行划分,也就是绿色线,也是lt,也就是说,绿色线下方的就是被划分为-1类,我们看到,也是有1个红色点被分错了,该分类器的阈值为:1.5222,可以看到,如果结合第一和第二两个分类器来划分数据,也是有一个点划分错误,为右下角的红点。

接着看第三个分类器,是对x轴进行划分,也就是红色线,也是lt,也就是说,红色线左方的被划分为-1类,也就是蓝色类,咋一看,好像有好几个点分错啊,怎么可能!我们要知道,AdaBoost算法是多个弱分类器算法进行串联划分数据的,所以允许单个弱分类器出现划分错误,但是如果我们结合这三个分类器来看的话,数据集刚好被无误差划分好,如下:


红色圆圈中的区域就是红色线右边,绿色线上边和蓝色线右边三者的合集,完美划分了数据。

数据测试

我们使用学习到的分类器来对数据进行划分,假如有两个点:[1,0.8],[1.8,2],我们对其进行划分,代码上面有,过程如下:
data predict…
[[-1.19894764]
[ 1.19894764]]
[[-2.72120886]
[ 2.72120886]]
[[-1.59556296]
[ 3.84685475]]

可以看到,第一个点被划分为-1类,第二个点被划分为+1类,而且从第一个分类器到第三个分类器,第一个点的分类结果越来越小于0,第二个点的分类结果越来越大于0,也就是分类结果越来越强。

最后一个问题,对于简单,量比较小的数据集,AdaBoost算法是可以让训练错误率为0的。但是真实情况并不是这样,很多真实数据一般都是比较大,比较难划分的。

这时候,AdaBoost算法的弱分类器的个数就是一个要考虑的问题。分类器多,算法学习得比较充分。但跟其他算法一样,并不是学习得越充分越好,太多的弱分类器会导致学习过拟合。在难数据集上,AdaBoost算法在某个数目弱分类器上会达到一个稳定的测试错误率,也就是最小的错误率,如果分类器的数目多于这个值或者是小于这个值,分类器的分类性能都会降低。

写得比较多,为的是尽可能容易看懂。

5 1
原创粉丝点击