《机器学习实战》学习笔记(六)

第七章 利用AdaBoost元算法提高分类性能

引言

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

当做重要决定时,考虑吸取多个专家而不只是一个人的意见。这就是元算法(meta -algorithm ) 背后的思路。e

AdaBoost (adaptive boosting)优缺点

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

AdaBoost的一般流程

  1. 收集数据:可以使用任意方法。
  2. 准备数据:依赖于所使用的弱分类器类型,本章使用的是单层决策树,这种分类器可以处理任何数据类型。 当然也可以使用任意分类器作为弱分类器,第2章到第6章中的任一分类器都可以充当弱分类器。作为弱分类器,简单分类器的效果更好。
  3. 分析数据:可以使用任意方法。
  4. 训练算法:AdaBoost的大部分时间都用在训絲上,分类器将多次在同一数据集上训练 弱分类器。
  5. 测试算法:计算分类的错误率
  6. 使用算法:同SVM—样,AdaBoost预测两个类别中的一个。如果想把它应用到多个类别的场合,那么就要像多类SVM中的做法一样对AdaBoost进行修改。

对比之前的过程,可以得机器学习分类算法的流程图下:

在这里插入图片描述

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

将不同的分类器组合起来,而这种组合结果被称为集成方法(ensemble method)或者元算法 (meta-algorithm)。

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

自举汇聚法(bootstrap aggregating),也称为bagging方法,是在从原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到的。这里的替换就意味着可以多次地选择同一样本 。这一性质就允许新数据集中可以有重复的值,而原始数据集的某些值在新集合中则不再出现。

在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们要对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果。

boosting

不论是在boosting还是bagging当中,所使用的多个分类器的类型都是一致的。但是在前者当中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能来进行训练。
boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。

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

弱分类器

弱分类器的“弱”意味着分类器的性能比随机猜测要略好,但是也不会好太多。这就是说,在二分类情况下弱分类器的错误率会高于50 %,而“强”分类器的错误率将会低很多。

Adasboost算法运行过程:

  1. 训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化成相等值。
  2. 首先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。
  3. 在分类器的第二次训练当中,将会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重将会提高。
  4. 为了从所有弱分类器中得到最终的分类结果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。

错误率 ε \varepsilon ε的定义为:
ε = 未正确分类的样本数目 所有样本数目 \varepsilon = \frac{未正确分类的样本数目}{ 所有样本数目} ε=所有样本数目未正确分类的样本数目

alpha 计算公式如下:

α = 1 2 l n ( 1 − ε ε ) \alpha = \frac{1}{2}ln(\frac{1-\varepsilon}{\varepsilon}) α=21ln(ε1ε)

AdaBoost算法的流程如图
在这里插入图片描述
左边是数据集,其中直方图的不同宽度表示每个样例上的不同权重。在经过一个分类器之后,加权的预测结果会通过三角形中的值进行加权。每个三角形中输出的加权结果在圆形中求和,从而得到最终的输出结果。

  1. 计算出alpha值之后,可以对权重向量乃进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法如下:
    如果某个样本被正确分类,那么该样本的权重更改为:

    D i ( t + 1 ) = D i t e − α S u m ( D ) D_i^{(t+1)} = \frac{D_i^te^{-\alpha}}{Sum(D)} Di(t+1)=Sum(D)Diteα

而如果某个样本被错分,那么该样本的权重更改为:

D i ( t + 1 ) = D i t e α S u m ( D ) D_i^{(t+1)} = \frac{D_i^te^{\alpha}}{Sum(D)} Di(t+1)=Sum(D)Diteα

  1. 在计算出D之后,AdaBoost又开始进入下一轮迭代。AdaBoost算法会不断地重复训练和调整权重的过程,直到训练错误率为0或者弱分类器的数目达到用户的指定值为止。

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

单层决策树(decision stump )

也称决策树桩,是一种简单的决策树仅基于单个特征来做决策。由于这棵树只有一次分裂过程,因此它实际上就是一个树桩。

构建算法所需数据集

实现代码如下:

import numpy as np
import matplotlib.pyplot as plt

#创建单层决策树的数据集
def loadSimpData():
    datMat = np.matrix([[ 1. ,  2.1],
        [ 1.5,  1.6],
        [ 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):
    #正样本
    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矩阵
    data_plus_np = np.array(data_plus)         
    #转换为numpy矩阵
    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()


dataArr,classLabels = loadSimpData()
showDataSet(dataArr,classLabels)

以上代码导入了数据与标签。并输出数据散点图如下:
在这里插入图片描述
黄色和蓝色分别是代表着一个类。
单层决策树伪代码:

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

实现代码如下:

# 单层决策树分类函数
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
    #初始化retArray为1
    retArray = np.ones((np.shape(dataMatrix)[0],1))                
    if threshIneq == 'lt':
         #如果小于阈值,则赋值为-1 
         # 这里比较的是矩阵
        retArray[dataMatrix[:,dimen] <= threshVal] = -1.0        
    else:
        #如果大于阈值,则赋值为-1
        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 = {}; bestClasEst = 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):                                   
            #大于和小于的情况,均遍历。lt:less than,gt:greater than  
            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                            
                #计算误差  权重D为矩阵5*1,转置为1*5
                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
                    bestClasEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump,minError,bestClasEst

dataArr,classLabels = loadSimpData()
# showDataSet(dataArr,classLabels)
D = np.mat(np.ones((5, 1)) / 5)
bestStump,minError,bestClasEst = buildStump(dataArr,classLabels,D)
print('bestStump:\n', bestStump)
print('minError:\n', minError)
print('bestClasEst:\n', bestClasEst)

输出结果如下:
在这里插入图片描述
遍历每个特征,每个特征下遍历设定的次数,在每一次数中循环两次,每次都对门限值进行修改,第一次调用单层决策树时将小于阈值的数据结果置-1,第二次循环调用决策树时,将大于阈值的样本判断结果置-1。之后判断输出结果,计算权重误差,通过比较权重误差。得到权重误差最小时对应的特征与对应的限值以及最小误差。

7.4 完整AdaBoost算法的实现

使用单层决策树来实现完整AdaBoost算法,实现伪代码如下:

对每次迭代:
	利用buildStump()函数找到最佳的单层决策树
	将最佳单层决策树加入到单层决策树数组
	计算alpha
	计算新的权重向量D
	更新累计类别估计值
	如果错误率等于0.0,则退出循环

实现代码如下:

#完整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,使error不等于0,因为分母不能为0
        alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))   
       
        #存储弱学习算法权重     
        bestStump['alpha'] = alpha                                          
        #存储单层决策树
        weakClassArr.append(bestStump)                                     
        print("classEst: ", classEst.T)
        #计算e的指数项
        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.0: break                                            
    return weakClassArr, aggClassEst

输出结果如下:在这里插入图片描述
程序设置了默认的训练次数为40次。但是当错误率达到0时程序便会跳出循环。一开始先创建一次单层决策树,得到错误率,分类结果以及最好分类信息。之后计算总体错误率。得到alpha之后便误差率,判断误差率。
由输出结果可知,循环进行了三次,前两次误差率为0,2,第三次为0,所以跳出了循环。第一次循环时,输出类别只有第一个搓了,所以后续权重变为了0.5,第二次迭代之后,第一个数据点已经正确分类了,但此时最后一个数据点却是错分了。D向量中的最后一个元素变为0.5,最后一次得到aggClassEst所有值的符号和真实类别标签都完全吻合,那么训练错误率为0,程序终止运行。

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

前面循环了三次,得到了三组数据,可以看做是三个分类器,利用前面的三个分类器的结果以其对应的alpha值作为权重。所有这些弱分类器的结果加权求和就得到了最后的结果。

AdaBoost分类函数:

# AdaBoost分类函数
def adaClassify(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'])    
        #每个分类器的alpha值乘以分类结果
        aggClassEst += classifierArr[i]['alpha'] * classEst
        print(aggClassEst)
    # 返回结果符号
    return np.sign(aggClassEst)

分类结果如下所示:
在这里插入图片描述
输入分类数据为(0,0)与(5,5)输出结果为-1,1,分类正确。
程序调用现有的分类器,得到分类器结果,在计算出总和,得到分类器的输出结果的符号作为分类结果输出。

7.6 示例:在一个难数据集上应用AdaBoost

使用第四章的数据集,所以首先要讲数据提取到程序中。
数据加载函数如下:

#数据加载函数
def loadDataSet(fileName):
    #读取一行,并测算特征量有几个
    numFeat = len((open(fileName).readline().split('\t')))
    dataMat = []; labelMat = []
    fr = open(fileName)
    # 每次读取一行
    for line in fr.readlines():
        lineArr = []
        #将数据分割,按回车分割
        curLine = line.strip().split('\t')
        # 将特征值提取出来
        for i in range(numFeat - 1):
            lineArr.append(float(curLine[i]))
        # 将数据存到dataMat中
        dataMat.append(lineArr)
        # 提取标签值
        labelMat.append(float(curLine[-1]))
    return dataMat, labelMat

将数据源加载好后,就进行训练与测试,代码如下:

# 加载训练集
dataArr, LabelArr = loadDataSet(r'./Ch07/horseColicTraining2.txt')
#得到分类器
weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, LabelArr)
#加载测算集
testArr, testLabelArr = loadDataSet(r'./Ch07/horseColicTest2.txt')
print(weakClassArr)
#将训练集进行分类
predictions = adaClassify(dataArr, weakClassArr)

errArr = np.mat(np.ones((len(dataArr), 1)))
#对比预测结果与实际样本不相同的值,得到错误总数
print('训练集的错误率:%.3f%%' % float(errArr[predictions != np.mat(LabelArr).T].sum() / len(dataArr) * 100))
#将测试集进行分类
predictions = adaClassify(testArr, weakClassArr)
errArr = np.mat(np.ones((len(testArr), 1)))
print('测试集的错误率:%.3f%%' % float(errArr[predictions != np.mat(testLabelArr).T].sum() / len(testArr) * 100)

输出结果如下
在这里插入图片描述

训练集的错误率:19.732%
。。。。。。

测试集的错误率:19.403%

得到训练集与测试集的错误率,对比第四章测试集错误率大多在47.76%,可以观察到准确率有了明显的提高。

7.7 非均衡分类问题

非均衡是指在大多数情况下不同类别的分类代价并不相等。

所以引入一种新的分类器性能度量方法,通过图像技术来对在上述非均衡问题下不同分类器的性能进行可视化处理。能够将不同决策的代价考虑在内。

其他分类性能度量指标:

正确率、召回率及ROC曲线

之前学习的分类器都是基于错误率来衡量分类器任务的成功程度的。
错误率指的是在所有测试样例中错分的样例比例。实际上,这样的度量错误掩盖了样例如何被分错的事实。

混淆矩阵(confusion matrix)

帮助人们更好地了解分类中的错误的一个普遍适用工具。
下图所示为一个三类问题的混淆矩阵:
在这里插入图片描述
如果矩阵中的非对角元素均为0,就会得到一个完美的分类器。
下图为一个二类问题的混淆矩阵
在这里插入图片描述

  • 在这个二分类问题中,如果对一个正例正确地判为正例,那么就可以认为产生了一个真正例(True Positive,TP,也称真阳);
  • 如果对一个反例正确地判为反例,则认为产生了一个真反例(True Negative,TN,也称真阴);
  • 如果对一个正例错误地判为反例,那么就可以认为产生了一个伪反例(False Negative,FN,为称假阴);
  • 如果对一个反例错误地判为正例,则认为产生了一个伪正例(False Positive,FP,也称假阳)。

在分类中,当某个类别的重要性高于其他类别时,我们就可以来利用上述定义来定义出多个比错误率更好的指标。

  1. 第一个指标是正确率(Precision),它等于TP/(TP+FF),给出的是预测为正例的样本中的真正正例的比例。
  2. 第二个指标是召回率(Recall) 它等于TP/(TP+FN),给出的是预测为正例的真实正例占所有真实正例的比例。
  3. 另一个用于度量分类中的非均衡性的工具是ROC曲 线 (ROC curve),ROC代表接收者操作特征 (receiver operating characteristic)

我们可以很容易构造一个高正确率或高召回率的分类器,但是很难同时保证两者成立。

代码参考输出ROC曲线
程序代码如下:

#绘制ROC
from matplotlib.font_manager import FontProperties
def plotROC(predStrengths, classLabels):

    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
    #绘制光标的位置
    cur = (1.0, 1.0)                      
    #用于计算AUC                                  
    ySum = 0.0                            
    #统计正类的数量                                     
    numPosClas = np.sum(np.array(classLabels) == 1.0)         
    #y轴步长                
    yStep = 1 / float(numPosClas)                                   
    #x轴步长            
    xStep = 1 / float(len(classLabels) - numPosClas)          
    #预测强度排序
    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]                                                 
        #绘制ROC   
        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.title('AdaBoost马疝病检测系统的ROC曲线', fontproperties = font)
    plt.xlabel('假阳率', fontproperties = font)
    plt.ylabel('真阳率', fontproperties = font)
    ax.axis([0, 1, 0, 1])
    #计算AUC
    print('AUC面积为:', ySum * xStep)                                         
    plt.show()
    
# 加载训练集
dataArr, LabelArr = loadDataSet(r'./Ch07/horseColicTraining2.txt')
#得到分类器
weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, LabelArr)
plotROC(aggClassEst.T, LabelArr)

输出结果为
AUC面积为: 0.8918191104095092
以及下图:AdaBoost马疝病检测系统的ROC曲线
在这里插入图片描述
ROC曲线中,给出了两条线,一条虚线一条实线。图中的横轴是伪正例的比例(假阳率=FP/(FP+TN)),而纵轴是真正例的比例(真阳率= =FP/(FP+FN))。ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。左下角的点所对应的是将所有样例判为反例的情况,而右上角的点对应的则是将所有样例判为正例的情况。

虚线给出的是随机猜测的结果曲线。ROC曲线不但可以用于比较分类器,还可以基于成本效益(cost-versus-benefit) 分析来做出决策。由于在不同的阈值下,不同的分类器的表现情况可能各不相同,因此以某种方式将它们组合起来或许会更有意义。如果只是简单地观察分类器的错误率,那么我们就难以得到这种更深入的洞察效果了。

在理想的情况下,最佳的分类器应该尽可能地处手左上角,这就意味着分类器在假阳率很低的同时获得了很高的真阳率。

对不同的ROC曲线进行比较的一个指标是曲线下的面积(Area Unser the Curve, AUC )。 AUC给出的是分类器的平均性能值,当然它并不能完全代替对整条曲线的观察。

一个完美分类器的AUC 为1.0 , 而随机猜测的AUC则为0.5。

为了画出ROC曲线,分类器必须提供每个样例被判为阳性或者阴性的可信程度值。

首先要将分类样例按照其预测强度排序。先从排名最低的样例开始,所有排名更低的样例都被判为反例,而所有排名更高的样例都被判为正例。该情况的对应点为<1.0,1.0>。然后,将其移到排名次低的样例中去,如果该样例属于正例,那么对真阳率进行修改;如果该样例属于反例,那么对假阴率进行修改。

7.8 本章小结

本章介绍的是对于算法的集成,集成方法通过组合多个分类器的分类结果,获得了比简单的单分类器更好的分类结果。

多个分类器组合可能会进一步凸显出单分类器的不足,比如过拟合问题。如果分类器之间差
别显著,那么多个分类器组合就可能会缓解这一问题。分类器之间的差别可以是算法本身或者是应用于算法上的数据的不同。

本章主要介绍了boosting方法中最流行的一个称为AdaBoost的算法。以单层决策树作为弱学习器构建了入AdaBoost分类器,最后,本文介绍了一些分类器性能评价指标,召回率、ROC曲线、AUC等。

本章没有额外提出一种独立的分类算法,而是提出了组合利用学过的分类算法,得到更好的分类效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值