XGBoost是陳天奇等人開發的一個開源項目,前文提到XGBoost是GBDT的一種提升和變異形式,其本質上還是一個GBDT,但力爭將GBDT的性能發揮到極致,因此這里的X指代的“Extreme”的意思。XGBoost通過在算法和工程上進行了改進,使其在性能和精度上都得到了很大的提升,也成為了Kaggle比賽和工程應用的大熱門。XGBoost是大規模並行的BoostingTree的工具,比通常的工具包快10倍以上,是目前最好的開源BoostingTree的工具包,在工業界規模方面,XGBoost的分布式版本有廣泛的可移植性,支持在YARN, MPI, Sungrid Engine等各個平台上面運行,並且保留了單機並行版本的各種優化,使得它可以很好地解決於工業界規模的問題。
XGBoost原理
從GBDT到XGBoost
引言中說到,XGBoost實際上也是一種GBDT,不過在算法和性能上做了很多優化和提升,其優化主要包括以下幾個方面:
- 就算法本身有以下優化:
- 在算法的弱分類器的選擇上,GBDT所選擇的只能是回歸樹,而XGBoost中則可以選擇其他很多的弱分類器;
- 在算法的損失函數上,XGBoost除了本身的損失,還加上了正則化提高模型的泛化能力;
- 在算法的優化過程中,GBDT采用負梯度(一階泰勒展開)來近似代替殘差,而在XGBoost算法中,使用二階泰勒展開來損失函數誤差進行擬合,使其更加精確。
- 就性能方面的優化主要是對每個弱分類器(如決策樹)的學習時,采用並行的方式進行選擇節點所要分裂的特征和特征值,其做法是,在進行分裂之前,首先對所有的特征值進行排序和分組,以便進行並行處理,對分組的特征,選擇合適的分組大小,利用CPU的緩存讀取加速,將分組保存在多個硬盤提高IO速度。
- 此外還有XGBoost在對缺失值進行處理時,通過枚舉所有可能的取值在當前節點是進入左子樹還是右子樹來決定缺失值的處理方式。
上面就是XGBoost在GBDT的基礎上進行的優化的幾個方面,其中主要是算法方面的優化,GBDT其實也可以以並行的方式進行處理,接下來就主要描述XGBoost在算法方面的具體優化過程。
首先回顧一下GBDT的算法過程:
"""
對於迭代次數1~M:
-
-
-
- 計算負梯度:
-
-
-
-
-
- 然后以數據(xi,rmi)構建一顆決策樹,所構建的決策樹葉子結點區域為Rmj。
- 分別對於每個葉子節點區域,擬合出該葉子節點最佳的輸出值:
-
-
-
-
-
- 然后更新強分類器:
-
-
"""
整個迭代過程就是首先獲得負梯度,然后以負梯度代替殘差擬合一顆最優的決策樹,接下來對葉子結點進行線性搜索,得到每個葉子節點的最優值,最終得到當前輪的強分類器。
從上面可以看出,在求解過程中,分別求了最優的決策樹,即最優的葉子節點區域和最優的葉子結點區域的取值,也就是進行了兩次優化,且兩次優化是順序進行的,那么對於XGBoost中希望能夠將兩步合並在一起進行,即一次求解出最優的J個葉子節點區域和葉子結點區域對應的最優值,在討論二者如何同時進行之前,先來看一下XGBoost的損失函數:
上面提到,XGBoost在GBDT的損失函數的基礎上加上了正則化項,其正則化項如下(假設當前迭代為第m輪):
這里的wmj就是GBDT中第j個葉子結點的最優值cmj,不過論文中是以w的形式給出,這里就以w表示葉子結點的最優值。
那么根據正則化項,XGBoost的損失函數變成了:
將本身損失函數部分按泰勒二階展開,如下:
令一階導數為hmi,令二階導數為gmi(這里一階和二階跟原文搞反了,后面就按這個推導吧),那么上式變成了:
上式中L(yi,fm-1(xi))是一個常數,對其沒有影響,忽略不計,那么:
由於葉子節點j,其對應的輸出值wmj,那么hm(xi)進一步轉化:
那么損失函數進一步合並,轉換為:
再次令:
那么最終,XGBoost的損失函數變成了:
那么接下來如何同時求得最優的葉子節點J和葉子節點最優的輸出值wmj*呢?
XGBoost的求解
這兩個問題並成一步其實就是:同時(1)找出一顆最優的決策樹,然后(2)使得最優的決策樹的所有葉子節點的輸出值最優。
首先先看第二個問題,要想找到葉子節點最優的輸出值,只需要讓損失函數Lm最小即可,這一點同GBDT是一樣的,對Lm進行求導,令其為0,即可求解出wmj*:
-----------------------------------------------------
Tips:
為什么說在GBDT中也是這樣的呢?,在GBDT中,第m顆樹的葉子結點j最優輸出值cmj的近似值是這樣的:
還記得二元分類中的對數似然損失函數:
那么這個損失函數的一階倒數hi和二階導數gi如下:
在GBDT中沒有正則項,那么可以看出cmj的近似表達式就是一階倒數和二階導數的比值,即:
-----------------------------------------------------
上述過程也解釋了在XGBoost算法中,使用二階泰勒展開來損失函數誤差進行擬合,那么接下來就要看樹是如何進行分裂,才能得到最優的決策樹及其葉子節點區域了,即如何選擇特征及其對應的特征值使得最終的損失函數Lm最小?
在GBDT中是采用的CART回歸樹的算法(前面在GBDT實例中有個標注,說無論損失函數是什么,在節點進行分裂時,都是使用的均方誤差最小作為划分依據,這樣看來那里理解是對的)進行樹的分裂,而在XGBoost中,樹的分裂不再使用均方誤差作為划分依據,而是采用貪心算法,在每次進行分裂時都期望最小化損失函數。
當每個葉子節點都取值達到最優的時候,即將wml*代入到原損失函數中,那么損失函數就變成了:
那么,如果每次進行分裂時,讓這個損失函數減小的越多就越好,假設當前節點的左右子節點的一階導數和分別為HL、HR,二階導數的和分別為:GL、GR,若當前節點不再進行分裂的損失為:
若進行分裂,則分裂出左右子節點,那么損失為:
我們期望兩者的差值越大越好,即:
使得上面這個式子值最大的划分點就是最好的。
舉個例子:
假設一組數據,其中有一個年齡特征,將年齡的值進行排序,同CART一樣對可能的年齡取值進行遍歷,可以將數據划分為兩部分,比如某個取值a,將年齡大於a的放在右邊,年齡小於a的放在左邊:
將數據划分為上圖中的兩個部分,然后分別計算左右子樹的一階導數和、二階導數和,代入到L-L'中求得一個分數(score),不斷調整a的值,重復上述過程,然后更換其他特征(相當於外循環),再次循環上述過程,最終最大的score即為最優的特征和對應的特征切分點。
那么,到這里就能理解了XGBoost論文中對於切分點搜索的算法了(下文會對此做一個描述):
到這里上述兩個過程已經解決了,之所以不同於GBDT就是上面的兩個方面是同步進行的,因為在進行樹的分裂的時候就已經朝着葉子節點最優輸出值的目標(wml*)進行的,而GBDT則是先根據上一輪殘差擬合出一顆決策樹,然后再去求最優的葉子節點最優值。
XGBoost算法流程
上面過程是XGBoost的核心步驟,那么整個算法在GBDT的基礎上,換掉其核心算法即為XGBoost的算法流程,下面對算法進行描述:
"""
輸入:訓練數據{(x1,y1),(x2,y2),...,(xN,yN)},迭代次數M,損失函數L,正則化參數γ、λ;
輸出:強分類器f(x);
- 初始化強分類器f0(x);
- 對於迭代次數m=1~M:
- 計算每個樣本在當前損失函數L對fm-1(xi)的一階導數hmi,二階導數gmi;
- 對當前節點開始嘗試進行分裂,默認score=0,並計算當前節點的一階導數和H,二階導數和G;
- 對於特征1~K:
- HL=0,GL=0:
- 將特征值按照從小到大進行排序,依次取出樣本xi將其放入左子樹,然后計算左右子樹的一階導數、二階導數和:
-
-
- 更新分數score:
-
-
- 選出最大的score所對應的特征及其特征值對該節點進行分裂;
- 若score值為0,則當前決策樹建立完畢,計算出所有的葉子節點的值wmj*,得到弱分類器hm(x),疊加得到強分類器fm(x),退出當前輪迭代,進入下一輪;若score不為0,則轉回節點分裂開始階段,繼續對下層節點進行樹的分裂;
"""
性能上的優化
XGBoost在對某一特征選取最優特征划分點的時候采用的是並行的方式處理的,將其放到多線程中運行提高運行效率。具體做法是:前面算法中提到,將特征值按照從小到大的順序進行排列,首先所有樣本默認放在右子樹中,然后從小到大迭代,依次放入左子樹中,尋找出最優的切分點,這樣減少了很多不必要的比較。此外,通過設置合理的分塊的大小,充分利用了CPU緩存進行讀取加速(cache-aware access),使得數據讀取的速度更快。另外,通過將分塊進行壓縮(block compressoin)並存儲到硬盤上,並且通過將分塊分區到多個硬盤上實現了更大的IO。
健壯性的優化
在對算法和性能優化的同時,XGBoost還對算法的健壯性進行了優化,除了前面提到的加入了正則化提高模型的泛化能力,XGBoost還對特征的缺失值進行了處理。
XGBoost沒有假設缺失值一定進入到左子樹還是右子樹,而是通過枚舉所有可能缺失值進入左子樹還是右子樹,來決定缺失值到底進入到左還是右。
也就是說,上面的算法步驟中節點分裂步驟會走兩次,假設缺失的特征為k,第一次所有的缺失樣本都走左子樹,第二次是都走右子樹,然后每次沒有缺失值的特征(除k以外)的樣本都走上述流程,而不是所有的樣本。由於默認樣本都在右子樹,步驟不變,當樣本都走左子樹時,要依次往右節點放入樣本,那么初始化需要HR、GR為0。
以上就是XGBoost的基本原理內容,XGBoost主要是對算法上的優化比較多,就整體算法流程而言,XGBoost與GBDT無異,主要體現在了弱學習器(樹)的生成方面,具體XGBoost的原論文:https://arxiv.org/pdf/1603.02754v1.pdf。后面會找一些XGBoost的實例和調參過程學習一下,了解其涉及的參數和優化過程。
XGBoost是一個比較重要的機器學習算法,后續有機會通過小實例重復一下其算法步驟,加深算法的理解,會再翻看一些大神的文章去發現自己理解問題,后面會再進行補充。