談談模型融合之三 —— GBDT
前言
本來應該是年後就要寫的一篇部落格,因為考完試後忙了一段時間課設和實驗,然後回家後又在摸魚,就一直沒開動。趁著這段時間只能呆在家裡來把這些部落格補上。在之前的文章中介紹了 Random Forest 和 AdaBoost,這篇文章將介紹介紹在資料探勘競賽中,最常用的演算法之一 —— GBDT(Gradient Boosting Decision Tree)。
GBDT
原理
GBDT
實際上是 GBM(Gradient Boosting Machine)
中的一種,採用 CART 樹作為基學習器,所以稱為 GBDT。與 AdaBoost 一樣,GBDT 為 Boosting 家族中的一員。其表示式為
其中\(T(x;\Theta_m)\)表示決策樹;\(\Theta_m\)為決策樹引數;M為樹的個數。
這裡來回顧下 AdaBoost,AdaBoost 通過不斷調整樣本權重,使得上一次分類錯誤的樣本權重變大,最後訓練出 m 個弱分類器,然後對 m 個弱分類器加權結合形成強分類器。
而 GBDT 又是另一思想,當我們假設前一輪迭代得到的學習器為 \(f_{m-1}(x)\) ,損失函式為 \(L(y, f_{m-1}(x))\) ,那麼,本輪迭代的目標就是使損失函式 \(L(y, f_{m-1}(x) + h_m(x))\) 的值儘可能的小。
我們先將損失函式假設為最常用的平方損失
令 \(r = y - f_{m-1}(x)\) ,那麼第 m 步的目標就是最小化 \(L(y, f_m(x)) = \frac{1}{2}(y-f_m(x))^2=\frac{1}{2}(r-h_m(x))^2\)
到這裡,似乎發現了點什麼,我們對損失函式求導,發現:
\[
\frac{\partial{L}}{\partial{h_m(x)}}=h_m(x)-r
\]
看出什麼來了沒?對其取個負號就變成 \(r-h_m(x)\) ,即我們常說的殘差
,所以,當為平方損失函式時,弱學習器擬合的目標為之前擬合結果的殘差。那到這裡程式碼就很好寫了,但是,我們實際中有很多其它的損失函式,而且在很多問題中,這些損失函式比平方損失要好得多。那這時候,如果我們還採用同樣的思路,那就沒辦法像上面那樣直接展開並擬合殘差了,這時候該怎麼辦?
這裡別忘了,我們最終的目標是使得 \(L(y, f_m(x))\) 最小,那麼只需要保證 \(L(y, f_{m-1}(x)+h_m(x))\) 的值比 \(L(y, f_{m-1}(x))\) 小就好了。
即
\[
max[L(y,f_{m-1}(x))-L(y,f_{m-1}(x)+h(x))]
\]
檢驗大一高數學的怎麼樣的時候到了 orz
我們前面說了第 m 輪迭代的損失函式為 \(L(y, f_{m-1}(x) + h_m(x))\) ,換一種形式,寫成 \(L(f_{m-1}(x) + h_m(x))\) ,對其進行一階泰勒展開,得
\[
L(f_{m-1}(x)+h_m(x)) \approx L(f_{m-1}(x)) + L'(f_{m-1}(x))h_m(x)
\]
所以,我們只需使得滿足
\[
\max L'(f_{m-1}(x))h_m(x) \\
L'(f_{m-1}(x))h_m(x)<0
\]
那我們的 \(h_m(x)\) 到底要擬合什麼呢?別忘了,我們是要求梯度的,在這裡我們已知的是 \(L'(f_{m-1}(x))\) ,我們肯定是根據上一次的擬合的結果來擬合這一次的結果,所以,要使得結果最大,自然就是梯度方向。那麼 \(h_m(x)=-L'(f_{m-1}(x))\) , 這樣原先的 \(r\) 也就變成了梯度。這裡如果把損失函式看作是平方損失,我們得到的結果也恰好就是我們所說的殘差!!
此時也總算明白了之前面騰訊的時候我說 GBDT 是擬合殘差的時候面試官讓我再回去重新康康這個演算法的原因了。
演算法步驟
輸入: 訓練資料集 \(T = {(x_1, y_1),(x_2, y_2), ..., (x_N, y_N)}, x_i \in X \subset R^n, y_i \in Y \subset R\); 損失函式 L(y,f(x)),樹的個數M.
輸出: 梯度提升樹\(F_M(x)\)
(1) 初始化 \(f_0(x) = argmin_c \Sigma_{i=1}^N L(y_i,c)\).
(2) 對 \(m=1,2,...,M\)
(a) 對\(i =1,2,...,N\),計算, \(r_{mi} = - [\frac{\partial L(y_i, f(x_i))}{\partial f(x_i)}]_{f(x) = F_{m-1}(x)}\).
(b) 擬合殘差\(r_{mi}\)學習一個迴歸樹,得到\(f_m(x)\).
(c) 更新\(F_m(x) = F_{m-1}(x) + f_m(x)\).
(3) 得到迴歸問題提升樹 \(F_M(x) = \Sigma_{i=0}^M f_i(x)\)
程式碼
這裡程式碼是採用了平方損失的方法來寫的,且解決的是分類問題
def sigmoid(x):
"""
計算sigmoid函式值
"""
return 1 / (1 + np.exp(-x))
def gbdt_classifier(X, y, M, max_depth=None):
"""
用於分類的GBDT函式
引數:
X: 訓練樣本
y: 樣本標籤,y = {0, +1}
M: 使用M個迴歸樹
max_depth: 基學習器CART決策樹的最大深度
返回:
F: 生成的模型
"""
# 用0初始化y_reg
y_reg = np.zeros(len(y))
f = []
for m in range(M):
# 計算r
r = y - sigmoid(y_reg)
# 擬合r
# 使用DecisionTreeRegressor,設定樹深度為5,random_state=0
f_m = DecisionTreeRegressor(max_depth=5, random_state=0)
# 開始訓練
f_m.fit(X, r)
# 更新f
f.append(f_m)
y_reg += f_m.predict(X)
def F(X):
num_X, _ = X.shape
reg = np.zeros((num_X))
for t in f:
reg += t.predict(X)
y_pred_gbdt = sigmoid(reg)
# 以0.5為閾值,得到最終分類結果0或1
one_position = y_pred_gbdt >= 0.5
y_pred_gbdt[one_position] = 1
y_pred_gbdt[~one_position] = 0
return y_pred_gbdt
return F
小節
到這裡 GBDT 也就講完了,從決策樹 ID3 開始一直到 GBDT,後面終於要迎來最開始想要梳理的資料探勘的兩大殺器 XGBoost 和 LightGBM 了,下一篇將介紹 XGBoost