全稱:eXtreme Gradient Boostinghtml
做者:陳天奇python
基礎:GBDTgit
所屬:boosting迭代型、樹類算法github
適用範圍:迴歸,分類,排序算法
xgboost工具包:sklearn xgboost連接 | xgboost工具包(中文)連接 | xgboost工具包(英文)連接chrome
優勢:api
缺點:(與LightGBM相比)數組
xgboost 也是使用與提高樹相同的前向分步算法。其區別在於:xgboost 經過結構風險極小化來肯定下一個決策樹的參數 :緩存
最初損失函數:
$L_t=\sum\limits_{i=1}^mL(y_i, f_{t-1}(x_i)+ h_t(x_i)) + \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2$
在GBDT損失函數$L(y, f_{t-1}(x)+ h_t(x))$的基礎上,加入正則項$\Omega(h_t) = \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2$其中,J是葉子節點的個數,$w_{tj}$是第j個葉子節點的最優值,這裏的$w_{tj}$和GBDT中的$c_{tj}$是一個意思,Xgboost論文中用的是w表示葉子的值,這裏和論文保持一致。
損失函數的二階展開:
$\begin{align} L_t & = \sum\limits_{i=1}^mL(y_i, f_{t-1}(x_i)+ h_t(x_i)) + \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2 \\ & \approx \sum\limits_{i=1}^m( L(y_i, f_{t-1}(x_i)) + \frac{\partial L(y_i, f_{t-1}(x_i) }{\partial f_{t-1}(x_i)}h_t(x_i) + \frac{1}{2}\frac{\partial^2 L(y_i, f_{t-1}(x_i) }{\partial f_{t-1}^2(x_i)} h_t^2(x_i)) + \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2 \end{align}$
爲了方便,記第i個樣本在第t個弱學習器的一階和二階導數分別爲:
$g_{ti} = \frac{\partial L(y_i, f_{t-1}(x_i) }{\partial f_{t-1}(x_i)}, \; h_{ti} = \frac{\partial^2 L(y_i, f_{t-1}(x_i) }{\partial f_{t-1}^2(x_i)}$
則損失函數能夠表達爲:
$L_t \approx \sum\limits_{i=1}^m( L(y_i, f_{t-1}(x_i)) + g_{ti}h_t(x_i) + \frac{1}{2} h_{ti} h_t^2(x_i)) + \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2$
第一項是常數,對最小化loss無影響,能夠去掉,同時因爲每一個決策樹的第j個葉子節點的取值最終是同一個值$w_{tj}$,所以損失函數簡化爲:
$\begin{align} L_t & \approx \sum\limits_{i=1}^m g_{ti}h_t(x_i) + \frac{1}{2} h_{ti} h_t^2(x_i)) + \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2 \\ & = \sum\limits_{j=1}^J (\sum\limits_{x_i \in R_{tj}}g_{ti}w_{tj} + \frac{1}{2} \sum\limits_{x_i \in R_{tj}}h_{ti} w_{tj}^2) + \gamma J + \frac{\lambda}{2}\sum\limits_{j=1}^Jw_{tj}^2 \\ & = \sum\limits_{j=1}^J [(\sum\limits_{x_i \in R_{tj}}g_{ti})w_{tj} + \frac{1}{2}( \sum\limits_{x_i \in R_{tj}}h_{ti}+ \lambda) w_{tj}^2] + \gamma J \end{align}$
把每一個葉子節點區域樣本的一階和二階導數的和單獨表示以下:
$G_{tj} = \sum\limits_{x_i \in R_{tj}}g_{ti},\; H_{tj} = \sum\limits_{x_i \in R_{tj}}h_{ti}$
最終損失函數的形式能夠表示爲:
$L_t = \sum\limits_{j=1}^J [G_{tj}w_{tj} + \frac{1}{2}(H_{tj}+\lambda)w_{tj}^2] + \gamma J$
xgboost須要目標函數的二階導數信息(或者hess矩陣),在迴歸問題中常常將MAE或MAPE做爲目標函數,然而,這兩個目標函數二階導數不存在。
$MAE=\frac{1}{n}\sum_1^n|y_i-\hat y_i|$,$MAPE=\frac{1}{n}\sum _i^n \frac{|y_i-\hat y_i|}{y_i}$
其中,$y_i$是真實值,$\hat y_i$是預測值
方法(1):利用可導的函數逼近MAE或MAPE---MSE、Huber loss、Pseudo-Huber loss
利用MSE逼近是能夠的,可是MSE在訓練初偏差較大的時候,loss是其平方,會使得訓練偏離MAE的目標函數,通常難以達到高精度的要求。
利用Huber loss進行逼近也能夠,可是Huber loss是分段函數,不方便計算,其中$\delta$是可調節參數。
實際採用Huber loss的可導逼近形式:Pseudo-Huber loss function
一階導數:
二階導數:
方法(2):自定義二階導數的值:$ln(cosh(x))$
用$ln(cosh(x))$以及$log(exp(-x) + exp(x))$進行逼近
$ln(cosh(x))$的一階導數:$tanh(x)$
$ln(cosh(x))$的二階導數:$1-tanh(x)*tanh(x)$
Xgboost框架用tree_method[默認爲’auto’] 指定了構建樹的算法,能夠爲下列的值(分佈式,以及外存版本的算法只支持 ‘approx’,’hist’,’gpu_hist’ 等近似算法):
‘auto’: 使用啓發式算法來選擇一個更快的tree_method: 對於小的和中等的訓練集,使用exact greedy 算法分裂節點 對於很是大的訓練集,使用近似算法分裂節點 舊版本在單機上老是使用exact greedy 分裂節點 ‘exact’: 使用exact greedy 算法分裂節點 ‘approx’: 使用近似算法分裂節點 ‘hist’: 使用histogram 優化的近似算法分裂節點(好比使用了bin cacheing 優化) ‘gpu_exact’: 基於GPU 的exact greedy 算法分裂節點 ‘gpu_hist’: 基於GPU 的histogram 算法分裂節點
(1)第一種方法是對現有的葉節點加入一個分裂,而後考慮分裂以後目標函數下降多少。
(2)對於一個葉節點,加入給定其分裂點,定義劃分到左子樣本節點的集合爲:$\mathbb{I_R}$,則有:
(3)定義葉節點的分裂增益爲:
其中,
每次只有一個葉節點分裂,所以其餘葉節點不會發生變化,所以:
(4)如今的問題是:不知道分裂點,對於每一個葉節點,存在多個分裂點,且可能不少分裂點都能帶來增益。
解決辦法:對於葉節點中的全部可能的分裂點進行一次掃描。而後計算每一個分裂點的增益,選取增益最大的分裂點做爲本葉節點的最優分裂點。
(5)最優分裂點貪心算法
輸入:$D={(X_1,y_1),(X_2,y_2), ...(X_m,y_m)}$,屬於當前葉節點的樣本集的下標集合$\mathbb{I}$
輸出:當前葉節點最佳分裂點
算法:
step1:初始化 $score \leftarrow 0$,$G \leftarrow_{i\in \mathbb{I}}g_i$,$H \leftarrow_{i\in \mathbb{I}}h_i$
step2:遍歷各維度 $k=1,2,...,m$:
a)初始化:$G_L \leftarrow 0,H_L \leftarrow 0$
b)若是第$k$維特徵爲連續值,則將當前葉節點中的樣本從小到大排序。而後用$j$順序遍歷排序後的樣本下標。
c)若是第$k$維特徵爲離散值${a_1,a_2,...,a_{n_k}},設當前葉節點中第$k$維取值$a_j$樣本的下標集合爲$\mathbb{I_j}$,則遍歷$j=1,2,...,n_k$:
step3:選取最大的$score$對應的維度和拆分點做爲最優拆分點。
分裂點貪心算法嘗試全部特徵和全部分裂位置,從而求得最優分裂點。當樣本太大且特徵爲連續值時,這種暴力作法的計算量太大。
(1)近似算法尋找最優分裂點時不會枚舉全部的特徵值,而是對特徵值進行聚合統計,而後造成若干個桶。而後僅僅將桶邊界上的特徵的值做爲分裂點的候選,從而獲取計算性能的提高。
(2)對第k個特徵進行分桶,分桶的數量l就是全部樣本在第k個特徵上的取值的數量。
若是第k個特徵爲連續特徵,則執行百分位分桶,獲得分桶的區間爲:$S_k={s_{k,1},s_{k,2},...,s_{k,l}}$,其中$s_{k,1}<s_{k,2}<...<s_{k,l}$,分桶的數量、分桶的區間都是超參數,須要仔細挑選
若是第k個特徵爲離散特徵,則執行按離散值分桶,獲得的分桶爲:$S_k={s_{k,1},s_{k_2},...,s_{k,l}}$,其中,$s_{k,1}<s_{k,2}<...<s_{k,l}$ 爲第k個特徵的全部可能的離散值。
(3)最優分裂點近似算法
算法流程:
輸入:數據集$D={(X_1,y_1),(X_2,y_2),...,(X_N,y_N)}$,屬於當前葉結點的樣本集的下標集合$\mathbb{I}$
輸出:當前葉節點最佳分裂點
step1:對每一個特徵進行分桶。假設對第k個特徵上的值進行分桶爲:$S_k={s_{k,1},s_{k,2},...,s_{k,l}}$,若是第k個特徵爲連續特徵,則要求知足$s_{k,1}<s_{k,2}<...<s_{k,l}$
step2:初始化:$score \leftarrow 0,G\leftarrow \sum_{i\in \mathbb{I}} g_i ,H\leftarrow \sum_{i\in \mathbb{I}} h_i$
step3:遍歷各維度:$k=1,...,n$
初始化:$G_L \leftarrow 0,H_L \leftarrow 0$
遍歷各拆分點,即遍歷$j=1,2,...,l$:
若是是連續特徵,即設葉節點的樣本中,第k個特徵取值在區間$(s_{k,j},s_{k,j+1}]$的樣本的下標集合爲$\mathbb{I}_j$,則:
若是是離散特徵,則設葉結點的樣本中,第k個特徵取值等於$s_{k,j}$的樣本的下標集合爲$\mathbb{I}$ ,則:
選取最大的score對應的維度和拆分點做爲最優拆分點。
(4)分桶有兩種模式:
全局模式:在算法開始時,對每一個維度分桶一次,後續的分裂都依賴於該分桶並再也不更新;
優勢:只須要計算一次,不須要重複計算;
缺點:在通過屢次分裂以後,葉節點的樣本有可能在不少全局桶中是空的。
局部模式:每次拆分以後再從新分桶;
優勢:每次分桶都能保證各桶中的樣本數量都是均勻的;
缺點:計算量較大。
全局模式會構造更多的候選拆分點,而局部模式會更適合構造構造更深的樹。
(5)分桶時的桶區間間隔大小是個重要的參數。區間間隔越小,則桶越多,劃分的越精細,候選的拆分點就越多。
Quantile就是ranking。若是有$N$個元素,那麼$\phi$-quantile就是指rank在$⌊\phi × N⌋$的元素。例如$S=[11,21,24,61,81,39,89,56,12,51]$,首先排序爲$[11,12,21,24,39,51,56,61,81,89]$,則$0.1-quantile=11, 0.5-quantile=39$. 上面的是exact quantile尋找方法,若是數據集很是大,難以排序,則須要引入$\epsilon-approximate \phi-quantiles$
該方法爲離線算法(全部的數必需要排序,再找分位點),是不適用於數據流的。
$\phi$-quantile是在區間$[⌊(\phi−\epsilon)×N⌋,⌊(\phi+\epsilon)×N⌋]$
當$N$增長時,$φ$-quantile的「正確」答案($\epsilon$-近似)的集合增長。所以,您能夠從輸入流中刪除一些元素,並仍保留ε近似分位數查詢的正確答案(=詢問$\epsilon$近似分位數的查詢)
回到XGBoost的建樹過程,在創建第i棵樹的時候已經知道數據集在前面i−1棵樹的偏差,所以採樣的時候是須要考慮偏差,對於偏差大的特徵值採樣粒度要加大,偏差小的特徵值採樣粒度能夠減少,也就是說採樣的樣本是須要權重的。
從新審視目標函數:
$$\begin{equation} \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) \end{equation}$$
經過配方能夠獲得
$$\begin{equation} \sum_{1}^n \left[ \frac {1}{2} h_i \left( f_t(x_i) - (-g_i/h_i)\right)^2 \right] + \Omega (f_t) + constant \end{equation}$$
所以能夠將該目標看作是第$m$棵決策樹,關於真實標籤爲$-\frac{g_i}{h_i}$和權重爲$h_i$的、損失函數爲平方損失的形式。
若是損失函數是Square loss,即$Loss(y, \widehat y) = (y - \widehat y)^2$,則$h=2$,那麼其實是不帶權;若是損失函數是Log Loss,則$h=pred * (1-pred)$。這是個開口朝下的一元二次函數,因此最大值在0.5。當$pred$在0.5附近,這個值是很是不穩定的,很容易誤判,h做爲權重則所以變大,那麼直方圖劃分,這部分就會被切分的更細。
假設候選樣本的第k維特徵,及候選樣本的損失函數的二階偏導數爲:$\begin{equation} D_k = \{(x_{1k}, h_1), (x_{2k}, h_2), \cdots (x_{nk}, h_n)\} \end{equation}$
定義排序函數:$x_{i,k}$表示樣本$x_i$的第$k$個特徵
它刻畫的是:第$k$維特徵小於$z$的樣本的$h$之和,佔總的$h$之和的比例,其中二階導數$h$能夠視爲權重,在這個排序函數下,找到一組點$\{ s_{k1}, s_{k2}, ... ,s_{kl} \}$,知足:$% <![CDATA[ \begin{equation} | r_k (s_{k,j}) - r_k (s_{k, j+1}) | < \varepsilon \end{equation} %]]>$。
其中,${s_{k1}} = \mathop {\min }\limits_i {x_{ik}},{s_{kl}} = \mathop {\max }\limits_i {x_{ik}}$,$\epsilon$爲採樣率,直觀上理解,最後會獲得$1/{\epsilon}$個分界點。其中$x_{i,k}$表示樣本$x_i$的第$k$個特徵,即:
最小的拆分點:全部樣本第$k$維的最小值;
最大的拆分點:全部樣本第$k$維的最大值;
中間的拆分點:選取拆分點,使得相鄰拆分點的排序函數值小於$\epsilon$(分桶的桶寬)。其意義爲:第$k$維大於等於$s_{k,j}$,小於$s_{k,j+1}$的樣本的$h$之和,佔總的$h$之和的比例小於$\epsilon$;這種拆分點使得每一個桶內的以$h$爲權重的樣本數量比較均勻,而不是樣本個數比較均勻。
舉例:
要切分爲3個,總和爲1.8,所以第1個在0.6處,第2個在1.2處。
對於每一個樣本都有相同權重的問題,有quantile sketch算法解決該問題,做者提出Weighted Quantile Sketch算法解決這種weighted datasets的狀況
問題:To design an algorithm, you must first design an adequate data structure to maintain the information used by the algorithm
該數據結構須要每插入值進行大量操做。 雖然它頗有用,但效率不高
這個數據結構存在問題:它不包含足夠的信息來刪除沒必要要的條目
定義:
$v_0$=目前爲止遇到的最小的數
$v_{s-1}$=目前爲止遇到的最大的數
三個性質:
舉例:
命題1:summary達到的準確度,偏差$e=max_{all i}(g_i+\triangle_i)/2$
推論1:Greenwald和Khanna算法的不變性
GK算法框架:先判斷是否要合併,再插入
插入算法:
刪除算法:
how to use the quantile summary?
直方圖聚合是樹木生長中的主要計算瓶頸。咱們引入了一種新的樹生長方法hist,其中只考慮了可能的分裂值的子集。與FastBDT和LightGBM同樣,連續特徵被分紅不連續的區域。因爲較少的索引操做,直方圖累積變得更快
新方法與tree_method = approx有何不一樣?
hist方法能夠實現approx方法沒法實現的額外優化,以下所示:
除了上述改進以外,還有一些亮點
如何使用?
(1)真實場景中,有不少可能致使產生稀疏。如:數據缺失、某個特徵上出現不少 0 項、人工進行 one-hot 編碼致使的大量的 0。
注意:每一個結點的默認分裂方向可能不一樣。
(2)在xgboost 算法的實現中,容許對數值0進行不一樣的處理。能夠將數值0視做缺失值,也能夠將其視做有效值。 若是數值0是有真實意義的,則建議將其視做有效值。
(3)缺失值處理算法
輸入:數據集$D={(X_1,y_1),(X_2,y_2),...,(X_N,y_N)}$
屬於當前葉結點的樣本集的下標集合$\mathbb{I}$
屬於當前葉節點,且第$k$維特徵有效的樣本的下標集合$\mathbb{I}_k = \{{i\in \mathbb{I}| x_{k,i}\neq missing}\}$
輸出:當前葉節點最佳分裂點
step1:初始化:$score \leftarrow 0,G\leftarrow \sum_{i\in \mathbb{I}} g_i ,H\leftarrow \sum_{i\in \mathbb{I}} h_i$
step3:遍歷各維度:$k=1,...,n$
先從左邊開始遍歷:
初始化:$G_L \leftarrow 0,H_L \leftarrow 0$
遍歷各拆分點:沿着第$k$維,將當前有效的葉節點的樣本從小到大排序。這至關於全部無效特徵值的樣本放在最右側,所以能夠保證無效的特徵值都在右子樹。而後用$j$順序遍歷排序後的樣本下標:
再從右邊開始遍歷:
初始化:$G_R \leftarrow 0,H_R \leftarrow 0$
遍歷各拆分點:沿着第$k$維,將當前有效的葉節點的樣本從大到小排序。這至關於全部無效特徵值的樣本放在最左側,所以能夠保證無效的特徵值都在左子樹。而後用$j$逆序遍歷排序後的樣本下標:
選取最大的score對應的維度和拆分點做爲最優拆分點。
缺失值處理算法中,經過兩輪遍歷能夠確保稀疏值位於左子樹和右子樹的情形。
xgboost在學習過程當中使用了以下的正則化策略來緩解過擬合:
xgboost在如下方面提出改進來提高計算速度:
(1)xgboost提出column block數據結構來下降排序時間。
時間複雜度減小:
(2)block能夠僅存放樣本的索引,而不是樣本自己,這樣節省了大量的存儲空間。
如:block_1表明全部樣本在feature_1上的從小到大排序:sample_no1,sample_no2,...
其中樣本編號出現的位置表明了該樣本的排序。
能夠看出,只需在建樹前排序依次,後面節點分裂時能夠直接根據索引獲得梯度信息。
(1)因爲在column block中,樣本的順序會被打亂,這會使得從導數數組中獲取$g_i$時的緩存命中率較低。
所以,xgboost提出了cache-aware預取算法,對每一個線程分配一個連續的buffer,讀取梯度信息並存入Buffer中(這樣就實現了非連續到連續的轉化),而後再統計梯度信息。該方式在訓練樣本數大的時候特別有用,用於提高緩存命中率。
(2)xgboost會以minibatch的方式累加數據,而後在後臺開啓一個線程來加載須要用到的導數$g_i$。
這裏有個折中:minibatch太大,會引發cache miss;過小,則並行程度較低。
(1)xgboost利用硬盤來處理超過內存容量的大數據量,其中使用了下列技術:
參考文獻:
【5】『我愛機器學習』集成學習(三)XGBoost - 細語呢喃
【6】gbdt.pdf
【7】Xgboost系統設計:分塊並行、緩存優化和Blocks for Out-of-core Computation - anshuai_aw1的博客 - CSDN博客