AdaBoost算法原理及实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_26658823/article/details/81045977

AdaBoost算法

输入:训练数据集T={(x1,y1),(x2,y2),,(xN,yN)},其中xiXRnyi{1,+1};弱学习算法;
输出:最终分类器G(x)
(1)初始化训练数据集的权值分布。我们假设训练数据集具有均匀的权值分布,也就是说每个训练样本在基分类器的学习中作用相同。

D(1)=(w1(1),w2(1),,wN(1)),wi(1)=1N,i=1,2,,N

每个w的上标表示当前迭代次数,与D的下标保持一致;w的下标表示第几个权值,与位置保持一致。
(2)对m=1,2,,M(M表示迭代次数,每迭代一次产生一个基学习器,最终生成M个学习器)
(a)使用具有权值分布Dm的训练数据集学习,得到基分类器
Gm(x):X{1,+1}

(b)计算Gm(x)在训练数据集上的分类误差
errm=P(Gm(xi)yi)=i=1Nwi(m)I(Gm(xi)yi)

这里,wi(m)表示第m轮迭代中第i个实例的权值,i=1Nwi(m)=1。这说明Gm(x)在带权重的训练数据集上的分类误差是被Gm(x)误分类样本的权值之和。
(c)计算Gm(x)的系数αm
αm=12ln1errmerrm

αm表示Gm(x)在最终分类器中的重要程度。当errm12时,αm0,且α随着errm的减小而增大,也就是说,分类误差越小的基分类器在最终分类器中的权重越大。
(d)更新训练数据集的权值分布,为下一轮迭代做准备
D(m+1)=(w1(m+1),w2(m+1),,wN(m+1))wi(m+1)=wi(m)Zmexp(αmyiGm(xi))

上面的式子可以写成
wi(m+1)={wi(m)Zmeαm,Gm(xi)=yiwi(m)Zmeαm,Gm(xi)yi

其中,Zm是规范化因子,它的存在保持了Dm+1是一个概率分布
Zm=i=1Nwi(m)exp(αmyiGm(xi))

由此可知,被基分类器Gm(x)误分类的样本的权值会增大,而被正确分类的样本的权值会减小。此消彼长之下,误分类样本的权值会被放大e2αm=1errmerrm倍。因此,误分类样本在下一轮学习中起到更大的作用。不改变所给的训练数据而不断改变训练数据权值的分布,使得训练数据在基分类器的学习中起到不同的作用,这是AdaBoost的一个特点。
(3)构建基本分类器的线性加权组合
f(x)=m=1MαmGm(x)

得到最终分类器
G(x)=sign(f(x))=sign(m=1MαmGm(x))

线性组合f(x)实现M个基分类器的加权表决。系数αm表示基分类器的权重,注意这里m=1Mαi1f(x)的符号决定了实例xi的所属类别。利用基分类器的线性组合构建最终分类器是AdaBoost的另一个特点。

AdaBoost算法的推导

AdaBoost有多种推导方式,比较容易理解的是基于加法模型和前向分步算法来最小化指数损失函数。下面我们首先介绍前向分步算法。

前向分步算法

考虑如下加法模型:

f(x)=m=1Mβmb(x;γm)

其中b(x;γm)为基函数,γm为基函数的参数,βm为基函数的系数。显然,f(x)=m=1MαmGm(x)是一个加法模型。
在给定训练数据及损失函数L(y,f(x))的条件下,学习加法模型f(x)成为经验风险极小化即损失函数极小化问题:
minβm,γmi=1NL(yi,m=1Mβmb(xi;γm))

因为涉及的参数较多,所以这是一个比较复杂的优化问题。前向分步算法求解这一优化问题的思路是:因为学习的是加法模型,如果能够从前向后,每一步只学习一个基函数及其系数,逐步逼近优化目标函数minβm,γmi=1NL(yi,m=1Mβmb(xi;γm)),那么就可以简化优化的复杂度。具体来说,每一步只需优化下面的损失函数:
minβ,γi=1NL(yi,βb(xi;γ))

给定训练数据集T={(x1,y1),(x2,y2),,(xN,yN)},其中xiXRnyi{1,+1}。损失函数L(y,f(x))和基函数集合{b(x,γ)},学习加法模型fx的前向分步算法如下。
输入:训练数据集T={(x1,y1),(x2,y2),,(xN,yN)};损失函数L(y,f(x));基函数集{b(x,γ)}
输出:加法模型f(x)
(1)初始化f0(x)=0
(2)对m=1,2,,M
(a)极小化损失函数
(βm,γm)=argminβ,γi=1NL(yi,fm1(xi)+βb(xi;γ))

得到参数βmγm
(b)更新
fm(x)=fm1(x)+βmb(x;γm)

(3)得到加法模型
f(x)=fM(x)=m=1Mβmb(x;γm)

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

前向分步算法与AdaBoost

可以看出,AdaBoost算法是前向分步算法的特例,此时,模型是由基分类器组成的加法模型,损失函数是指数函数。下面我们证明AdaBoost算法是前向分步算法的特殊情形。
首先,前向分步算法学习的是加法模型,当基函数为基分类器时,该加法模型等价于AdaBoost算法的最终分类器:

f(x)=m=1MαmGm(x)

该分类器由基分类器Gm(x)及其系数αm组成。前向分步算法逐一学习基函数,这一过程与AdaBoost算法逐一学习基分类器的过程类似。下面证明前向分步算法的损失函数是指数损失函数
L(y,f(x))=exp[yf(x)]

时,其学习的具体过程等价于AdaBoost算法学习的具体过程。
假设经过m1轮迭代,前向分步算法已经得到fm1(x)
fm1(x)=fm2(x)+αm1Gm1(x)=fm3(x)+αm2Gm2(x)+αm1Gm1(x)=α1G1(x)+α2G2(x)++αm1Gm1(x)

在第m轮迭代得到αmGm(x)fm(x)
fm(x)=fm1(x)+αmGm(x)

目标是使前向分步算法得到的αmGm(x)使fm(x)在训练集T上的指数损失最小,即
(αm,Gm(x))=argminα,Gi=1Nexp[yi(fm1(xi)+αG(xi))]

上式可表示为
(αm,Gm(x))=argminGi=1Nwi(m)exp[yiαG(xi)]

其中,wi(m)=exp[yifm1(x)]。因为wi(m)即不依赖α也不依赖G,所以与最小化无关。但它依赖于fm1(x),随着每一轮迭代而改变。
现在证明使(αm,Gm(x))=argminGi=1Nwi(m)exp[yiαG(xi)]达到最小的αmGm(x)就是AdaBoost算法所得到的αmGm(x)。求解该式可分两步:
首先,求解Gm(x)。对于任意的α>0,使该式最小的Gm(x)由下式得到:
Gm(x)=argminGi=1Nwi(m)I(yiG(xi))

此分类器Gm(x)即为AdaBoost算法的基分类器Gm(x),因为它是使第m轮加权训练数据分类误差最小的基分类器。
之后,求αm
    i=1Nwi(m)exp[yiαG(xi)]=yi=Gm(xi)wi(m)eα+yiGm(xi)wi(m)eα=yi=Gm(xi)wi(m)eα+yiGm(xi)wi(m)eα+yiGm(xi)wi(m)eαyiGm(xi)wi(m)eα=(eαeα)i=1Nwi(x)I(yiG(xi))+eαi=1Nwi(x)

yiG(xi)的符号一致时,指数为负,反之为正。第二个等号也是利用这个原理,只不过换成了用指示函数I(·)表述。将已求得的Gm(x)代回上式,对α求偏导,得到
Lα=(eα+eα)i=1Nwi(m)I(yiG(xi))eαi=1Nwi(m)

Lα=0,得
eαi=1Nwi(m)I(yiG(xi))=eα[i=1Nwi(m)i=1Nwi(m)I(yiG(xi))]

两边同时取自然对数,得
α+lni=1Nwi(m)I(yiG(xi))=α+ln[i=1Nwi(m)i=1Nwi(m)I(yiG(xi))]

移项,得
2α=ln[i=1Nwi(m)i=1Nwi(m)I(yiG(xi))]lni=1Nwi(m)I(yiG(xi))

最终得到
α=12lni=1Nwi(m)i=1Nwi(m)I(yiG(xi))i=1Nwi(m)I(yiG(xi))

其中,errm是分类误差
errm=i=1Nwi(m)I(yiG(xi))i=1Nwi(m)

所以αm可以写成
αm=12ln1errmerrm

这与AdaBoost算法第2(c)步的权值更新公式相同。
求出了Gm(x)αm,就可以写出每一轮迭代中f(x)的更新公式
fm(x)=fm1(x)+αmGm(x)

根据wi(m)=exp(yifm1(xi)),可以得到w的更新公式
wi(m+1)=exp(yifm(xi))=exp(yi(fm1(xi)+αmGm(xi)))=wi(m)exp(yiαmGm(xi))

上式与AdaBoost算法第2(d)步的样本权值更新只相差规范化因子,因而等价。至此,我们证明了当前向分布算法的模型是由基本分类器组成的加法模型、损失函数为指数函数时,AdaBoost算法是前向分步算法的特例。

AdaBoost实现

import numpy as np


class AdaBoost(object):
    """AdaBoost classifier, only support binary classification problem.

    Parameters
    ----------
    base_estimator : estimator object
        base esitimator which implements `fit` and `predict` method.

    n_estimators : int, default : 10
        numbers of base estimator
    """

    def __init__(self, base_estimator=None, n_estimators=10):
        self.base_estimator_ = base_estimator
        self.n_estimators_ = n_estimators
        self.estimators_ = None

    def fit(self, X, y):
        """Fit Adaboost classifier.

        Patameters
        ----------
        X : ndarray, shape=[n_samples, n_features]
            Training data.

        y : ndarray, shape=[n_samples, ]
            Training label.
        Returns
        -------
        self : object
            Return self.
        """
        y = np.where(y == 0, -1, y).reshape(-1, 1)
        n_samples = X.shape[0]
        D = np.ones(y.shape) / n_samples
        n_estimators = self.n_estimators_

        estimators = []

        for _ in range(n_estimators):
            base_estimator = self.base_estimator_
            base_estimator.fit(X, y)
            y_pred = base_estimator.predict(X)
            y_pred = np.where(y_pred == 0, -1, 1).reshape(-1, 1)

            # calculate error
            error = np.ones(y_pred.shape)
            error[y == y_pred] = 0
            weighted_error = np.dot(D.T, error)

            # update alpha
            alpha = .5 * np.log((1 - weighted_error) / max(weighted_error, 1e-16))

            estimators.append((base_estimator, alpha))

            # calculate normalization factor
            Z = np.sum(np.multiply(D, np.exp(-alpha * y_pred * y)))

            # update data distribution
            D = (D / Z) * np.exp(-alpha * y_pred * y)
        self.estimators_ = estimators
        return self

    def predict(self, X):
        """Predict class labels for each sample in X.

        Parameters
        ----------
        X : ndarray

        Returns
        -------
        y_pred : ndarray, shape=[n_samples, ]
            Predicted class label per sample. 
        """
        y_pred = np.zeros((X.shape[0], 1))
        for i in range(self.n_estimators_):
            base_estimator = self.estimators_[i][0]
            alpha = self.estimators_[i][1]
            tmp_y_pred = base_estimator.predict(X).reshape(-1, 1)
            y_pred = y_pred + tmp_y_pred * alpha
        y_pred = np.where(y_pred >= 0, 1, 0).astype(int)
        return y_pred.flatten()

参考资料
[1] 李航. 统计学习方法[M]. 清华大学出版社, 2012.
[2] Hastie T, Tibshirani R, Friedman J H, et al. The Elements of Statistical Learning[M]. 世界图书出版公司, 2015.
[3] 周志华. 机器学习[M]. 清华大学出版社, 2016.

展开阅读全文

没有更多推荐了,返回首页