《统计学习方法》第八章提升方法及python实现

本文结合《统计学习方法》与《机器学习实战》,详细介绍了Adaboost算法原理及提升树方法,并提供了Python实现代码。

一、写在前面

本文是《统计学习方法》第8章提升方法的笔记,整合了《机器学习实战》中的提升树Python代码。《方法》重理论,但不易理解,《实战》重实践,但缺乏理论基础,特别是AdaBoost算法的解释、提升树与加法模型的关系等。两相结合,应该能获得较为全面的知识。本文是第八章,往后翻了翻,《方法》还有十四章。路漫漫其修远兮,年轻人骑猪仔不能放弃。

二、提升方法

提升方法是从弱学习算法出发,反复学习,得到一系列弱分类器(又称为基本分类器),然后组合这些弱分类器,构成一个强分类器。大多数的提升方法都是改变训练数据的概率分布(训练数据的权值分布),针对不同的训练数据分布调用弱学习算法学习的一系列弱分类器。
对于提升学习中的Adaboost算法,选择提高那些被前一轮弱分类器错误分类的样本的权值,而降低那些被正确分类的样本的权值。这样一来,那些没有得到正确分类的数据,由于其权值加大而受到后一轮的弱分类器的更大关注。于是,分类问题被一系列弱分类器“分而治之”。至于弱分类器的组合,Adaboost采用加权多数表决的方法。具体地,加大分类误差率小的弱分类器的权值,使其在表决中起较大的作用;减小分类误差率大的弱分类器的权值,使其在表决中起较小的作用。
算法流程:
输入:训练数据集、弱学习算法;
输出:最终分类器G(x)G(x)G(x)
(1)初始化训练数据的权值分布:
在这里插入图片描述
每个w的下标由两部分构成,前一个数表示当前迭代次数,与D的下标保持一致,后一个数表示第几个权值,与位置保持一致。初始情况下,每个权值都是均等的。
(2)
(a)使用具有权值分布的训练数据集学习,得到基本分类器
(b)计算G(x)G(x)G(x)在训练数据集上的分类误差率:
在这里插入图片描述
(c)计算分类器的系数:
在这里插入图片描述
这里的对数是自然对数,计算的是该分类器的权值系数。分类误差率越小,分类器权重越大。为什么一定要用这个式子呢?这与前向分步算法的推导有关,在后面的章节会介绍。
(d)更新样本权重:
在这里插入图片描述
对于上式的理解:在上一轮中被正确分类的样本,将按一定尺度削弱它下一轮的权重;在上一轮中被错误分类的样本,将按一定尺度加强它下一轮的权重;且当前分类器的误分率越低、权重越大,那么这个尺度也越大。
ZmZ_mZm是规范化因子:
在这里插入图片描述
(3)构建基本分类器的线性组合
在这里插入图片描述
这里,分类器权重之和并不为1.
得到最终分类器:
在这里插入图片描述
Adaboost具有适应性,即它能适应弱分类器各自的训练误差率,这也是它名称的由来,Ada是Adaptive的简写。

三、提升树

提升树是以分类树或回归树为基本分类器的提升方法。提升树被认为是统计学习中性能最好的方法之一。
提升树算法采用前向分步算法。首先确定初始提升树,然后第m歩的模型是:
在这里插入图片描述
其中:fm−1(x)f_{m-1}(x)fm1(x)是前一轮的弱分类器。通过经验风险极小化确定下一棵决策树的参数θ\thetaθ
在这里插入图片描述
由于树的线性组合可以很好地拟合训练数据,即使数据中的输入与输出之间的关系很复杂也是如此,所以提升树是一个髙功能的学习算法。

不同问题有大同小异的提升树学习算法,其主要区别在于使用的损失函数不同。包括用平方误差损失函数的回归问题,用指数损失函数的分类问题,以及用一般损失函数的一般决策问题。

对于二类分类问题,提升树算法只需将AdaBoost算法中的基本分类器限制为二类分类树即可,可以说这时的提升树算法是AdaBoost算法的特殊情况,接下来通过《机器学习实战》中的代码学习其应用。

四、python实现二分类Adaboost算法

测试数据;

def loadSimpData():
    """
加载简单数据集
    :return:
    """
    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 plotData(datMat, classLabels):
    xcord0 = []
    ycord0 = []
    xcord1 = []
    ycord1 = []
 
    for i in range(len(classLabels)):
        if classLabels[i]==1.0:
            xcord1.append(datMat[i,0]), ycord1.append(datMat[i,1])
        else:
            xcord0.append(datMat[i,0]), ycord0.append(datMat[i,1])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord0,ycord0, marker='s', s=90)
    ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
    plt.title('decision stump test data')
    plt.show()
dataMat,classLabels=loadSimpData()
plotData(dataMat,classLabels)

调用:

dataMat,classLabels=loadSimpData()
plotData(dataMat,classLabels)

单层决策树分类:单层决策树分类就是一个树桩,传入数据和分裂点,输出分类结果。

def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):  # just classify the data
    """
用只有一层的树桩决策树对数据进行分类
    :param dataMatrix: 数据
    :param dimen: 特征的下标
    :param threshVal: 阈值
    :param threshIneq: 大于或小于
    :return: 分类结果
    """
    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=shape(dataMatrix)
    numStep=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)/numStep
        for j in range(-1,int(numStep)+1):
            for inequal in  ['lt','gt']:
                threshVal=(rangeMin+float(j)*stepSize)
                predicteVals=stumpClassify(dataMatrix,i,threshVal,inequal)
                errArr=mat(ones(((m,1))))
                errArr[predicteVals==labelMat]=0
                weightedError=D.T*errArr
                if weightedError<minError:
                    minError=weightedError
                    bestClasEst=predicteVals.copy()
                    bestStump['dim']=i
                    bestStump['thresh']=threshVal
                    bestStump['ineq']=inequal
    return  bestStump,minError,bestClasEst

AdaBoost训练:有了上面的基础,就不难看懂完整的训练代码了。

def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    """
基于单层决策树的ada训练
    :param dataArr: 样本特征矩阵
    :param classLabels: 样本分类向量
    :param numIt: 迭代次数
    :return: 一系列弱分类器及其权重,样本分类结果
    """
    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)  # 构建树桩决策树,这是一个若分类器,只能利用一个维度做决策
        # print "D:",D.T
        alpha = float(
            0.5 * log((1.0 - error) / max(error, 1e-16)))  # 计算 alpha, 防止发生除零错误
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)  # 保存树桩决策树
        # print "classEst: ",classEst.T
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)  # 每个样本对应的指数,当预测值等于y的时候,恰好为-alpha,否则为alpha
        D = multiply(D, exp(expon))  # 计算下一个迭代的D向量
        D = D / D.sum() # 归一化
        # 计算所有分类器的误差,如果为0则终止训练
        aggClassEst += alpha * classEst
        # print "aggClassEst: ",aggClassEst.T
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1))) # aggClassEst每个元素的符号代表分类结果,如果与y不等则表示错误
        errorRate = aggErrors.sum() / m
        print("total error: ", errorRate)
        if errorRate == 0.0: break
    return weakClassArr, aggClassEst

上面的流程在理论部分已有详细解释,可以参照理论部分的公式对应着看。
下面是运用提升树进行分类:

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)
datArr, labelArr = loadSimpData()
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 30)
print(adaClassify([1, 1], classifierArr))

输出:

total error:  0.2
total error:  0.2
total error:  0.0
[[-0.69314718]]
[[-1.66610226]]
[[-0.77022252]]
[[-1.]]

可以看到,分类过程中,随着加法模型的不断叠加,对于(1,1)这个点,其累加结果是“负”得越来越厉害的,最终取符号输出-1类别。而在训练集中,真实数据确实属于-1,可见adaboost算法是有效的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值