《机器学习实战》之七——提升方法AdaBoost算法

一、前言

当做重要决定时,大家可能都会考虑吸取多个专家而不只是一个人的意见。机器学习处理问题时又何尝不是如此。这就是元算法(meta-algorithm)背后的思路。元算法是对其它算法进行组合的一种方式。前面我们已经学习了五种不同的分类算法,他们各有优缺点。我们自然可以将不同的分类器组合起来,而这种组合结构则被称为集成方法(ensemble method)或者元算法(meta-algorithm)。接下来我们将介绍两种主要的集成方法。然后再集中关注一个称作AdaBoost的最流行的元算法。

二、集成方法

集成方法(ensemble method)通过组合多个学习器来完成学习任务,颇有点“三个臭皮匠顶个诸葛亮”的意味。基本分类器一般采用的是弱可学习(weakly learnable)分类器,通过集成方法,组合成一个强可学习(strongly learnable)分类器。
历史上,Kearns 和 Valiant首先提出了“强可学习”和“弱可学习”的概念。指出:在概率近似正确(probably approximately correct, PAC)学习的框架中,一个概念(一个类),如果存在一个多项式的学习算法能够学习它,并且正确率很高,那么就称这个概念是强可学习的;一个概念如果存在一个多项式的学习算法能够学习它,学习的正确率仅比随机猜测略好,那么就称这个概念是弱可学习的。非常有趣的是Schapire后来证明强可学习与弱可学习是等价的,也就是说,在PAC学习框架下,一个概念是强可学习的充分必要条件是这个概念是弱可学习的。
这样,问题便成为,在学习中,如果已经发现了“弱可学习算法”,那么能否将它提升(boost)为“强学习算法”。如何具体实施提升,便成为开发提升方法时所要解决的问题。
集成方法主要包括Bagging和Boosting两种方法,Bagging和Boosting都是将已有的分类或回归算法通过一定方式组合起来,形成一个性能更加强大的分类器,更准确的说这是一种分类算法的组装方法,即将弱分类器组装成强分类器的方法。

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

自举汇聚法(bootstrap aggregating),也称为bagging方法。Bagging对训练数据采用自举采样(boostrap sampling),即有放回地采样数据,主要思想:

  • 从原始样本集中抽取训练集。每轮从原始样本集中使用Bootstraping的方法抽取n个训练样本(在训练集中,有些样本可能被多次抽取到,而有些样本可能一次都没有被抽中)。共进行k轮抽取,得到k个训练集。(k个训练集之间是相互独立的)
  • 每次使用一个训练集得到一个模型,k个训练集共得到k个模型。(注:这里并没有具体的分类算法或回归方法,我们可以根据具体问题采用不同的分类或回归方法,如决策树、感知器等)
  • 对分类问题:将上步得到的k个模型采用投票的方式得到分类结果;对回归问题,计算上述模型的均值作为最后的结果。(所有模型的重要性相同)
    在这里插入图片描述

2、Boosting

Boosting是一种与Bagging很类似的技术。Boosting的思路则是采用重赋权(re-weighting)法迭代地训练基分类器,主要思想:

  • 每一轮的训练数据样本赋予一个权重,并且每一轮样本的权值分布依赖上一轮的分类结果。
  • 基分类器之间采用序列式的线性加权方式进行组合。
    在这里插入图片描述

3、二者的区别

比较BaggingBoosting
样本选择上训练集是在原始集中有放回选取的,从原始集中选出的各轮训练集之间是独立的。没一轮的训练集不变,只是训练集中每个样例在分类器中的权重发生变化。而权值是根据上一轮的分类结果进行调整
样例权重使用均匀取样,每个样例的权重相等根据错误率不断调整样例的权值,错误率越大则权重越大。
预测函数所有预测函数的权重相等每个弱分类器都有相应的权重,对于分类误差小的分类器会有更大的权重
并行计算各个预测函数可以并行生成各个预测函数智能顺序生成,因为后一个模型参数需要前一轮模型的结果。

总结:
这两种方法都是把若干分类器整合为一个分类器的方法,只是整合的方式不一样,最终得到不一样的效果,将不同的分类算法套入到次类算法框架中一定程度上会提高原单一分类器的分类效果,但是也增大了计算量。

  • Bagging + 决策树 = 随机森林
  • AdaBoost + 决策树 = 提升树
  • Gradient Boosting + 决策树 = GBDT

集成方法很多,本章主要关注Boosting方法中的一种最流行的版本——AdaBoost

三、AdaBoost

AdaBoost是adaptive boosting(自适应boosting)的缩写,其运行过程:

1、选了数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化成相等值。设定每个样本的权重都是相等的,即1/n。
在这里插入图片描述
2、在训练数据上训练处一个弱分类器并计算该分类器的错误率
利用第一个弱学习算法h1对其进行学习,学习完成后进行错误率ε的统计:
在这里插入图片描述
3、计算弱学习算法权重

弱学习算法也有一个权重,用向量α表示,利用错误率计算权重α:
在这里插入图片描述
4、更新样本权重

在第一次学习完成后,需要重新调整样本的权重,以使得在第一分类中被错分的样本的权重,在接下来的学习中可以重点对其进行学习:
在这里插入图片描述
其中,ht(x_i) = yi表示对第i个样本训练正确,不等于则表示分类错误。Zt 是一个归一化因子:
在这里插入图片描述
这个公式我们可以继续化简,将两个公式进行合并,化简如下:
在这里插入图片描述
5、AdaBoost算法

重复进行学习,这样经过t轮的学习后,就会得到t个弱学习算法、权重、弱分类器的输出以及最终的AdaBoost算法的输出,分别如下:
在这里插入图片描述
其中,sign(x)是符号函数。具体过程如下所示:
在这里插入图片描述

在这里插入图片描述

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

建立AdaBoost算法之前,我们必须先建立弱分类器,并保存样本的权重。接下来我们使用的弱分类器为单层决策树。
单层决策树(decision stump,也称决策树桩)是一种简单的决策树,它是仅基于单个特征来做决策。由于这棵树只有一次分裂过程,因此它实际上就是一个树桩

1、构造数据集,并将数据集可视化,进行观察

新建adaboost.py文件,编写代码如下:

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import boost

"""
创建单层决策树的数据集
Parameters:
   无
Returns:
    dataMat - 数据矩阵
    classLabels - 数据标签
"""
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

"""
数据可视化
Parameters:
   dataMat - 数据矩阵
   classLabels - 数据标签
Returns:
    无
"""
def showData(datMat, classLabels):
    data_plus = []
    data_minus = []
    for i in range(len(datMat)):
        if classLabels[i] > 0:
            data_plus.append(datMat[i])
        else:
            data_minus.append(datMat[i])
    data_plus_np = np.array(data_plus)           #转换为numpy矩阵
    data_minus_np = np.array(data_minus)               #转换为numpy矩阵

    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])  #负样本散点图
    #X = np.linspace(1.0,2.0,10)  # np.linspace 返回一个一维数组,第三个参数指定数组长度
    #Y = np.linspace(1.5, 1.5, 10)
    #plt.plot(X, Y) #绘制直线
    plt.show()

运行结果如下:
在这里插入图片描述
从上图可以看出,如果想要将数据从某个坐标轴上选择一个值(即选择一条与坐标轴平行的直线)来将所有的蓝色点和橙色点分开,这显然是不可能。这就是单层决策树难处理的一个著名的问题,通过使用多棵决策树,我们可以构建一个能够对该数据集完全正确的分类器。

2、构建单层决策树

我们设置一个分类阈值为y=1.5,比如我横向切分,如下图所示:
在这里插入图片描述
蓝线将数据分为两个类别,显然,此时有两个蓝点被分错了。计算分类误差为2/5=0.4。这个横线与坐标轴y的交点,就是我们设置的阈值,通过改变阈值的大小,找到使单层决策树的分类误差最小的阈值。同理,竖线也是如此。找到最佳的分类的阈值,就找到了最佳单层决策树。

新建boost.py文件,在其中写入如下代码:

# -*- coding: utf-8 -*-

import numpy as np
import adaboost

"""
单层决策树分类函数
Parameters:
    dataMatrix: 数据矩阵
    dimen: 第dimen列,也就是第几个特征
    threshVal: 阈值
    threshIneq: 标志
Returns:
        retArray: 分类结果
"""

def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = np.ones((np.shape(dataMatrix)[0], 1))  #初始化分类结果均为1
    if threshIneq == 'lt': #标记‘lt’表示: less than; 标记为‘gt’表示:greater than
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0 #如果小于阈值赋值为-1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray


"""
找到数据集上最佳的单层决策树
Parameters:
    dataArr:数据矩阵
    classLabels: 数据标签
    D:样本权重
Returns:
    bestStump:最佳单层决策树信息
    minError:最小误差
    bestClasEst:最佳的分类结果
"""
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 = np.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  #分类正确的赋值为0
                weightedError = D.T*errArr  #计算误差
                print("split: dim %d, thresh %.2f, thresh inequal : %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


if __name__ == '__main__':
    dataMat, classLabels = adaboost.loadSimpData()
    D = np.mat(np.ones((5,1))/5)
    bestStump, minError, bestClasEst = buildStump(dataMat, classLabels, D)
    print('bestStump:\n', bestStump)
    print('minError:\n', minError)
    print('bestClasEst:\n', bestClasEst)

运行结果如下
在这里插入图片描述
通过遍历,改变不同的阈值,计算最终的分类误差,找到分类误差最小的分类方式,即为我们要找的最佳单层决策树。经过遍历,我们找到,训练好的最佳单层决策树的最小分类误差为0.2,就是对于该数据集,无论用什么样的单层决策树,分类误差最小就是0.2。这就是我们训练好的弱分类器。接下来,使用AdaBoost算法提升分类器性能,将分类误差缩到0,看下AdaBoost算法是如何实现的。

上述我到现在一直有一处不理解,就是第二层循环,lt 和 gt
在这里插入图片描述

五、AdaBoost算法的实现

打开adaboost.py文件,并写入下列代码:


"""使用AdaBoost算法提升弱分类器性能
Parameters:
    dataArr:数据矩阵
    classLabels:数据标签
    numIt: 最大迭代次数
Returns:
    weakClassArr:训练好的分类器
    aggClassEst:类别估计累计值
"""
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))) #初始化分类结果均为1
    for i in range(numIt):
        bestStump, error, classEst = boost.buildStump(dataArr, classLabels, D) #构建单层决策树
        print("D:", D.T)
        alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16))) #计算alpha
        bestStump['alpha'] = alpha #存储alpha
        weakClassArr.append(bestStump) #存储单层决策树
        print("classEst: ", classEst.T)
        expon = np.multiply(-1*alpha*np.mat(classLabels).T, classEst) #计算e的指数项
        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  #如果误差为0,则退出循环
    return weakClassArr, aggClassEst

运行结果如下:
在这里插入图片描述
第一次迭代时,权重D都一样,均为0.2,通过变量aggClassEst的符号来了解总的类别,发现第一个点分错了
在这里插入图片描述
第二次迭代时,将第一点的权重更新为0.5,其他点的权重相应减小,通过变量aggClassEst的符号来看,现在又将最后一个点分错了,
在这里插入图片描述
第三次迭代时,将最后一个点的权重更新为0.5,其它点的权重都减小,aggClassEst所有值的符号和真实类别标签都完全吻合,那么训练错误率为0,程序终止运行。
在这里插入图片描述
一共迭代了3次,所以训练了3个弱分类器构成一个使用AdaBoost算法优化过的分类器,分类器的错误率为0。

一旦拥有了多个弱分类器以及其对应的alpha值,进行测试就变得想当容易了。在adaboost.py文件中编写代码如下:

"""
AdaBoost分类函数
Parameters:
    datToClass:待分类样例
    classifierArr:训练好的分类器
Returns:
    分类结果
"""
def adaClassify(datToClass, classifierArr):
    dataMatrix = np.mat(datToClass) #将datToClass转换为一个numpy矩阵
    m = np.shape(dataMatrix)[0] #计算待分类样例的个数m
    aggClassEst = np.mat(np.zeros((m ,1))) #初始化分类结果均为0
    for i in range(len(classifierArr)):  #遍历所有分类器,进行分类
        classEst = boost.stumpClassify(dataMatrix, classifierArr[i]['dim'],\
                                 classifierArr[i]['thresh'],\
                                 classifierArr[i]['ineq'])
        aggClassEst += classifierArr[i]['alpha']*classEst
        print(aggClassEst)
    return np.sign(aggClassEst)

if __name__ == '__main__':
    datMat, classLabels = loadSimpData()
    showData(datMat, classLabels)
    weakClassArr, aggClassEst = adaBoostTrainDS(datMat, classLabels, numIt=40)
#    print("weakClassArr:",weakClassArr)
#    print("aggClassEst:", aggClassEst)
    print(adaClassify([[0,0],[5,5]], weakClassArr))

运行结果如下:
在这里插入图片描述
代码很简单,在之前代码的基础上,添加adaClassify()函数,该函数遍历所有训练得到的弱分类器,利用单层决策树,输出的类别估计值乘以该单层决策树的分类器权重alpha,然后累加到aggClassEst上,最后通过sign函数来看最终的结果。可以看到,分类没有问题,(5,5)属于正类,(0,0)属于负类。

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

在第4章,我们曾经利用Logistic回归来预测患有疝病的马是否能够存活。现在,我们利用多个单层决策树和AdaBoost来进行预测。
logistic回归预测的结果如下:
在这里插入图片描述
可以看到错误率还是挺高的,现在我们使用AdaBoost算法,训练一个更强的分类器。这里的数据集需要进行修改,之前的标签式0和1,现在将标签改为1和-1。

1、用自己的AdaBoost算法进行测试

使用自己的用Python写的AbaBoost算法进行训练,添加loadDataSet函数用于加载数据集。在adaboost.py文件中编写代码如下:

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.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat, labelMat

if __name__ == '__main__':
#    datMat, classLabels = loadSimpData()
#    showData(datMat, classLabels)
#    weakClassArr, aggClassEst = adaBoostTrainDS(datMat, classLabels, numIt=40)
##    print("weakClassArr:",weakClassArr)
##    print("aggClassEst:", aggClassEst)
#    print(adaClassify([[0,0],[5,5]], weakClassArr))
    datArr, labelArr = loadDataSet('horseColicTraining2.txt')
    weakClassArr, aggClassEst = adaBoostTrainDS(datArr, labelArr)
    errArr = np.mat(np.ones((len(datArr), 1)))
    predictions = adaClassify(datArr, weakClassArr)
    print("训练集的错误率:%.3f%%" % float(errArr[predictions != np.mat(labelArr).T].sum() / len(datArr) * 100))
    testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
    errArr = np.mat(np.ones((len(testArr), 1)))
    prediction10 = adaClassify(testArr, weakClassArr)  
    print("测试集的错误率:%.3f%%" % float(errArr[prediction10 != np.mat(testLabelArr).T].sum() / len(testArr) * 100))

运行结果如下:
在这里插入图片描述
在这里插入图片描述
这里输出了AdaBoost算法训练好的分类器的组合,我们只迭代了40次,也就是训练了40个弱分类器。最终,训练集的错误率为19.732%,测试集的错误率为19.403%,可以看到相对于Logistic回归方法,错误率降低了很多。这个仅仅是我们训练40个弱分类器的结果,如果训练更多弱分类器,效果会更好。但是当弱分类器数量过多的时候,你会发现训练集错误率降低很多,但是测试集错误率提升了很多,这种现象就是过拟合(overfitting)。分类器对训练集的拟合效果好,但是缺失了普适性,只对训练集的分类效果好,这是我们不希望看到的。

2、用sklearn库中的AdaBoostClassifier算法进行测试

官方的sklearn手册:
https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.AdaBoostClassifier.html
sklearn库中的adaBoostClassifier分类器,有五个参数:
在这里插入图片描述
在这里插入图片描述
新建sklearn_adaboost.py文件,并编写代码如下:

# -*- coding: utf-8 -*-

import numpy as np
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier

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.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat, labelMat

if __name__ == '__main__':
    bdt = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2), algorithm="SAMME", n_estimators=10)
    
    datArr, labelArr = loadDataSet('horseColicTraining2.txt')
    bdt.fit(datArr, labelArr)
    errArr = np.mat(np.ones((len(datArr), 1)))
    predictions = bdt.predict(datArr)
    print("训练集的错误率:%.3f%%" % float(errArr[predictions != labelArr].sum() / len(datArr) * 100))
    
    testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
    errArr = np.mat(np.ones((len(testArr), 1)))
    prediction10 = bdt.predict(testArr)  
    print("测试集的错误率:%.3f%%" % float(errArr[prediction10 != testLabelArr].sum() / len(testArr) * 100))

运行结果如下:
在这里插入图片描述
我们使用DecisionTreeClassifier作为使用的弱分类器,使用AdaBoost算法训练分类器。可以看到训练集的错误率为16.054%,测试集的错误率为:17.910%。更改n_estimators参数,你会发现跟我们自己写的代码,更改迭代次数的效果是一样的。n_enstimators参数过大,会导致过拟合。
在编写代码是出现了如下错误:在计算训练集的错误率和测试集的错误率时,不需要转置。
在这里插入图片描述

七、分类器的性能度量指标

不同类别的分类代价并不相等,这就是非均衡分类问题。我们将会考察一种新的分类器性能度量方法,而不再是简单的通过错误率进行评价,并且通过图像技术来对上述非均衡问题下不同分类器性能进行可视化处理。

1、分类性能度量指标

在之前,我们都是基于错误率来衡量分类器任务的成功程度的。错误率指的是在所有测试样本中错分的样本比例。实际上,这样的度量错误掩盖了样例如何被错分的事实。在机器学习中,有一个普遍适用的称为混淆矩阵(confusion matrix)的工具,它可以帮助人们更好地了解分类中的错误。有这样一个关于在房子周围可能发现的动物类型的预测,这个预测的三个类问题的混淆矩阵如下图所示:

在这里插入图片描述
利用混淆矩阵就可以更好地理解分类中的错误了。如果矩阵中的飞对角元素均为0,就会得到一个完美的分类器。

接下来,我们考虑另外一个混淆矩阵,这次的矩阵只针对一个简单的二类问题。混淆矩阵如下图所示:
在这里插入图片描述

  • (1)Accuracy
    模型的精度,即模型预测正确的个数/样本的总个数,一般情况下,模型的精度越高,说明模型的效果越好。

在这里插入图片描述

  • (2)Positive predictive value(PPV, Precision)
    正确率,阳性预测值,在模型预测为正类的样本中,真正的正样本所占的比例。一般情况下,正确率越高,说明模型的效果越好。
    在这里插入图片描述
  • (3)False discovery rate(FDR)

伪发现率,也是错误发现率,表示在模型预测为正类的样本中,真正的负类的样本所占的比例。一般情况下,错误发现率越小,说明模型的效果越好。
在这里插入图片描述

  • (4)False omission rate(FOR)
    错误遗漏率,表示在模型预测为负类的样本中,真正的正类所占的比例。即评价模型"遗漏"掉的正类的多少。
    在这里插入图片描述

  • (5)Negative predictive value(NPV)

阴性预测值,在模型预测为负类的样本中,真正为负类的样本所占的比例。一般情况下,NPV越高,说明的模型的效果越好。
在这里插入图片描述

  • (6)True positive rate(Recall)

召回率,真正类率,表示的是,模型预测为正类的样本的数量,占总的正类样本数量的比值。一般情况下,Recall越高,说明有更多的正类样本被模型预测正确,模型的效果越好。
在这里插入图片描述

  • (7)False positive rate(FPR),Fall-out

假正率,表示的是,模型预测为正类的样本中,占模型负类样本数量的比值。一般情况下,假正类率越低,说明模型的效果越好。
在这里插入图片描述
(8)False negative rate(FNR),Miss rate

假负类率,缺失率,模型预测为负类的样本中,是正类的数量,占真实正类样本的比值。缺失值越小,说明模型的效果越好。
在这里插入图片描述

2、ROC曲线

除了上述的评价指标,另一个用于度量分类中的非均衡的工具是ROC曲线(ROC curve),ROC代表接收者操作特征(receiver operating characteristic),它最早在二战期间由电气工程师构建雷达系统时使用过。

在adaboost.py文件中编写代码如下:

"""
绘制ROC
Parameters:
    predStrengths: 分类器的预测强度
    classLabels: 类别
Returns:
    无
"""
def plotROC(predStrengths, classLabels):
    
    from matplotlib.font_manager import FontProperties
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
    cur = (1.0,1.0)  #绘制光标的位置
    ySum = 0.0 #用于计算AUC
    numPosClas = 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('假阳率', FontProperties = font)
    plt.ylabel('真阳率', FontProperties = font)
    plt.title('AdaBoost马疝病检测系统的ROC曲线', FontProperties = font)
    ax.axis([0,1,0,1])
    plt.show()
    print("AUC面积为: ", ySum*xStep) #计算AUC
if __name__ == '__main__':
    datArr, labelArr = loadDataSet('horseColicTraining2.txt')
    weakClassArr, aggClassEst = adaBoostTrainDS(datArr, labelArr)
    plotROC(aggClassEst.T, labelArr)

运行结果如下:
在这里插入图片描述
  先解释ROC,图中的横坐标是伪正例的比例(假阳率=FP/(FP+TN)),而纵坐标是真正例的比例(真阳率=TP/(TP+FN))。ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。左下角的点所对应的将所有样例判为反例的情况,而右上角的点对应的则是将所有样例判为正例的情况。虚线给出的是随机猜测的结果曲线。
  ROC曲线不但可以用于比较分类器,还可以基于成本效益(cost-versus-benefit)分析来做出决策。由于在不同的阈值下,不用的分类器的表现情况是可能各不相同,因此以某种方式将它们组合起来或许更有意义。如果只是简单地观察分类器的错误率,那么我们就难以得到这种更深入的洞察效果了。

  在理想的情况下,最佳的分类器应该尽可能地处于左上角,这就意味着分类器在假阳率很低的同时获得了很高的真阳率。例如在垃圾邮件的过滤中,就相当于过滤了所有的垃圾邮件,但没有将任何合法邮件误识别为垃圾邮件而放入垃圾邮件额文件夹中。

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

这个ROC曲线是怎么画的呢?
对于分类器而言,都有概率输出的功能,拿逻辑回归来举例,我们得到的是该样本属于正样本的概率和属于负样本的概率,属于正样本的概率大,那么就判为正类,否则判为负类,那么实质上这里的阈值是0.5。

现在假设我们已经得到了所有样本的概率输出(属于正样本的概率),我们根据每个测试样本属于正样本的概率值从大到小排序。如下图所示:
在这里插入图片描述
图中共有20个测试样本,“Inst”一栏表示样本编号,“Class”一栏表示每个测试样本真正的标签(p表示正样本,n表示负样本),“Score”表示每个测试样本属于正样本的概率。其中,一共10个正样本,10个负样本。接下来,我们从高到低,依次将“Score”值作为阈值,当测试样本属于正样本的概率大于或等于这个threshold时,我们认为它为正样本,否则为负样本。

举例来说:

  •    当阈值为0.9时,样本1的“Score”值为0.9,大于等于阈值,那么样本1为正类,其余为负类,则TPR=TP/(TP+FN)=1/10=0.1,FPR=FP/(TP+FN)=0/10=0;
    
  • 当阈值为0.8时,样本1的“Score”值为0.9,样本2的“Score”值为0.8,大于等于阈值,那么样本1,2为正类,其余为负类,则TPR=2/10=0.2,FPR=0/10=0;
  • 当阈值为0.7时,样本1,2,3的"Score"值大于等于阈值,那么样本1,2,3为正类,其余为负类,则TPR=2/10=0.2,FPR=1/10=0.1;
  • 当阈值为0.6时,样本1,2,3,4的"Score"值大于等于阈值,那么样本1,2,3,4为正类,其余为负类,则TPR=3/10=0.3,FPR=1/10=0.1;
    ---------以此类推,此处省略一系列求解---------
  • 当阈值为0.1时,所有样本的"Score"值大于等于阈值,那么所有样本均为正类,则TPR=10/10=1,FPR=10/10=1。
    将它们的结果画出来,就构成了下图:
    在这里插入图片描述也许你已经发现了这样一个规律:
    多了一个TP,向Y轴移动一步,多了一个FP,向X轴移动一步。现在回头看一看绘制ROC曲线的程序吧,你会发现程序中也是如此计算的,只不过区别在于,程序是从<1.0,1.0>这个点开始画的。

AUC又是如何计算的呢?
很简单,这些小矩形的宽度都是固定的xStep,因此先对所有矩形的高度进行累加,即ySum,最后面积就是ySum*xStep。

八、总结

   本章介绍了boosting方法中最流行的一个称为AdaBoost的算法。AdaBoost以弱学习器作为基本分类器,并且输入数据,使其通过权重向量进行加权。在第一次迭代中,所有数据都等权重。但是在后续的迭代当中,前次迭代中分错的数据的权重会增大。这种针对错误的调节能力正是AdaBoost的长处。
  本章以单层决策树作为弱学习器构建了AdaBoost分类器。实际上,AdaBoost函数可以应用于任意分类器,只要改分类器能够处理加权数据即可。
  最后本章还介绍了分类性能度量指标:正确率、召回率以及ROC曲线。

参考资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值