Boosting方法其實是採用加法模型與前向分佈算法。在上一篇提到的Adaboost算法也能夠用加法模型和前向分佈算法來表示。以決策樹爲基學習器的提高方法稱爲提高樹(Boosting Tree)。對分類問題決策樹是CART分類樹,對迴歸問題決策樹是CART迴歸樹。算法
一、前向分佈算法多線程
引入加法模型dom
在給定了訓練數據和損失函數$L(y, f(x))$ 的條件下,能夠經過損失函數最小化來學習加法模型機器學習
然而對於這個問題是個很複雜的優化問題,並且要訓練的參數很是的多,前向分佈算法的提出就是爲了解決模型的優化問題,其核心思想是由於加法模型是由多各模型相加在一塊兒的,並且在Boosting中模型之間又是有前後順序的,所以能夠在執行每一步加法的時候對模型進行優化,那麼每一步只須要學習一個模型和一個參數,經過這種方式來逐步逼近全局最優,每一步優化的損失函數:函數
具體算法流程以下:性能
1)初始化$f_0(x) = 0$;學習
2)第m次迭代時,極小化損失函數優化
3)更新模型,則$f_m (x)$:spa
4)獲得最終的加法模型線程
Adaboost算法也能夠用前向分佈算法來描述,在這裏輸入的數據集是帶有權重分佈的數據集,損失函數是指數損失函數。
二、GBDT算法
GBDT是梯度提高決策樹(Gradient Boosting Decision Tree)的簡稱,GBDT能夠說是最好的機器學習算法之一。GBDT分類和迴歸時的基學習器都是CART迴歸樹,由於是擬合殘差的。GBDT和Adaboost同樣能夠用前向分佈算法來描述,不一樣之處在於Adaboost算法每次擬合基學習器時,輸入的樣本數據是不同的(每一輪迭代時的樣本權重不一致),由於Adaboost旨在重點關注上一輪分類錯誤的樣本,GBDT算法在每一步迭代時是輸出的值不同,本輪要擬合的輸出值是以前的加法模型的預測值和真實值的差值(模型的殘差,也稱爲損失)。用於一個簡單的例子來講明GBDT,假如某人的年齡爲30歲,第一次用20歲去擬合,發現損失還有10歲,第二次用6歲去擬合10歲,發現損失還有4歲,第三次用3歲去擬合4歲,依次下去直到損失在咱們可接受範圍內。
以平方偏差損失函數的迴歸問題爲例,來看看以損失來擬合是個什麼樣子,採用前向分佈算法:
在第$m$次迭代時,咱們要優化的損失函數:
此時咱們採用平方偏差損失函數爲例:
則上面損失函數變爲:
問題就成了對殘差r的擬合了
然而對於大多數損失函數,卻沒那麼容易直接得到模型的殘差,針對該問題,大神Freidman提出了用損失函數的負梯度來擬合本輪損失的近似值,擬合一個迴歸樹
關於GBDT通常損失函數的具體算法流程以下:
1)初始化$f_0(x)$:
2)第$m$次迭代時,計算當前要擬合的殘差$r_{mi}$:
以$r_{mi}$爲輸出值,對$r_{mi}$擬合一個迴歸樹(此時只是肯定了樹的結構,可是還未肯定葉子節點中的輸出值),而後經過最小化當前的損失函數,並求得每一個葉子節點中的輸出值$c_{mj}$,$j$表示第$j$個葉子節點
更新當前的模型$f_m(x)$爲:
3)依次迭代到咱們設定的基學習器的個數$M$,獲得最終的模型,其中$M$表示基學習器的個數,$J$表示葉子節點的個數
GBDT算法提供了衆多的可選擇的損失函數,經過選擇不一樣的損失函數能夠用來處理分類、迴歸問題,好比用對數似然損失函數就能夠處理分類問題。大概的總結下經常使用的損失函數:
1)對於分類問題能夠選用指數損失函數、對數損失函數。
2)對於迴歸問題能夠選用均方差損失函數、絕對損失函數。
3)另外還有huber損失函數和分位數損失函數,也是用於迴歸問題,能夠增長迴歸問題的健壯性,能夠減小異常點對損失函數的影響。
三、GBDT的正則化
在Adaboost中咱們會對每一個模型乘上一個弱化係數(正則化係數),減少每一個模型對提高的貢獻(注意:這個係數和模型的權重不同,是在權重上又乘以一個0,1之間的小數),在GBDT中咱們採用一樣的策略,對於每一個模型乘以一個係數λ (0 < λ ≤ 1),下降每一個模型對擬合損失的貢獻,這種方法也意味着咱們須要更多的基學習器。
第二種是每次經過按比例(推薦[0.5, 0.8] 之間)隨機抽取部分樣原本訓練模型,這種方法有點相似Bagging,能夠減少方差,但一樣會增長模型的誤差,可採用交叉驗證選取,這種方式稱爲子採樣。採用子採樣的GBDT有時也稱爲隨機梯度提高樹(SGBT)。
第三種就是控制基學習器CART樹的複雜度,能夠採用剪枝正則化。
四、GBDT的優缺點
GBDT的主要優勢:
1)能夠靈活的處理各類類型的數據
2)預測的準確率高
3)使用了一些健壯的損失函數,如huber,能夠很好的處理異常值
GBDT的缺點:
1)因爲基學習器之間的依賴關係,難以並行化處理,不過能夠經過子採樣的SGBT來實現部分並行。
五、XGBoost算法
事實上對於樹模型爲基學習器的集成方法在建模過程當中能夠分爲兩個步驟:一是肯定樹模型的結構,二是肯定樹模型的葉子節點中的輸出值。
5.1 定義樹的複雜度
首先把樹拆分紅結構部分$q$和葉子節點輸出值$w$,在這裏$w$是一個向量,表示各葉子節點中的輸出值。在這裏就囊括了上面提到的兩點,肯定樹結構$q$和葉子結點的輸出值$w$。從下圖中能夠看出,$q(x)$其實是肯定輸入值最終會落到哪一個葉子節點上,而$w$將會給出相應的輸出值。
具體表現示例以下,引入正則化項 $\Omega (f_t)$來控制樹的複雜度,從而實現有效的控制模型的過擬合,這是xgboost中的第一個重要點。式子中的$T$爲葉子節點數
5.2 XGBoost中的Boosting Tree模型
和GBDT方法同樣,XGBoost的提高模型也是採用殘差,不一樣的是分裂結點選取的時候不必定是最小平方損失,其損失函數以下,較GBDT其根據樹模型的複雜度加入了一項正則化項:
5.3 對目標函數進行改寫
上面的式子是經過泰勒展開式將損失函數展開爲具備二階導的平方函數。
在GBDT中咱們經過求損失函數的負梯度(一階導數),利用負梯度替代殘差來擬合樹模型。在XGBoost中直接用泰勒展開式將損失函數展開成二項式函數(前提是損失函數一階、二階都連續可導,並且在這裏計算一階導和二階導時能夠並行計算),假設此時咱們定義好了樹的結構(在後面介紹,和GBDT中直接用殘差擬合不一樣),假設咱們的葉節點區域爲:
上面式子中$i$表明樣本$i$,$j$表明葉子節點$j$。
則咱們的目標優化函數能夠轉換成(由於$l(y_i, y_i^{t-1})$是個已經肯定的常數,能夠捨去):
上面式子把樣本都合併到葉子節點中了。
此時咱們對$w_j$求導並令導數爲0,可得:
其中 $ G_j = \sum_{i \in I_j} g_i, H_j = \sum_{i \in T_j} h_j $。
5.4 樹結構的打分函數
上面的Obj值表明當指定一個樹結構時,在目標上面最多減小多少,咱們能夠把它稱爲結構分數。能夠認爲這是一個相似與基尼指數同樣更通常的對樹結構進行打分的函數。以下面的例子所示
對於求得Obj分數最小的樹結構,咱們能夠枚舉全部的可能性,而後對比結構分數來得到最優的樹結構,然而這種方法計算消耗太大,更經常使用的是貪心法(事實上絕大多數樹模型都是這樣的,只考慮當前節點的劃分最優),每次嘗試對已經存在的葉節點(最開始的葉節點是根節點)進行分割,而後得到分割後的增益爲:
在這裏以$Gain$做爲判斷是否分割的條件,這裏的$Gain$能夠看做是未分割前的Obj減去分割後的左右Obj,所以若是$Gain < 0$,則此葉節點不作分割,然而這樣對於每次分割仍是須要列出全部的分割方案(對於特徵的值的個數爲n時,總共有2^n - 2 種劃分)。而實際中是採用近似貪心方法,咱們先將全部樣本按照$g_i$從小到大排序,而後進行遍歷,查看每一個節點是否須要分割(對於特徵的值的個數爲$n$時,總共有$n-1$種劃分),具體示例以下:
最簡單的樹結構就是一個節點的樹。咱們能夠算出這棵單節點的樹的好壞程度obj*。假設咱們如今想按照年齡將這棵單節點樹進行分叉,咱們須要知道:
1)按照年齡分是否有效,也就是是否減小了obj的值
2)若是可分,那麼以哪一個年齡值來分。
此時咱們就是先將年齡特徵從小到大排好序,而後再從左到右遍歷分割
這樣的分割方式,咱們就只要對樣本掃描一遍,就能夠分割出$G_L$,$G_R$,而後根據$Gain$的分數進行分割,極大地節省了時間。因此從這裏看,XGBoost中重新定義了一個劃分屬性,也就是這裏的$Gain$,而這個劃分屬性的計算是由其目標損失決定obj的。
5.5 XGBoost中其餘的正則化方法
1)像Adaboost和GBDT中同樣,對每個模型乘以一個係數$\lambda(0 < \lambda ≤ 1)$,用來下降每一個模型對結果的貢獻。
2)採用特徵子採樣方法,和RandomForest中的特徵子採樣同樣,能夠下降模型的方差
六、XGBoost和GBDT的區別
1)將樹模型的複雜度加入到正則項中,來避免過擬合,所以泛化性能會因爲GBDT。
2)損失函數是用泰勒展開式展開的,同時用到了一階導和二階導,能夠加快優化速度。
3)和GBDT只支持CART做爲基分類器以外,還支持線性分類器,在使用線性分類器的時候可使用L1,L2正則化。
4)引進了特徵子採樣,像RandomForest那樣,這種方法既能下降過擬合,還能減小計算。
5)在尋找最佳分割點時,考慮到傳統的貪心算法效率較低,實現了一種近似貪心算法,用來加速和減少內存消耗,除此以外還考慮了稀疏數據集和缺失值的處理,對於特徵的值有缺失的樣本,XGBoost依然能自動找到其要分裂的方向。
6)XGBoost支持並行處理,XGBoost的並行不是在模型上的並行,而是在特徵上的並行,將特徵列排序後以block的形式存儲在內存中,在後面的迭代中重複使用這個結構。這個block也使得並行化成爲了可能,其次在進行節點分裂時,計算每一個特徵的增益,最終選擇增益最大的那個特徵去作分割,那麼各個特徵的增益計算就能夠開多線程進行。