1. 程式人生 > >談談模型融合之三 —— GBDT

談談模型融合之三 —— GBDT

前言

本來應該是年後就要寫的一篇部落格,因為考完試後忙了一段時間課設和實驗,然後回家後又在摸魚,就一直沒開動。趁著這段時間只能呆在家裡來把這些部落格補上。在之前的文章中介紹了 Random Forest 和 AdaBoost,這篇文章將介紹介紹在資料探勘競賽中,最常用的演算法之一 —— GBDT(Gradient Boosting Decision Tree)。

GBDT

原理

GBDT 實際上是 GBM(Gradient Boosting Machine) 中的一種,採用 CART 樹作為基學習器,所以稱為 GBDT。與 AdaBoost 一樣,GBDT 為 Boosting 家族中的一員。其表示式為

\[ f_m(x) = \sum_{m=1}^{M} T(x;\Theta_m) \]
其中\(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