放牧代码和思想
专注自然语言处理、机器学习算法
    This thing called love. Know I would've. Thrown it all away. Wouldn't hesitate.

提升方法

目录

本文是《统计学习方法》第8章提升方法的笔记,整合了《机器学习实战》中的提升树Python代码,并添加了注解和PR值计算代码。《方法》重理论,但不易理解,《实战》重实践,但缺乏理论基础,特别是AdaBoost算法的解释、提升树与加法模型的关系等。两相结合,应该能获得较为全面的知识。

提升方法AdaBoost算法

提升方法的思路是综合多个分类器,得到更准确的分类结果。    adaboost.png

AdaBoost算法的归类

《统计学习方法》称AdaBoost是提升算法的代表,所谓提升算法,指的是一种常用的统计学习方法,应用广泛且有效。在分类问题中,它通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提髙分类的性能。

《机器学习实战》称AdaBoost是最流行的元算法,所谓元算法,指的是“学习算法的算法”。

AdaBoost算法的基本思想

  1. 多轮训练,多个分类器

  2. 每轮训练增加错误分类样本的权值,降低正确分类样本的权值

  3. 降低错误率高的分类器的权值,增加正确率高的分类器的权值

AdaBoost算法

给定一个二类分类的训练数据集

屏幕快照 2016-04-05 下午9.45.50.png

其中,每个样本点由实例与标记组成。实例屏幕快照 2016-04-05 下午9.48.06.png,标记屏幕快照 2016-04-05 下午9.49.10.png屏幕快照 2016-04-05 下午9.50.08.png是实例空间,屏幕快照 2016-04-05 下午9.50.48.png是标记集合。AdaBoost利用以下算法,从训练数据中学习一系列弱分类器或基本分类器,并将这些弱分类器线性组合成为一个强分类器。

AdaBoost算法

输入:训练数据集屏幕快照 2016-04-05 下午9.45.50.png:弱学习算法;

输出:最终分类器屏幕快照 2016-04-05 下午9.55.46.png

(1)初始化训练数据的权值分布

屏幕快照 2016-04-05 下午9.57.10.png

每个w的下标由两部分构成,前一个数表示当前迭代次数,与D的下标保持一致,后一个数表示第几个权值,与位置保持一致。初始情况下,每个权值都是均等的。

(2)屏幕快照 2016-04-05 下午9.57.59.png(这里的M原著未做解释,其实是表示训练的迭代次数,是由用户指定的。每轮迭代产生一个分类器,最终就有M个分类器):

(a)使用具有权值分布的训练数据集学习,得到基本分类器

屏幕快照 2016-04-05 下午10.04.06.png

(b)计算屏幕快照 2016-04-05 下午10.04.40.png在训练数据集上的分类误差率

屏幕快照 2016-04-05 下午10.05.04.png

分类误差率这个名字可能产生误解,这里其实是个加权和。

(c)计算屏幕快照 2016-04-05 下午10.04.40.png的系数

屏幕快照 2016-04-05 下午10.06.18.png

这里的对数是自然对数。屏幕快照 2016-04-06 上午9.08.00.png表示屏幕快照 2016-04-05 下午10.04.40.png在最终分类器中的重要性。由式屏幕快照 2016-04-05 下午10.06.18.png可知,当屏幕快照 2016-04-06 上午9.09.34.png时,屏幕快照 2016-04-06 上午9.09.54.png,并且屏幕快照 2016-04-06 上午9.08.00.png随着屏幕快照 2016-04-06 上午9.10.19.png的减小而增大,所以分类误差率越小的基本分类器在最终分类器中的作用越大。

为什么一定要用这个式子呢?这与前向分步算法的推导有关,在后面的章节会介绍。

(d)更新训练数据集的权值分布

屏幕快照 2016-04-05 下午10.09.44.png

y只有正负一两种取值,所以上式可以写作:

屏幕快照 2016-04-06 上午9.12.53.png

这里,屏幕快照 2016-04-05 下午10.12.44.png是规范化因子

屏幕快照 2016-04-05 下午10.13.05.png

它使屏幕快照 2016-04-05 下午10.13.35.png成为一个概率分布。

由此可知,被基本分类器误分类样本的权值得以扩大,而被正确分类样本的权值却得以缩小。两相比较,误分类样本的权值被放大屏幕快照 2016-04-06 上午9.14.37.png倍。因此,误分类样本在下一轮学习中起更大的作用。不改变所给的训练数据,而不断改变训练数据权值的分布,使得训练数据在基本分类器的学习中起不同的作用,这是AdaBoost的一个特点。

(3)构建基本分类器的线性组合

屏幕快照 2016-04-05 下午10.14.24.png

得到最终分类器

屏幕快照 2016-04-05 下午10.14.51.png

AdaBoost算法的解释

AdaBoost算法还有另一个解释,即可以认为AdaBoost算法是模型为加法模型、损失函数为指数函数、学习算法为前向分步算法时的二类分类学习方法。

为什么还要学习前向分步算法呢?直接给我AdaBoost的代码不就好了吗?因为只有理解了前向分步算法,才能理解AdaBoost为什么能跟决策树组合起来。

前向分步算法

考虑加法模型(additive model)

屏幕快照 2016-04-06 上午10.30.25.png

其中,屏幕快照 2016-04-06 上午10.31.45.png为基函数,屏幕快照 2016-04-06 上午10.43.40.png为基函数的参数,屏幕快照 2016-04-06 上午10.44.10.png为基函数的系数。显然,屏幕快照 2016-04-05 下午10.14.24.png是一个加法模型。

在给定训练数据及损失函数的条件下,学习加法模型屏幕快照 2016-04-06 下午4.26.02.png成为经验风险极小化即损失函数极小化问题:

屏幕快照 2016-04-06 下午4.28.18.png

通常这是一个复杂的优化问题。前向分步算法(forward stage wise algorithm)求解这一优化问题的想法是:因为学习的是加法模型,如果能够从前向后,每一步只学习一个基函数及其系数,逐步逼近优化目标函数式屏幕快照 2016-04-06 下午4.28.18.png(L应该是loss的缩写,表示一个损失函数,输入正确答案yi和模型预测值,输出损失值),那么就可以简化优化的复杂度。具体地,每步只需优化如下损失函数:

屏幕快照 2016-04-06 下午4.31.17.png

也就是说,原来有M个分类器,现在只专注优化一个。

给定训练数据集屏幕快照 2016-04-06 下午4.35.00.png。损失函数屏幕快照 2016-04-06 下午4.35.38.png和基函数的集合屏幕快照 2016-04-06 下午4.35.59.png,学习加法模型屏幕快照 2016-04-06 下午4.26.02.png的前向分步算法如下:

算法(前向分步算法)

输入:训练数据集屏幕快照 2016-04-06 下午4.35.00.png,损失函数屏幕快照 2016-04-06 下午4.35.38.png,基函数集屏幕快照 2016-04-06 下午4.35.59.png;

输出:加法模型屏幕快照 2016-04-06 下午4.26.02.png

(1)初始化屏幕快照 2016-04-06 下午4.39.23.png

(2)屏幕快照 2016-04-06 下午4.39.46.png

(a极小化损失函数

屏幕快照 2016-04-06 下午4.40.17.png

得到参数屏幕快照 2016-04-06 下午4.41.11.png

 (b)更新

屏幕快照 2016-04-06 下午4.41.45.png

(3)得到加法模型

屏幕快照 2016-04-06 下午4.42.39.png

这样,前向分步算法将同时求解从m=1到M所有参数屏幕快照 2016-04-06 下午4.41.11.png的优化问题简化为逐次求解各个屏幕快照 2016-04-06 下午4.41.11.png的优化问题。

前向分步算法与AdaBoost

由前向分步算法可以推导出AdaBoost,用定理叙述这一关系。

定理 AdaBoost算法是前向分歩加法算法的特例。这时,模型是由基本分类器组成的加法模型,损失函数是指数函数。

证明 前向分步算法学习的是加法模型,当基函数为基本分类器时,该加法模型等价于AdaBoost的最终分类器

由基本分类器屏幕快照 2016-04-06 下午4.49.34.png及其系数屏幕快照 2016-04-06 下午4.49.56.png组成,m=1,2,…,M。前向分步算法逐一学习基函数,这一过程与AdaBoost算法逐一学习基本分类器的过程一致。下面证明前向分步算法的损失函数是指数损失函数(exponential loss function)

屏幕快照 2016-04-06 下午4.50.55.png

时,其学习的具体操作等价于AdaBoost算法学习的具体操作。

假设经过m-1轮迭代前向分步算法已经得到屏幕快照 2016-04-06 下午4.51.36.png:

屏幕快照 2016-04-06 下午4.51.59.png

在第m轮迭代得到屏幕快照 2016-04-06 下午4.58.19.png屏幕快照 2016-04-06 下午4.58.52.png

屏幕快照 2016-04-06 下午4.59.10.png

目标是使前向分步算法得到的屏幕快照 2016-04-06 下午4.58.19.png使屏幕快照 2016-04-06 下午4.58.52.png在训练数据集T上的指数损失最小,即

屏幕快照 2016-04-06 下午5.06.49.png

上式可以表示为

屏幕快照 2016-04-06 下午5.07.28.png

其中,屏幕快照 2016-04-06 下午5.08.44.png(指数中的加法可以拿出来做乘法)。因为屏幕快照 2016-04-06 下午5.12.24.png既不依赖α也不依赖于G,所以与最小化无关。但屏幕快照 2016-04-06 下午5.12.24.png依赖于屏幕快照 2016-04-06 下午4.51.36.png,随着每一轮迭代而发生改变。

现证使式屏幕快照 2016-04-06 下午5.07.28.png达到最小的屏幕快照 2016-04-06 下午5.15.55.png就是AdaBoost算法所得到的屏幕快照 2016-04-06 下午4.58.19.png

求解式屏幕快照 2016-04-06 下午5.07.28.png可分两步:

首先,求屏幕快照 2016-04-06 下午5.19.45.png。对任意a>0,使式屏幕快照 2016-04-06 下午5.07.28.png最小的屏幕快照 2016-04-06 下午5.17.39.png由下式得到:

屏幕快照 2016-04-06 下午5.19.04.png

其中,屏幕快照 2016-04-06 下午5.20.20.png

此分类器屏幕快照 2016-04-06 下午5.19.45.png即为AdaBoost算法的基本分类器屏幕快照 2016-04-06 下午5.21.24.png,因为它是使第m轮加权训练数据分类误差率最小的基本分类器。

之后,求屏幕快照 2016-04-06 下午5.22.56.png屏幕快照 2016-04-06 下午5.07.28.png

屏幕快照 2016-04-06 下午5.23.24.png

这个转换很简单,当y和G一致时,指数为负,反之为正,第二个等号也是利用这个原理,只不过换成了用指示变量I表述。

将已求得的屏幕快照 2016-04-06 下午5.19.45.png代入式屏幕快照 2016-04-06 下午5.23.24.png,对α求导并使导数为0,即得到使式屏幕快照 2016-04-06 下午5.07.28.png最小的a。

屏幕快照 2016-04-06 下午7.00.44.png

其中,屏幕快照 2016-04-06 下午7.01.08.png是分类误差率:

这里的屏幕快照 2016-04-06 下午7.01.58.png与AdaBoost算法第2(c)步的屏幕快照 2016-04-06 下午7.02.35.png完全一致。

最后来看每一轮样本权值的更新。由

屏幕快照 2016-04-06 下午7.04.04.png

以及屏幕快照 2016-04-06 下午7.04.22.png,可得

屏幕快照 2016-04-06 下午7.04.51.png

这与AdaBoost算法第2(d)步的样本权值的更新,只相差规范化因子,因而等价。

提升树

提升树是以分类树或回归树为基本分类器的提升方法。提升树被认为是统计学习中性能最好的方法之一。

提升方法实际采用加法模型(即基函数的线性组合)与前向分步算法。以决策树为基函数的提升方法称为提升树(boosting tree)。对分类问题决策树是二叉分类树,对回归问题决策树是二叉回归树。在原著例题中看到的基本分类器,可以看作是由一个根结点直接连接两个叶结点的简单决策树,即所谓的决策树桩(decision stump)。提升树模型可以表示为决策树的加法模型:

屏幕快照 2016-04-06 下午7.11.14.png

其中,屏幕快照 2016-04-06 下午7.12.15.png表示决策树;屏幕快照 2016-04-06 下午7.12.28.png为决策树的参数;M为树的个数。

提升树算法

提升树算法采用前向分步算法。首先确定初始提升树/e(x)=0,第m歩的模型是

屏幕快照 2016-04-06 下午7.14.10.png

其中,屏幕快照 2016-04-06 下午7.14.33.png为当前模型,通过经验风险极小化确定下一棵决策树的参数屏幕快照 2016-04-06 下午7.14.55.png

屏幕快照 2016-04-06 下午7.15.09.png

由于树的线性组合可以很好地拟合训练数据,即使数据中的输入与输出之间的关系很复杂也是如此,所以提升树是一个髙功能的学习算法。

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

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

提升树的Python实现

AdaBoost+决策树=提升树,来看看具体用Python怎么实现。

测试数据

老规矩,写代码前先看数据,有什么样的数据写什么样的代码。

考虑到学习代码的最佳方式是单步,而单步的时候,测试数据越简单越好。所以这里先上一份简单的测试数据:

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

数据很简单,就是一个二维平面上的5个点。

写一段可视化代码:

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()

画出来是这种效果:

简单数据集.png

单层决策树分类

单层决策树就是一个树桩,只能利用一个维度的特征进行分类。

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

dimen决定了这个树桩能用的唯一特征。

构建决策树桩

上个函数到底取什么参数才好呢?给定训练数据集的权重向量,利用该向量计算错误率加权和,取最低的参数作为训练结果。

def buildStump(dataArr, classLabels, D):
    """
构建决策树(一个树桩)
    :param dataArr: 数据特征矩阵
    :param classLabels: 标签向量
    :param D: 训练数据的权重向量
    :return: 最佳决策树,最小的错误率加权和,最优预测结果
    """
    dataMatrix = mat(dataArr)
    labelMat = mat(classLabels).T
    m, n = shape(dataMatrix)
    numSteps = 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) / 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)  # 使用参数 i, j, lessThan 调用树桩决策树分类
                errArr = mat(ones((m, 1)))
                errArr[predictedVals == labelMat] = 0 # 预测正确的样本对应的错误率为0,否则为1
                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

上面的这一句

weightedError = D.T * errArr  # 计算错误率加权和

其实对应着这个公式:

屏幕快照 2016-04-05 下午10.05.04.png

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

其中alpha的计算:

        alpha = float(
            0.5 * log((1.0 - error) / max(error, 1e-16)))  # 计算 alpha, 防止发生除零错误

对应着

屏幕快照 2016-04-05 下午10.06.18.png

之后记录下这个alpha和对应的决策树桩,然后算法更新训练数据的权重:

        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)  # 每个样本对应的指数,当预测值等于y的时候,恰好为-alpha,否则为alpha
        D = multiply(D, exp(expon))  # 计算下一个迭代的D向量
        D = D / D.sum() # 归一化

对应着

屏幕快照 2016-04-05 下午10.09.44.png

更新完毕后就可以利用这个权值分布训练下一个决策树桩了,当然,作为一个节省时间的策略,可以检查一下当前错误率是否满足要求,如果满足,则终止训练。

提升树分类

分类很简单,就是一个多数表决的过程:

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([0, 0], classifierArr)

输出

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

可见前向分步训练过程中的误差率是逐渐降低的:

total error:  0.2
total error:  0.2
total error:  0.0

分类过程中,随着加法模型的不断叠加,对于(0,0)这个点,其累加结果是“负”得越来越厉害的,最终取符号输出-1类别。对应训练数据:

简单数据集.png

的确应该属于-1。

复杂数据集

《机器学习实战》还给出了一个马疝病数据集:horseColicTraining2.txt

加载代码:

def loadDataSet(fileName):
    """
 从文件中加载以制表符分割的数据集(浮点数格式)
    :param fileName:
    :return:
    """
    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

训练代码:

datArr, labelArr = loadDataSet("horseColicTraining2.txt")
classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 10)

我们可以直接将aggClassEst打印出来观察对训练集的预测结果,但这样太抽象,而且我们希望计算准确率和召回率,特别是在测试集上的准确率和召回率。

准确率与召回率

对于二分类问题:

屏幕快照 2016-04-06 下午9.56.32.png

准确率的计算方法为

TP / (TP + FP)

召回率的计算方法为

TP / (TP + FN)

测试集:horseColicTest2.txt

写一段代码计算在不同的数据集下的准确率与召回率:

def evaluate(aggClassEst, classLabels):
    """
计算准确率与召回率
    :param aggClassEst:
    :param classLabels:
    :return: P, R
    """
    TP = 0.
    FP = 0.
    TN = 0.
    FN = 0.
    for i in range(len(classLabels)):
        if classLabels[i] == 1.0:
            if (sign(aggClassEst[i]) == classLabels[i]):
                TP += 1.0
            else:
                FP += 1.0
        else:
            if (sign(aggClassEst[i]) == classLabels[i]):
                TN += 1.0
            else:
                FN += 1.0

    return TP / (TP + FP), TP / (TP + FN)


def train_test(datArr, labelArr, datArrTest, labelArrTest, num):
    classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, num)
    prTrain = evaluate(aggClassEst, labelArr)
    aggClassEst = adaClassify(datArrTest, classifierArr)
    prTest = evaluate(aggClassEst, labelArrTest)
    return prTrain, prTest


datArr, labelArr = loadDataSet("horseColicTraining2.txt")
datArrTest, labelArrTest = loadDataSet("horseColicTest2.txt")
prTrain, prTest = train_test(datArr, labelArr, datArrTest, labelArrTest, 10)
print prTrain, prTest

输出

(0.8595505617977528, 0.7766497461928934) (0.7872340425531915, 0.8604651162790697)

事实上,通过调整分类器的数量(train_test的最后一个参数),可以得到不同的性能。《机器学习实战》验证了1到10000个分类器的错误率:

屏幕快照 2016-04-06 下午10.07.14.png

上图说明分类器并不是越多越好,50是个最好的值,过了这个值,模型发生过拟合,性能越来越低。

ROC曲线

给定分类器在某个数据集上的输出,我们就能计算出假阳率与真阳率,这两个值决定一个坐标点。以假阳率作为x轴,真阳率作为y轴,制定坐标系。

完美的分类器应该是左上角那个点,分类器越接近左上角,就越完美。这样我们就可以较为直观地比较两个不同的分类器了。

在决策函数中,我们使用的是sign来二值化预测强度,以得到最终分类。也就是说,我们取的阈值是0。分类强度离阈值越远,分类的可信度越高。如果我们改变阈值,我们就能得到同一个分类器在坐标系中不同的点,将它们按照阈值大小顺序连成线,就能得到一条ROC曲线。完美的分类器的ROC曲线应该是正方形的左上角对应的两条边,而随机猜测的ROC曲线则是连接左下角与右上角的一条直线。ROC曲线下的面积(AUC)反映了分类器的平均性能。

绘制代码:

def plotROC(predStrengths, classLabels):
    import matplotlib.pyplot as plt
    cur = (1.0, 1.0)  # 中间变量,初始状态为右上角
    ySum = 0.0  # variable to calculate AUC
    numPosClas = sum(array(classLabels) == 1.0)  # TP
    yStep = 1 / float(numPosClas)
    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面积的一个小长条
        # 从 cur 到 (cur[0]-delX,cur[1]-delY) 画一条线
        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--')  # 随机猜测的ROC线
    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
plotROC(aggClassEst.T, labelArr)

效果:

ROC.png

随机猜测的ROC曲线是一条y=x直线,这是因为对真阳率和假阳率相等,意味着分类器猜对和猜错的比率相等,说明该分类器就是在随机猜。

References

《统计学习方法》

《机器学习实战》

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 提升方法

评论 3

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #2

    在adaboost裡面的決策樹,每一個的層數是否都要一樣?

    Minse5年前 (2019-02-20)回复
  2. #1

    您好,最近我在搭建一个日汉的机器翻译系统,但是苦于没有合适的日语分句程序能对text文本进行分句,请问您知道有这样的日语分句工具吗?谢谢~

    cibiluozu8年前 (2016-05-03)回复

我的作品

HanLP自然语言处理包《自然语言处理入门》