机器学习实战(六)AdaBoost元算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhq9695/article/details/83302437

目录

0. 前言

1. AdaBoost

2. 单层决策树

3. 非均衡数据

4. 实战案例

4.1. 马病死亡案例


学习完机器学习实战的AdaBoost元算法,简单的做个笔记。文中部分描述属于个人消化后的理解,仅供参考。

所有代码和数据可以访问 我的 github

如果这篇文章对你有一点小小的帮助,请给个关注喔~我会非常开心的~

0. 前言

在分类任务中,我们通常可以不仅仅采用一个算法,而是多个算法模型结合使用,综合每个弱分类器的结果,称为集成方法(ensemble method)或元算法(meta-algorithm)。

通常有以下两种方法:

  • bagging:自举汇聚法(bootstrap aggregating),从原数据集中选择若干次与原数据集大小相等的新数据集,每个数据的选择是随机的,即新数据集中,数据可能重复,原数据集中有些数据可能也不会出现在新数据集中。
  • boosting:分类器是串行训练的,新分类器根据已有的分类器性能进行训练,集中关注被已有分类器分错的那些数据。

bagging 中,分类器的权重是相等的,boosting 中,分类器的权重不相等。

本篇中,主要介绍 boosting 中的 AdaBoost(adaptive boosting)。

  • 优点:泛化错误率低,易编码
  • 缺点:对离群点敏感
  • 适用数据类型:数值型和标称型数据

1. AdaBoost

AdaBoost 算法流程可描述如下:

  1. 对每个训练样本设定相等的权重,即数据集构成权重向量 D
  2. 训练一个加权错误率最低的最佳弱分类器
  3. 根据加权错误率,计算弱分类器的分类器权重 \alpha
  4. 根据权重向量 D 和弱分类器的分类器权重 \alpha ,更新权重向量 D
  5. 继续训练下一个弱分类器......
  6. 直到弱分类器达到指定数量,或者弱分类器的训练错误率为 0 为止
  7. 预测时,将数据通过每个弱分类器,将其结果加权求和

初始时,权重向量 D 被设定为每个样本相同,即 \frac{1}{m} 。

弱分类器的加权错误率 \varepsilon 定义为错误样本的权重相加。

弱分类器的分类器权重 \alpha 定义如下:

\alpha=\frac{1}{2}\ln(\frac{1-\varepsilon }{\varepsilon })

更新权重向量 D 时,若样本分类正确,则权重下降,若样本分类错误,则权重上升:

\begin{align*} D_i^{(t+1)} &= \frac{D_i^{(t)}e^{-\alpha}}{\sum_{i=1}^{m}D^{(t)}_i}\ (if\ correct) \\ D_i^{(t+1)} &= \frac{D_i^{(t)}e^{\alpha}}{\sum_{i=1}^{m}D^{(t)}_i}\ (if\ error) \end{align*}

注:AdaBoost 每次训练的弱分类器,会集中关注那些被分类错误的样本。

2. 单层决策树

单层决策树(decision stump,也称决策树桩),是仅仅基于单个特征进行分类的弱分类器。

在本篇中,使用单层决策树作为弱分类器,使用三重循环构建最佳的单层决策树:

  1. 第一层循环:遍历每一个特征
  2. 第二层循环:遍历此特征的每一个阈值(即小于或者大于阈值,属于一类)
  3. 第三层循环:遍历小于阈值属于正类大于阈值属于反类,和小于阈值属于反类大于阈值属于正类

每一次都计算加权错误率,最后选择加权错误率最小的单层决策树作为这次的弱分类器。

3. 非均衡数据

对于数据是非均匀的情况,可根据实际情况选择:

  • Precision、Recall、F-score(F1-measure)
  • TPR、FPR、TNR、FNR、AUC
  • Accuracy

例如,宁可将反类判成正类,也不愿将正类判成反类,就可以使用 Recall。

这部分可以详见 吴恩达机器学习(九)Precision、Recall、F-score、TPR、FPR、TNR、FNR、AUC、Accuracy

或者可对数据进行欠抽样(undersampling)和过抽样(oversampling):

  • 欠抽样:删除部分样例,可将离决策边界较远的样例删除
  • 过抽样:复制部分样例,或者可加入与已有样例相似的点

4. 实战案例

以下将展示书中案例的代码段,所有代码和数据可以在github中下载:

4.1. 马病死亡案例

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

"""
马病死亡案例
"""


# 加载数据集
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


# 根据特征和阈值划分数据类别
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = ones((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 = 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)
                # 计算样本是否预测错误
                errArr = mat(ones((m, 1)))
                errArr[predictedVals == labelMat] = 0
                # 计算加权错误率
                weightedError = D.T * errArr
                if weightedError < minError:
                    minError = weightedError
                    bestClasEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump, minError, bestClasEst


# 构建adaboost分类器
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    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)))
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        print("classEst: ", classEst.T)
        # 更新样本权重向量
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
        D = multiply(D, exp(expon)) / D.sum()
        # 更新每个样本的加权分类结果
        aggClassEst += alpha * classEst
        print("aggClassEst: ", aggClassEst.T)
        # 计算当前加权的错误率
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
        errorRate = aggErrors.sum() / m
        print("total error: ", errorRate)
        if errorRate == 0.0: break
    return weakClassArr, aggClassEst


# 分类函数
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)


# 画ROC曲线
def plotROC(predStrengths, classLabels):
    cur = (1.0, 1.0)
    ySum = 0.0
    # 正类数量
    numPosClas = sum(array(classLabels) == 1.0)
    # 1/正类数量
    yStep = 1 / float(numPosClas)
    # 1/反类数量
    xStep = 1 / float(len(classLabels) - numPosClas)
    # 按照从小到大,索引排序
    sortedIndicies = predStrengths.argsort()
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    # 遍历排序后的索引
    # 表示属于正类的概率
    # 因排序,属于正类的概率越来越大,条件越来越苛刻
    # 由初始 TPF->1 FPR->1
    # 最终 TPR->0 FPR->0
    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 = (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()
    # 微积分算AUC面积
    # 对多个小长方块求面积之和
    # 小长方块的宽为 xStep
    # 每一次的长为 cur[1]_i
    # 即 cur[1]_1 * xStep + ... + cur[1]_n * xStep
    # 即 ySum * xStep
    print("the Area Under the Curve is: ", ySum * xStep)


if __name__ == '__main__':
    datArr, labelArr = loadDataSet('horseColicTraining2.txt')
    classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 50)
    testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
    pred = adaClassify(testArr, classifierArr)
    errArr = mat(ones((67, 1)))
    errNum = errArr[pred != mat(testLabelArr).T].sum()
    print(float(errNum) / len(testLabelArr))
    plotROC(aggClassEst.T, labelArr)

如果这篇文章对你有一点小小的帮助,请给个关注喔~我会非常开心的~

猜你喜欢

转载自blog.csdn.net/zhq9695/article/details/83302437