XGBoost
和GBDT一樣,XGBoost也是一種基於CART樹的Boosting算法,讓我們來看一下如何通俗的去理解XGBoost。
先簡單的回想一下,在我們之前提到過的GBDT中是怎樣用很多棵樹去做預測的?很簡單,我們給了每棵樹不同的訓練數據,得到多種不同的結果,最終我們把這些結果相加作爲最終的預測值就可以了。
XGBoost的定義
舉一個簡單的例子,我們要預測一家人對電子遊戲的喜好程度,考慮到年輕和年老相比,年輕更可能喜歡電子遊戲,以及男性和女性相比,男性更喜歡電子遊戲,故先根據年齡大小區分小孩和大人,然後再通過性別區分開是男是女,最終給每個人在電子遊戲上的喜愛程度進行打分,如下圖所示:
我們把上圖中得到的樹記爲tree1,同樣我們可以根據日常是否使用電腦來進行新一次的打分,如下圖所示:
這樣我們就訓練好的兩棵樹tree1和tree2,我們把兩棵樹的評分結果相加就是最終的結果,如上圖中,男孩的得分是2+0.9=2.9,爺爺的得分是-1-0.9=-1.9。
看完了這個例子,你可能會疑惑,這個過程和GBDT不是一樣的嗎?其實在本質上XGBoost和GBDT的差異就在於目標函數的定義上,別急,繼續往下看。
XGBoost的目標函數
看懂了上節中的例子,不難說出XGBoost的核心就是不斷的添加樹,不斷地進行特徵分裂來生長一棵樹,每次添加一個樹,其實是學習一個新函數,去擬合上次預測的殘差。用數學來表示這個模型,如下所示:
y^i=k=1∑Kfk(xi),fk∈F
這裏的K就是樹的棵樹,F表示所有可能的CART樹,f表示一顆具體的CART樹,這個模型由K棵CART樹組成。有了模型之後,我們的問題也就來了,模型的參數是什麼?優化參數的目標函數是什麼?
先不考慮參數的問題,我們先來明確一下我們的目標。
顯然,我們的目標是要使得樹羣的預測值y^i儘量接近真實值yi,而且要有儘量大的泛化能力。
所以從數學的角度看,目標就變成了一個泛函數的最優化問題,我們把目標函數簡化成如下的形式:
obj(θ)=i=1∑nl(yi,yi^)+k=1∑KΩ(fk)
這個目標函數分爲兩部分:損失函數和正則化項。且損失函數揭示訓練誤差(即預測分數和真實分數的差距),這裏的正則化項由K棵樹的正則化項相加而來,也可以說**正則化定義複雜度。**損失函數我們都接觸過,樹的正則化又是怎樣的形式呢,我們後面再做說明。
XGBoost的訓練
上節中我們得到了XGBoost的損失函數是加法的形式,我們不妨使用加法訓練的方式分步驟對目標函數進行優化,首先優化第一棵樹,接下來是第二棵…直至K棵樹全被優化完成。優化的過程如下所示:
y^i(0)=0
y^i(1)=f1(xi)=y^i(0)+f1(xi)
y^i(2)=f1(xi)+f2(xi)=y^i(1)+f2(xi)
…
y^i(k)=∑k=1tfk(xi)=y^i(t−1)+ft(xi)
在第t次迭代的時候,我們添加了一棵最優的CART樹ft,那麼這棵樹是怎麼得到的呢?其實就是在現有的t-1棵樹的基礎上,使得目標函數最小的那棵CART樹,如下所示:
obj(t)=∑i=1nl(yi,y^i(t))+∑i=1tΩ(fi)
=∑i=1nl(yi,y^i(t−1)+ft(xi))+Ω(ft)+constant
根據化簡後的式子,我們知道里面的損失函數,也知道里面的正則項,那麼constant又是哪來的呢?(這裏是不是有很多問號),先別急,馬上就會揭曉答案。
說到這,我們一直都是用抽象的方式去表示損失函數,我們可以考慮當損失函數是均方誤差(MSE)的情況(也就是:l(yi,y^i)=(yi−y^i)2),此時我們的目標函數可以改寫成如下的形式:
obj(t)=∑i=1n(yi−(y^i(t−1)+ft(xi)))2+Ω(ft)+constant
=∑i=1n[2(y^i(t−1)−yi)ft(xi)+ft(xi)2]+Ω(ft)+constant
替換成MSE後,又有新的問題出現了,我們是如何得到上面的式子的呢?其實,在這裏我們利用了泰勒二階展開去做了目標函數的近似(XGBoost的核心所在),我們來看一下進行泰勒二階展開的過程。
我們的目標函數如下:
obj(t)==∑i=1nl(yi,y^i(t−1)+ft(xi))+Ω(ft)+constant
首先我們給出泰勒二階展開的近似式:
f(x+Δx)≃f(x)+f′(x)Δx+21f′′(x)Δx2
由於目標函數中的yi是真實值,而且通常真實值都是已知的,所以我們不去考慮,對其他的項我們來看一下目標函數和泰勒二階展開式的對應關係:
得到目標函數的近似式:
obj(t)≃∑i=1n[l(yi,y^i(t−1))+gift(xi)+21hift2(xi)]+Ω(ft)+constant
其中:gi和hi的表示如下:
gi=∂y^(t−1)l(yi,y^i(t−1))
hi=∂y^(t−1)2l(yi,y^i(t−1))
如果是MSE則結果如下:
gi=∂y^(t−1)l(yi,y^i(t−1))=2(y^i(t−1)−yi)
hi=∂y^(t−1)2l(yi,y^i(t−1))=2
這也就解釋了我們在將MSE作爲損失函數的時候,爲什麼會得到一個奇怪的目標函數。同樣這個過程也能夠讓我們對不同損失函數下的目標函數進行一個近似化簡。
接下來,考慮到我們第t棵迴歸樹是根據前面的t-1棵迴歸樹的殘差得來的,相當於t-1顆樹的預測值y^i(t−1)是已知的,也就是說,l(yi,y^i(t−1))對目標函數的優化不影響,我們可以直接去掉,同樣常數項也可以直接移除,從而得到比較統一的目標函數:
obj(t)=∑i=1n[gift(xi)+21hift2(xi)]+Ω(ft)
gi=∂y^(t−1)l(yi,y^i(t−1))
hi=∂y^(t−1)2l(yi,y^i(t−1))
到這裏我們的目標函數就確定了,但是還有最後的一個小東西我們還沒有介紹,下面再讓我們來說一下正則項。
XGBoost的正則項
這裏我們所說的正則項,也可以看作是樹的複雜度。
我們先對CART做一個新的定義,式子及結構表示如下:
對於圖中的式子的解釋:一棵樹有T個葉子結點,這T個葉子節點的值組成了一個T維向量w,q(x)是一個映射,用來將樣本映射成1到T的某個值,也就是把它分到某個葉子節點,q(x)其實就代表了CART樹的結構。wq(x)自然就是這棵樹對樣本x的預測值了(得分)。
根據上面的定義,我們可以設定XGBoost的正則項包含兩個部分:
- 一個是樹裏面葉子節點的個數T
- 一個是樹上葉子節點的得分w的L2模平方(對w進行L2正則化,相當於針對每個葉結點的得分增加L2平滑,目的是爲了避免過擬合)
最終表示成下圖所示的形式:
這裏出現了兩個參數γ和λ,我們可以設定他們的值去調節樹的結構,顯然γ越大,表示越希望獲得結構簡單的樹,因爲此時對較多葉子節點的樹的懲罰越大,λ越大也是希望獲得結構越簡單的樹。
XGBoost的最終形態
我們得到了目標函數,又得到了正則化的表達形式,不妨把他們組合在一起得到最終的結果表達:
obj(t)=∑i=1n[gift(xi)+21hift2(xi)]+Ω(ft)
obj(t)=∑i=1n[giwq(xi)+21hiwq(xi)2]+γT+λ21∑j=1Twj2
obj(t)=∑j=1T[(∑i∈Ijgi)wj+21(∑i∈Ijhi+λ)wj2]+γT
上式子中的Ij它代表一個集合Ij={i∣q(xi)=j},集合中每個值代表一個訓練樣本的序號,整個集合就是被第t棵CART樹分到了第j個葉子節點上的訓練樣本(這個定義裏的q(xi)要表達的是:每個樣本值xi 都能通過函數q(xi)映射到樹上的某個葉子節點,從而通過這個定義把兩種累加統一到了一起)。葉子結點的個數計做T,葉子結點的分數計做W。
這樣加了正則項的目標函數裏就出現了兩種累加:
進一步我們可以接着對式子進行簡化,定義:
Gj=∑i∈Ijgi
Hj=∑i∈Ijhi
簡化後的式子如下:
obj(t)=∑j=1T[Gjwj+21(Hj+λ)wj2]+γT
通過對wj求導等於0,可以得到:
wj∗=−Hj+λGj
然後把最優解帶入得到:
obj∗=−21∑j=1THj+λGj2+γT
這裏要說明一下我們最終求得的obj表示的是這棵樹的結構的好壞,值越小也就意味着結構越好,也可以說,它是衡量第t棵CART樹的結構好壞的標準。但是需要注意的是,這裏的好壞是與葉子結點的值沒有關係的,不妨可以看一下我們的推導過程,最終的obj值與Gj,Hj,T有關,這三者呢又只與結構q(x)有關,所以便可以得出結論就是:與葉子結點的值無關。
打分方式
拓展:我們再來換個角度理解一下wj∗
假設我們分到j這個葉子結點上的樣本只有一個,則有如下的形式:
wj∗=(hj+λ1)(−gj)
此時第一個括號中表示的內容可以看作是學習率,第二個括號中表示的內容可以看作是反向梯度。
XGBoost的最優樹結構
有了評判樹的結構好壞的標準,我們就可以先求最佳的樹結構,這個定出來後,最佳的葉子結點的值實際上在上面已經求出來了。
對於我們最開始的是否喜歡電子遊戲的例子,最簡單的樹結構就是一個結點的樹,我們可以計算出這棵單結點樹的好壞obj∗,假設我們現在想按照年齡將這棵單節點樹進行分叉,我們需要知道:
1、按照年齡分是否有效,也就是是否減少了obj的值;
2、如果可分,那麼以哪個年齡值來分。
我們按照年齡進行排序,找出所有的切分點,對於每一個切分點我們去衡量切分的好壞。示例圖和計算方式如下表示:
這個Gain實際上就是單節點的obj∗減去切分後的兩個節點的樹obj∗,Gain如果是正的,並且值越大,表示切分後obj∗越小於單節點的obj∗,就越值得切分。同時,我們還可以觀察到,Gain的左半部分如果小於右側的γ,則Gain就是負的,表明切分後obj∗反而變大了。γ在這裏實際上是一個臨界值,它的值越大,表示我們對切分後obj∗下降幅度要求越嚴。這個值也是可以在XGBoost中設定的。
掃描結束後,我們就可以確定是否切分,如果切分,對切分出來的兩個節點,遞歸地調用這個切分過程,我們就能獲得一個相對較好的樹結構。
注意: xgboost的切分操作和普通的決策樹切分過程是不一樣的。普通的決策樹在切分的時候並不考慮樹的複雜度,而依賴後續的剪枝操作來控制。xgboost在切分的時候就已經考慮了樹的複雜度,就是那個γ參數。所以,它不需要進行單獨的剪枝操作。
問題小結
至此,我們的XGBoost的原理就全都說通了,下面再來看幾個通常會遇到的問題:
- obj中的constant是怎麼來的?
文中我們還留了一個問題沒有解決,那就是constant具體是怎麼來的,讓我們來看一下複雜度(正則項)的迭代過程:
這樣就很清晰的可以看出所謂的常數,其實就是我們前t-1棵樹的複雜度加和。
- XGBoost爲什麼用泰勒展開?
XGBoost使用了一階和二階偏導, 二階導數有利於梯度下降的更快更準. 使用泰勒展開取得函數做自變量的二階導數形式, 可以在不選定損失函數具體形式的情況下, 僅僅依靠輸入數據的值就可以進行葉子分裂優化計算, 本質上也就把損失函數的選取和模型算法優化/參數選擇分開了. 這種去耦合增加了xgboost的適用性, 使得它按需選取損失函數, 可以用於分類, 也可以用於迴歸。簡單的說:使用二階泰勒展開是爲了xgboost能夠自定義loss function。
- XGBoost如何尋找最優特徵?是有放回還是無放回的呢?
XGBoost在訓練的過程中給出各個特徵的評分,從而表明每個特徵對模型訓練的重要性。
XGBoost利用梯度優化模型算法, 樣本是不放回的,想象一個樣本連續重複抽出,梯度來回踏步,這顯然不利於收斂。
XGBoost支持子採樣, 也就是每輪計算可以不使用全部樣本。