目錄:
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 )
運行結果
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的優點:泛化錯誤率低,可以用在大部分分類器上,無參數調整(自適應).
缺點:對離群點敏感.
