Paper-summary-2: XGBoost- A Scalable Tree Boosting System

Introduction

XGBoost對大部分人而言應該是不陌生的,在kaggle等數據科學競賽的平臺上憑藉其準確快速的特性吸引了一大批擁躉。這次花了幾天時間自己讀了一下該論文,作者的基於GBDT的算法改進以及計算機系統設計層面的優化非常有創新性。不僅如此,作者在paper中將複雜度分析以及僞代碼的部分也寫的非常的詳細。總的來說這是一篇很有價值的paper,適合精讀一次。

Goal of the paper

Describe a scalable end-to-end tree boosting system called XGBoost, which is used widely by data scientist to achieve SOTA results.

這是paper中的原話,而關鍵也在於這個system。相較於別的提升樹模型,XGboost的一個主要優勢也是對不同問題的高度兼容性以及穩定性。穩定主要體現在算法層面及系統層面。算法層面優化針對稀疏數據的處理方法以及對連續特徵的分組方法。而系統層面提出了一種緩存管理的方法用來降低資源損耗。二者結合後使得XGBoost用相對當時現有的系統更少的計算資源在億級數據層面獲得不錯效果。

Major contribution from this paper

  1. 提出一個highly scalable的端對端提升樹系統。
  2. 提出加權版的quantile sketch用來處理連續數據分段。
  3. 提出適合稀疏數據的並行學習算法。
  4. 提出一種高效的緩存結構用來輔助out-of-core learning.

Pre-defined Notations

在正文之前,先來統一一下數學符號以及一些關鍵性的概念。

  • D={(xi,yi)}(D=n,xiRm,yiR)\mathcal{D} = \{(x_i, y_i)\} (|\mathcal{D}| = n,x_i \in\mathbb{R}^m, y_i\in \mathbb{R}) : 樣本數爲nn的數據集,其中feature的個數爲mm

  • Dk={(x1k,h1),(x2k,h2),...,(xnk,hn)}\mathcal{D}_k = \{(x_{1k}, h_{1}),(x_{2k}, h_{2}),...,(x_{nk}, h_{n})\} : 第k個feature裏的值及對應的二階導數。

  • KK : boosting過程中子模型個數。

  • F={f(x)=wq(x)}(q:RmT,wRT)\mathcal{F} = \{f(\mathbf{x}) = w_{q(\mathbf{x})}\} (q:\mathbb{R}^m \rightarrow T,w\in \mathbb{R}^T) : F\mathcal{F}爲所有迴歸樹的空間,qq表示樹的一個結構。

  • x0||\mathbf{x}||_0 : x\mathbf{x}中有效元素的個數。

  • ll : 二階可導的單樣本損失函數。

  • L\mathcal{L} : 整個模型的損失函數。

  • L(t)\mathcal{L}^{(t)} : 在第tt步模型損失函數。

  • ϕ(xi)=k=1Kfk(xi),fkF\phi (\mathbf{x}_i) = \sum_{k=1}^K f_k(\mathbf{x}_i), f_k \in \mathcal{F} : ϕ\phi代表最終模型的輸出,fkf_k指構成模型的一系列子模型。

  • gig_i : loss function 對yi(t1)y_i^{(t-1)}的一階導數。

  • hih_i : loss function 對yi(t1)y_i^{(t-1)}的二階導數。

  • rk(z)=1(x,h)Dkh(x,h)DkhI(x<z)r_k(z) = \frac{1}{\sum_{(x,h)\in \mathcal{D}_k}h}\sum_{(x,h)\in \mathcal{D}_k}h\mathbb{I}(x<z) : rank function,feature k中小於z的樣本點佔的比重。

  • skjs_{kj} : (quantile)分割點,其中sk1=minixiks_{k1}=min_i \mathbf{x}_{ik}skl=maxixiks_{kl}=max_i\mathbf{x}_{ik}

Methodology Part-1: Gradient Boosting

(a) Regularized Learning objective

模型的損失函數爲所有單個樣本的損失和。相對與GBDT,XGBoost加入了一個正則項,用來控制樹的深度和參數的數值來做到防過擬合。當正則項係數設爲0的時候,XGBoost和GBDT在優化方向方面是一致的。加入正則化後,模型的損失函數變成了
L(ϕ)=il(yi^,yi)+kΩ(fk)\mathcal{L}(\phi) = \sum_i l(\hat{y_i}, y_i) + \sum_k \Omega(f_k) Ω(f)=γT+12λw2\Omega(f) = \gamma T + \frac{1}{2}\lambda ||w||^2

(b) Gradient Tree Boosting

XGBoost本質還是一個梯度提升樹,所以訓練loss的時候跟GBDT有很大一部分是相似的。例如同爲加法模型,在每一步迭代(假設當前爲tt步),我們需要找到一個ftf_t,來使得當前loss最小
L(t)=il(yi^,yi(t1)+ft(xi))+Ω(ft)\mathcal{L}^{(t)} = \sum_i l(\hat{y_i}, y_i^{(t-1)} + f_t(\mathbf{x}_i)) + \Omega(f_t)
但是在加速收斂方面,GBDT採用的是first-order approximation,對loss做了關於yi(t1)y_i^{(t-1)}的一階泰勒展開,而XGBoost採用的是second-order approximation,對loss做了二階泰勒展開。列在一起對比如下(當然GBDT是沒有regularized部分的)

L(t)i=1nl(yi,yi^(t1))+gift(xi)+Ω(ft) \mathcal{L}^{(t)} \approxeq \sum_{i=1}^nl(y_i, \hat{y_i}^{(t-1)}) + g_if_t(\mathbf{x}_i) + \Omega(f_t) L(t)i=1nl(yi,yi^(t1))+gift(xi)+12hift2(xi)+Ω(ft) \mathcal{L}^{(t)} \approxeq \sum_{i=1}^nl(y_i, \hat{y_i}^{(t-1)}) + g_if_t(\mathbf{x}_i) + \frac{1}{2}h_if_t^2(\mathbf{x}_i) + \Omega(f_t)
在針對ft(x)f_t(x) 進行優化的時候,兩個式子的首項都與ftf_t無關,所以我們都可以提前將他們刪掉。因此GBDT最後的優化目標即爲每一步殘差的一階導數,而XGBoost則同時擁有一階及二階導數。兩者在最終結果上應該是一致的,但是不同approximation方式使得XGBoost的收斂速度變得快了幾分。兩者的算法對比可以參考梯度下降法和牛頓法的對比。

最後將Ω(ft)\Omega(f_t)代入式子,並去掉首項的常數項,我們最後獲得一個關於ftf_t的函數,並把ft(xi)=wq(x)f_t(\mathbf{x}_i) = w_{q(\mathbf{x})}也代入,依照樹模型的原理,每一個xi\mathbf{x}_i會被分到一個區間內,對應的模型輸出爲wjw_j。描述的比較抽象但是寫出來還是蠻清晰的。
Lˉ(t)=i=1n[gift(xi)+12hift2(xi)]+γT+12λj=1Twj2Lˉ(t)=j=1T[(iIjgi)wj+12(iIjhi+λ)wj2]+γT\begin{array}{ll} \bar{\mathcal{L}}^{(t)}=&\sum_{i=1}^n[g_if_t(\mathbf{x}_i) + \frac{1}{2}h_if_t^2(\mathbf{x}_i)] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ \bar{\mathcal{L}}^{(t)}=&\sum_{j=1}^T[(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i + \lambda)w_j^2] + \gamma T \end{array}
wjw_j就是我們要獲得的用來定義ftf_t的模型參數,所以現在既然已有了函數,我們可以直接對wjw_j求導來得到關於wjw_j的極值點。
wj=iIjgiiIjhi+λ w_j^* = -\frac{\sum_{i \in I_j}g_i}{\sum_{i\in I_j}h_i + \lambda}
代入上式我們即獲得
Lˉ(t)=12j=1T(iIjgi)2iIjhi+λ+γT \bar{\mathcal{L}}^{(t)}=-\frac{1}{2}\sum_{j=1}^T\frac{(\sum_{i\in I_j}g_i)^2}{\sum_{i\in I_j}h_i+\lambda} + \gamma T
以上,我們獲得了一個新的loss function,而這個function除了TT以外,只與一階導數和二階導數有關。而作爲樹模型,同樣也有一個類似於信息增益那樣基於loss reduction的分割子數的評價標準。假設當前feature被分爲ILI_LIRI_R而且有Ij=ILIRI_j = I_L \cup I_R,那麼對應的loss reduction即爲
Lsplit=12[(iILgi)2iILhi+λ+(iIRgi)2iIRhi+λ(iIjgi)2iIjhi+λ] \mathcal{L}_{split} = \frac{1}{2}[\frac{(\sum_{i\in I_L}g_i)^2}{\sum_{i\in I_L}h_i+\lambda} + \frac{(\sum_{i\in I_R}g_i)^2}{\sum_{i\in I_R}h_i+\lambda}-\frac{(\sum_{i\in I_j}g_i)^2}{\sum_{i\in I_j}h_i+\lambda}]
該式子在形式還是挺直觀,在本質上與信息增益等傳統方法工作原理是一樣的。分割用的特徵以及分割點需要使loss reduction最大。

(cc) Techniques to Avoid over-fitting

爲了解決過擬合,除了正則化項,XGBoost同時還加入了兩種“傳統”辦法,所謂傳統就是之前模型中出現的方法。
例如:

  1. Shringkage: 出自GBDT,簡單原理就是在boosting過程中,當準備加入一棵新樹的來更新模型殘差的時候,工作原理類似梯度下降法中的step size。
  2. Column(feature) Subsampling: 出自random forest,指的是在基於某個feature進行樹分割的時候,只對一部分feature值進行遍歷。因爲遍歷的點少了,所以這個方法同時還能加速計算。

Methodology Part-2: Split Finding Algorithm

(a.) Exact Greedy Algorithm

構建樹模型的時候最重要的仍舊是分割點的搜尋問題。最直接的辦法還是Exact Greedy Search,也就是在要分割的時候測試每一個feature的每一個樣本點,選擇loss reduction最大的那個feature及相應的樣本點作爲分割點。paper中提供了一個XGBoost版的:

在這裏插入圖片描述
這個算法是要求我們算出每個feature的在每個點分割後,產生的loss reduction,在這裏預先對feature裏的值進行排序使這個搜索過程變成y一個動態規劃的過程。降低了求loss reduction的成本。雖然排序的過程很耗時間,但XGBoost的一個系統層面上的優化點,就是預先對所有的feature進行了排序並存儲。

(b.) Approximate Algorithm

與上面提到的Exact greedy算法不同,爲了高效處理數據我們很多時候不太可能對所有feature所有點進行遍歷。Approximate Algorithm就是爲了在不影響整體結果的情況下,適當地對feature進行重組,分成好多個bucket,每個bucket裏輸出一個可以代表整個bucket的整合後的值。這樣我們在進行搜索的時候,只需要測試這幾個bucket的值就可以了。對應的XGBoost版本爲:

在這裏插入圖片描述
請注意算法中提到的了globallocal,這裏指的是分桶的時候的兩種變體。

  • Global: 每個feature分好bucket後,在接下來每次split finding的時候,都用這次分好的bucket。
  • Local: 每次split finding的時候,額外分一次bucket。

(c.) Weighted Quantile Sketch

分桶的過程還是挺講究的。分桶時候還是要保證不會對模型的質量產生影響的,所以要確保各個桶裏的樣本點產生的loss是接近的,這樣在分割後不會出現左子樹和右子樹loss不平衡。當各個樣本點產生的loss的權重相等時,可以用一個叫quantile sketch的方法來分桶。但是在paper中,作者提出了一個改進方法,首先將XGBoost的loss function重寫成了weighted loss的形式
i=1n12hi(ft(xi)gihi)2+Ω(ft)+constant \sum_{i=1}^n\frac{1}{2} h_i(f_t(\textbf{x}_i)-\frac{g_i}{h_i})^2+\Omega(f_t) + constant
可以看出hih_i在這裏扮演了個類似權重的作用。可以理解爲每個樣本點產生的對bucket產生的loss不是等價的。爲了配合這種表達,作者順便提出了一個weighted quantile sketch算法來解決這個問題。由於這部分內容並沒有在paper中說明,有需要可以參考作者批註的supplementary material

另外,作者還是提供了明確的數學定義來描述算法的目的。假設手上有Dk\mathcal{D}_k,以及閾值ϵ\epsilon,算法的目的爲找到一組quantile的分割點,{sk1,sk2,sk3,...skl}\{s_{k1},s_{k2},s_{k3},...s_{kl}\}使
rk(sk,j)rk(sk,j+1)<ϵ|r_k(s_{k,j})-r_k(s_{k,j+1})| < \epsilon
可以估計,大約有1/ϵ1/\epsilon個分割點。參照前面列出來的rkr_k的定義,權重是由hih_i來構定義的,分母是一個normalization項從而使rkr_k的輸出爲一個分數。

(d.) Sparsity-aware split finding.

這部分主要是講解如何處理缺失值的,我認爲非常有借鑑意義。雖然名字裏帶着sparsity(稀疏),但是產生的原因主要也是因爲存在缺失點。作者在這裏的處理方式很巧妙,大致可以歸納爲:

  • 只處理非缺失的部分。
  • 假設missing data會被分到右邊,並得出最優的模型以及score。
  • 假設missing data會被分到左邊,並得出最優的模型以及score。
  • 比較兩個score,得z最優模型及最佳missing value的默認方向。

算法截圖爲

在這裏插入圖片描述

System Design

這部分講的全是系統層面上的優化,主要包括XGBoost在分佈式下的並行實現。同時也是我讀的最吃力的地方。由於非計算機科班我就不對這些描述太多了。主要貢獻包括

  • block structure: 預存各個特徵的排序結j及相應index來優化split finding
  • cache-aware access: 來達到高速訪問緩存,來實現依照index快速調取gradient的目的。

Time complexity analysis

如題:
假設一共需要K個子模型,每個模型由一個深度爲d的樹構成。那麼paper中列舉的算法複雜度爲.

  1. Exact Greedy Algorithm, O(Kdx0logn)O(Kd||\mathbf{x}||_0logn)
    • KdKd次split finding。
    • x0||x||_0個有效元素需要被排序。
    • 被排序元素大概需要遍歷lognlogn
  2. Tree boosting + block structure, O(Kdx0+x0logn)O(Kd||\mathbf{x}||_0 + ||\mathbf{x}||_0logn)
    • KdKd次split finding,以及每次x0||\mathbf{x}||_0次遍歷。
    • x\mathbf{x}進行一次排序,x0logn||\mathbf{x}||_0logn
  3. Approximate Algorithm, 假設q個quantile,O(Kdx0logq)O(Kd||\mathbf{x}||_0logq)
    • 由於這次其實只有qq值元素在被排序,所以替換爲logqlogq,其餘與exact greedy 保持一致。
  4. Approximate Algorithm using block structure,假設每個Block中有BB行,O(Kdx0+x0logB)O(Kd||\mathbf{x}||_0 + ||\mathbf{x}||_0logB)
    • 每個block中只有BB個元素被排序,所以爲logBlogB,其餘與2保持一致。

Conclusion.

作者同時從數學及計算機的角度對Boosting tree的方法進行了優化,整個paper的信息量還是挺大的,尤其是system design的部分對操作系統方面也是有要求的。本質上XGBoost還是一個gradient boosting tree,在後面的算法對比中XGBoost也在不同任務裏,與GBDT達到了同等高度的模型準確度,但是在這麼多優化方法下,時間效率上以及資源利用率上提升了非常多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章