XGBoost論文閱讀及其原理

weixin_34320159發表於2018-05-13
6673934-a6a8d573d55198aa.jpeg

1. Abstract

Boosting tree是一種有廣泛應用的技術。聽到boosting一詞都知道它是一種迭代的更新的逐步降低模型整體的誤差的辦法如Adboost,當年Adboost跟SVM統治了整個機器學習界。最近我閱讀了XGboost(下面簡稱XGB)論文,想跟大家分享一下自己的讀後感,也自己的學習做個筆記。首先先說說XGB在實戰上面的成就吧。

Take the challenges hosted by the machine learning competition site Kaggle for example. Among the 29 challenge winning solutions 3 published at Kaggle’s blog during 2015, 17 solutions used XGBoost. Among these solutions, eight solely used XGBoost to train the model, while most others combined XGBoost with neural nets in ensembles.

以機器學習競賽網站Kaggle舉辦的挑戰為例。在2015年Kaggle部落格釋出的29個挑戰獲勝解決方案3中,17個解決方案使用XGBoost。在這些解決方案中,八個單獨使用XGBoost來訓練模型,而其他大多數人則將XGBoost與神經網路合並在一起。

可見XGB在kaggle上是大殺傷力武器,除了實戰以外XGB在理論上面也是state-of-the-art。

  1. 相對GBDT來說,XGB在增加二階梯度有更高的精度。
  2. XGB的節點劃分策略帶有進行預排序,利用樣本在損失函式上面的二階梯度作為權值。
  3. XGB對稀疏的特徵劃分方式。
  4. 在處理特徵的粒度上進行多執行緒的優化。
  5. 使用近似演算法替代每個樣本逐個判斷最佳分裂點的Exact Greedy Algorithm演算法。

以上是本文對XGB幾個要點的展開。(由於簡書對公式支援比較差,大家可以轉到我的知乎上檢視XGBoost論文閱讀及其原理,以後可能更多文章在知乎上優先發表)。今日是母親節,也祝媽媽身體健康,每天都開開心心咯!


2. Main Work

2.1 Tree Boosting with Loss function

給定一個資料集D中有n個樣本,每個樣本有m維特徵。通過訓練資料集D,我們得到K棵樹。這K棵樹累加的值為我們的預測值。

6673934-d18da06af202b0fc.png
Boosting Tree的最終預測結果

其中fk(xi)是樣本xi在第k棵樹的葉子上的權值。因此我們也可以這樣定義。

6673934-564cc302079b7030.png
樣本xi在第k棵樹上的的權值

有了輸出值,我們就可以代入損失函式當中,損失函式可以是Mean Square Error,也可以是Cross entropy Loss。當然這個不是特別重要,因為我們最後要的是他們梯度。最後我們加上我們的Regularized Learning Objective,整個損失函式就出來了。

6673934-159847d7293eac9e.png
XGB的Loss Function

那我們就有優化的目標了。

2.2 Gradient Tree Boosting

對於損失函式我們可以將它預測值展開成k棵樹的預測疊加形式。在t次迭代當中,我們可以將樹展開成:

yi = f1(xi) + f2(xi) + ... + f(t-1)(xi) + ft(xi)

那麼損失函式可以變成:


6673934-2ac740b7aa041619.png
提取出第t次迭代樹的預測值,進行單獨優化

這是我們可以利用我們熟悉的二階泰勒展開進行近似:

6673934-e1b26b548280dbd6.png
對ft(xi)進行二階泰勒展開

其中gi為第i個樣本預測值在前t次迭代的損失函式的一階梯度,hi為第i個樣本預測值在前t次迭代的損失函式的二階梯度。

6673934-a829e324145da481.png
第i個樣本預測值在前t次迭代的損失函式的一階梯度
6673934-8d94572e33de8599.png
第i個樣本預測值在前t次迭代的損失函式的二階梯度

接下來我們就對損失函式的展開式進行轉換為關於ft(xi)的二元函式並移除與ft(xi)無關的常數項:


6673934-f0300a91d922e5f8.png
簡化後的損失函式

然後我們再定義:


6673934-64914e8c7471ae29.png
第k棵當中的第j個葉子節點所在的樣本

進一步簡化損失函式為:

6673934-622ac3b3c9436ecf.png
損失函式的關於wj的二元函式

顯然我們就可以知道在當前第t次迭代當中,損失函式的極值點值(葉子節點的權值),

6673934-c5ec03830d9639b2.png
極值點,也就是葉子節點的權值

並計算處最優損失函式值

6673934-3442eede8d672c7b.png
最優損失函式值

其實eq 6就是用來衡量用哪個特徵做分裂,用在哪個特徵值上做分裂。這樣說起來就有點像我們在DT裡面的“純度”衡量值(Gini,Entropy)但我們裡是最能夠使得損失函式值最小的分裂點。跟DT一樣我們會將左右兩個位元組點“衡量值”(在這裡就是eq 6)與父節點的“衡量值”相減,得到分支後的損失函式降低程度,當然我們希望下降程度越大越好啦。

6673934-909e89cd45a842ac.png
分支判斷

如下圖所示,損失值越低結構也就越好啦。


6673934-bbd084585826501b.png

2.3 子取樣和權值收縮

其實這兩個技巧都是為了減低overfitting,首先子取樣分為樣本子取樣和特徵子取樣。子取樣算是RF的代表了,通常來說我們使用特徵取樣更多。鄙人私下認為,特徵子取樣從不同角度去考量樣本能讓模型更具有多樣性。另外權值收縮也就是對葉子節點的權值乘上收縮因子,該收縮因子是人為設定的引數。其作用是為了給後面的迭代保留優化空間。大家想想假如一棵樹把損失函式降得很低很低,那麼後續的優化空間就少了,訓練的樣本和特徵也就少了,最後也就overfitting。當然XGB不僅僅只有這些tricks,例如leaf-wise, 後剪枝,樹高度控制等。

2.4 Approximate Algorithm

大多數的DT裡面分支演算法是極其關鍵的演算法,對於連續型的特徵,我們通常都是對某一系列的特徵的所有樣本逐個進行分裂判斷。這樣也是Exact Greedy Algorithm啦。

6673934-bcad2c844acad47f.png
Exact Greedy Algorithm

Exact Greedy Algorithm這個演算法的時間複雜度O(d * m + m * log(m)) = O(m * (d + log(m))),m * log(m)是因為對樣本特徵進行約排序,d * m是對每個樣本的每個特徵進行分裂判斷。

然後就有近似演算法(Approximate Algorithm),其實它就是對連續型特徵進行離散化。那麼在XGB又是怎麼做特徵離散化呢。其實這裡利用到的還是二階梯度。對於資料集D={(x1k , h1), (x2k , h2) · · · (xnk , hn)},其中k是樣本的第k個特徵值,h是xik在損失函式的二階梯度。對於特徵而言,我們不在乎特徵值是1, 2, 3, 4.... , n還是說百分比,只要分裂節點的“純度”提高或者說損失函式值降低就可以了。於是我們定義下面rank公式用於排序使用。

6673934-a771b3820dfc0cb9.png
用於分裂排序的Rank

rank的計算是對某一個特徵上,樣本特徵值小於z的二階梯度除以所有的二階梯度總和。其實就是對樣本的二階梯度進行累加求和,那二階梯度在這裡就是代表樣本在特徵k上的權值。於是我們就對樣本重新組合成為{sk1 , sk2 , · · · skl}分裂點,ski是指每個buckets的邊界點。再有:

6673934-337bd95e3cc8503d.png
定義eps用於設定最大的

上面的eps用於設定最大的rank值,其中eps在(0, 1)的區間上。我們可以大致的認為有1 / eps個buckets。而我們對這麼多個buckets進行分支判斷。顯然,比起對m個樣本找分裂節點,對1 / eps個buckets找分裂節點更快捷,而且eps越大buckets數量越少,粒度越粗。至於我們為什麼用二階梯度作為樣本的權值,我們可以回顧一下eq (3)裡面的損失函式,將其轉換為(這裡這個說話好像有點牽強,我待會再細看appendix):

6673934-ae19454c0bc65f5f.png
rewrite eq (3)

這裡hi可以看作是樣本Square Error的權值。

對於這種離散化的方式有兩種:一種是在建立第k棵樹的時候利用樣本的二階梯度對樣本進行離散化,每一維的特徵都建立buckets。在建樹的過程中,我們就重複利用這些buckets去做。另一種是每次進行分支時,我們都重新計算每個樣本的二階梯度並重新構建buckets,再進行分支判斷。前者我們稱之為全域性選擇,後者稱為區域性選擇。顯然區域性選擇的編碼複雜度更高,但是實驗當中效果極其的好,甚至與Exact Greedy Algorithm一樣。

6673934-7ed7e12ab63903ff.png

2.5 Sparsity-aware Split Finding

很多時候訓練資料都是稀疏的(如TF-IDF),資料都是有缺失值的。很多機器學習的演算法都是沒有具體辦法處理稀疏資料,如SVM,NN等。XGB訓練資料的時候,它使用沒有缺失的資料去進行節點分支。然後我們將特徵上缺失的資料嘗試放左右節點上,看缺失值應當分到那個分支節點上。我們把缺失值分配到的分支稱為預設分支。

6673934-f28dd21993c50922.png
Sparsity-aware Split Finding

3 Other work

在整個系統設計上,XGB也是很講究的。下面我就簡要的介紹一下:

  1. 利用列塊進行平行計算:在我們訓練過程中我們主要是做分支處理,分支處理就要對每一列(特徵)找出適合的分裂點。通常來說,我們更青睞使用csc儲存,這樣我們就方便取出來。再者我們在分支的時候都會預先對資料按照其特徵值進行排序。所以我們將資料按照列儲存成一個資料塊方便我們在分支的時候並行處理。所以我們要知道XGB的平行計算的粒度不在樹上,而是在特徵上,尤其是不同分支節點上(leaf-wise)。當然這也成為XGB的一個問題所在,需要額外的空間儲存pre-sort的資料。而且每次分支後,我們都要找處落在下一個子節點上的樣本,並組織好它。後來就有了LightGBM,下次我再將其整理出來

  2. 快取處理能力:對於有大量資料或者說分散式系統來說,我們不可能將所有的資料都放進記憶體裡面。因此我們都需要將其放在外存上或者分散式儲存。但是這有一個問題,這樣做每次都要從外存上讀取資料到記憶體,這將會是十分耗時的操作。因此我們使用預讀取(prefetching)將下一塊將要讀取的資料預先放進記憶體裡面。其實就是多開一個執行緒,該執行緒與訓練的執行緒獨立並負責資料讀取。此外,我還要考慮block的大小問題。如果我們設定最大的block來儲存所有樣本在k特徵上的值和梯度的話,cache未必能一次性處理如此多的梯度做統計。如果我們設定過少block size,這樣不能充分利用的多執行緒的優勢,也就是訓練執行緒已經訓練完資料,但是prefetching thread還沒把資料放入記憶體或者cache中。經過測試,作者發現block size設定為2^16個examples最好:

    6673934-213b2b7e34efebbd.png
    block size比較

  1. 資料塊以外的計算力提高:對於超大型的資料,我們不可能都放入放入記憶體,因此大部分都放入外存上。假如我們將資料存於外存上將給我們帶來讀寫速度受限的問題。文中有兩種方法,一種是對資料進行壓縮存於外存中,到記憶體中需要訓練時再解壓,這樣來增加系統的吞吐率,儘管消耗了一些時間來做編碼和解碼但還是值得的。另一種就是多外存儲存,其實本質上就是分散式儲存。這樣說有多個執行緒對分散式結構管理,吞吐率自然高啦。

4 References

相關文章