机器学习实战学习笔记(六)利用AdaBoost元算法提高分类性能

本文深入解析AdaBoost算法,介绍其工作原理,包括基于数据集多重抽样的分类器、Bagging和Boosting方法,以及如何使用单层决策树构建弱分类器。文章还探讨了非均衡分类问题,并提供了在复杂数据集上的AdaBoost应用案例。

PS:该系列数据都可以在图灵社区(点击此链接)中随书下载中下载(如下)
在这里插入图片描述

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

  我们可以将不同的分类器组合起来,而这种这结果则被称为集成方法(ensemble method) 或者元算法(meta-algorithm)。使用集成方法时会有多种形式:可以是不同算法的集成,也可以是同一算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。

                                                     AdaBoost
	优点:泛化错误率低,易编码,可以应用在发部分分类器上,无参数调整。
	缺点:对离群点敏感。
	适用数据类型:数值型和标称型数据。

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

  自然汇聚法(bootstrap aggregating),也称为bagging方法,是从原始数据集选择S次后得到S个新数据集的一种技术。在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们要对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果。

1.2 boosting

  boosting与bagging类似,即在两种中,所使用的多个分类器的类型是一致的。但是在boosting中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能进行训练。boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。
boosting分类的结果基于所有分类器的加权求和结果,bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等,每个权重代表的是其对应分类器上一轮迭代中的成功度。

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

  AdaBoost:使用弱分类器和多个实例构建一个强分类器。AdaBoost是adaptive boosting的缩写,其运行过程如下:训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量 D D D。一开始,这些权重都初始化成相等值。首先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练中,会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重会提高。为了从所有弱分类器中得到最终的分类结果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。其中,错误率 ε \varepsilon ε定义为:
ε = 未正确分类的样本数目 所有样本的数目 \varepsilon=\frac{\text {未正确分类的样本数目}}{\text{所有样本的数目}} ε=所有样本的数目未正确分类的样本数目
alpha计算公式: α = 1 2 l n ( 1 − ε ε ) \alpha=\frac{1}{2}ln(\frac{1-\varepsilon}{\varepsilon}) α=21ln(ε1ε)
  AdaBoost计算流程:
在这里插入图片描述
  计算出alpha之后,对权重向量 D D D进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。
  如果某个样本被正确分类,那么该样本的权重更改为:
D i ( t + 1 ) = D i ( t ) e − α Sum ⁡ ( D ) D_{i}^{(t+1)}=\frac{D_{i}^{(t)} \mathrm{e}^{-\alpha}}{\operatorname{Sum}(D)} Di(t+1)=Sum(D)Di(t)eα
  如果某个样本被错误分类,那么该样本的权重更改为:
D i ( t + 1 ) = D i ( t ) e α Sum ⁡ ( D ) D_{i}^{(t+1)}=\frac{D_{i}^{(t)} \mathrm{e}^{\alpha}}{\operatorname{Sum}(D)} Di(t+1)=Sum(D)Di(t)eα
  计算出D之后,AdaBoost又开始进入下一轮迭代。AdaBoost算法会不断地重复训练和调整权重的过程,直到训练错误率为0或者弱分类器的数目达到用户的指定值为止。

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

  单层决策树(decision stump,也称为决策树桩) 是一种简单的决策树,它仅基于单个特征来做决策。
  创建adaboost.py文件,编写代码:

import numpy as np
import matplotlib.pyplot as plt

def loadSimpData():
    datMat = 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 datMat, classLabels

def showDataSet(dataMat, labelMat):
    '''数据可视化'''
    dataPlus = []; dataMinus = []
    for i in range(len(dataMat)):
        if labelMat[i] > 0: 
            dataPlus.append(dataMat[i])
        else:
            dataMinus.append(dataMat[i])
    dataPlusArr = np.array(dataPlus)
    dataMinusArr = np.array(dataMinus)
    plt.scatter(np.transpose(dataPlusArr)[0], np.transpose(dataPlusArr)[1])
    plt.scatter(np.transpose(dataMinusArr)[0], np.transpose(dataMinusArr)[1])
    plt.show()

  在python命令行进行测试:
在这里插入图片描述
在这里插入图片描述
  由数据集示意图可以看出,从某个坐标轴上选择一个值(即选择一条与坐标轴平行的直线)将所有的蓝色点和橙色点分开是不可能的。单层决策树难以处理,所以通过使用多棵单层决策树,就可以构建出一个能够对该数据集完全正确分类的分类器。
  伪代码:

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

  向ababoost.py添加代码,保存重载该模块,并在python命令行进行测试:

def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    '''
    通过阈值比较对数据进行分类
    threshVal:阈值   threshIneq:标志
    '''
    retArray = np.ones((np.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 = np.mat(dataArr); labelMat = np.mat(classLabels).T
    m, n = np.shape(dataMatrix)
    numSteps = 10.0 
    #bestStump:最佳单层决策树信息 bestClassEst:最佳分类结果
    bestStump = {}; bestClassEst = 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 #计算步长
        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)))
                errArr[predictedVals == labelMat] = 0
                weightedError = D.T * errArr #加权错误率
                #print("split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error 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

在这里插入图片描述
  上述单层决策树的生成函数是决策树的一个简化版本。它就是所谓的弱学习器,即弱分类算法。

4 完整AdaBoost算法的实现

  伪代码:

对每次迭代:
	利用buildStump()函数找到最佳的单层决策树
	将最佳的单层决策树加入到单层决策树数组
	计算alpha
	计算新的权重向量D
	更新累计类别估计值
	如果错误率等于0.0,则退出循环
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    '''基于单层决策树的AdaBoost训练过程'''
    weakClassArr = []
    m = np.shape(dataArr)[0]
    #D:每个数据点的权重,初始时赋予相同的权重
    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))) #max保证不会有除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()
        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, "\n")
        if errorRate == 0.0:
            break
    return weakClassArr

在这里插入图片描述
  数据的类别标签为[1.0, 1.0, -1.0, -1.0, 1.0],在第一轮迭代中,D中的所有值都相等,于是只有第一个数据点被错分了。因此在第二轮迭代中,D向量给第一个点0.5的权重,迭代之后发现第一个数据点已经被正确分类,但此时最后一个数据点却是错分了。D向量中的最后一个元素变为0.5,而D向量中的其他值都变得非常小。最后,在第三次迭代之后aggClassEst所有值得符号和真实类别标签都完全吻合,那么训练错误率为0,程序就此退出。

5 测试算法:基于AdaBoost的分类

def adaClassify(datToClass, classifierArr):
    '''
    AdaBoost分类函数
    datToClass:一个或多个待分类样例 classifierArr:多个弱分类器组成的数组
    '''
    dataMatrix = np.mat(datToClass)
    m = np.shape(dataMatrix)[0]
    aggClassEst = np.mat(np.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 np.sign(aggClassEst)

在这里插入图片描述
  可以发现,随着迭代的进行,数据点[0, 0]和[5, 5]的分类结果越来越强。

6 在一个难数据集上应用AdaBoost

  将之前用于Logistic回归来预测患有疝病的马是否能够存活的数据horseColicTraining2和horseColicTest2.txt文件拷贝到adaboost.py所在的文件夹中:

def loadDataSet(fileName):
    numFeat = len(open(fileName).readline().split('\t'))
    dataMat = []; labelMat = []
    with open(fileName, 'r') as fileObject:
        for line in fileObject.readlines():
            lineArr = []
            curLine = line.strip().split('\t')
            for i in range(numFeat - 1):
                lineArr.append(float(curLine[i]))
            dataMat.append(lineArr)
            labelMat.append(float(curLine[-1]))
    return dataMat, labelMat

在这里插入图片描述
  要得到错误率,只需将上述错分样例的个数除以67即可。
  将弱分类器的数目设定为1到10000之间的几个不同数字,并运行上述过程。在该数据集上得到的错误率比较低,在同一数据集上采用Logistic回归得到的平均错误率为0.35。
在这里插入图片描述
  发现测试错误率在到达了一个最小值之后又开始上升了,这类现象称之为过拟合(overfitting,也称过学习)

7 非均衡分类问题

7.1 其他分类性能度量指标:正确率、召回率及ROC曲线

  二类问题混淆矩阵(confusion matrix)
在这里插入图片描述
  真正例(True Positive,TP,也称真阳)、真反例(True Negative,TN,也称真阴)、伪反例(False Negetive,FN,也称假阴)、伪正例(False Positive,FP,也称假阳)。
  正确率(Precision) T P / ( T P + F P ) TP/(TP+FP) TP/(TP+FP),预测为正例的样本中的真正正例的比例。
  召回率(Recall) T P / ( T P + F N ) TP/(TP+FN) TP/(TP+FN),预测为正例的真实正例占所有真实正例的比例。
  ROC曲线(ROC curve),ROC代表接收者操作特征(receiver operating characteristic),如下图就是马疝病检测系统的ROC曲线:
在这里插入图片描述
  ROC曲线中,横轴是伪正例的比例( 假 阳 率 = F P / ( F P + T N ) 假阳率=FP/(FP+TN) =FP/(FP+TN)),纵轴是真正例的比例( 真 阳 率 = T P / ( T P + F N ) 真阳率=TP/(TP+FN) =TP/(TP+FN))。ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。左下角的点对应的是将所有样例判为反例的情况,而右上角的点是将所有样例判为正例的情况。虚线给出的是随机猜测的结果曲线
  在理想的的情况下,最佳的分类器应该尽可能地处于左上角,这意味着分类器在假阳率很低的同时获得了很高的真阳率。例如在垃圾邮件的过滤中,这就相当于过滤了所有的垃圾邮件,但是没有将合法邮件误识为垃圾邮件而放入垃圾邮件的文件夹中。
  曲线下的面积(Area Under the Curve,AUC):AUC给出的是分类器的平均性能值,当然它并不能完全代替对整条曲线的观察。一个完美分类器的AUC为1.0,而随机猜测的AUC为0.5。

def plotROC(predStrengths, classLabels):
    '''
    绘制ROC曲线及计算AUC
    predStrengths:分类器的预测强度
    '''
    cur = (1.0, 1.0) #绘制光标的位置
    ySum = 0.0 #用于计算AUC
    numPosClass = sum(np.array(classLabels) == 1.0)
    yStep = 1 / float(numPosClass)
    xStep = 1 / float(len(classLabels) - numPosClass)
    #获取排序之后的索引
    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')
        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])
    plt.show()
    print("the Area Under the Curve is: ", ySum * xStep)

  按照predStrengths(分离器预测强度,即上述的aggClassEst变量)排序,predStrengths小的(负数)被判为反例,大的(正数)被判为正例。那么开始点在(1,1),随着predStrengths的增加,如果样例是正例(FN),那么 真 阳 率 = T P / ( T P + F N ) 真阳率=TP/(TP+FN) =TP/(TP+FN)就会下降, 假 阳 率 = F P / ( F P + T N ) 假阳率=FP/(FP+TN) =FP/(FP+TN)保持不变;相反如果样例是反例(TN),那么 假 阳 率 = F P / ( F P + T N ) 假阳率=FP/(FP+TN) =FP/(FP+TN)会下降, 真 阳 率 = T P / ( T P + F N ) 真阳率=TP/(TP+FN) =TP/(TP+FN)保持不变。
  为了了解实际运行结果。需要将adaboostTrainDS()的最后一行代码替换为:
  return weakClassArr, aggClassEst
以得到aggClassEst的值。然后在python命令行进行测试,即得到上述的ROC曲线,得到AUC≈0.8583:
在这里插入图片描述

7.2 基于代价函数的分类器决策控制

  代价敏感的学习(cost-sensitive learning)
在这里插入图片描述
  第一张表的分类代价计算公式: T P ∗ 0 + F N ∗ 1 + F P ∗ 1 + T N ∗ 0 TP*0+FN*1+FP*1+TN*0 TP0+FN1+FP1+TN0
  第二张表的分类代价计算公式: T P ∗ ( − 5 ) + F N ∗ 1 + F P ∗ 50 + T N ∗ 0 TP*(-5)+FN*1+FP*50+TN*0 TP(5)+FN1+FP50+TN0

7.3 处理非均衡问题的数据抽样方法

  欠抽样(undersampling) 或者过抽样(oversampling):过抽样意味着复制样例,而欠抽样意味着删除样例。

8 小结

  AdaBoost以若学习器作为基分类器,并且输入数据,使其通过权重向量进行加权。在第一次迭代中,所有数据同等权重。在后续的迭代中,前次得迭代中分错的数据的权重会增加。
  这里使用单层决策树作为若学习器构建了AdaBoost分类器,实际上任意分类器均可,只要分类器能够处理加权数据。
  非均衡分类问题是指在分类器训练时正例数目和反例数目不相等(相差很大),上述介绍了几种方法处理该问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值