基於Python的機器學習實戰:AadBoost


目錄:

1. Boosting方法的簡介

2. AdaBoost算法

3.基於單層決策樹構建弱分類器

4.完整的AdaBoost的算法實現

5.總結

1. Boosting方法的簡介 返回目錄

Boosting方法的基本思想:對於一個復雜的任務來說,將多個專家的判斷進行適當的綜合所得出的判斷,要比其中任何一個專家單獨的判斷好. 實際上就是“三個臭皮匠頂個諸葛亮的道理。”(參考:李航 《統計學習方法》)

對於分類問題而言, 給定一個訓練集,求比較粗糙的分類規則(弱分類器)要比求精確的分類規則(強分類器)容易得多。Boosting方法就是從弱學習算法出發,反復學習,得到一系列弱分類器(又稱基本分類器),然后組合這些弱分類器,構成一個強分類器。

對於Boosting方法來說,需要回答兩個問題:

  • 每一輪如何改變訓練數據的權值或者概率分布
  • 如何將若分類器組合成一個強分類器

2. AdaBoost算法 返回目錄

boosting 方法擁有多個版本,其中最流行的一個版本就是AdaBoost,即adaptive boosting.

對與上面提到的兩個問題,AdaBoost的做法分別是:

  • 對於第一個問題:提高那些被前一輪弱分類器錯誤分類的樣本的權值,而降低那些被正確分類樣本的權值.
  • 對於第二個問題:采取加權多數表決的方法,具體就是,加大分類誤差率較小的弱分類器的權值,使其在表決中起較大的作用,減小分類誤差率大的弱分類器的權值,使其在表決中起較小的作用.

具體算法流程描述如下:

假定給定一個二分類的訓練數據集

$T=\{(x_{1},y_{1}),(x_{2,}y_{2}),\cdots,(x_{N},y_{N})\}$

其中,每個樣本點由實例與標記組成. 實例 $x_{i}\in X\subseteq R^{n}$ ,標記 $y_{i}\in Y$$\subseteq R^{n}$, $X$ 是實例空間,$Y$ 是標記集合. 

輸入:訓練數據集 $T=\{(x_{1},y_{1}),(x_{2,}y_{2}),\cdots,(x_{N},y_{N})\}$,其中 $x_{i}\in X\subseteq R^{n}$, $y_{i}\in Y=\{-1,+1\}$;弱分類器;

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

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

$D_{1}=\{w_{11},\cdots,w_{1i},\cdots,w_{1N}\},w_{1N}=\frac{1}{N},i=1,2,\cdots,N$

初始化的時候讓每個訓練樣本在基本分類器的學習中作用相同

(2) 對 $m=1,2,\cdots,M$

(a) 使用具有權值分布 $D_{m}$ 的訓練數據學習,得到基本分類器

$G_{m}(x):X\longrightarrow\{-1,+1\}$

(b) 計算 $G_{m}(x)$ 在訓練數據集上的分類誤差

$e_{m}=P(G_{m}(x_{i})\neq y_{i})=\sum_{i=1}^{N}w_{mi}I(G_{m}(x_{i})\neq y_{i})$ (1)

(c) 計算 $G_{m}(x)$ 的系數

$\alpha_{m}=\frac{1}{2}\log\frac{1-e_{m}}{e_{m}}$ (2)

這里對數是自然對數. $\alpha_{m}$ 表示 $G_{m}(x)$ 在最終分類器中的重要性,由該式可知,當 $e_{m}\leq\frac{1}{2}$ 時,$\alpha_{m}\geq0$,並且 $\alpha_{m}$ 隨着 $e_{m}$ 的減小而增大,所以誤差率越小的基本分類器在最終分類器中的作用越大.

(d) 更新訓練數據集的權值分布

$D_{m+1}=\{w_{m+1,1},\cdots,w_{m+1,i},\cdots,w_{m+1,N}\}$ (3)

$w_{m+1,i}=\frac{w_{mi}}{Z_{m}}\exp(-\alpha_{m}y_{i}G_{m}(x_{i})),i=1,2,\cdots,N$ (4)

這里 $Z_{m}$ 是歸一化因子.

$Z_{m}=\sum_{i=1}^{N}w_{mi}\exp(-\alpha_{m}y_{i}G_{m}(x_{i}))$

它使 $D_{m+1}$ 成為一個概率分布. 式 (4) 還可以寫成:

\[w_{m+1,i}=\left\{ \begin{array}{c}\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{array}\right.\]

由此可知,被基本分類器 $G_{m}(x)$ 誤分類樣本的權值得以擴大,而被正確分類樣本的權值卻得以縮小,因此誤分類樣本在下一輪學習中起更大作用.

(3) 構建基本分類器的線性組合

$f(x)=\sum_{i=1}^{N}\alpha_{m}G_{m}(x)$

得到最終分類器

$G(x)=$sign$(f(x))=$sign$(\sum_{m=1}^{M}\alpha_{m}G_{m}(x))$

線性組合 $f(x)$ 實現 $M$ 個基本分類器的加權表決. $f(x)$ 的符號決定了實例 $x$ 的類別,$f(x)$ 的絕對值表示分類的確信度.

3.基於單層決策樹構建弱分類器 返回目錄

所謂單層決策樹(decision stump, 也稱決策樹樁)就是基於簡單的單個特征來做決策,由於這棵樹只有一次分裂過程,因此實際上就是一個樹樁。

首先通過一個簡單的數據集來確保在算法實現上一切就緒.

def loadSimpData():
    dataMat = np.matrix( [ [ 1., 2.1],
                           [ 2., 1.1],
                           [ 1.3, 1.],
                           [ 1., 1. ],
                           [ 2., 1. ] ] )
    classLabels = [ 1.0, 1.0, -1.0, -1.0, 1.0 ]                           
    return dataMat, classLabels

 

下圖給出了上面數據集的示意圖

 

如果使用上面所述的單層決策樹來對上面的樣本點進行分類,即試着從某個坐標軸選擇一個值(選擇一條與坐標軸平行的直線)來將所有藍色樣本和褐色樣本分開,這顯然不可能。但是使用多棵單層決策樹,就可以構建出一個能對該數據集完全正確的分類器.

首先給出單層決策樹生成函數

def stumpClassify( dataMatrix, dimen, threshVal, threshIneq ):
    '''
    通過閾值比較對數據進行分類,所有在閾值一邊的數據會分到類別-1,而在
    另外一邊的數據分到類別+1.    
    '''
    retArray = np.ones( ( np.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 = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    m,n = np.shape(dataMatrix)
    numSteps = 10.0 #用於在特征的所有可能值上進行遍歷
    bestStump = {} #存儲給定權重向量 D 時所得到的最佳單層決策樹的信息
    bestClassEst = np.mat( np.zeros( (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
                predictedVals = stumpClassify( dataMatrix, i, 
                                              threshVal, inequal )
                errArr = np.mat( np.ones( ( m, 1 ) ) )
                errArr[ predictedVals == labelMat ] = 0
                weightedError = D.T * errArr
#                print "split: dim %d, thresh %.2f, thresh inequal: \
#                    %s, the weighted error is %.3f" % \
#                    ( i, threshVal, inequal, weightedError )
                if weightedError < minError:
                    minError =  weightedError
                    bestClassEst = predictedVals.copy()
                    bestStump[ 'dim' ] = i
                    bestStump[ 'thresh' ] = threshVal
                    bestStump[ 'ineq' ] = inequal
    return bestStump, minError, bestClassEst

 

上面兩個函數作用是對於給定的數據集選出最佳的單層決策樹.

4.完整的AdaBoost的算法實現 返回目錄

下面給出完整的AdaBoost的算法實現

def adaBoostTrainDS( dataArr, classLabels, numIt = 40 ):
    '''
    基於單層決策樹的AdaBoost訓練過程    
    '''
    weakClfArr = []
    m = np.shape( dataArr )[ 0 ]
    D = np.mat( np.ones( ( m, 1 ) ) / m )
    aggClassEst = np.mat( np.zeros( ( m, 1 ) ) )
    for i in range( numIt ):
        # 每一次循環 只有樣本的權值分布 D 發生變化
        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
        weakClfArr.append( bestStump )
        print "classEst: ", classEst.T
        
        # 更新訓練數據集的權值分布
        expon = np.multiply( -1 * alpha * np.mat( classLabels ).T, classEst )
        D = np.multiply( D, np.exp( expon ) )
        D = D / D.sum()
        
        # 記錄對每個樣本點的類別估計的累積值
        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, "\n"
        
        # 如果完全正確,終止迭代
        if errorRate == 0.0:
            break
    return weakClfArr
    
if __name__ == '__main__':
    print __doc__
    datMat, classLabels = loadSimpData()
#    plt.scatter(datMat[:, 0], datMat[:, 1], c=classLabels, markers=classLabels, s=200, cmap=plt.cm.Paired)
    print adaBoostTrainDS( datMat, classLabels, 9 )
AdaBoost

 

 

運行結果

 

 D:  [[ 0.2  0.2  0.2  0.2  0.2]]
classEst:  [[-1.  1. -1. -1.  1.]]
aggClassEst:  [[-0.69314718  0.69314718 -0.69314718 -0.69314718  0.69314718]]
total error:  0.2 

 D:  [[ 0.5    0.125  0.125  0.125  0.125]]
classEst:  [[ 1.  1. -1. -1. -1.]]
aggClassEst:  [[ 0.27980789  1.66610226 -1.66610226 -1.66610226 -0.27980789]]
total error:  0.2 

 D:  [[ 0.28571429  0.07142857  0.07142857  0.07142857  0.5       ]]
classEst:  [[ 1.  1.  1.  1.  1.]]
aggClassEst:  [[ 1.17568763  2.56198199 -0.77022252 -0.77022252  0.61607184]]
total error:  0.0 

[{'dim': 0, 'ineq': 'lt', 'thresh': 1.3, 'alpha': 0.6931471805599453}, {'dim': 1, 'ineq': 'lt', 'thresh': 1.0, 'alpha': 0.9729550745276565}, {'dim': 0, 'ineq': 'lt', 'thresh': 0.90000000000000002, 'alpha': 0.8958797346140273}]

 

 

可以看到錯誤率逐步被降到 $0.0$, 最終的分類器包含 $3$ 個基本分類器.

下面基於AdaBoost進行分類,需要做的就只是將弱分類器的訓練過程從程序中抽取出來,然后應用到某個具體實例上去。每個弱分類器的結果以其對應的 alpha 值作為權值. 所有這些弱分類器的結果加權求和就得到了最后的結果.

AdaBoost分類函數

adaClassify

 

測試

if __name__ == '__main__':
    print __doc__
    datMat, classLabels = loadSimpData()
#    plt.scatter(datMat[:, 0], datMat[:, 1], c=classLabels, markers=classLabels, s=200, cmap=plt.cm.Paired)
    classifierArr = adaBoostTrainDS( datMat, classLabels, 9 )
    print adaClassify( [ 0, 0 ], classifierArr )

 

測試結果

[[-0.69314718]]
[[-1.66610226]]
[[-2.56198199]]
[[-1.]]

 

可以發現,隨着迭代進行,數據點 $[0, 0]$ 的分類確信度越來越強.

5.總結 返回目錄

AdaBoost的優點:泛化錯誤率低,可以用在大部分分類器上,無參數調整(自適應).

缺點:對離群點敏感.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM