參考資料(要是對於本文的理解不夠透徹,必須將以下博客認知閱讀,方可更加了解Xgboost):
1.對xgboost的理解(參考資料1和4是我認為對Xgboost理解總結最透徹的兩篇文章,其根據作者paper總結!)
2.手動還原XGBoost實例過程(提供了一個實例,方便讀者更加了解算法過程)
3.手寫xgboost(利用python手寫實現xgb)
4.XGBoost超詳細推導(參考資料1和4是我認為對Xgboost理解總結最透徹的兩篇文章)
目錄
一、什么是XGBoost?
二、XGBoost改進總結
三、XGBoost的基學習器
四、XGBoost的模型推導
五、樹的生成
六、其他優化方法
七、XGBoost實例
一、什么是XGBoost?
XGBoost是Exterme Gradient Boosting(極限梯度提升)的縮寫,它是基於決策樹的集成機器學習算法,它以梯度提升(Gradient Boost)為框架。XGBoost是由由GBDT發展而來,同樣是利用加法模型與前向分步算法實現學習的優化過程,但與GBDT是有區別的。主要區別包括以下幾點:
- 目標函數:XGBoost的損失函數添加了正則化項,使用正則用以控制模型的復雜度,正則項里包含了樹的葉子節點個數、每個葉子節點權重(葉結點的socre值)的平方和。
- 優化方法:GBDT在優化時只使用了一階導數信息,XGBoost在優化時使用了一、二介導數信息。
- 缺失值處理:XBGoost對缺失值進行了處理,通過學習模型自動選擇最優的缺失值默認切分方向。
- 防止過擬合: XGBoost除了增加了正則項來防止過擬合,還支持行列采樣的方式來防止過擬合。
- 結果:它可以在最短時間內用更少的計算資源得到更好的結果。
二、Xgboost改進總結
1、目標函數通過二階泰勒展開式做近似
2、定義了樹的復雜度,並應用到目標函數中
3、分裂結點處通過結構打分(式12)和分割損失動態生長
4、分裂結點的候選集合通過一種分布式Quantile Sketch得到(處理帶權重的候選切分點)
5、可以處理稀疏、缺失數據
6、可以通過特征的行、列采樣防止過擬合
三、XGBoost的基學習器
XGBoost的可以使用Regression Tree(CART)作為基學習器,也可以使用線性分類器作為基學習器。以CART作為基學習器時,其決策規則和決策樹是一樣的,但CART的每一個葉節點具有一個權重,也就是葉節點的得分或者說是葉節點的預測值。CART的示例如下圖:
圖中為兩顆回歸樹(左右兩個),其中樹下方的輸出值即為葉節點的權重(得分),當輸出一個樣本進行預測時,根據每個內部節點的決策條件進行划分節點,最終被划分到的葉節點的權重即為該樣本的預測輸出值。
四、XGBoost的模型
XGBoost模型的定義為:給定一個包含n個樣本m個特征的數據集, ,集成模型的預測輸出表示為:
(1)
其中, 表示回歸樹,
為回歸樹的數量。整個式(1)表示給定一個輸入
,輸出值為
顆回歸樹得預測值(即按照相應回歸樹的決策規則,所划分到的葉節點的權重)相加。那么我們如何去學習這個模型?
1、模型的學習
通常情況下,怎樣去學習一個模型?
- 定義目標函數,即損失函數以及正則項。
- 優化目標函數。
第一步:定義XGBoost的目標函數
(2)
其中
公式(2)右邊第一部分是度量預測值與真實值之間的損失函數,第二部分表示對模型復雜度的懲罰項(正則項),
-
、
表示懲罰系數(超參數,提前設定),
-
表示給定一顆樹的葉節點數,
-
表示每顆樹葉節點上的輸出分數的平方(相當於L2正則)。
從目標函數的定義可以看出XGBoost對模型復雜度(樹的復雜度)考慮了每顆樹的葉節點個數,以及每顆樹葉節點輸出得分值得平方和。
第二步:優化目標函數。
在通常的模型中針對這類目標函數可以使用梯度下降的方式進行優化,但注意到 表示的是一顆樹,而非一個數值型的向量,所以不能使用梯度下降的方式去優化該目標函數。那么怎么優化這個目標函數呢?前向分步算法!!!
使用前向分步算法優化目標函數。設 是第i個樣本在第t次迭代(第t顆樹)的預測值。則
(3)
公式(3)表示樣本i在t次迭代后的預測值 = 樣本i在前 t - 1 次迭代后的預測值 + 當前第 t 顆樹預測值。則目標函數可以表示為:
式(4)表示貪心地添加使得模型提升最大的 其中constant表示常數項,這個常數項是指前 t - 1 次迭代的懲罰項為一個常數,即公式中
部分,在第 t 次迭代時,前 t - 1 次迭產生的 t - 1 顆樹已經完全確定,則 t - 1 顆樹的葉節點以及權重都已經確定,所以變成了常數。.在式(4)中如果考慮平方損失函數,則式(4)可以表示為:
式5中 表示殘差,即經過前 t - 1 顆樹的預測之后與真實值之間的差距,也就是在GBDT中所使用的殘差概念。在XGBoost中提出使用二階泰勒展開式近似表示式5.泰勒展開式的二階形式為:
(式6)
定義 則根據式6可以將式4表示為:
(式7)
注意式7中 部分表示前 t - 1 次迭代的損失函數,在當前第 t 次迭代來說已經是一個確定的常數,省略常數項則得到下面的式子:
(式8)
則我們的目標函數變成了公式8.可以看出目標函數只依賴於數據點的一階和二階導數。其中
重新定義一顆樹
包括兩個部分:
- 葉子結點的權重向量 ω ;
- 實例 -> 葉子結點的映射關系q(本質是樹的分支結構);
一棵樹的表達形式定義如下:
定義樹的復雜度
我們定義一顆樹的復雜度 Ω,它由兩部分組成:
- 葉子結點的數量;
- 葉子結點權重向量的L2范數;
定義完樹以及樹的復雜度之后,接下來針對公式8進行細分。首先定義集合 為樹的第j個葉節點上的所有樣本點的集合,即給定一顆樹,所有按照決策規則被划分到第j個葉節點的樣本集合。則根據式2中對模型復雜度懲罰項的定義,將其帶入式8有:
- 式9就是最終的損失函數的形式了。不知道面試直接寫這個行不行。T表示葉子結點個數,
- 解釋一下式9,以葉子節點為基礎進行求和,每一個葉子節點中所有樣本的的一階梯度與葉節點的輸出值的乘積 加上 0.5的這個葉子節點所有樣本的二階梯度與lambada正則化系數之和與葉子節點的輸出值的平方的乘積,這一系列式子求和之后再加上gamma與這棵樹的所有葉子節點的個數的和的成績,就得到了最終的損失函數的形式了。
對式9進行求導:
插入一個小知識點:
這里我們和傳統的gbdt做對比,傳統的gbdt的葉節點值是這樣的:
傳統的gbdt的基樹是cart tree,葉節點值的預測輸出是這個葉節點值的所有樣本的標簽的平均值,單純從單個cart tree的角度(不涉及gbm框架)來說,如果是回歸問題,某個葉子節點的預測輸出就是這個葉子節點所有樣本的標簽值的平均,比如說回歸問題中某個葉子節點所有的樣本的標簽是[0.5,0.4,0.0],則預測輸出就是三者的平均0.3,傳統的gbdt也是一樣的,只不過每一輪要擬合的標簽值是前面所有所有tree預測結果與真實標簽值計算出來的負梯度而已,比如第t輪,某個葉子節點的值為[0.3,0.4,0.2](這里的值是上一輪擬合得到的損失函數的負梯度),則預測輸出就是簡單的求平均0.3。也就是上面的這個式子:
而xgboost通過復雜的推導最后得出結論,葉子節點值不應該是簡單的上一輪負梯度的均值,應該加入二階負梯度和樹的正則化系數,於是就得到了:
將(式10)帶入(式9)中得到(式11):
(式11)
令 則,(式11)可以簡化為(式12)
(式12)
到目前我們得到了(式12)[損失函數的最終形式],這個損失函數(式12)是可以做為得分值評價一顆樹的好壞,那么評價一顆樹的好壞有什么用呢?
- 可以用於對的剪枝操作(防止過擬合),和決策樹中的剪枝是一樣的,給定一個損失函數,判斷剪枝后,根據損失函數是否減小來決定是否執行剪枝,只是XGBoost是運用式12來作為損失函數判斷的標准。注意到評價一顆樹的還好的前提是我們能得到一顆樹,上式也是基於給定一個樹的前提下推導而來的,那么這顆樹怎么來得到呢?
XGB模型的學習用一句話總結:xgb引入了樹的正則化的概念,在原始的損失函數的基礎上加入了正則項(類似與邏輯回歸的損失函數加入了正則項)並且通過將帶正則項的損失函數進行二階泰勒展開推導出了葉子節點的新的計算方式(GBDT使用一階泰勒展開式)。但每一輪要擬合的值還是負梯度。
五、樹的生成
在決策樹的生成中,我們用ID3、C4.5、Gini指數等指標去選擇最優分裂特征、切分點(CART時),XGBoost同樣定義了特征選擇和切分點選擇的指標:
(式13)
XGBoost中使用過(式13)判斷切分增益,Gain值越大,說明分裂后能使目標函數減少越多,就越好。其中 表示在某個節點按條件切分后左節點的得分,
表示在某個節點按條件切分后右節點的得分,
表示切分前的得分,
表示切分后模型復雜度的增加量。現在有了判斷增益的方法,就需要使用該方法去查找最優特征以及最優切分點。
1、分裂查找算法
關於最優特征以及最優切分點的選取XGBoost提供了兩個算法。Basic Exact Greedy Algorithm (精確貪心算法)和Approximate Algorithm(近似算法)
(1)精確貪心算法
類似於CART中最優特征與切分點的查找,通過遍歷每個特征下的每個可能的切分點取值,計算切分后的增益,選擇增益最大的特征及切分點。具體算法流程如下
因為精確貪心算法需要遍歷所有特征和取值,當數據量非常大的時候,無法將所有數據同時加載進內存時,精確貪心算法會非常耗時,XGBoost的作者引進了近似算法。
(2)Approximate Algorithm(近似算法)
近似算法對特征值進行了近似處理,即根據每個特征k的特征值分布,確定出候選切分點 ,即按特征分布將連續的特征值划分到
個候選點對應的桶(buckets)中,並且對每個桶中每個樣本的
進行累加。候選切分點的划分以及
的累加過程如下:
划分好候選切分點之后,按照精確貪心算法描述的算法步驟進行選擇最優切分特征與最優切分點,不同的是切分點被上述候選切分點所代替,但原理和操作過程是一樣的。
(3)近似算法之global、local
在近似算法的偽代碼圖中,作者提到可以按照global的方式提出候選切分點,也可以按照local的方式提出候選切分點。
什么是global方式?什么是local方式?簡單的說就是什么時候提取候選切分點,即在哪一個步驟進行候選切分點的提取。global表示在生成樹之前進行候選切分點的提取,即開始之前為整顆樹做一次提取即可,在每次的節點划分時都使用已經提取好的候選切分點。而local則是在每次節點划分時才進行候選切分點的提取。那么區別是什么呢?
- global方式進行候選切分點提取的次數少。因為只是在初始化的階段進行一次即可,以后的節點切分均使用同一個,而local方式是在每次節點切分時才進行,需要很多次的提取。
- global方式需要更多的候選點,即對候選點提取數量比local更多,因為沒有像local方式一樣每次節點划分時,對當前節點的樣本進行細化,local方式更適合樹深度較大的情況。
上圖是作者在Higgs boson 數據集上對兩種方式的測試,可以看出local需要更少的候選切分點,當global方式有足夠多的候選點時正確率與local相當。
下面對近似算法舉個栗子做為說明。
圖示中特征值被切分成三個候選切分點,位於0~ 處的樣本被划分到第一個桶中, 位於
之間的樣本分別被划分到第二個和第三個桶中。那么在計算最優切分點時,就有兩種划分方式,第一種方式,即第一個桶的樣本被切分到左節點中,第二、三個桶的樣本被切分到右節點中。其增益gain為max函數中第二項。第二種划分方式為,第一、二個桶中的樣本切分到左節點中,第三個桶中的樣本切分到右節點中,其增益為max函數中第三項。那么max函數第一項表示什么呢?第一項表示的是其他特征切分點確認后的最大增益,與當前特征的兩種切分方式比較,選擇最優的特征及其切分點。
注意到栗子在對樣本進行划分時,考慮的是樣本的個數,即每個桶中樣本個數相同為出發點來划分的,如果樣本有權重呢?直觀上的想法是,那就考慮權重而不是個數唄。那么每個樣本應該賦予什么樣的權重?又怎樣去處理這個權重?
2、加權分位數略圖(Weighted Quantile Sketch)
為了處理帶權重的候選切分點的選取,作者提出了Weighted Quantile Sketch算法。加權分位數略圖算法提出一種數據結構,這種數據結構支持merge和prune操作。作者在論文中給出了該算法的詳細描述和證明鏈接,這里不做詳細介紹。可以參考鏈接加權分位數略圖定義及證明說明。簡單介紹加權分位數略圖侯選點的選取方式。
設數據集 表示每個樣本的第k個特征值(
)和二階導數(
)的集合。定義排名函數
:
(式14)
(式14)表示數據集中第k個特征值小於z的樣本所在比例(公式看起來貌似有點奇怪,簡單來說就是特征值小於z的樣本的權重和,占所有樣本權重總和的百分比)。我們的目標是找到一個候選切分點(也就是說怎么去划分候選點的問題),可以根據下式進行侯選點的選取 (式15)
簡單的說(式15)表示落在兩個相鄰的候選切分點之間樣本占比小於某個值 (很小的常數),那么我們就有
個候選切分點。
由(式14)看的樣本是以二階導數作為加權考慮占比的,那么問題來了,為什么使用二階導數作為加權呢?
對目標函數(式8)進行改寫,過程如下:
(式16)可以看成權重 為 的label為
(ps:這個值比作者論文中多出一個負號,有文章說作者論文里面少寫了負號。個人覺得這個正負號不是關注重點,它不是一種定量的計算,而是表示一種以二階導數作為加權的合理性說明。)的平方損失函數,其權重
則為二階導數。由此表明將二階導數作為樣本權重的考慮是合理的。
六、其他優化方法
- 稀疏值處理(Sparsity-aware Split Finding)。實際工程中一般會出現輸入值稀疏的情況。比如數據的缺失、one-hot編碼都會造成輸入數據稀疏。論文中作者提出了關於稀疏值的處理,思路是:對於缺失數據讓模型自動學習默認的划分方向。算法具體的方法如下:
從算法中可以看出,作者采用的是在每次的切分中,讓缺失值分別被切分到左節點以及右節點,通過計算得分值比較兩種切分方法哪一個更優,則會對每個特征的缺失值都會學習到一個最優的默認切分方向。乍一看這個算法會多出相當於一倍的計算量,但其實不是的。因為在算法的迭代中只考慮了非缺失值數據的遍歷,缺失值數據直接被分配到左右節點,所需要遍歷的樣本量大大減小。
作者通過在Allstate-10K數據集上進行了實驗,從結果可以看到稀疏算法比普通算法在處理數據上快了超過50倍。
- 分塊並行(Column Block for Parallel Learning)。在樹生成過程中,需要花費大量的時間在特征選擇與切分點選擇上,並且這部分時間中大部分又花費在了對特征值得排序上。那么怎么樣減小這個排序時間開銷呢?作者提出通過按特征進行分塊並排序,在塊里面保存排序后的特征值及對應樣本的引用,以便於獲取樣本的一階、二階導數值。具體方式如圖:
通過順序訪問排序后的塊遍歷樣本特征的特征值,方便進行切分點的查找。此外分塊存儲后多個特征之間互不干涉,可以使用多線程同時對不同的特征進行切分點查找,即特征的並行化處理。XGBoost的並行,指的是特征維度的並行:在訓練之前,每個特征按特征值對樣本進行預排序,並存儲為Block結構,在后面查找特征分割點時可以重復使用,而且特征已經被存儲為一個個block結構,那么在尋找每個特征的最佳分割點時,可以利用多線程對每個block並行計算。
注意到,在順序訪問特征值時,訪問的是一塊連續的內存空間,但通過特征值持有的索引(樣本索引)訪問樣本獲取一階、二階導數時,這個訪問操作訪問的內存空間並不連續,這樣可能造成cpu緩存命中率低,影響算法效率。那么怎么解決這個問題呢?緩存訪問 Cache-aware Access 。
- 緩存訪問(Cache-aware Access)。為了減小非連續內存的訪問帶來緩存命中率低問題,作者提出了緩存訪問優化機制。解決思路是:既然是非連續內存訪問帶來問題,那么去掉非連續內存訪問就可以解決。那么怎么能去掉非連續內存空間的訪問呢?轉非連續為連續----緩存預取。即提起將要訪問的非連續內存空間中的梯度統計信息(一階、二階導數),放置到連續的內存空間中。具體的操作上就是為每個線程在內存空間中分配一個連續的buffer緩存區,將需要的梯度統計信息存放在緩沖區中。這種方式對數據量大的時候很有用,因為大數據量時,不能把所有樣本都加入到內存中,因此可以動態的將相關信息加入到內存中。
上圖給出了在Higgs數據集上使用緩存訪問和不使用緩存訪問的式樣對比。可以發現在數據量大的時候,基於精確的貪心算法使用緩存預取得處理速度幾乎是普通情況下的兩倍。那么對於block塊應該選擇多大才合理呢?作者通過實驗證明選擇每個塊存放 個樣本時效率最高。
- "核外"塊計算(Blocks for Out-of-core Computation)。當數據量非常大的是時候我們不能把所有數據都加載內存中,因為裝不下。那么就必須的將一部分需要加載進內存的數據先存放在硬盤中,當需要時在加載進內存。這樣操作具有很明顯的瓶頸,即硬盤的IO操作速度遠遠低於內存的處理速度,那么肯定會存在大量等待硬盤IO操作的情況。針對這個問題作者提出了“核外”計算的優化方法。具體操作為,將數據集分成多個塊存放在硬盤中,使用一個獨立的線程專門從硬盤讀取數據,加載到內存中,這樣算法在內存中處理數據就可以和從硬盤讀取數據同時進行。為了加載這個操作過程,作者提出了兩種方法。
- 塊壓縮(Block Compression)。論文使用的是按列進行壓縮,讀取的時候用另外的線程解壓。對於行索引,只保存第一個索引值,然后用16位的整數保存與該block第一個索引的差值。作者通過測試在block設置為
個樣本大小時,壓縮比率幾乎達到26%
29%(貌似沒說使用的是什么壓縮方法.......)。
- 塊分區(Block Sharding )。塊分區是將特征block分區存放在不同的硬盤上,以此來增加硬盤IO的吞吐量。
- 防止過擬合。從XGBoost的模型上可以看到,為了防止過擬合加入了兩項懲罰項
、
,除此之外XGBoost還有另外兩個防止過擬合的方法。1.學習率。和GBDT一樣XGBoost也采用了學習率(步長)來防止過擬合,表現為:
,其中
就是學習率,通常取0.1。2.行、列采樣。和隨機森林一樣XGBoost支持對樣本以及特征進行采樣,取采樣后的樣本和特征作為訓練數據,進一步防止過擬合。