1. 程式人生 > 其它 >機器學習——開啟整合方法的大門,手把手帶你實現AdaBoost模型

機器學習——開啟整合方法的大門,手把手帶你實現AdaBoost模型



今天是機器學習專題的第25篇文章,我們一起來聊聊AdaBoost。

我們目前為止已經學過了好幾個模型,光決策樹的生成演算法就有三種。但是我們每次進行分類的時候,每次都是採用一個模型進行訓練和預測。我們日常在做一個決策的時候,往往會諮詢好幾個人,綜合採納他們的意見。那麼有沒有可能把這個思路照搬到機器學習領域當中,建立多個模型來綜合得出結果呢?

這當然是可以的,這樣的思路就叫做整合方法(ensemble method)。

整合方法

整合方法本身並不是某種具體的方法或者是演算法,只是一種訓練機器學習模型的思路。它的含義只有一點,就是訓練多個模型,然後將它們的結果匯聚在一起。

根據這個思路,業內又衍生出了三種特定的方法,分別是Bagging、Boosting和Stacking。

Bagging

Bagging是bootstrap aggregating的縮寫,我們從字面上很難理解它的含義。我們記住這個名字即可,在Bagging方法當中,我們會通過有放回隨機取樣的方式建立K個數據集。對於每一個數據集來說,可能有一些單個的樣本重複出現,也可能有一些樣本從沒有出現過,但整體而言,每個樣本出現的概率是相同的。

之後,我們用抽樣出來的K個數據集訓練K個模型,這裡的模型沒有做限制,我們可以使用任何機器學習方模型。K個模型自然會得到K個結果,那麼我們採取民主投票的方式對這K個模型進行聚合。

舉個例子說,假設K=25,在一個二分類問題當中。有10個模型預測結果是0,15個模型預測結果是1。那麼最終整個模型的預測結果就是1,相當於K個模型民主投票,每個模型投票權一樣

。大名鼎鼎的隨機森林就是採取的這種方式。

Boosting

Boosting的思路和Bagging非常相似,它們對於樣本的取樣邏輯是一致的。不同的是,在Boosting當中,這K個模型並不是同時訓練的,而是序列訓練的。每一個模型在訓練的時候都會基於之前模型的結果,更加關注於被之前模型判斷錯誤的樣本。同樣,樣本也會有一個權值,錯誤判斷率越大的樣本擁有越大的權值。

並且每一個模型根據它能力的不同,會被賦予不同的權重,最後會對所有模型進行加權求和,而不是公平投票。由於這個機制,使得模型在訓練的時候的效率也有差異。因為Bagging所有模型之間是完全獨立的,我們是可以採取分散式訓練的。而Boosting中每一個模型會依賴之前模型的效果,所以只能序列訓練。

Stacking

Stacking是Kaggle比賽當中經常使用的方法,它的思路也非常簡單。我們選擇K種不同的模型,然後通過交叉驗證的方式,在訓練集上進行訓練和預測。保證每個模型都對所有的訓練樣本產出一個預測結果。那麼對於每一條訓練樣本,我們都能得到K個結果。

之後,我們再建立一個第二層的模型,它的訓練特徵就是這K個結果。也就是說Stacking方法當中會用到多層模型的結構,最後一層模型的訓練特徵是上層模型預測的結果。由模型自己去訓練究竟哪一個模型的結果更值得采納,以及如何組合模型之間的特長。

我們今天介紹的AdaBoost顧名思義,是一個經典的Boosting演算法。

模型思路

AdaBoost的核心思路是通過使用Boosting的方法,通過一些弱分類器構建出強分類器來。

強分類器我們都很好理解,就是效能很強的模型,那麼弱分類器應該怎麼理解呢?模型的強弱其實是相對於隨機結果來定義的,比隨機結果越好的模型,它的效能越強。從這點出發,弱分類器也就是隻比隨機結果略強的分類器。我們的目的是通過設計樣本和模型的權重,使得可以做出最佳決策,將這些弱分類器的結果綜合出強分類器的效果來。

首先我們會給訓練樣本賦予一個權重,一開始的時候,每一條樣本的權重均相等。根據訓練樣本訓練出一個弱分類器並計算這個分類器的錯誤率。然後在同一個資料集上再次訓練弱分類器,在第二次的訓練當中,我們將會調整每個樣本的權重。其中正確的樣本權重會降低,錯誤的樣本權重會升高

同樣每一個分類器也會分配到一個權重值,權重越高說明它的話語權越大。這些是根據模型的錯誤率來計算的。錯誤率定義為:

這裡的D表示資料集表示分類錯誤的集合,它也就等於錯誤分類的樣本數除以總樣本數。

有了錯誤率之後,我們可以根據下面這個公式得到

得到了之後,我們利用它對樣本的權重進行更新,其中分類正確的權重更改為:

分類錯誤的樣本權重更改為:

這樣,我們所有的權重都更新完了,這也就完成了一輪迭代。AdaBoost會反覆進行迭代和調整權重,直到訓練錯誤率為0或者是弱分類器的數量達到閾值。

程式碼實現

首先,我們來獲取資料,這裡我們選擇了sklearn資料集中的乳腺癌預測資料。和之前的例子一樣,我們可以直接import進來使用,非常方便:

importnumpyasnp
importpandasaspd
fromsklearn.datasetsimportload_breast_cancer

breast=load_breast_cancer()
X,y=breast.data,breast.target
#reshape,將一維向量轉成二維
y=y.reshape((-1,1))

接著,我們將資料拆分成訓練資料和測試資料,這個也是常規做法了,沒有難度:

fromsklearn.model_selectionimporttrain_test_split

X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2,random_state=23)

在AdaBoost模型當中,我們選擇的弱分類器是決策樹的樹樁。所謂的樹樁就是樹深為1的決策樹。樹深為1顯然不論我們怎麼選擇閾值,都不會得到特別好的結果,但是由於我們依然會選擇閾值和特徵,所以結果也不會太差,至少要比隨機選擇要好。所以這就保證了,我們可以得到一個比隨機選擇效果略好一些的弱分類器,並且它的實現非常簡單。

在我們實現模型之前,我們先來實現幾個輔助函式。

defloss_error(y_pred,y,weight):
returnweight.T.dot((y_pred!=y_train))

defstump_classify(X,idx,threshold,comparator):
ifcomparator=='lt':
returnX[:,idx]<=threshold
else:
returnX[:,idx]>threshold

defget_thresholds(X,i):
min_val,max_val=X[:,i].min(),X[:,i].max()
returnnp.linspace(min_val,max_val,10)

這三個函式應該都不難理解,第一個函式當中我們計算了模型的誤差。由於我們每一個樣本擁有一個自身的權重,所以我們對誤差進行加權求和。第二個函式是樹樁分類器的預測函式,邏輯非常簡單,根據閾值比較大小。這裡有兩種情況,有可能小於閾值的樣本是正例,也有可能大於閾值的樣本是正例,所以我們還需要第三個引數記錄這個資訊。第三個買二手手遊地圖函式是生成閾值的函式,由於我們並不需要樹樁的效能特別好,所以我們也沒有必要去遍歷閾值的所有取值,簡單地把特徵的範圍劃分成10段即可。

接下來是單個樹樁的生成函式,它等價於決策樹當中選擇特徵進行資料拆分的函式,邏輯大同小異,只需要稍作修改即可。

defbuild_stump(X,y,weight):
m,n=X.shape
ret_stump,ret_pred=None,[]
best_error=float('inf')

#列舉特徵
foriinrange(n):
#列舉閾值
forjinget_thresholds(X,i):
#列舉正例兩種情況
forcin['lt','gt']:
#預測並且求誤差
pred=stump_classify(X,i,j,c).reshape((-1,1))
err=loss_error(pred,y,weight)
#記錄下最好的樹樁
iferr<best_error:
best_error,ret_pred=err,pred.copy()
ret_stump={'idx':i,'threshold':j,'comparator':c}
returnret_stump,best_error,ret_pred

接下來要做的就是重複生成樹樁的操作,計算,並且更新每一條樣本的權重。整個過程也沒有太多的難點,基本上就是照著實現公式:

defadaboost_train(X,y,num_stump):
stumps=[]
m=X.shape[]
#樣本權重初始化,一開始全部相等
weight=np.ones((y_train.shape[],1))/y_train.shape[]
#生成num_stump個樹樁
foriinrange(num_stump):
best_stump,err,pred=build_stump(X,y,weight)
#計算alpha
alpha=0.5*np.log((1.0-err)/max(err,1e-10))
best_stump['alpha']=alpha
stumps.append(best_stump)

#更新每一條樣本的權重
forjinrange(m):
weight[j]=weight[j]*(np.exp(-alpha)ifpred[j]==y[j]elsenp.exp(alpha))
weight=weight/weight.sum()
#如果當前的準確率已經非常高,則退出
iferr<1e-8:
break
returnstumps

樹樁生成結束之後,最後就是預測的部分了。整個預測過程依然非常簡單,就是一個加權求和的過程。這裡要注意一下,我們在訓練的時候為了突出錯誤預測的樣本,讓模型擁有更好的能力,維護了樣本的權重。然而在預測的時候,我們是不知道預測樣本的權重的,所以我們只需要對模型的結果進行加權即可。

defadaboost_classify(X,stumps):
m=X.shape[]
pred=np.ones((m,1))
alphs=0.0
fori,stumpinenumerate(stumps):
y_pred=stump_classify(X,stump['idx'],stump['threshold'],stump['comparator'])
#根據alpha加權求和
pred=y_pred*stump['alpha']
alphs+=stump['alpha']
pred/=alphs
#根據0.5劃分0和1類別
returnnp.sign(pred).reshape((-1,1))

到這裡,我們整個模型就實現完了,我們先來看下單個樹樁在訓練集上的表現:

可以看到準確率只有0.54,只是比隨機預測略好一點點而已。

然而當我們綜合了20個樹樁的結果之後,在訓練集上我們可以得到0.9的準確率。在預測集上,它的表現更好,準確率有接近0.95!

這是因為AdaBoost當中,每一個分類器都是弱分類器,它根本沒有過擬合的能力,畢竟在訓練集的表現都很差,這就保證了分類器學到的都是實在的泛化能力,在訓練集上適用,在測試集上很大概率也適用。這也是整合方法最大的優點之一。

總結

整合方法可以說是機器學習領域一個非常重要的飛躍,整合方法的出現,讓設計出一個強分類器這件事的難度大大降低,並且還保證了模型的效果。

因為在一些領域當中,設計一個強分類器可能非常困難,然而設計一個弱一些的分類器則簡單得多,再加上模型本身效能很好,不容易陷入過擬合。使得在深度學習模型流行之前,整合方法廣泛使用,幾乎所有機器學習領域的比賽的冠軍,都使用了整合學習。

整合學習當中具體的思想或許各有不同,但是核心的思路是一致的。我們理解了AdaBoost之後,再去學習其他的整合模型就要容易多了。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。