AdaBoost算法实例详解

AdaBoost算法其实很精炼,算法流程也好理解,但是看了算法的解释版本之后,什么前向分布算法,什么指数损失函数之后有点迷糊了。抛开这些理论性的推导不谈(其实是因为能力有限),通过例子直观的了解AdaBoost算法的计算过程。


简要叙述一下AdaBoost算法的主要过程:

AdaBoost为每个数据样本分配权重,权重符合概率分布,初始权重符合均匀分布,串行训练M个模型,依据每轮训练的模型的错误率(被误分类样本的权重之和)确定当前模型在最终模型中的权重,以及更新训练样本的权重,误分类样本权重升高,分类正确的样本权重降低。

下图的算法流程来自于《统计学习方法》。

AdaBoost算法实例详解_第1张图片

AdaBoost算法实例详解_第2张图片


下面通过具体的实例来理解AdaBoost算法的流程,例子来自于《统计学习方法》。

AdaBoost算法实例详解_第3张图片

第一轮迭代:

AdaBoost算法实例详解_第4张图片

此时得到的组合模型中只有一个 ,此时 的分类结果就是最终模型的分类结果。第一轮迭代中6,7,8(6,7,8指的是x的值,不是指的序号)被误分类。此时得到的组合模型在训练数样本上的预测结果如下:

X

y

分类结果

0

1

0.4236

0.4236

1

正确

1

1

0.4236

0.4236

1

正确

2

1

0.4236

0.4236

1

正确

3

-1

-0.4236

-0.4236

-1

正确

4

-1

-0.4236

-0.4236

-1

正确

5

-1

-0.4236

-0.4236

-1

正确

6

1

-0.4236

-0.4236

-1

错误

7

1

-0.4236

-0.4236

-1

错误

8

1

-0.4236

-0.4236

-1

错误

9

-1

-0.4236

-0.4236

-1

正确

其中sign符号函数如下:

第二轮迭代:

AdaBoost算法实例详解_第5张图片

第二轮迭代中3,4,5被误分类,此时得到的最终模型是前两轮模型的线性组合。那么在当前的组合条件下 的分类结果是怎样的?

X

y

分类结果

0

1

0.4236

0.6496

1.0732

1

正确

1

1

0.4236

0.6496

1.0732

1

正确

2

1

0.4236

0.6496

1.0732

1

正确

3

-1

-0.4236

0.6496

0.226

1

错误

4

-1

-0.4236

0.6496

0.226

1

错误

5

-1

-0.4236

0.6496

0.226

1

错误

6

1

-0.4236

0.6496

0.226

1

正确

7

1

-0.4236

0.6496

0.226

1

正确

8

1

-0.4236

0.6496

0.226

1

正确

9

-1

-0.4236

-0.6496

-1.0732

-1

正确

第三轮迭代:

AdaBoost算法实例详解_第6张图片

AdaBoost算法实例详解_第7张图片

第三轮迭代中0,1,2,9被误分类,此时得到的最终模型是前三轮模型的线性组合。那么在当前的组合条件下 的分类结果是怎样的?

X

y

分类结果

0

1

0.4236

0.6496

-0.7514

0.3218

1

正确

1

1

0.4236

0.6496

-0.7514

0.3218

1

正确

2

1

0.4236

0.6496

-0.7514

0.3218

1

正确

3

-1

-0.4236

0.6496

-0.7514

-0.5254

-1

正确

4

-1

-0.4236

0.6496

-0.7514

-0.5254

-1

正确

5

-1

-0.4236

0.6496

-0.7514

-0.5254

-1

正确

6

1

-0.4236

0.6496

0.7514

0.9774

1

正确

7

1

-0.4236

0.6496

0.7514

0.9774

1

正确

8

1

-0.4236

0.6496

0.7514

0.9774

1

正确

9

-1

-0.4236

-0.6496

0.7514

-0.3218

-1

正确

经过三轮迭代之后,在训练集上的错误率为0。


python实现的AdaBoost算法如下,代码来源于《机器学习实战》。

# coding:utf-8

import numpy as np


def loadSimpData():
    dataMat = [
        [1.0, 2.1],
        [2.0, 1.1],
        [1.3, 1.0],
        [1.0, 1.0],
        [2.0, 1.0]
    ]
    classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
    return dataMat, classLabels


def stumpClassify(dataMatrix, dimen, threshval, threshIneq):
    """
    单层决策树分类决策函数,根据指定特征,指定特征的划分阈值,指定特征的划分条件(大于或者小于)构建决策树
    :param dataMatrix: 数据矩阵
    :param dimen: 分支特征索引编号,按照每个特征进行节点分支
    :param threshval: 分支阈值
    :param threshIneq: 分支类别(大于或者小于)
    :return: 返回分类结果
    """
    retArray = np.ones(shape=(dataMatrix.shape[0], 1))  # 初始化所有样本的分类结果为+1
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshval] = -1.0  # 将第dimen个特征小于threshval的样本标记为-1
    else:
        retArray[dataMatrix[:, dimen] > threshval] = -1.0  # 将第dimen个特征大于threshval的样本标记为+1
    return retArray


def buildStump(dataArr, classLabels, D):
    """
    遍历决策树的所有特征,所有特征的划分阈值,所有特征的划分条件,寻找最佳单层决策树桩(只有一层的决策树)
    :param dataArr: 数据样本
    :param classLabels: 数据样本标签
    :param D: 数据样本权重
    :return: 最佳单层决策树分类信息、最低错误率、最优分类结果
    """
    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    m, n = dataMatrix.shape
    numSteps = 10.0  # 为了寻找最优的划分点,总共尝试的次数
    bestStump = {}
    bestClasEst = np.mat(np.zeros(shape=(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)  # 计算决策树的分支阈值,当j=-1或j=numSteps + 1时,是单分支决策树
                predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)  # 计算在当前分支阈值条件下,决策树的分类结果
                errArr = np.mat(np.ones(shape=(m, 1)))  # errArr矩阵用于保存决策树的预测结果
                errArr[predictedVals == labelMat] = 0   # 将errArr矩阵中被当前决策树分类正确的样本对应位置的值置为0
                weightedError = D.T * errArr  # 计算分类错误率(按位相乘并求和),错误率=所有分类错误样本的权重求和
                # print("split: dimension %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


def adaBoostingTrainDS(dataArr, classLabels, numIter=40):
    """
    训练AdaBoost集成模型
    :param dataArr: 输入样本数据
    :param classLabels: 样本数据标签
    :param numIter: 训练迭代次数
    :return: 每轮迭代的最佳弱决策树信息(特征索引编号、分类阈值、分类条件、分类器权重alpha)
    """
    dataArr = np.mat(dataArr)
    weakClassArr = []   # 用于记录各个弱分类器的信息
    m = dataArr.shape[0]  # 训练样本数量
    D = np.mat(np.ones(shape=(m, 1)) / m)  # 向量D用来保存样本权重,初始权重相等
    aggClassEst = np.mat(np.zeros(shape=(m, 1)))  # 最终得到的分类函数
    for i in range(numIter):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)  # 最佳决策树信息(特征编号,阈值,分支条件)、错误率、分类结果
        # print("D: ", D.T)
        alpha = float(0.5 * np.log((1 - error) / max(error, 1e-16)))  # 基于当前弱分类器的分类错误率计算该分类器的最终决策权重
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        # print("classEst: ", classEst.T)

        # 对应统计学习方法中公式8.3、8.4、8.5
        expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)  # 更新样本权重,分类错误的样本权重增加,分类正确的样本权重减少
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()  # 将D归一化为一个概率分布,D中元素的和为1
        aggClassEst += alpha * classEst  # 根据权重整合弱分类器,就是将每个样本对应弱分类器的分类结果乘以对应的alpha然后求和,然后使用sign进行符号化
        # print("aggClassEst: ", aggClassEst.T)
        # print("np.sign -> ", np.sign(aggClassEst) != np.mat(classLabels).T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones(shape=(m, 1)))  # 统计集成后的模型预测错误的样本数量
        errorRate = aggErrors.sum() / m  # 计算错误率
        print("total error: ", errorRate)
        if errorRate == 0.0:  # 直到aggClassEst对全部样本预测正确为止
            break
    return weakClassArr, aggClassEst


def adaBoostingClassify(dataToClassify, classifierArr):
    """
    使用训练好的AdaBoost集成模型进行预测
    :param dataToClassify: 数据样本
    :param classifierArr: 训练得到的AdaBoost弱模型以及模型对应的权重alpha
    :return: 返回数据集的分类结果
    """
    dataMatrix = np.mat(dataToClassify)  # 待预测的输入数据
    m = dataMatrix.shape[0]
    aggClassEst = np.mat(np.zeros(shape=(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: ", aggClassEst)
    return np.sign(aggClassEst)  # 输出最终分类结果,累加结果符号化


if __name__ == '__main__':
    dataMat, labelMat = loadSimpData()
    D = np.mat(np.ones(shape=(5, 1)) / 5)
    # print(D)
    # 计算每轮迭代的最佳决策树分类方法
    print("#" * 80)
    bestStump, minError, bestClasEst = buildStump(dataMat, labelMat, D)
    print(bestStump)
    print(minError)
    print(bestClasEst)

    # 训练得到的一些列弱分类器信息
    print("#" * 80)
    classifyArr, aggClassEst = adaBoostingTrainDS(dataMat, labelMat, numIter=9)
    print(classifyArr)
    print(aggClassEst)

    # 测试adaBoosting算法
    print("#" * 80)
    testSet = np.array(
        [
            [0.5, 1.0],
            [1.5, 2.0],
            [0.5, 1.1],
            [0.0, 1.0],
            [1.0, 2.0],
            [0.0, 0.0]
        ]
    )
    classifierResult = adaBoostingClassify(testSet, classifyArr)
    print(classifierResult)

 

你可能感兴趣的:(机器学习,python编程)