《機器學習Python實現_10_02_集成學習_boosting_adaboost分類器實現》

一.簡介

adaboost是一種boosting方法,它的要點包括如下兩方面:

1.模型生成

每一個基分類器會基於上一輪分類器在訓練集上的表現,對樣本做權重調整,使得錯分樣本的權重增加,正確分類的樣本權重降低,所以當前輪的訓練更加關注於上一輪誤分的樣本;

2.模型組合

adaboost是採用的加權投票的方法

簡單來說,adaboost演算法涉及兩種權重的計算:樣本權重分類器權重,接下來直接講演算法流程

二.演算法流程

輸入:訓練集\(T=\{(x_1,y_1),(x_2,y_2),…,(x_N,y_N)\}\),其中\(x_i\in R^n,y_i\in\{+1,-1\},i=1,2,…,N\)

輸出:最終分類器\(G(x)\)

(1)初始化訓練數據的權重分布:

\[D_1=(w_{11},…,w_{1i},…,w_{1N}),w_{1i}=\frac{1}{N},i=1,2,…,N
\]

(2)對\(m=1,2,…,M:\)

(2.1)使用具有權重分布\(D_m\)的訓練數據集學習,得到基本分類器:\(G_m(x)\)
(2.2)計算\(G_m(x)\)在訓練集上的分類誤差率:\(e_m=\sum_{i=1}^NP(G_m(x_i)\neq y_i)=\sum_{i=1}^Nw_{mi}I(G_m(x_i)\neq y_i)\)
(2.3)計算\(G_m(x)\)的權重係數:\(\alpha_m=\frac{1}{2}ln\frac{1-e_m}{e_m}\)
(2.4)更新訓練樣本權重:

\[w_{m+1,i}=\frac{w_{mi}}{Z_m}exp(-\alpha_my_iG_m(x_i)),i=1,2,…,N
\]

這裡\(Z_m\)是歸一化因子

(3)基於基分類器,構建最終的分類器:

\[G(x)=sign(\sum_{m=1}^M\alpha_mG_m(x))
\]

簡單來說大致流程如下:
png

三.程式碼實現

import os
os.chdir('../')
from ml_models import utils
from ml_models.tree import CARTClassifier
import copy
import numpy as np
%matplotlib inline


"""
AdaBoost分類器的實現,封裝到ml_models.ensemble
"""

class AdaBoostClassifier(object):
    def __init__(self, base_estimator=None, n_estimators=10, learning_rate=1.0):
        """
        :param base_estimator: 基分類器,允許異質;異質的情況下使用列表傳入比如[estimator1,estimator2,...,estimator10],這時n_estimators會失效;
                                同質的情況,單個estimator會被copy成n_estimators份
        :param n_estimators: 基分類器迭代數量
        :param learning_rate: 學習率,降低後續基分類器的權重,避免過擬合
        """
        self.base_estimator = base_estimator
        self.n_estimators = n_estimators
        self.learning_rate = learning_rate
        if self.base_estimator is None:
            # 默認使用決策樹樁
            self.base_estimator = CARTClassifier(max_depth=2)
        # 同質分類器
        if type(base_estimator) != list:
            estimator = self.base_estimator
            self.base_estimator = [copy.deepcopy(estimator) for _ in range(0, self.n_estimators)]
        # 異質分類器
        else:
            self.n_estimators = len(self.base_estimator)

        # 記錄estimator權重
        self.estimator_weights = []

    def fit(self, x, y):
        n_sample = x.shape[0]
        sample_weights = np.asarray([1.0] * n_sample)
        for index in range(0, self.n_estimators):
            self.base_estimator[index].fit(x, y, sample_weight=sample_weights)

            indicates = (self.base_estimator[index].predict(x) == y).astype(int)
            # 計算誤分率
            error_rate = np.sum([sample_weights[j] * (1.0 - indicates[j]) for j in range(0, n_sample)]) / n_sample

            # 計算權重係數
            alpha_rate = 1.0 / 2.0 * np.log((1 - error_rate) / (error_rate + 1e-7))
            alpha_rate = min(10.0, alpha_rate)
            self.estimator_weights.append(alpha_rate)

            # 更新樣本權重
            for j in range(0, n_sample):
                sample_weights[j] = sample_weights[j] * np.exp(-1.0 * alpha_rate * np.power(-1.0, 1 - indicates[j]))
            sample_weights = sample_weights / np.sum(sample_weights) * n_sample
        # 更新estimator權重
        for i in range(0, self.n_estimators):
            self.estimator_weights[i] *= np.power(self.learning_rate, i)

    def predict_proba(self, x):
        # TODO:並行優化
        result = np.sum(
            [self.base_estimator[j].predict_proba(x) * self.estimator_weights[j] for j in
             range(0, self.n_estimators)],
            axis=0)
        return result / result.sum(axis=1, keepdims=True)

    def predict(self, x):
        return np.argmax(self.predict_proba(x), axis=1)
#造偽數據
from sklearn.datasets import make_classification
data, target = make_classification(n_samples=100, n_features=2, n_classes=2, n_informative=1, n_redundant=0,
                                   n_repeated=0, n_clusters_per_class=1, class_sep=.5,random_state=21)

# 同質
classifier = AdaBoostClassifier(base_estimator=CARTClassifier(max_depth=2),n_estimators=10)
classifier.fit(data, target)
utils.plot_decision_function(data, target, classifier)

png

#異質
from ml_models.linear_model import LogisticRegression
from ml_models.svm import SVC
classifier = AdaBoostClassifier(base_estimator=[LogisticRegression(),SVC(kernel='rbf',C=5.0),CARTClassifier()])
classifier.fit(data, target)
utils.plot_decision_function(data, target, classifier)

png

# 權重衰減
classifier = AdaBoostClassifier(base_estimator=[LogisticRegression(),SVC(kernel='rbf',C=5.0),CARTClassifier()],learning_rate=0.5)
classifier.fit(data, target)
utils.plot_decision_function(data, target, classifier)

png

四.問題討論

1.基本要求:弱可學習

注意有個基本要求,那就是\(e_m<0.5\),即分類器至少是弱可學習的,這樣才能保證\(\alpha_m>0\),此時樣本的權重調整(如下公式)才有意義,即正確分類的樣本權重降低,錯誤分類的樣本權重升高:

\[w_{m+1,i}=\left\{\begin{matrix}
\frac{w_{mi}}{Z_m}e^{-\alpha_m}, & G_m(x_i)= y_i \\
\frac{w_{mi}}{Z_m}e^{\alpha_m} & G_m(x_i)\neq y_i
\end{matrix}\right.
\]

對於二分類問題,弱可學習其實是很容易保證的,對於\(e_m>0.5\)的情況,只需要對其預測取反,即可得到\(1-e_m<0.5\)的錯誤率

2.基分類器不支援樣本權重怎麼辦?

對於不能支援樣本權重訓練的基分類器,可以通過樣本重取樣來實現

五.訓練誤差分析

這一部分證明訓練誤差會隨著基分類器的數量增加而指數下降,首先拋出第一個不等式關係:

\[關係式1:\frac{1}{N}\sum_{i=1}^NI(G(x_i)\neq y_i)\leq \frac{1}{N}\sum_{i=1}^Nexp(-y_if(x_i))=\prod_{m=1}^MZ_m
\]

這裡\(f(x)=\sum_{m=1}^M\alpha_mG_m(x),G(x)=sign(f(x)),Z_m\)與上面的定義一樣,前半部分很好證明:如果\(G(x_i)\neq y_i\),則\(y_if(x_i)<0\),所以\(exp(-y_if(x_i))\geq 1=I(G(x_i)\neq y_i)\),而對於\(G(x_i)= y_i\)的情況,顯然有\(exp(-y_if(x_i))\geq 0=I(G(x_i\neq y_i))\)

接下來證明後半部分,根據之前的推導,有如下的兩點條件需要注意:

\[條件1:w_{1i}=\frac{1}{N},i=1,2,…,N\\
條件2:w_{mi}exp(-\alpha_my_iG_m(x_i))=Z_mw_{m+1,i},i=1,2,…,N,m=1,2,…,M
\]

所以:

\[\frac{1}{N}\sum_{i=1}^Nexp(-y_if(x_i))\\
=\frac{1}{N}\sum_{i=1}^Nexp(-\sum_{m=1}^M\alpha_my_iG_m(x_i)))\\
=\sum_{i=1}^N \frac{1}{N}\prod_{m=1}^Mexp(-\alpha_my_iG_m(x_i))\\
=\sum_{i=1}^N w_{1i}\prod_{m=1}^Mexp(-\alpha_my_iG_m(x_i))(用到了條件1)\\
=\sum_{i=1}^N w_{1i}exp(-\alpha_1y_iG_1(x_i))\prod_{m=2}^Mexp(-\alpha_my_iG_m(x_i))\\
=\sum_{i=1}^N Z_1w_{2i}\prod_{m=2}^Mexp(-\alpha_my_iG_m(x_i))(用到了條件2)\\
=Z_1\sum_{i=1}^N w_{2i}\prod_{m=2}^Mexp(-\alpha_my_iG_m(x_i))\\
=Z_1Z_2\sum_{i=1}^N w_{3i}\prod_{m=3}^Mexp(-\alpha_my_iG_m(x_i))\\
=\cdots\\
=\prod_{m=1}^MZ_m
\]

接下來要拋出第二個關係式,對於二分類問題有如下不等式成立:

\[關係式2:\prod_{m=1}^MZ_m=\prod_{m=1}^M[2\sqrt{e_m(1-e_m)}]=\prod_{m=1}^M\sqrt{1-4\gamma_m^2}\leq exp(-2\sum_{i=1}^M\gamma_m^2)
\]

這裡:\(\gamma_m=\frac{1}{2}-e_m\),首先證明等式部分,由前面的演算法部分,我們知道\(e_m=\sum_{i=1}^Nw_{mi}I(G_m(x_i)\neq y_i)\),所以:

\[Z_m=\sum_{i=1}^Nw_{mi}exp(-\alpha_my_iG_m(x_i))\\
=\sum_{y_i=G_m(x_i)}w_{mi}e^{-\alpha_m}+\sum_{y_i\neq G_m(x_i)}w_{mi}e^{\alpha_m}\\
=(1-e_m)e^{-\alpha_m}+e_me^{\alpha_m}\\
=2\sqrt{e_m(1-e_m)}\\
=\sqrt{1-4\gamma_m^2}
\]

至於不等式部分,其實對於\(\forall 0\leq x\leq 1\),都有\(e^{-x/2}\geq \sqrt{1-x}\)恆成立(證明從略,直觀理解如下圖),將\(x\)替換為\(4\gamma_m^2\)即可得到上面的不等式,從而關係式2得到證明;

接下來簡單做一個推論:一定能找到一個\(\gamma>0\),對所有\(\gamma_m\geq\gamma\)成立,則有如下關係:

\[關係式3:exp(-2\sum_{i=1}^M\gamma_m^2)\leq exp(-2M\gamma^2)
\]

結合關係式1、2、3可以得出:

\[\frac{1}{N}\sum_{i=1}^NI(G(x_i)\neq y_i)\leq exp(-2M\gamma^2)
\]

即adaboost的誤差上界會隨著\(M\)的增加以指數速率下降

import matplotlib.pyplot as plt
x=np.linspace(0,1,10)
plt.plot(x,np.sqrt(1-x),'b')
plt.plot(x,np.exp(-0.5*x),'r')
[<matplotlib.lines.Line2D at 0x21a6b0c1048>]

png