机器学习实战—利用AdaBoost元算法提高分类性能

本文介绍了AdaBoost这一机器学习中的元算法,通过结合多个弱分类器(如单层决策树)来构建强分类器。AdaBoost利用数据重抽样(bagging和boosting)来提升分类性能,特别是通过boosting的加权训练过程,使得弱分类器逐步聚焦于错误分类的样本。文章详细阐述了AdaBoost的训练过程,包括样本权重的动态调整和单层决策树的最佳构建方法,并讨论了在非均衡分类问题中的应用。

元算法:是对其他算法进行组合的一种方式。

一、基于数据集多重抽样的分类器

将不同的分类器组合起来,这种组合结果被称为集成方法或者元算法。

集成方法的形式:
1.不同算法的集成;
2.同一算法在不同设置下的集成;
3.数据集不同部分分配给不同分类器之后的集成;
本文介绍的两种集成算法都是基于同一分类器多个不同样本实例的方法。

1.1 bagging:基于数据随机重抽样的分类器构建方法

也称为自举汇聚法,是从原始数据集选择S次后得到的S个数据集的一种技术,新数据集和原始数据集大小相等。允许新数据集中可以有重复值,而原始数据集中的某些值在新数据集合中则不会出现。
S个数据集建好之后,将S个数据集分别输入到S某个学习算法中就得到了S个弱分类器,最终集成的分类器结果由投票机制对多个弱分类器所得结果进行分类。

1.2 boosting

boosting和bagging类似,两者使用的多个弱分类器的类型都是一致的,但是boosting的不同弱分器是通过串行训练得到的,每个弱分类器都根据已经训练出的分类器的性能来进行训练,这里的性能指的是弱分类器的预测结果的加权值,以此实现各个弱分类器与强分类器之间的连通。boosting算法关注的是被以训练好的弱分类器分错的样本实例,在此基础上获得串行分类器数组中的下一分类器。boosting集成后的强分类器结果是由各个弱分类器分类结果的加权和,所以boosting和bagging其实是有明显的不同点的。

boosting和bagging的不同:
1.bagging中各个弱分类器的权重值alpha是相等的;但是boosting中各个弱分类器的权重值alpha是不相等的,错误率低的弱分类器其对最终结果的影响程度大(该弱分类器的权重值alpha值较大)。
2.bagging集成算法的最终分类结果是对各个弱分类器所得的分类结果投票选出分类可能性大的分类;而boosting集成方法的最终分类结果是对于每个弱分类器分类结果的加权和。

二、训练算法:基于错误提升分类器的性能

使用多个弱分类器和多个样本实例构建一个强分类器,弱分类器一般指其分类效果仅仅比随机分类好一点点的分类器,而强分类器的分类效果则要好很多,其错误率会很低。

在正式介绍AdaBoost算法前,先说明以下在AdaBoost中使用的弱分类器,也就是单层决策树,每个决策树节点就是根据数据集中的一个特征的一个阈值进行分类,所以决策树节点分类器很显然是一个二分类器,在AdaBoost中我们使用的弱分类器就是这样的单节点决策树分类器,通过串行训练出多个分类器,这些弱分类器逻辑上处于同一层,通过不同的alpha值集成为一个强分类器,其形式如下:
这里写图片描述
注:图中输入样本的不同长短代表了该样本的不同权重。

AdaBoost是boosting方法中较为流行的一个版本,其运行过程如下:
1.初始情况下对训练集中的每个样本赋予相同的权重值,这些权重值构成了样本权重向量D(假设样本数量为n,则初始向量D中的每一个值都是1/n)。
2.将这些数据应用在一个分类算法上(这里为单层决策树算法),获得第一个弱分类器,并计算该弱分类器的错误率alpha。计算公式如下:
这里写图片描述
其中的错误率可通过以下公式计算得到:
这里写图片描述
3.第一个弱分类器训练结束后,需要迭代的训练后续弱分类器,由于后面的分类器是基于已经训练出的分类器的性能来进行训练的,所以此处需要重新调整每个样本的权重,将弱分类器分错的样本权重增大,将分类正确的样本权重降低。样本权重向量D更新公式为:
这里写图片描述
其中h(x)表示分类器算法函数,其值也就是该分类其的预测结果,yi是该样本实例的真实标签值,所以该公式表明当分类器对样本分类正确时对其样本权重值进行降低,而在分类器对样本分类错误时对该样本权重值进行增加。
4.1~3步骤是在一定的迭代周期中循环进行的,当迭代停止,算法训练出n个弱分类器,利用集成方法获得集成分类器对预测样本的最终分类(最终分类结果为各个弱分类器分类结果的权重和,这个和是个浮点数,通过sign()函数获得其分类值)。

三、基于单层决策树构建弱分类器

单层决策树,就是仅仅基于单个特征来做决策。通过使用多个单层决策树来构建一个能够对数据进行完全正确分类的分类器。

首先,创建一个简单数据集:

import numpy as np
import matplotlib.pyplot as plt

#创建数据集和标签集函数
def simpleDataLoad():
    dataMat = np.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 showDataSet(dataMat, labelMat):
    #定义正样本、负样本矩阵
    data_plus = []
    data_minus = []
    #遍历整个样本集
    for i in range(len(dataMat)):
        if labelMat[i] > 0:
            data_plus.append(dataMat[i])
        else:
            data_minus.append(dataMat[i])
    #将数据转换为numpy.array形式
    data_plus_np = np.array(data_plus)
    data_minus_np = np.array(data_minus)
    #画散点图
    plt.scatter(np.transpose(data_plus_np)[0],np.transpose(data_plus_np)[1])
    plt.scatter(np.transpose(data_minus_np)[0],np.transpose(data_minus_np)[1])
    plt.show()

创建最佳单层年决策树的过程:
1.将最小错误率minError设为无穷大
2.对数据集中的每一个特征(第一层循环):
对每一个步长(第二层循环):
对每个不等号(第三层循环):
建立一棵单层决策树并利用加权数据对它进行测试
如果错误率低于minError,则将当前单层决策树设为最佳决策树
返回最佳决策树

注:此处创建最佳决策树的过程实际上是对于当前要创建的分类器寻找一种最佳的分类算法,寻找该分类算法需要确定该算法基于数据集的哪个特征,对于该特征值的划分阈值是是什么,阈值划分的逻辑标志符号是什么。所以需要在数据集上的每个特征中遍历,在固定特征下对于该列特征的所有值进行遍历,在固定阈值的情况下,对于逻辑标志符号的确定。上述过程中的三层循环就是用来确定弱分器的最佳分类特征,在该特征下的最优阈值和逻辑标志符号,这三个参数固定了,则此次迭代所要创建的弱分类器也就实现了,弱分类器的存储使用字典这种数据结构。

单层决策树生成函数:

#单层决策树分类函数,根据数据集中的一个特征中设定的阈值进行分类
def stumpClassify(dataMat, dimen, threshVal, threshIneq):
    retArray = np.ones((np.shape(dataMat)[0],1))
    if threshIneq == 'lt':
        retArray[dataMat[:,dimen] <= threshVal] = -1.0
    else:
        retArray[dataMat[:,dimen] > threshVal] = -1.0
    #返回以此列作为输入、以阈值判断作为输出的结果
    return retArray

#找到数据集上最佳的单层决策树
def buildStump(dataArr, classLabels, D):
    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    m, n = np.shape(dataMatrix)
    numSteps = 10.0
    #最佳单层决策树信息
    bestStump = {}
    #最佳分类结果
    bestClsEst = np.mat(np.zeros((m, 1)))
    #最小误差
    minError = float('inf')
    #遍历所有特征
    for i in range(n):
        #获取当前列的最小值和最大值
        rangeMin = dataMatrix[:,i].min()
        rangeMax = dataMatrix[:,i].max()
        #计算步长
        stepSize = (rangeMax - rangeMin) / numSteps
        #使得2后面阈值可以取得在此列特征中的最大值和最小范围内的任意一个正数
        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 = np.mat(np.ones((m,1)))
                #预测值与实际标签值一致的样本误差为0
                errArr[predictedVals==labelMat] = 0
                #计算权重误差和
                weightError = D.T * errArr
                # print("split:dim %d,thresh %.2f,thresh ineqal:%s,the weighted error is %.3f"%(i,threshVal,inequal,weightError))
                #找到误差最小的分类方式
                if weightError < minError:
                    minError = weightError
                    bestClsEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump, minError, bestClsEst

四、完整AdaBoost算法的实现

实现的过程:
对于每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳单层决策树加入单层决策树数组中
计算alpha
计算新的样本权重向量D
更新累计类别估计值
如过错误率等于0.0,则停止迭代

基于单层决策树的AdaBoost训练过程

#基于单层决策树的AdaBoost训练过程
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
    #定义弱分类器数组
    weakClassArr = []
    m = np.shape(dataArr)[0]
    #初始化样本权重向量
    D = np.mat(np.ones((m,1))/m)
    aggClassEst = np.mat(np.zeros((m,1)))
    #开始迭代
    for i in range(numIt):
        #训练出一个最佳弱分类器
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        # print("D:",D.T)
        #计算此分类器的权重
        alpha = float(0.5 * np.log((1.0 - error)/max(error,1e-16)))#此处是为了防止除数为0的情况发生
        #将分类器权重加入分类器信息中
        bestStump['alpha'] = alpha
        #将此分类器加入分类器数组中
        weakClassArr.append(bestStump)
        # print("classEst:",classEst.T)
        #更新样本权重向量
        expon = np.multiply(-1*alpha*np.mat(classLabels).T,classEst)
        D = np.multiply(D,np.exp(expon))
        D = D/D.sum()
        #计算adaBoost误差,当误差为0的时候,推出循环
        aggClassEst += alpha*classEst
        # print("aggClassEst:",aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst)!=np.mat(classLabels).T,np.ones((m,1)))
        errorRate = aggErrors.sum()/m
        # print("total error:",errorRate)
        #如果整合之后的强分类器错误率为0,则停止迭代;否则就继续下一次迭代
        if errorRate==0:
            break
    return weakClassArr,aggClassEst

注:单层决策树是AdaBoost中最流行的弱分类器类型,但是这并不是唯一的类型,弱分类器也可以是其他类型。

五、测试算法:基于AdaBoost的分类

AdaBoost分类函数:

对测试集进行测试的函数
def adaClassfy(dataToClass,classfierArr):
    dataMatrix = np.mat(dataToClass)
    #获取样本数量
    m = np.shape(dataMatrix)[0]
    aggClassEst = np.zeros((m,1))
    #遍历每个弱分器
    for i in range(len(classfierArr)):
        #获得当前分类器的分类结果
        classEst = stumpClassify(dataMatrix,classfierArr[i]['dim'],classfierArr[i]['thresh'],classfierArr[i]['ineq'])
        #最终强分类器的分类结果是每个若分类器的权重分类之和
        aggClassEst += classfierArr[i]['alpha']*classEst
        print(aggClassEst)
    #返回强分类器的二值分类结果
    return np.sign(aggClassEst)

六、示例:在一个难数据集上应用AdaBoost
这里使用马疝病数据集应用AdaBoost分类器。此数据集可在网上查找下载
数据集文件内容如下:
这里写图片描述

这里写图片描述

注:其分类标签为1或-1

自适应数据加载函数:

#示例应用的数据加载函数
def loadDataSet(filename):
    #获取数据集中的特征个数
    numFeat = len((open(filename).readline().split('\t')))
    dataMat = []
    labelsMat = []
    for line in open(filename).readlines():
        #对每一行样本去除空格并且以制表符分割
        curLine = line.strip().split('\t')
        #给数据特征集赋值
        lineArr = []
        #一行中除了最后一个值为标签,前面的值都是特征
        for i in range(numFeat-1):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
        labelsMat.append(float(curLine[-1]))
    return dataMat,labelsMat

注:以上函数均在main函数中调用,main函数如下,注释掉的语句为各个函数的测试语句。

if __name__ == "__main__":
    # dataArr, classLabels = simpleDataLoad()
    dataArr, classLabels = loadDataSet('horseColicTraining2.txt')
    # showDataSet(dataArr, classLabels)
    #初始化样本权重向量
    # D = np.mat(np.ones((5,1))/5)
    # bestStump, minError, bestClsEst = buildStump(dataArr,classLabels,D)
    # print("bestStump:\n",bestStump)
    # print("minError:\n",minError)
    # print("bestClasEst:\n",bestClsEst)
    #只返回函数返回值列表的第一个元素
    weakClassArr,aggClassEst = adaBoostTrainDS(dataArr,classLabels,10)
    # print("classArr[0]:\n",weakClassArr[0])
    #对测试集进行测试
    # testDataArr,testLabels = loadDataSet('horseColicTest2.txt')
    #迭代十次,即训练了10个弱分类器
    # prediction10 = adaClassfy(testDataArr,weakClassArr)
    # errArr = np.mat(np.ones((67,1)))
    # errNum = errArr[prediction10!=np.mat(testLabels).T].sum()
    # print("errNum:",errNum)
    # print(weakClassArr)
    # print(aggClassEst)
    plotROC(aggClassEst.T,classLabels)

七、非均衡分类问题
之前我们都假设认为所有的类别代价是一样的,但是在大多情况下,不同类别的分类的分类代价并不相等。
有关非均衡分类问题的阐述,有一篇文章讲的很详细,文章对AdaBoost算法也进行了非常透彻的解析,其链接如下:
https://zhuanlan.zhihu.com/p/30035094
ROC曲线的绘制及AUC计算函数:

def plotROC(predStrengths, classLabels):
    import matplotlib.pyplot as plt
    cur = (1.0, 1.0)                                                         #绘制光标的位置
    ySum = 0.0                                                                 #用于计算AUC
    numPosClas = np.sum(np.array(classLabels) == 1.0)                        #统计正类的数量
    yStep = 1 / float(numPosClas)                                             #y轴步长
    xStep = 1 / float(len(classLabels) - numPosClas)                         #x轴步长
    sortedIndicies = predStrengths.argsort()                                 #预测强度排序
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    for index in sortedIndicies.tolist()[0]:
        if classLabels[index] == 1.0:
            delX = 0; delY = yStep
        else:
            delX = xStep; delY = 0
            ySum += cur[1]                                                     #高度累加
        ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c = 'b')     #绘制ROC
        cur = (cur[0] - delX, cur[1] - delY)                                 #更新绘制光标的位置
    ax.plot([0,1], [0,1], 'b--')
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('ROC curve for AdaBoost Horse Colic Detection System')
    ax.axis([0, 1, 0, 1])
    print('AUC面积为:', ySum * xStep)                                         #计算AUC
    plt.show()

另外一种针对非均衡问题调节分类器的方法,就是对分类器的训练数据进行改造。这可以通过欠抽样或者过抽样来实现。过抽样以为着复制样本,而欠抽样以为着删除样本。

总结:
AdaBoost优点:泛化错误率低,易编码,可以应用在大部分分类器上,无参数调整;
AdaBoost缺点:对离群点敏感。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值