標籤: 機器學習 集成學習 GBM GBDT XGBoost前端
梯度提高(Gradient boosting)是一種用於迴歸、分類和排序任務的機器學習技術,屬於Boosting算法族的一部分。Boosting是一族可將弱學習器提高爲強學習器的算法,屬於集成學習(ensemble learning)的範疇。Boosting方法基於這樣一種思想:對於一個複雜任務來講,將多個專家的判斷進行適當的綜合所得出的判斷,要比其中任何一個專家單獨的判斷要好。通俗地說,就是「三個臭皮匠頂個諸葛亮」的道理。梯度提高同其餘boosting方法同樣,經過集成(ensemble)多個弱學習器,一般是決策樹,來構建最終的預測模型。python
Boosting、bagging和stacking是集成學習的三種主要方法。不一樣於bagging方法,boosting方法經過分步迭代(stage-wise)的方式來構建模型,在迭代的每一步構建的弱學習器都是爲了彌補已有模型的不足。Boosting族算法的著名錶明是AdaBoost,AdaBoost算法經過給已有模型預測錯誤的樣本更高的權重,使得先前的學習器作錯的訓練樣本在後續受到更多的關注的方式來彌補已有模型的不足。與AdaBoost算法不一樣,梯度提高方法在迭代的每一步構建一個可以沿着梯度最陡的方向下降損失(steepest-descent)的學習器來彌補已有模型的不足。經典的AdaBoost算法只能處理採用指數損失函數的二分類學習任務,而梯度提高方法經過設置不一樣的可微損失函數能夠處理各種學習任務(多分類、迴歸、Ranking等),應用範圍大大擴展。另外一方面,AdaBoost算法對異常點(outlier)比較敏感,而梯度提高算法經過引入bagging思想、加入正則項等方法可以有效地抵禦訓練數據中的噪音,具備更好的健壯性。這也是爲何梯度提高算法(尤爲是採用決策樹做爲弱學習器的GBDT算法)如此流行的緣由,有種觀點認爲GBDT是性能最好的機器學習算法,這固然有點過於激進又固步自封的味道,但一般各種機器學習算法比賽的贏家們都很是青睞GBDT算法,因而可知該算法的實力不可小覷。git
基於梯度提高算法的學習器叫作GBM(Gradient Boosting Machine)。理論上,GBM能夠選擇各類不一樣的學習算法做爲基學習器。現實中,用得最多的基學習器是決策樹。爲何梯度提高方法傾向於選擇決策樹(一般是CART樹)做爲基學習器呢?這與決策樹算法自身的優勢有很大的關係。決策樹能夠認爲是if-then規則的集合,易於理解,可解釋性強,預測速度快。同時,決策樹算法相比於其餘的算法須要更少的特徵工程,好比能夠不用作特徵標準化,能夠很好的處理字段缺失的數據,也能夠不用關心特徵間是否相互依賴等。決策樹可以自動組合多個特徵,它能夠毫無壓力地處理特徵間的交互關係而且是非參數化的,所以你沒必要擔憂異常值或者數據是否線性可分(舉個例子,決策樹能輕鬆處理好類別A在某個特徵維度x的末端,類別B在中間,而後類別A又出如今特徵維度x前端的狀況)。不過,單獨使用決策樹算法時,有容易過擬合缺點。所幸的是,經過各類方法,抑制決策樹的複雜性,下降單顆決策樹的擬合能力,再經過梯度提高的方法集成多個決策樹,最終可以很好的解決過擬合的問題。因而可知,梯度提高方法和決策樹學習算法能夠互相取長補短,是一對完美的搭檔。至於抑制單顆決策樹的複雜度的方法有不少,好比限制樹的最大深度、限制葉子節點的最少樣本數量、限制節點分裂時的最少樣本數量、吸取bagging的思想對訓練樣本採樣(subsample),在學習單顆決策樹時只使用一部分訓練樣本、借鑑隨機森林的思路在學習單顆決策樹時只採樣一部分特徵、在目標函數中添加正則項懲罰複雜的樹結構等。如今主流的GBDT算法實現中這些方法基本上都有實現,所以GBDT算法的超參數仍是比較多的,應用過程當中須要精心調參,並用交叉驗證的方法選擇最佳參數。github
本文對GBDT算法原理進行介紹,從機器學習的關鍵元素出發,一步一步推導出GBDT算法背後的理論基礎,讀者能夠從這個過程當中瞭解到GBDT算法的前因後果。對於該算法的工程實現,本文也有較好的指導意義,實際上對機器學習關鍵概念元素的區分對應了軟件工程中的「開放封閉原則」的思想,基於此思想的實現將會具備很好的模塊獨立性和擴展性。算法
先複習下監督學習的關鍵概念:模型(model)、參數(parameters)、目標函數(objective function)緩存
模型就是所要學習的條件機率分佈或者決策函數,它決定了在給定特徵向量\(x\)時如何預測出目標\(y\)。定義\(x_i \in R^d\) 爲訓練集中的第\(i\)個訓練樣本,則線性模型(linear model)能夠表示爲:\(\hat{y}_i = \sum_j{w_j x_{ij}}\)。模型預測的分數\(\hat{y}_i\)在不一樣的任務中有不一樣的解釋。例如在邏輯迴歸任務中,\(1/(1+exp(-\hat{y}_i))\)表示模型預測爲正例的機率;而在排序學習任務中,\(\hat{y}_i\)表示排序分。app
參數就是咱們要從數據中學習獲得的內容。模型一般是由一個參數向量決定的函數。例如,線性模型的參數能夠表示爲:\(\Theta=\{w_j|j=1,\cdots,d\}\)。機器學習
目標函數一般定義爲以下形式:\[ Obj(\Theta)=L(\Theta)+\Omega(\Theta)\]
其中,\(L(\Theta)\)是損失函數,用來衡量模型擬合訓練數據的好壞程度;\(\Omega(\Theta)\)稱之爲正則項,用來衡量學習到的模型的複雜度。訓練集上的損失(Loss)定義爲:\(L=\sum_{i=1}^n l(y_i, \hat{y}_i)\)。經常使用的損失函數有平方損失(square loss): \(l(y_i, \hat{y}_i)=(y_i - \hat{y}_i)^2\);Logistic損失: \(l(y_i, \hat{y}_i)=y_i ln(1+e^{y_i}) + (1-y_i)ln(1+e^{\hat{y}_i})\)。經常使用的正則項有L1範數\(\Omega(w)=\lambda \Vert w \Vert_1\)和L2範數\(\Omega(w)=\lambda \Vert w \Vert_2\)。Ridge regression就是指使用平方損失和L2範數正則項的線性迴歸模型;Lasso regression就是指使用平方損失和L1範數正則項的線性迴歸模型;邏輯迴歸(Logistic regression)指使用logistic損失和L2範數或L1範數正則項的線性模型。ide
目標函數之因此定義爲損失函數和正則項兩部分,是爲了儘量平衡模型的誤差和方差(Bias Variance Trade-off)。最小化目標函數意味着同時最小化損失函數和正則項,損失函數最小化代表模型可以較好的擬合訓練數據,通常也預示着模型可以較好地擬合真實數據(groud true);另外一方面,對正則項的優化鼓勵算法學習到較簡單的模型,簡單模型通常在測試樣本上的預測結果比較穩定、方差較小(奧坎姆剃刀原則)。也就是說,優化損失函數儘可能使模型走出欠擬合的狀態,優化正則項儘可能使模型避免過擬合。函數
從概念上區分模型、參數和目標函數給學習算法的工程實現帶來了益處,使得機器學習的各個組成部分之間耦合儘可能鬆散。
GBDT算法能夠當作是由K棵樹組成的加法模型:\[\hat{y}_i=\sum_{k=1}^K f_k(x_i), f_k \in F \tag 0\]
其中\(F\)爲全部樹組成的函數空間,以迴歸任務爲例,迴歸樹能夠看做爲一個把特徵向量映射爲某個score的函數。該模型的參數爲:\(\Theta=\{f_1,f_2, \cdots, f_K \}\)。於通常的機器學習算法不一樣的是,加法模型不是學習d維空間中的權重,而是直接學習函數(決策樹)集合。
上述加法模型的目標函數定義爲:\(Obj=\sum_{i=1}^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k)\),其中\(\Omega\)表示決策樹的複雜度,那麼該如何定義樹的複雜度呢?好比,能夠考慮樹的節點數量、樹的深度或者葉子節點所對應的分數的L2範數等等。
如何來學習加法模型呢?
解這一優化問題,能夠用前向分佈算法(forward stagewise algorithm)。由於學習的是加法模型,若是可以從前日後,每一步只學習一個基函數及其係數(結構),逐步逼近優化目標函數,那麼就能夠簡化複雜度。這一學習過程稱之爲Boosting。具體地,咱們從一個常量預測開始,每次學習一個新的函數,過程以下:
\[ \begin{split} \hat{y}_i^0 &= 0 \\ \hat{y}_i^1 &= f_1(x_i) = \hat{y}_i^0 + f_1(x_i) \\ \hat{y}_i^2 &= f_1(x_i) + f_2(x_i) = \hat{y}_i^1 + f_2(x_i) \\ & \cdots \\ \hat{y}_i^t &= \sum_{k=1}^t f_k(x_i) = \hat{y}_i^{t-1} + f_t(x_i) \\ \end{split} \]
那麼,在每一步如何決定哪個函數\(f\)被加入呢?指導原則仍是最小化目標函數。
在第\(t\)步,模型對\(x_i\)的預測爲:\(\hat{y}_i^t= \hat{y}_i^{t-1} + f_t(x_i)\),其中\(f_t(x_i)\)爲這一輪咱們要學習的函數(決策樹)。這個時候目標函數能夠寫爲:
\[ \begin{split} Obj^{(t)} &= \sum_{i=1}^nl(y_i, \hat{y}_i^t) + \sum_{i=i}^t \Omega(f_i) \\ &= \sum_{i=1}^n l\left(y_i, \hat{y}_i^{t-1} + f_t(x_i) \right) + \Omega(f_t) + constant \end{split}\tag{1} \]
舉例說明,假設損失函數爲平方損失(square loss),則目標函數爲:
\[ \begin{split} Obj^{(t)} &= \sum_{i=1}^n \left(y_i - (\hat{y}_i^{t-1} + f_t(x_i)) \right)^2 + \Omega(f_t) + constant \\ &= \sum_{i=1}^n \left[2(\hat{y}_i^{t-1} - y_i)f_t(x_i) + f_t(x_i)^2 \right] + \Omega(f_t) + constant \end{split}\tag{2} \]
其中,\((\hat{y}_i^{t-1} - y_i)\)稱之爲殘差(residual)。所以,使用平方損失函數時,GBDT算法的每一步在生成決策樹時只須要擬合前面的模型的殘差。
泰勒公式:設\(n\)是一個正整數,若是定義在一個包含\(a\)的區間上的函數\(f\)在\(a\)點處\(n+1\)次可導,那麼對於這個區間上的任意\(x\)都有:\(\displaystyle f(x)=\sum _{n=0}^{N}\frac{f^{(n)}(a)}{n!}(x-a)^ n+R_ n(x)\),其中的多項式稱爲函數在\(a\)處的泰勒展開式,\(R_ n(x)\)是泰勒公式的餘項且是\((x-a)^ n\)的高階無窮小。
----維基百科
根據泰勒公式把函數\(f(x+\Delta x)\)在點\(x\)處二階展開,可獲得以下等式:
\[f(x+\Delta x) \approx f(x) + f'(x)\Delta x + \frac12 f''(x)\Delta x^2 \tag 3\]
由等式(1)可知,目標函數是關於變量\(\hat{y}_i^{t-1} + f_t(x_i)\)的函數,若把變量\(\hat{y}_i^{t-1}\)當作是等式(3)中的\(x\),把變量\(f_t(x_i)\)當作是等式(3)中的\(\Delta x\),則等式(1)可轉化爲:
\[ Obj^{(t)} = \sum_{i=1}^n \left[ l(y_i, \hat{y}_i^{t-1}) + g_if_t(x_i) + \frac12h_if_t^2(x_i) \right] + \Omega(f_t) + constant \tag 4\]
其中,\(g_i\)定義爲損失函數的一階導數,即\(g_i=\partial_{\hat{y}^{t-1}}l(y_i,\hat{y}^{t-1})\);\(h_i\)定義爲損失函數的二階導數,即\(h_i=\partial_{\hat{y}^{t-1}}^2l(y_i,\hat{y}^{t-1})\)。
假設損失函數爲平方損失函數,則\(g_i=\partial_{\hat{y}^{t-1}}(\hat{y}^{t-1} - y_i)^2 = 2(\hat{y}^{t-1} - y_i)\),\(h_i=\partial_{\hat{y}^{t-1}}^2(\hat{y}^{t-1} - y_i)^2 = 2\),把\(g_i\)和\(h_i\)代入等式(4)即得等式(2)。
因爲函數中的常量在函數最小化的過程當中不起做用,所以咱們能夠從等式(4)中移除掉常量項,得:
\[ Obj^{(t)} \approx \sum_{i=1}^n \left[ g_if_t(x_i) + \frac12h_if_t^2(x_i) \right] + \Omega(f_t) \tag 5\]
因爲要學習的函數僅僅依賴於目標函數,從等式(5)能夠看出只需爲學習任務定義好損失函數,併爲每一個訓練樣本計算出損失函數的一階導數和二階導數,經過在訓練樣本集上最小化等式(5)便可求得每步要學習的函數\(f(x)\),從而根據加法模型等式(0)可得最終要學習的模型。
一顆生成好的決策樹,假設其葉子節點個數爲\(T\),該決策樹是由全部葉子節點對應的值組成的向量\(w \in R^T\),以及一個把特徵向量映射到葉子節點索引(Index)的函數\(q:R^d \to \{1,2,\cdots,T\}\)組成的。所以,策樹能夠定義爲\(f_t(x)=w_{q(x)}\)。
決策樹的複雜度能夠由正則項\(\Omega(f_t)=\gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2\)來定義,即決策樹模型的複雜度由生成的樹的葉子節點數量和葉子節點對應的值向量的L2範數決定。
定義集合\(I_j=\{ i \vert q(x_i)=j \}\)爲全部被劃分到葉子節點\(j\)的訓練樣本的集合。等式(5)能夠根據樹的葉子節點從新組織爲T個獨立的二次函數的和:
\[ \begin{split} Obj^{(t)} &\approx \sum_{i=1}^n \left[ g_if_t(x_i) + \frac12h_if_t^2(x_i) \right] + \Omega(f_t) \\ &= \sum_{i=1}^n \left[ g_iw_{q(x_i)} + \frac12h_iw_{q(x_i)}^2 \right] + \gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2 \\ &= \sum_{j=1}^T \left[(\sum_{i \in I_j}g_i)w_j + \frac12(\sum_{i \in I_j}h_i + \lambda)w_j^2 \right] + \gamma T \end{split}\tag 6 \]
定義\(G_j=\sum_{i \in I_j}g_i\),\(H_j=\sum_{i \in I_j}h_i\),則等式(6)可寫爲:
\[Obj^{(t)} = \sum_{j=1}^T \left[G_iw_j + \frac12(H_i + \lambda)w_j^2 \right] + \gamma T\]
假設樹的結構是固定的,即函數\(q(x)\)肯定,令函數\(Obj^{(t)}\)的一階導數等於0,便可求得葉子節點\(j\)對應的值爲:\[w_j^*=-\frac{G_j}{H_j+\lambda} \tag 7\] 此時,目標函數的值爲\[Obj = -\frac12 \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T \tag 8\]
綜上,爲了便於理解,單顆決策樹的學習過程能夠大體描述爲:
然而,可能的樹結構數量是無窮的,因此實際上咱們不可能枚舉全部可能的樹結構。一般狀況下,咱們採用貪心策略來生成決策樹的每一個節點。
在上述算法的第二步,樣本排序的時間複雜度爲\(O(n \log n)\),假設公用K個特徵,那麼生成一顆深度爲K的樹的時間複雜度爲\(O(dKn\log n)\)。具體實現能夠進一步優化計算複雜度,好比能夠緩存每一個特徵的排序結果等。
如何計算每次分裂的收益呢?假設當前節點記爲\(C\),分裂以後左孩子節點記爲\(L\),右孩子節點記爲\(R\),則該分裂得到的收益定義爲當前節點的目標函數值減去左右兩個孩子節點的目標函數值之和:\(Gain=Obj_C-Obj_L-Obj_R\),具體地,根據等式(8)可得:\[Gain=\frac12 \left[ \frac{G_L^2}{H_L+\lambda} + \frac{G_R^2}{H_R+\lambda} - \frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma\] 其中,\(-\gamma\)項表示由於增長了樹的複雜性(該分裂增長了一個葉子節點)帶來的懲罰。
最後,總結一下GBDT的學習算法:
一般在第四步,咱們把模型更新公式替換爲:\(\hat{y}_i^t = \hat{y}_i^{t-1} + \epsilon f_t(x_i)\),其中\(\epsilon\)稱之爲步長或者學習率。增長\(\epsilon\)因子的目的是爲了不模型過擬合。
[1] Gradient Boosting 的更多內容
[2] XGBoost 是一個優秀的GBDT開源軟件庫,有多種語言接口
[3] Pyramid 是一個基於Java語言的機器學習庫,裏面也有GBDT算法的介紹和實現
[4] Friedman的論文《Greedy function approximation: a gradient boosting machine》是比較早的GBDT算法文獻,可是比較晦澀難懂,不適合初學者,高階選手能夠進一步學習
[5] "A Gentle Introduction to Gradient Boosting"是關於Gradient Boosting的一個通俗易懂的解釋,比較適合初學者或者是已經對GBDT算法原理印象不深的從業者
[6] 關於GBDT算法調參的經驗和技巧能夠參考這兩篇博文:《GBM調參指南》、
《XGBoost調參指南》,做者使用的算法實現工具來自於著名的Python機器學習工具scikit-learn
[7] GBDT算法在搜索引擎排序中的應用能夠查看這篇論文《Web-Search Ranking with Initialized Gradient Boosted Regression Trees》,這篇論文提出了一個很是有意思的方法,用一個已經訓練好的隨機森林模型做爲GBDT算法的初始化,再用GBDT算法優化最終的模型,取得了很好的效果