AdaBoost

本文介绍了AdaBoost算法,它是二类分类学习方法,基于加性模型,学习中最小化指数损失函数,训练误差以指数速率下降。还阐述了前向分步算法,以及以决策树为基函数的提升树,包括分类和回归问题的提升树,最后指出AdaBoost泛化错误率低但对离群点敏感。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整体把握

大多数提升方法是改变训练数据的概率分布(权值分布).

  • 提高前一轮被错误分类的样本的权重,降低正确分类样本的权重;对于无法接受带权样本的基学习器,可以采用“重采样”
  • 加权多数表决,加大分类误差小的弱分类器权重

算法流程

输入:训练数据集D,y_{i} \in \left \{ -1,+1 \right \}

输出:最终分类器G(x)

  • 1.初始化训练数据的权值分布: D_{1} = \left \{ w_{11},...,w_{1i},...,w_{1N} \right \},\quad w_{1i}=\frac{1}{N}
  • 2.对M = 1,2,...,m

(a)使用具有权值分布D_{m}的训练数据集学习,得到基本分类器G_{m}(x) = \chi \rightarrow \left\{ -1,+1 \right\}

(b)计算在训练数据集上的分类误差率e_{m} = P(G_{m}(x_{i}) \neq y_{i})=\sum_{i=1}^{N}w_{mi}I(G_{m}(x_{i})\neq y_{i}),e_{m}> 0.5,只要基学习器的分类效果比随机效果好,就能改善模型效果

(c)计算G_{m}(x)的系数: \alpha_{m} = \frac{1}{2}ln\frac{1-e_{m}}{e_{m}}

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

D_{m+1} = \left \{ w_{m+1,1},...,w_{m+1,i},...,w_{m+1,N} \right \},\quad w_{m+1,i}=\frac{w_{mi}}{Z_{m}}exp(-\alpha_{m}y_{i}G_{m}(x_{i}))

Z_{m}=\sum_{i=1}^{N}w_{mi}exp(-\alpha_{m}y_{i}G_{m}(x_{i}))是规范化因子,让D_{m+1}成为一个概率分布

  • 3.构建基本分类器的线性组合f(x) = \sum_{m=1}^{M}\alpha_{m}G_{m}(x),得到最终分类器G(x) = sign(f(x))=sign\left ( \sum_{m=1}^{M}\alpha_{m}G_{m}(x) \right )

训练误差分析

AdaBoost最基本的性质是基于"加性模型",在学习过程中不断最小化指数损失函数:

L(G(x)|D) = E_{x \in D}\left [ exp(-f(x)G(x)) \right ]

由此得到, AdaBoost算法最终分类器的训练误差界为, 其中\gamma >0,对所有m有\gamma _{m}\geqslant \gamma

\frac{1}{N}\sum_{i=1}^{N}I(G(x_{i})\neq y_{i})\leqslant \frac{1}{N}exp(-y_{i}f(x_{i}))=\prod _{m} Z_{m}\leqslant exp(-2M\gamma ^{2})

这表明在此条件下AdaBoost的训练误差是以指数速率下降的. AdaBoost具有适应性,即它能适应弱分类器各自的训练误差率。这也是它的名称(适应的提升)的由来,Ada是Adaptive的简写

为什么用指数损失函数?

其可微性,可以替代l_{0,1}损失函数作为优化目标

AdaBoost算法是模型为加法模型、损失函数为指数函数、学习算法为前向分步算法时的二类分类学习方法。

前向分步算法

加法模型:f(x) = \sum_{m=1}^{M}\beta _{m}b(x;\gamma _{m}),b(x;\gamma _{m})为基函数;\gamma _{m}基函数参数,\beta _{m}基函数权重系数

在给定训练数据及损失函数L(Y,f(X))的条件下,学习加法模型f(x)成为经验风险极小化即损失函数极小化问题:

\min_{\beta_{m},\gamma_{m}}\sum_{i=1}^{N}L(y_{i},\sum_{i=1}^{N}\beta _{m}b(x_{i};\gamma _{m}))

这是复杂的优化问题。前向分步算法求解这一优化问题的想法是:从前向后,每一步只学习一个基函数及其系数,逐步逼近优化目标函数式。每步只需优化如下损失函数:

\min_{\beta,\gamma}\sum_{i=1}^{N}L(y_{i},\beta b(x_{i};\gamma ))

输入:训练数据集D,y_{i} \in \left \{ -1,+1 \right \},损失函数L(Y,f(X));基函数集\left \{ b(x;\gamma ) \right \}

输出:加法模型f(x)

  • 1.初始化f_{0}(x)=0
  • 2.对m=1,2,...,M

(a) 极小化损失函数(\beta_{m},\gamma_{m} ) = arg\min_{\beta,\gamma}\sum_{i=1}^{N}L(y_{i},f_{m-1}(x_{i})}+\beta b(x_{i};\gamma )),得到参数\beta_{m} ,\gamma_{m}

(b)更新f_{m}(x) = f_{m-1}(x) + \beta _{m}b(x;\gamma_{m})

  • 3.得到加法模型f(x) = f_{M}(x) = \sum_{m=1}^{M}\beta_{m}b(x;\gamma _{m})

这样,前向分步算法将同时求解从m=1到M所有参数\beta_{m} ,\gamma_{m}的优化问题简化为逐次求解各个\beta_{m} ,\gamma_{m}的优化问题

boosting tree

Boosting实际采用加法模型与前向分步算法。以决策树为基函数的提升方法称为提升树。对分类问题决策树是二叉分类树,对回归问题决策树是二叉回归树。提升树模型可以表示为决策树的加法模型:

f_{M}(x)= \sum_{m=1}^{M}T(x;\Theta _{m})

其中,T(x;\Theta _{m})表示决策树;\Theta _{m}为决策树的参数;M为树的个数

提升树算法采用前向分步算法。

  • 首先确定初始提升树f_{0}(x)=0
  • 第m歩的模型是f_{m}(x) = f_{m-1}(x)+T(x;\Theta _{m})其中,f_{m-1}(x)为当前模型,
  • 通过经验风险极小化确定下一棵决策树的参数\widehat{\Theta} _{m}= arg\min_{\Theta _{m}}\sum_{i=1}^{N}L(y_{i},f_{m-1}(x_{i})+T(x_{i};\Theta _{m}))

不同问题的提升树学习算法,其主要区别在于使用的损失函数不同。包括用平方误差损失函数的回归问题,用指数损失函数的分类问题

分类问题的提升树

对于二类分类问题,提升树算法只需将AdaBoost算法中的基本分类器限制为二类分类树即可,可以说这时的提升树算法是AdaBoost算法的特殊情况


"""基于单层决策树构建AdaBoost分类器"""
import numpy as np
from sklearn.datasets import make_hastie_10_2
import matplotlib.pyplot as plt


def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    """
    通过阈值比较对数据进行分类的,阈值一边分为-1,另一边为+1。通过数组过滤实现
    :param dataMatrix:
    :param dimen: 特征维度的索引
    :param threshVal: 阈值
    :param threshIneq: < 或者 其他
    :return:
    """
    retArray = np.ones((np.shape(dataMatrix)[0], 1))            #[n_samples,1]
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray


def buildStump(dataArr, classLabels, D):
    """
    遍历stumpClassify函数所有的可能输入值,并找到数据集上最佳的单层决策树,这里"最佳"是基于数据的权重D来定义
    :param dataArr:
    :param classLabels:
    :param D:
    :return:
    流程:
    将最小误差率 minError设为无穷
    For every feature in the dataset(第一层循环):
        对每个步长(第二层循环):
            对每个不等号(第三层循环):
                建立一颗单层决策树并利用加权数据集对它进行测试
                如果错误率低于minError,则将当前单层决策树设为最佳单层决策树
    Return the best stump
    """
    dataMatrix = np.mat(dataArr);
    labelMat = np.mat(classLabels).T
    m, n = np.shape(dataMatrix)
    numSteps = 10.0                                         # 用于在特征的所有可能值上进行遍历
    bestStump = {}                                          # 存储给定权重向量D时所得到的最佳单层决策树的weights
    bestClasEst = np.mat(np.zeros((m, 1)))
    minError = np.inf                                          # 初始化为无穷大,之后寻找最小错误率
    for i in range(n):  # loop over all dimensions
        rangeMin = dataMatrix[:, i].min()
        rangeMax = dataMatrix[:, i].max()
        stepSize = (rangeMax - rangeMin) / numSteps
        for j in range(-1, int(numSteps) + 1):  # loop over all range in current dimension
            for inequal in ['lt', 'gt']:  # go over less than and greater than
                threshVal = (rangeMin + float(j) * stepSize)
                predictedVals = stumpClassify(dataMatrix, i, threshVal,
                                              inequal)  # call stump classify with i, j, lessThan
                errArr = np.mat(np.ones((m, 1)))
                errArr[predictedVals == labelMat] = 0
                weightedError = D.T * errArr  # calc total error multiplied by D
                # 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


def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    """
    对每次迭代:
        利用buildStump()函数找到最佳的单层决策树
        将最佳单层决策树加入到单层决策树数组
        计算alpha
        计算新的权重向量D
        更新累计类别估计值
        如果错误率为0。0,则退出循环
    :param dataArr:
    :param classLabels:
    :param numIt:
    :return:
    """
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m, 1)) / m)  # init D to all equal
    aggClassEst = np.mat(np.zeros((m, 1)))
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)  # build Stump
        # print "D:",D.T
        alpha = float(
            0.5 * np.log((1.0 - error) / max(error, 1e-16)))  # calc alpha, throw in max(error,eps) to account for error=0
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)  # store Stump Params in Array
        # print "classEst: ",classEst.T
        expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)  # exponent for D calc, getting messy
        D = np.multiply(D, np.exp(expon))  # Calc New D for next iteration
        D = D / D.sum()
        # calc training error of all classifiers, if this is 0 quit for loop early (use break)
        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)
        if errorRate == 0.0: break
    return weakClassArr, aggClassEst


def adaClassify(datToClass, classifierArr):
    """
    利用训练得到的多个弱分类器进行分类
    :param datToClass:
    :param classifierArr:
    :return:
    """
    dataMatrix = np.mat(datToClass)  # do stuff similar to last aggClassEst in adaBoostTrainDS
    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'])  # call stump classify
        aggClassEst += classifierArr[i]['alpha'] * classEst
        print(aggClassEst)
    return np.sign(aggClassEst)

if __name__ == "__main__":
    X, y = make_hastie_10_2(n_samples=12000, random_state=1)
    X_test, y_test = X[2000:], y[2000:]
    X_train, y_train = X[:2000], y[:2000]
    classifierArray = adaBoostTrainDS(X_train, y_train, 20)
    prediction10 = adaClassify(X_test, classifierArray[0])
    errArr = np.mat(np.ones((10000, 1)))
    print(errArr[prediction10!=np.mat(y_test).T].sum())

回归问题的提升树

如果将输入空间x划分为J个互不相交的区域R1,R2,…,RJ,并且在每个区域上确定输出的常量cj,那么树可表示为

T(x;\Theta )=\sum_{j=1}^{J}c_{j}I(x \in R_{j})

参数\Theta = \left \{ (R_{1},c_{1}),...,(R_{J},c_{J}) \right \}表示树的区域划分和各区域上的常数。J是回归树的复杂度即叶结点个数.

当采用平方误差损失函数时L(y,f(x)) = (y-f(x))^{2}

其损失变为L(y,f_{m-1}(x)+T(x;\Theta _{m})) = \left [ y-f_{m-1}(x)-T(x;\Theta _{m}) \right ]^{2} = \left [ r-T(x;\Theta _{m}) \right ]^{2}

这里r =y-f_{m-1}(x),是当前模型拟合数据的残差,即将残差作为下一个回归树的输出目标值。所以,对回归问题的提升树算法来说,只需简单地拟合当前模型的残差。

  • 1.初始化f_{0}(x)=0
  • 2.对m=1,2,...,M

(a) 计算残差r_{mi} =y_{i}-f_{m-1}(x_{i}),\quad i=1,2,...,N

(b)拟合残差r_{mi}学习一个回归树,得到T(x;\Theta _{m})

(c)更新f_{m}(x) = f_{m-1}(x)+T(x;\Theta _{m})

  • 3.得到回归问题提升树

f(x) = f_{M}(x) = \sum_{m=1}^{M}T(x;\Theta _{m})

gradient boosting

提升树利用加法模型与前向分歩算法实现学习的优化过程。当损失函数是平方损失和指数损失函数时,每一步优化是很简单的。但对一般损失函数而言,往往每一步优化并不那么容易。如果我们的模型能够让损失函数持续的下降,则说明我们的模型在不停的改进,而最好的方式就是让损失函数在其梯度方向上下降。针对这一问题, 利用最速下降法的近似方法,其关键是利用损失函数的负梯度在当前模型的值

-\left [ \frac{\partial L(y,f(x_{i}))}{\partial f(x_{i})} \right ]_{f(x)=f_{m-1}(x)}

作为回归问题提升树算法中的残差的近似值,拟合一个回归树.即利用损失函数的负梯度在当前模型的值,作为回归问题中提升树算法的残差的近似值,拟合一个回归树

“输入:训练数据集T;损失函数L(Y,f(X))
输出:回归树\widehat{f}(x)

  • 1.初始化f_{0}(x)=arg\min_{c}\sum_{i=1}^{N}L(y_{i},c),估计使损失函数极小化的常数值,它是只有一个根结点的树

    2.对m=1,2,…,M

         (a)对i=1,2,…,N,计算r_{mi} = -\left [ \frac{\partial L(y,f(x_{i}))}{\partial f(x_{i})} \right ]_{f(x)=f_{m-1}(x)}

        (b)对r_{mi}拟合一个回归树,得到第m棵树的叶结点区域R_{mj},j=1,2,…,J
         (c)对j=1,2,…,J,计算c_{mj}=arg\min_{c}\sum_{x_{i} \in R_{mj}}L(y_{i},f_{m-1}(x_{i})+c),线性搜索估计叶结点区域的值,使损失函数极小化

         (d)更新f_{m}(x) = f_{m-1}(x)+\sum_{j=1}^{J}c_{mj}I(x \in R_{mj})

  • 3.得到回归树

\widehat{f}(x) = f_{M}(x) = \sum_{m=1}^{M}\sum_{j=1}^{J}c_{mj}I(x \in R_{mj})

总结

优点:泛化错误率低;无参数调整

缺点:对离群点数据敏感

### AdaBoost算法的实现原理 AdaBoost(Adaptive Boosting)是一种经典的提升(Boosting)算法,最初由Yoav Freund和Robert Schapire于1995年提出[^1]。该算法的核心思想在于通过多次迭代训练多个弱学习器,并根据每次预测的结果调整样本权重,使得后续的学习器更加关注之前被错误分类的样本。 具体来说,AdaBoost的工作流程如下: 1. 初始化数据集中的每个样本权重为相等值。 2. 在每一轮迭代中,基于当前样本权重训练一个弱学习器。 3. 计算弱学习器的误差率以及对应的权重系数α。 4. 更新样本权重,增加那些被误分类样本的权重,减少正确分类样本的权重。 5. 将本轮得到的弱学习器加入到最终模型中,形成加权投票机制。 6. 循环上述过程直到达到预设的最大迭代次数或满足其他停止条件。 这种逐步改进的过程能够有效提高整体模型的表现能力。 ```python from sklearn.ensemble import AdaBoostClassifier from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split # 创建模拟数据集 X, y = make_classification(n_samples=1000, n_features=20, n_informative=2, n_redundant=10, random_state=42) # 划分训练集与测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42) # 构建AdaBoost分类器,默认基估计器为决策树桩 ada_clf = AdaBoostClassifier(n_estimators=50, learning_rate=1.0, random_state=42) # 模型拟合 ada_clf.fit(X_train, y_train) # 输出模型得分 print(f"Training Accuracy: {ada_clf.score(X_train, y_train)}") print(f"Test Accuracy: {ada_clf.score(X_test, y_test)}") ``` 以上代码展示了如何利用`scikit-learn`库快速构建并评估一个简单的AdaBoost分类器[^2]。 ### 使用案例分析 AdaBoost在实际应用中有许多成功案例,尤其是在二分类问题上表现出色。例如,在金融领域可以用于信用评分;在医疗诊断方面可用于疾病检测;另外还常见于图像处理任务如人脸识别等领域。由于其灵活性高且易于扩展至多类别的场景下工作,因此具有广泛的适用范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值