【Python機器學習實戰】決策樹與整合學習(六)——整合學習(4)XGBoost原理篇

Uniqe發表於2021-09-11

XGBoost是陳天奇等人開發的一個開源專案,前文提到XGBoost是GBDT的一種提升和變異形式,其本質上還是一個GBDT,但力爭將GBDT的效能發揮到極致,因此這裡的X指代的“Extreme”的意思。XGBoost通過在演算法和工程上進行了改進,使其在效能和精度上都得到了很大的提升,也成為了Kaggle比賽和工程應用的大熱門。XGBoost是大規模並行的BoostingTree的工具,比通常的工具包快10倍以上,是目前最好的開源BoostingTree的工具包,在工業界規模方面,XGBoost的分散式版本有廣泛的可移植性,支援在YARN, MPI, Sungrid Engine等各個平臺上面執行,並且保留了單機並行版本的各種優化,使得它可以很好地解決於工業界規模的問題。


XGBoost原理

從GBDT到XGBoost

  引言中說到,XGBoost實際上也是一種GBDT,不過在演算法和效能上做了很多優化和提升,其優化主要包括以下幾個方面:

  • 就演算法本身有以下優化:就效能方面的優化主要是對每個弱分類器(如決策樹)的學習時,採用並行的方式進行選擇節點所要分裂的特徵和特徵值,其做法是,在進行分裂之前,首先對所有的特徵值進行排序和分組,以便進行並行處理,對分組的特徵,選擇合適的分組大小,利用CPU的快取讀取加速,將分組儲存在多個硬碟提高IO速度。
    • 在演算法的弱分類器的選擇上,GBDT所選擇的只能是迴歸樹,而XGBoost中則可以選擇其他很多的弱分類器;
    • 在演算法的損失函式上,XGBoost除了本身的損失,還加上了正則化提高模型的泛化能力;
    • 在演算法的優化過程中,GBDT採用負梯度(一階泰勒展開)來近似代替殘差,而在XGBoost演算法中,使用二階泰勒展開來損失函式誤差進行擬合,使其更加精確。
  • 此外還有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是一個比較重要的機器學習演算法,後續有機會通過小例項重複一下其演算法步驟,加深演算法的理解,會再翻看一些大神的文章去發現自己理解問題,後面會再進行補充。

相關文章