菜菜sklearn——XGBoost(1)
1 在學習XGBoost之前
1.1 機器學習競賽的勝利女神
數據領域人才濟濟,而機器學習競賽一直都是數據領域中最重要的自我展示平臺之一。無數數據工作者希望能夠通過競賽進行修煉,若能斬獲優秀排名,也許就能被伯樂發現,一舉登上人生巔峰。不過,競賽不只是數據工作者的舞臺,也是算法們激烈競爭的舞臺,若要問這幾年來各種機器學習比賽中什么算法風頭最盛,XGBoost可謂是獨孤求敗了。從2016年開始,各大競賽平臺排名前列的解決方案逐漸由XGBoost算法統治,業界甚至將其稱之為“機器學習競賽的勝利女神”。Github上甚至列舉了在近年來的多項比賽中XGBoost斬獲的冠軍列表,其影響力可見一斑。
XGBoost全稱是eXtreme Gradient Boosting,可譯為極限梯度提升算法。它由陳天奇所設計,致力于讓提升樹突破自身的計算極限,以實現運算快速,性能優秀的工程目標。和傳統的梯度提升算法相比,XGBoost進行了許多改進,它能夠比其他使用梯度提升的集成算法更加快速,并且已經被認為是在分類和回歸上都擁有超高性能的先進評估器。除了比賽之中,高科技行業和數據咨詢等行業也已經開始逐步使用XGBoost,了解這個算法,已經成為學習機器學習
中必要的一環。
性能超強的算法往往有著復雜的原理,XGBoost也不能免俗,因此它背后的數學深奧復雜。除此之外,XGBoost與多年前就已經研發出來的算法,比如決策樹,SVM等不同,它是一個集大成的機器學習算法,對大家掌握機器學習中各種概念的程度有較高的要求。雖然要聽懂今天這堂課,你不需要是一個機器學習專家,但你至少需要了解樹模型是什么。如果你對機器學習比較好的了解,基礎比較牢,那今天的課將會是使你融會貫通的一節課。理解XGBoost,一定
能讓你在機器學習上更上一層樓。面對如此復雜的算法,我們幾個小時的講解顯然是不能夠為大家揭開它的全貌的。但我希望這周的課程內容會成為你在梯度提升算法和XGB上的一個向導,一塊敲門磚。本周內容中,我會為大家抽絲剝繭,解析XGBoost原理,帶大家了解XGBoost庫,并幫助大家理解如何使用和評估梯度提升模型。
本周課中,我將重點為大家回答以下問題:
學完這周課,我會讓你們從這里帶走在自己的機器學習項目中能夠使用的技術和技能。其中,大部分原理會基于回歸樹來進行講解,回歸樹的參數調整會在講解中解讀完畢,XGB用于分類的用法將會在案例中為大家呈現。至于很復雜的數學原理,我不會帶大家刨根問底,而是只會帶大家了解一些基本流程,只要大家能夠把XGB運用在我們的機器學習項目中來創造真實價值就足夠了。
1.2 xgboost庫與XGB的sklearn API
在開始講解XGBoost的細節之前,我先來介紹我們可以調用XGB的一系列庫,模塊和類。陳天奇創造了XGBoost之后,很快和一群機器學習愛好者建立了專門調用XGBoost庫,名為xgboost。xgboost是一個獨立的,開源的,專門提供梯度提升樹以及XGBoost算法應用的算法庫。它和sklearn類似,有一個詳細的官方網站可以供我們查看,并且可以與C,Python,R,Julia等語言連用,但需要我們單獨安裝和下載。
xgboost documents:https://xgboost.readthedocs.io/en/latest/index.html
我們課程全部會基于Python來運行。xgboost庫要求我們必須要提供適合的Scipy環境,如果你是使用anaconda安裝的Python,你的Scipy環境應該是沒有什么問題。以下為大家提供在windows中和MAC使用pip來安裝xgboost的代碼:
#windows pip install xgboost #安裝xgboost庫 pip install --upgrade xgboost #更新xgboost庫 #MAC brew install gcc@7 pip3 install xgboost安裝完畢之后,我們就能夠使用這個庫中所帶的XGB相關的類了。
import xgboost as xgb現在,我們有兩種方式可以來使用我們的xgboost庫。第一種方式,是直接使用xgboost庫自己的建模流程。
其中最核心的,是DMtarix這個讀取數據的類,以及train()這個用于訓練的類。與sklearn把所有的參數都寫在類中的方式不同,xgboost庫中必須先使用字典設定參數集,再使用train來將參數及輸入,然后進行訓練。會這樣設計的原因,是因為XGB所涉及到的參數實在太多,全部寫在xgb.train()中太長也容易出錯。在這里,我為大家準備了params可能的取值以及xgboost.train的列表,給大家一個印象。
params {eta, gamma, max_depth, min_child_weight, max_delta_step, subsample, colsample_bytree,colsample_bylevel, colsample_bynode, lambda, alpha, tree_method string, sketch_eps, scale_pos_weight, updater,refresh_leaf, process_type, grow_policy, max_leaves, max_bin, predictor, num_parallel_tree}
xgboost.train (params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, maximize=False,early_stopping_rounds=None, evals_result=None, verbose_eval=True, xgb_model=None, callbacks=None,learning_rates=None)
或者,我們也可以選擇第二種方法,使用xgboost庫中的sklearn的API。這是說,我們可以調用如下的類,并用我們sklearn當中慣例的實例化,fit和predict的流程來運行XGB,并且也可以調用屬性比如coef_等等。當然,這是我們回歸的類,我們也有用于分類,用于排序的類。他們與回歸的類非常相似,因此了解一個類即可。
class xgboost.XGBRegressor (max_depth=3, learning_rate=0.1, n_estimators=100, silent=True,objective=‘reg:linear’, booster=‘gbtree’, n_jobs=1, nthread=None, gamma=0, min_child_weight=1, max_delta_step=0,subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, reg_lambda=1, scale_pos_weight=1,
base_score=0.5, random_state=0, seed=None, missing=None, importance_type=‘gain’, **kwargs)
看到這長長的參數條目,可能大家會感到頭暈眼花——沒錯XGB就是這門復雜。但是眼尖的小伙伴可能已經發現了,調用xgboost.train和調用sklearnAPI中的類XGBRegressor,需要輸入的參數是不同的,而且看起來相當的不同。但其實,這些參數只是寫法不同,功能是相同的。比如說,我們的params字典中的第一個參數eta,其實就是我們XGBRegressor里面的參數learning_rate,他們的含義和實現的功能是一模一樣的。只不過在sklearnAPI中,開發團
隊友好地幫助我們將參數的名稱調節成了與sklearn中其他的算法類更相似的樣子。
所以對我們來說,使用xgboost中設定的建模流程來建模,和使用sklearnAPI中的類來建模,模型效果是比較相似的,但是xgboost庫本身的運算速度(尤其是交叉驗證)以及調參手段比sklearn要簡單。我們的課是sklearn課堂,因此在今天的課中,我會先使用sklearnAPI來為大家講解核心參數,包括不同的參數在xgboost的調用流程和sklearn的API中如何對應,然后我會在應用和案例之中使用xgboost庫來為大家展現一個快捷的調參過程。如果大家希望探索一下這兩者是否有差異,那必須具體到大家本身的數據集上去觀察。
1.3 XGBoost的三大板塊
XGBoost本身的核心是基于梯度提升樹實現的集成算法,整體來說可以有三個核心部分:集成算法本身,用于集成的弱評估器,以及應用中的其他過程。三個部分中,前兩個部分包含了XGBoost的核心原理以及數學過程,最后的部分主要是在XGBoost應用中占有一席之地。我們的課程會主要集中在前兩部分,最后一部分內容將會在應用中少量給大家提及。接下來,我們就針對這三個部分,來進行一一的講解。
2 梯度提升樹
class xgboost.XGBRegressor (max_depth=3, learning_rate=0.1, n_estimators=100, silent=True,objective=‘reg:linear’, booster=‘gbtree’, n_jobs=1, nthread=None, gamma=0, min_child_weight=1, max_delta_step=0,subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, reg_lambda=1, scale_pos_weight=1,base_score=0.5, random_state=0, seed=None, missing=None, importance_type=‘gain’, **kwargs)
2.1 提升集成算法:重要參數n_estimators
XGBoost的基礎是梯度提升算法,因此我們必須先從了解梯度提升算法開始。梯度提升(Gradient boosting)是構建預測模型的最強大技術之一,它是集成算法中提升法(Boosting)的代表算法。集成算法通過在數據上構建多個弱評估器,匯總所有弱評估器的建模結果,以獲取比單個模型更好的回歸或分類表現。弱評估器被定義為是表現至少比
隨機猜測更好的模型,即預測準確率不低于50%的任意模型。
集成不同弱評估器的方法有很多種。有像我們曾經在隨機森林的課中介紹的,一次性建立多個平行獨立的弱評估器的裝袋法。也有像我們今天要介紹的提升法這樣,逐一構建弱評估器,經過多次迭代逐漸累積多個弱評估器的方法。提升法的中最著名的算法包括Adaboost和梯度提升樹,XGBoost就是由梯度提升樹發展而來的。梯度提升樹中可以有回歸樹也可以有分類樹,兩者都以CART樹算法作為主流,XGBoost背后也是CART樹,這意味著XGBoost中所有的樹
都是二叉的。接下來,我們就以梯度提升回歸樹為例子,來了解一下Boosting算法是怎樣工作的。
首先,梯度提升回歸樹是專注于回歸的樹模型的提升集成模型,其建模過程大致如下:最開始先建立一棵樹,然后逐漸迭代,每次迭代過程中都增加一棵樹,逐漸形成眾多樹模型集成的強評估器。
對于決策樹而言,每個被放入模型的任意樣本 最終一個都會落到一個葉子節點上。而對于回歸樹,每個葉子節點上的值是這個葉子節點上所有樣本的均值。
對于梯度提升回歸樹來說,每個樣本的預測結果可以表示為所有樹上的結果的加權求和:
y^i(k)=∑kKγkhk(xi)\hat{y}_{i}^{(k)}=\sum_{k}^{K} \gamma_{k} h_{k}\left(x_{i}\right) y^?i(k)?=k∑K?γk?hk?(xi?)
其中, K是樹的總數量, k代表第k棵樹,γk\gamma_{k}γk?是這棵樹的權重,hkh_{k}hk?表示這棵樹上的預測結果。
值得注意的是,XGB作為GBDT的改進,在y^\hat{y}y^?上卻有所不同。對于XGB來說,每個葉子節點上會有一個預測分數(prediction score),也被稱為葉子權重。這個葉子權重就是所有在這個葉子節點上的樣本在這一棵樹上的回歸取值,用fk(xi)f_{k}(x_{i})fk?(xi?)或者www來表示,其中fkf_{k}fk?表示第k棵決策樹,xix_{i}xi?表示樣本i對應的特征向量。當只有一棵樹的時候,f1(xi)f_{1}(x_{i})f1?(xi?)就是提升集成算法返回的結果,但這個結果往往非常糟糕。當有多棵樹的時候,集成模型的回歸結果就是所有樹的預測分數之和,假設這個集成模型中總共有K棵決策樹,則整個模型在這個樣本i上給出的預測結果為:
y^i(k)=∑kKfk(xi)\hat{y}_{i}^{(k)}=\sum_{k}^{K} f_{k}\left(x_{i}\right) y^?i(k)?=k∑K?fk?(xi?)
從上面的式子來看,在集成中我們需要的考慮的第一件事是我們的超參數 ,究竟要建多少棵樹呢?
試著回想一下我們在隨機森林中是如何理解n_estimators的:n_estimators越大,模型的學習能力就會越強,模型也越容易過擬合。在隨機森林中,我們調整的第一個參數就是n_estimators,這個參數非常強大,常常能夠一次性將模型調整到極限。在XGB中,我們也期待相似的表現,雖然XGB的集成方式與隨機森林不同,但使用更多的弱分類器來增強模型整體的學習能力這件事是一致的。
先來進行一次簡單的建模試試看吧。
6. 使用參數學習曲線觀察n_estimators對模型的影響
7. 進化的學習曲線:方差與泛化誤差
回憶一下我們曾經在隨機森林中講解過的方差-偏差困境。在機器學習中,我們用來衡量模型在未知數據上的準確率的指標,叫做泛化誤差(Genelization error)。一個集成模型(f)在未知數據集(D)上的泛化誤差 ,由方差(var),偏差(bais)和噪聲(ε)共同決定。其中偏差就是訓練集上的擬合程度決定,方差是模型的穩定性決定,噪音是不可控的。而泛化誤差越小,模型就越理想。
E(f;D)=bias?2+var+?2E(f ; D)=\operatorname{bias}^{2}+v a r+\epsilon^{2} E(f;D)=bias2+var+?2
在過去我們往往直接取學習曲線獲得的分數的最高點,即考慮偏差最小的點,是因為模型極度不穩定,方差很大的情況其實比較少見。但現在我們的數據量非常少,模型會相對不穩定,因此我們應當將方差也納入考慮的范圍。在繪制學習曲線時,我們不僅要考慮偏差的大小,還要考慮方差的大小,更要考慮泛化誤差中我們可控的部分。當然,并不是說可控的部分比較小,整體的泛化誤差就一定小,因為誤差有時候可能占主導。讓我們基于這種思路,來改進學習
曲線:
8. 細化學習曲線,找出最佳n_estimators
從這個過程中觀察n_estimators參數對模型的影響,我們可以得出以下結論:
首先,XGB中的樹的數量決定了模型的學習能力,樹的數量越多,模型的學習能力越強。只要XGB中樹的數量足夠了,即便只有很少的數據, 模型也能夠學到訓練數據100%的信息,所以XGB也是天生過擬合的模型。但在這種情況下,模型會變得非常不穩定。
第二,XGB中樹的數量很少的時候,對模型的影響較大,當樹的數量已經很多的時候,對模型的影響比較小,只能有微弱的變化。當數據本身就處于過擬合的時候,再使用過多的樹能達到的效果甚微,反而浪費計算資源。當唯一指標或者準確率給出的n_estimators看起來不太可靠的時候,我們可以改造學習曲線來幫助我們。
第三,樹的數量提升對模型的影響有極限,最開始,模型的表現會隨著XGB的樹的數量一起提升,但到達某個點之后,樹的數量越多,模型的效果會逐步下降,這也說明了暴力增加n_estimators不一定有效果。
這些都和隨機森林中的參數n_estimators表現出一致的狀態。在隨機森林中我們總是先調整n_estimators,當n_estimators的極限已達到,我們才考慮其他參數,但XGB中的狀況明顯更加復雜,當數據集不太尋常的時候會更加復雜。這是我們要給出的第一個超參數,因此還是建議優先調整n_estimators,一般都不會建議一個太大的數目,300以下為佳。
2.2 有放回隨機抽樣:重要參數subsample
確認了有多少棵樹之后,我們來思考一個問題:建立了眾多的樹,怎么就能夠保證模型整體的效果變強呢?集成的目的是為了模型在樣本上能表現出更好的效果,所以對于所有的提升集成算法,每構建一個評估器,集成模型的效果都會比之前更好。也就是隨著迭代的進行,模型整體的效果必須要逐漸提升,最后要實現集成模型的效果最優。要實現這個目標,我們可以首先從訓練數據上著手。
我們訓練模型之前,必然會有一個巨大的數據集。我們都知道樹模型是天生過擬合的模型,并且如果數據量太過巨大,樹模型的計算會非常緩慢,因此,我們要對我們的原始數據集進行有放回抽樣(bootstrap)。有放回的抽樣每次只能抽取一個樣本,若我們需要總共N個樣本,就需要抽取N次。每次抽取一個樣本的過程是獨立的,這一次被抽到的樣本會被放回數據集中,下一次還可能被抽到,因此抽出的數據集中,可能有一些重復的數據。
在無論是裝袋還是提升的集成算法中,有放回抽樣都是我們防止過擬合,讓單一弱分類器變得更輕量的必要操作。實際應用中,每次抽取50%左右的數據就能夠有不錯的效果了。sklearn的隨機森林類中也有名為boostrap的參數來幫助我們控制這種隨機有放回抽樣。同時,這樣做還可以保證集成算法中的每個弱分類器(每棵樹)都是不同的模型,基于不同的數據建立的自然是不同的模型,而集成一系列一模一樣的弱分類器是沒有意義的。
在梯度提升樹中,我們每一次迭代都要建立一棵新的樹,因此我們每次迭代中,都要有放回抽取一個新的訓練樣本。不過,這并不能保證每次建新樹后,集成的效果都比之前要好。因此我們規定,在梯度提升樹中,每構建一個評估器,都讓模型更加集中于數據集中容易被判錯的那些樣本。來看看下面的這個過程。
首先我們有一個巨大的數據集,在建第一棵樹時,我們對數據進行初次有放回抽樣,然后建模。建模完畢后,我們對模型進行一個評估,然后將模型預測錯誤的樣本反饋給我們的數據集,一次迭代就算完成。緊接著,我們要建立第二棵決策樹,于是開始進行第二次有放回抽樣。但這次有放回抽樣,和初次的隨機有放回抽樣就不同了,在這次的抽樣中,我們加大了被第一棵樹判斷錯誤的樣本的權重。也就是說,被第一棵樹判斷錯誤的樣本,更有可能被我們抽中。
基于這個有權重的訓練集來建模,我們新建的決策樹就會更加傾向于這些權重更大的,很容易被判錯的樣本。建模完畢之后,我們又將判錯的樣本反饋給原始數據集。下一次迭代的時候,被判錯的樣本的權重會更大,新的模型會更加傾向于很難被判斷的這些樣本。如此反復迭代,越后面建的樹,越是之前的樹們判錯樣本上的專家,越專注于攻克那些之前的樹們不擅長的數據。對于一個樣本而言,它被預測錯誤的次數越多,被加大權重的次數也就越多。我們相
信,只要弱分類器足夠強大,隨著模型整體不斷在被判錯的樣本上發力,這些樣本會漸漸被判斷正確。如此就一定程度上實現了我們每新建一棵樹模型的效果都會提升的目標。
在sklearn中,我們使用參數subsample來控制我們的隨機抽樣。在xgb和sklearn中,這個參數都默認為1且不能取到0,這說明我們無法控制模型是否進行隨機有放回抽樣,只能控制抽樣抽出來的樣本量大概是多少。
那除了讓模型更加集中于那些困難樣本,采樣還對模型造成了什么樣的影響呢?采樣會減少樣本數量,而從學習曲線來看樣本數量越少模型的過擬合會越嚴重,因為對模型來說,數據量越少模型學習越容易,學到的規則也會越具體越不適用于測試樣本。所以subsample參數通常是在樣本量本身很大的時候來調整和使用。
我們的模型現在正處于樣本量過少并且過擬合的狀態,根據學習曲線展現出來的規律,我們的訓練樣本量在200左右的時候,模型的效果有可能反而比更多訓練數據的時候好,但這不代表模型的泛化能力在更小的訓練樣本量下會更強。正常來說樣本量越大,模型才不容易過擬合,現在展現出來的效果,是由于我們的樣本量太小造成的一個巧合。從這個角度來看,我們的subsample參數對模型的影響應該會非常不穩定,大概率應該是無法提升模型的泛化能力的,但也不乏提升模型的可能性。依然使用波士頓房價數據集,來看學習曲線:
axisx = np.linspace(0,1,20) rs = [] for i in axisx:reg = XGBR(n_estimators=180,subsample=i,random_state=420)rs.append(CVS(reg,Xtrain,Ytrain,cv=cv).mean()) #r^2 print(axisx[rs.index(max(rs))],max(rs)) plt.figure(figsize=(20,5)) plt.plot(axisx,rs,c="green",label="XGB") plt.legend() plt.show() #0.7368421052631579 0.837609040251761 #繼續細化學習曲線 axisx = np.linspace(0.05,1,20) rs = [] var = [] ge = [] for i in axisx:reg = XGBR(n_estimators=180,subsample=i,random_state=420)cvresult = CVS(reg,Xtrain,Ytrain,cv=cv)rs.append(cvresult.mean())var.append(cvresult.var())ge.append((1 - cvresult.mean())**2+cvresult.var()) print(axisx[rs.index(max(rs))],max(rs),var[rs.index(max(rs))]) print(axisx[var.index(min(var))],rs[var.index(min(var))],min(var)) print(axisx[ge.index(min(ge))],rs[ge.index(min(ge))],var[ge.index(min(ge))],min(ge)) rs = np.array(rs) var = np.array(var) plt.figure(figsize=(20,5)) plt.plot(axisx,rs,c="black",label="XGB") plt.plot(axisx,rs+var,c="red",linestyle='-.') plt.plot(axisx,rs-var,c="red",linestyle='-.') plt.legend() plt.show() #0.65 0.8302530801197368 0.008708816667924316 #0.7999999999999999 0.8277414964661117 0.007159903723250457 #0.7999999999999999 0.8277414964661117 0.007159903723250457 0.036832895762985055 #細化學習曲線 axisx = np.linspace(0.75,1,25) rs = [] var = [] ge = [] for i in axisx:reg = XGBR(n_estimators=180,subsample=i,random_state=420)cvresult = CVS(reg,Xtrain,Ytrain,cv=cv)rs.append(cvresult.mean())var.append(cvresult.var())ge.append((1 - cvresult.mean())**2+cvresult.var()) print(axisx[rs.index(max(rs))],max(rs),var[rs.index(max(rs))]) print(axisx[var.index(min(var))],rs[var.index(min(var))],min(var)) print(axisx[ge.index(min(ge))],rs[ge.index(min(ge))],var[ge.index(min(ge))],min(ge)) rs = np.array(rs) var = np.array(var) plt.figure(figsize=(20,5)) plt.plot(axisx,rs,c="black",label="XGB") plt.plot(axisx,rs+var,c="red",linestyle='-.') plt.plot(axisx,rs-var,c="red",linestyle='-.') plt.legend() plt.show() #0.7708333333333334 0.833489187182165 0.005575077682875093 #0.7708333333333334 0.833489187182165 0.005575077682875093 #0.7708333333333334 0.833489187182165 0.005575077682875093 0.033300928468131166 reg = XGBR(n_estimators=180,subsample=0.7708333333333334,random_state=420).fit(Xtrain,Ytrain) reg.score(Xtest,Ytest) #0.9159462982185405 MSE(Ytest,reg.predict(Xtest)) #7.821523502888769參數的效果在我們的預料之中,總體來說這個參數并沒有對波士頓房價數據集上的結果造成太大的影響,由于我們的數據集過少,降低抽樣的比例反而讓數據的效果更低,不如就讓它保持默認。
2.3 迭代決策樹:重要參數eta
從數據的角度而言,我們讓模型更加傾向于努力攻克那些難以判斷的樣本。但是,并不是說只要我新建了一棵傾向于困難樣本的決策樹,它就能夠幫我把困難樣本判斷正確了。困難樣本被加重權重是因為前面的樹沒能把它判斷正確,所以對于下一棵樹來說,它要判斷的測試集的難度,是比之前的樹所遇到的數據的難度都要高的,那要把這些樣本都判斷正確,會越來越難。如果新建的樹在判斷困難樣本這件事上還沒有前面的樹做得好呢?如果我新建的樹剛好是一
棵特別糟糕的樹呢?所以,除了保證模型逐漸傾向于困難樣本的方向,我們還必須控制新弱分類器的生成,我們必須保證,每次新添加的樹一定得是對這個新數據集預測效果最優的那一棵樹。
平衡算法表現和運算速度是機器學習的藝術,我們希望能找出一種方法,直接幫我們求解出最優的集成算法結果。求解最優結果,我們能否把它轉化成一個傳統的最優化問題呢?
E(f;D)=bias?2+var+?2y(x)=11+e?θTxE(f ; D)=\operatorname{bias}^{2}+v a r+\epsilon^{2}y(x)=\frac{1}{1+e^{-\boldsymbol{\theta}^{T} \boldsymbol{x}}} E(f;D)=bias2+var+?2y(x)=1+e?θTx1?
來回顧一下最優化問題的老朋友,我們的邏輯回歸模型。在邏輯回歸當中,我們有方程:
θk+1=θk?α?dki\boldsymbol{\theta}_{k+1}=\boldsymbol{\theta}_{k}-\alpha * d_{k i} θk+1?=θk??α?dki?
我們讓第k次迭代中的θk\theta_{k}θk?減去通過步長和特征取值x計算出來的一個量,以此來得到第k+1次迭代后的參數向量θk+1\theta_{k+1}θk+1?。我們可以讓這個過程持續下去,直到我們找到能夠讓損失函數最小化的參數θ\thetaθ為止。這是一個最典型的最優化過程。這個過程其實和我們現在希望做的事情是相似的。
現在我們希望求解集成算法的最優結果,那我們應該可以使用同樣的思路:我們首先找到一個損失函數Obj,這個損失函數應該可以通過帶入我們的預測結果y^i\hat{y}_{i}y^?i?來衡量我們的梯度提升樹在樣本的預測效果。然后,我們利用梯度下降來迭代我們的集成算法:
y^i(k+1)=y^i(k)+fk+1(xi)\hat{y}_{i}^{(k+1)}=\hat{y}_{i}^{(k)}+f_{k+1}\left(x_{i}\right) y^?i(k+1)?=y^?i(k)?+fk+1?(xi?)
在k次迭代后,我們的集成算法中總共有k棵樹,而我們前面講明了,k棵樹的集成結果是前面所有樹上的葉子權重的累加∑kKfk(xi)\sum_{k}^{K} f_{k}\left(x_{i}\right)∑kK?fk?(xi?)。所以我們讓k棵樹的集成結果y^i(k)\hat{y}_{i}^{(k)}y^?i(k)?加上我們新建的樹上的葉子權重fk+1(xi)f_{k+1}\left(x_{i}\right)fk+1?(xi?),就可以得到第k+1次迭代后,總共k+1棵樹的預測結果y^i(k+1)\hat{y}_{i}^{(k+1)}y^?i(k+1)?了。我們讓這個過程持續下去,直到找到能夠讓損失函數最小化的y^\hat{y}y^?,這個y^\hat{y}y^?就是我們模型的預測結果。參數可以迭代,集成的樹林也可以迭代,萬事大吉!
但要注意,在邏輯回歸中參數θ\thetaθ迭代的時候減去的部分是我們人為規定的步長和梯度相乘的結果。而在我們的GBDT和XGB中,我們卻希望能夠求解出讓我們的預測結果y^\hat{y}y^?不斷迭代的部分fk+1(xi)f_{k+1}\left(x_{i}\right)fk+1?(xi?)。但無論如何,我們現在已經有了最優化的思路了,只要順著這個思路求解下去,我們必然能夠在每一個數據集上找到最優的y^\hat{y}y^?。
在邏輯回歸中,我們自定義步長α\alphaα來干涉我們的迭代速率,在XGB中看起來卻沒有這樣的設置,但其實不然。在XGB中,我們完整的迭代決策樹的公式應該寫作:
y^i(k+1)=y^i(k)+ηfk+1(xi)\hat{y}_{i}^{(k+1)}=\hat{y}_{i}^{(k)}+\eta f_{k+1}\left(x_{i}\right) y^?i(k+1)?=y^?i(k)?+ηfk+1?(xi?)
其中η\etaη讀作"eta",是迭代決策樹時的步長(shrinkage),又叫做學習率(learning rate)。和邏輯回歸中的α\alphaα類似,η\etaη越大,迭代的速度越快,算法的極限很快被達到,有可能無法收斂到真正的最佳。η\etaη越小,越有可能找到更精確的最佳值,更多的空間被留給了后面建立的樹,但迭代速度會比較緩慢。
在sklearn中,我們使用參數learning_rate來干涉我們的學習速率:
學習率和n_estimators一陰一陽!
讓我們來探索一下參數eta的性質:
#首先我們先來定義一個評分函數,這個評分函數能夠幫助我們直接打印Xtrain上的交叉驗證結果 def regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2"],show=True):score = []for i in range(len(scoring)):if show:print("{}:{:.2f}".format(scoring[i] #模型評估指標的名字,CVS(reg,Xtrain,Ytrain,cv=cv,scoring=scoring[i]).mean()))score.append(CVS(reg,Xtrain,Ytrain,cv=cv,scoring=scoring[i]).mean())return score reg = XGBR(n_estimators=180,random_state=420) regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2","neg_mean_squared_error"]) #r2:0.80 #neg_mean_squared_error:-13.48 #[0.8038787848970184, -13.482301822063182] regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2","neg_mean_squared_error"],show=False) #[0.8038787848970184, -13.482301822063182]from time import time import datetime for i in [0,0.2,0.5,1]:time0=time()reg = XGBR(n_estimators=180,random_state=420,learning_rate=i)print("learning_rate = {}".format(i))regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2","neg_mean_squared_error"])print(datetime.datetime.fromtimestamp(time()-time0).strftime("%M:%S:%f"))print("\t") #learning_rate = 0 #r2:-6.76 #neg_mean_squared_error:-567.55 #00:01:561781 # #learning_rate = 0.2 #r2:0.81 #neg_mean_squared_error:-13.32 #00:01:848888 # #learning_rate = 0.5 #r2:0.81 #neg_mean_squared_error:-13.24 #00:01:541875 # #learning_rate = 1 #r2:0.72 #neg_mean_squared_error:-19.11 #00:01:499027除了運行時間,步長還是一個對模型效果影響巨大的參數,如果設置太大模型就無法收斂(可能導致R2R^{2}R2很小或者MSE很大的情況),如果設置太小模型速度就會非常緩慢,但它最后究竟會收斂到何處很難由經驗來判定,在訓練集上表現出來的模樣和在測試集上相差甚遠,很難直接探索出一個泛化誤差很低的步長。
axisx = np.arange(0.05,1,0.05) rs = [] te = [] for i in axisx:reg = XGBR(n_estimators=180,random_state=420,learning_rate=i)score = regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2","neg_mean_squared_error"],show=False)test = reg.fit(Xtrain,Ytrain).score(Xtest,Ytest)rs.append(score[0])te.append(test) print(axisx[rs.index(max(rs))],max(rs)) plt.figure(figsize=(20,5)) plt.plot(axisx,te,c="gray",label="test") plt.plot(axisx,rs,c="green",label="train") plt.legend() plt.show() #0.55 0.8125604372670463
雖然從圖上來說,默認的0.1看起來是一個比較理想的情況,并且看起來更小的步長更利于現在的數據,但我們也無法確定對于其他數據會有怎么樣的效果。所以通常,我們不調整η\etaη,即便調整,一般它也會在[0.01,0.2]之間變動。如果我們希望模型的效果更好,更多的可能是從樹本身的角度來說,對樹進行剪枝,而不會寄希望于調整η\etaη。
梯度提升樹是XGB的基礎,本節中已經介紹了XGB中與梯度提升樹的過程相關的四個參數:n_estimators,learning_rate ,silent,subsample。這四個參數的主要目的,其實并不是提升模型表現,更多是了解梯度提升樹的原理。現在來看,我們的梯度提升樹可是說是由三個重要的部分組成:
XGBoost是在梯度提升樹的這三個核心要素上運行,它重新定義了損失函數和弱評估器,并且對提升算法的集成手段進行了改進,實現了運算速度和模型效果的高度平衡。并且,XGBoost將原本的梯度提升樹拓展開來,讓XGBoost不再是單純的樹的集成模型,也不只是單單的回歸模型。只要我們調節參數,我們可以選擇任何我們希望集成的算法,以及任何我們希望實現的功能。
總結
以上是生活随笔為你收集整理的菜菜sklearn——XGBoost(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 特征选择(feature_selecti
- 下一篇: 菜菜sklearn——XGBoost(2