1. 程式人生 > >機器學習筆記(七)Boost演算法(GDBT,AdaBoost,XGBoost)原理及實踐

機器學習筆記(七)Boost演算法(GDBT,AdaBoost,XGBoost)原理及實踐

在上一篇部落格裡,我們討論了關於Bagging的內容,其原理是從現有資料中有放回抽取若干個樣本構建分類器,重複若干次建立若干個分類器進行投票,今天我們來討論另一種演算法:提升(Boost)。

簡單地來說,提升就是指每一步我都產生一個弱預測模型,然後加權累加到總模型中,然後每一步弱預測模型生成的的依據都是損失函式的負梯度方向,這樣若干步以後就可以達到逼近損失函式區域性最小值的目標。

下面開始要不說人話了,我們來詳細討論一下Boost演算法。首先Boost肯定是一個加法模型,它是由若干個基函式及其權值乘積之和的累加,即


其中b是基函式,beta是基函式的係數,這就是我們最終分類器的樣子,現在的目標就是想辦法使損失函式的期望取最小值,也就是


一下子對這M個分類器同時實行優化,顯然不太現實,這問題也太複雜了,所以人們想了一個略微折中的辦法,因為是加法模型,所以我每一步只對其中一個基函式及其係數進行求解,這樣逐步逼近損失函式的最小值,也就是說


那聰明的你一定想到了,要使損失函式最小,那就得使新加的這一項剛好等於損失函式的負梯度,這樣不就一步一步使得損失函式最快下降了嗎?沒錯,就是這樣,那麼就有了


Lambda是我隨便寫的一個引數,可以和beta合併表示步長,那麼對於這個基函式而言,其實它就是關於x和這個函式梯度的一個擬合,然後步長的選擇可以根據線性搜尋法,即尋找在這個梯度上下降到最小值的那個步長,這樣可以儘快逼近損失函式的最小值。

到這裡,梯度提升的原理其實就講完了,接下來我們就講幾個實際情況中的特例,包括梯度下降提升樹(GDBT),自適應提升(AdaBoost),以及Kaggle競賽的王者極限提升?翻譯不知道對不對,就是(XGBoost)。

第一個,GDBT。

對於這個,一旦對上面梯度提升的想法理解了那就很容易解釋了。首先既然是樹,那麼它的基函式肯定就是決策樹啦,而損失函式則是根據我們具體的問題去分析,但方法都一樣,最終都走上了梯度下降的老路,比如說進行到第m步的時候,首先計算殘差


有了殘差之後,我們再用(xi,rim)去擬合第m個基函式,假設這棵樹把輸入空間劃分成j個空間R1m,R2m……,Rjm,假設它在每個空間上的輸出為bjm,這樣的話,第m棵樹可以表示如下:


下一步,對樹的每個區域分別用線性搜尋的方式尋找最佳步長,這個步長可以和上面的區域預測值bjm進行合併,最後就得到了第m步的目標函式


當然了,對於GDBT比較容易出現過擬合的情況,所以有必要增加一點正則項,比如葉節點的數目或葉節點預測值的平方和,進而限制模型複雜度的過度提升,這裡在下面的實踐中的引數設定我們可以繼續討論。

第二個,AdaBoost。

首先要說的是是AdaBoost是用於分類的。然後套路想必你已經非常瞭解了,前面幾步完全和上面的GDBT一樣,區別在於AdaBoost給出了損失函式為指數損失函式,即


很好理解,預測正確了yf(x)為正值,損失函式值就小,預測錯誤yf(x)為正值,損失函式值較大,然後我們來看一下第m步的損失函式


現在就是分別求alpha和G(x)使得損失函式最小值,按照之前的想法,直接算偽殘差然後用G(x)擬合,不過這邊我們先不著急。指數項中,yi與fm-1的乘積是不依賴於alpha和G(x)的,所以可以提出來不用考慮,對於任意alpha>0,在exp(-yi*fm-1)權值分佈下,要exp(-yi*alpha*G(x))取最小值,也就是要G(x)對加權y預測的正確率最高。接下來,求alpha很愉快,直接求導位0,懶癌發作,公式推導過程就不打了,最後的結果如下:


得到了引數之後就能愉快的迭代,使得訓練資料上的正確率蹭蹭蹭地往上漲。

再回過頭來看看AdaBoost的標準做法和我們推導的是否一致

1 第一步假設平均分佈,權值都為1/N,訓練資料得到分類器。

2 求第一步的分類器預測資料的錯誤率,計算G(x)的係數alpha。

3 更新權值分佈,不過加了歸一化因子,使權值滿足概率分佈。

4 基於新的權值分佈建立新的分類器,累加在之前的模型中。

5 重複上述步驟,得到最終的分類器。

可以看出,除了在更新權值分佈處加了一個歸一化因子之外,其他的都和我們推導的一樣,所以,所以什麼呀……你不僅會用還會推導啦?O(∩_∩)O哈哈~

第三個,XGBoost。

其實說白了也很簡單,之前用的梯度下降的方法我們都只考慮了一階資訊,根據泰勒展開,

我們可以把二階資訊也用上,假如目標函式如下


啊啊啊,這公式打得我真要吐血了。其中Ω為正則項,正如上面講的,可表示如下


然後對於決策樹而言,最重要的就是一共有多少個節點以及每個節點的權值,所以決策樹可以表示為


這樣就有了下一步的推導,鑑於它實在是太長了,我就直接截圖了



第二步是因為不管fm如何取值第一項的值都不變,所以優化過程中可以不用考慮,第三步是因為對於每個樣本而言其預測值就是對應輸入空間對應的權值,第四步則是把樣本按照劃分區域重新組合,然後定義


帶入對w求偏導使其為0,這樣就求得了


再回代,就可以把J(fm)中的w給消去了,得到了


這樣我們就把新一步函式的損失函式變成了只與上一步相關的一個新的損失函式,這樣我們就可以遍歷資料中所有的分割點,尋找新的損失函式下降最多的分割點,然後重複上述操作。

相比於梯度下降提升,XGBoost在劃分新的樹的時候還是用了二階資訊,因此能夠更快地收斂,而且XGBoost包是用C/C++寫的,所以速度更快,而且在尋找最佳分割點的時候,可以引入平行計算,因此速度進一步提高,廣受各大競賽參賽者的喜愛啊。

說到現在的理論推導,有耐心看到這裡的少年我只能說你接近成功了,Boost演算法要被你拿下了,接下來我們就來一把實戰試試。

Sklearn中有GDBT和AdaBoost演算法,它用的方法和前面的Bagging什麼的一模一樣,具體的引數設定大家可以參考幫助,這裡給出一個簡單的例子,沒錯,又是那個鳶尾花,它又來了,用它的前兩個特徵進行訓練,我們來看看訓練集上的正確率,按道理,Boost演算法在訓練集上的效果應該是十分卓越的,畢竟它有過擬合的趨勢啊。

import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import AdaBoostClassifier
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn import datasets

iris=datasets.load_iris()
x=iris.data[:,:2]
y=iris.target

model1=DecisionTreeClassifier(max_depth=5)
model2=GradientBoostingClassifier(n_estimators=100)
model3=AdaBoostClassifier(model1,n_estimators=100)
model1.fit(x,y)
model2.fit(x,y)
model3.fit(x,y)
model1_pre=model1.predict(x)
model2_pre=model2.predict(x)
model3_pre=model3.predict(x)
res1=model1_pre==y
res2=model2_pre==y
res3=model3_pre==y
print '決策樹訓練集正確率%.2f%%'%np.mean(res1*100)
print 'GDBT訓練集正確率%.2f%%'%np.mean(res2*100)
print 'AdaBoost訓練集正確率%.2f%%'%np.mean(res3*100)

輸出為

決策樹訓練集正確率84.67%

GDBT訓練集正確率92.00%

AdaBoost訓練集正確率92.67%

在訓練集上表現全是很好啊,在測試集上就不好說了,但有一句話說的好,過擬合總比欠擬合好啊……沒事兒,還有調參大法,根據自己的需要去試試就好了。

XGBoost在sklearn裡沒有,所以需要額外安裝一下,我是按照這個網址的教程這裡,親測有效,只有一步,就是在安裝MinGW-W64的時候這個部落格裡提供的地址下載下來安裝總是失敗,大家去官網下一個就行,其他的按照教程一步一步做就行了,沒什麼其他的么蛾子。

所以,又是鳶尾花登場了

import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn import datasets

iris=datasets.load_iris()
x=iris.data[:,:2]
y=iris.target
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.7, random_state=1)
data_train = xgb.DMatrix(x_train,label=y_train)
data_test=xgb.DMatrix(x_test,label=y_test)
param = {}
param['objective'] = 'multi:softmax'
param['eta'] = 0.1
param['max_depth'] = 6
param['silent'] = 1
param['nthread'] = 4
param['num_class'] = 3
watchlist = [ (data_train,'train'), (data_test, 'test') ]
num_round = 10
bst = xgb.train(param, data_train, num_round, watchlist );
pred = bst.predict( data_test );
print ('predicting, classification error=%f' % (sum( int(pred[i]) != y_test[i] for i in range(len(y_test))) / float(len(y_test)) ))

這裡主要解釋以下param的設定,objective設定的是你的分類的目標及方法,除了我們用的多分類的multi:softmax,還可以是binary:logistic,reg:logistic等等,根據你的目標需要去設定。eta設定的是衰減因子,就是在步長前面乘以一個係數,設定過小容易導致計算時間太長,太大又很容易過擬合,max_depth是所用的樹的最大深度,silent是列印執行資訊,沒什麼太大的意義,num_class應該是類的數目吧,nthread是呼叫的執行緒數,num_round是迭代計算次數。

看一下輸出吧

[0]    train-merror:0.133333  test-merror:0.266667

[1]    train-merror:0.12381   test-merror:0.266667

[2]    train-merror:0.114286  test-merror:0.266667

[3]    train-merror:0.114286  test-merror:0.266667

[4]    train-merror:0.114286  test-merror:0.266667

[5]    train-merror:0.114286  test-merror:0.266667

[6]    train-merror:0.114286  test-merror:0.266667

[7]    train-merror:0.114286  test-merror:0.266667

[8]    train-merror:0.104762  test-merror:0.288889

[9]    train-merror:0.104762  test-merror:0.288889

predicting, classification error=0.288889

確實,在訓練集上的錯誤率不斷下降,但測試集並非如此,到了第八步第九步已經出現了過擬合的嫌疑,反正後面就是調參的工作嗎,訓練集上表現不好,衰減因子大一點,樹的深度大一點,迭代次數多一點等等,反之,如果過擬合了,就反過來做好啦~

不行了,餓死了,先去吃飯了。

如果XGBoost的安裝和使用有問題,大家可以討論下啊,最近剛好想好好研究一下這個,後面有必要的話再專門寫一篇吧,感覺這個調參還是有點意思。