在上一篇博客里,我們討論了關於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)
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)) ))
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的安裝和使用有問題,大家可以討論下啊,最近剛好想好好研究一下這個,后面有必要的話再專門寫一篇吧,感覺這個調參還是有點意思。
轉載自博客:
http://blog.csdn.net/sinat_22594309/article/details/60957594