1. 程式人生 > >機器學習入門:極度舒適的GBDT原理拆解

機器學習入門:極度舒適的GBDT原理拆解

## 機器學習入門:極度舒適的GBDT拆解 本文旨用小例子+視覺化的方式拆解GBDT原理中的每個步驟,使大家可以徹底理解GBDT ## Boosting**→**Gradient Boosting Boosting是整合學習的一種基分類器(弱分類器)生成方式,核心思想是通過迭代生成了一系列的學習器,給誤差率低的學習器高權重,給誤差率高的學習器低權重,結合弱學習器和對應的權重,生成強學習器。 ![前文我們講過的AdaBoost就是典型的Boosting演算法](https://static01.imgkr.com/temp/bb574df2f7184437aa619a5861a93722.png) Boosting演算法要涉及到兩個部分,加法模型和前向分步演算法。 加法模型就是說強分類器由一系列弱分類器線性相加而成。一般組合形式如下: $$F_M(x;P)=\sum_{m=1}^n\beta_mh(x;a_m)$$ 其中,$h(x;a_m)$就是一個個的弱分類器,$a_m$是弱分類器學習到的最優引數,$β_m$就是弱學習在強分類器中所佔比重,P是所有$α_m$和$β_m$的組合。這些弱分類器線性相加組成強分類器。 前向分步就是說在訓練過程中,下一輪迭代產生的分類器是在上一輪的基礎上訓練得來的。也就是可以寫成這樣的形式: $$F_m (x)=F_{m-1}(x)+ \beta_mh_m (x;a_m)$$ **Gradient Boosting = Gradient Descent + Boosting** Boosting 演算法(以AdaBoost為代表)用錯分資料點來識別問題,通過調整錯分資料點的權重來改進模型。Gradient Boosting通過負梯度來識別問題,通過計算負梯度來改進模型。 Gradient Boosting每次迭代的目標是為了減少上一次的殘差,在殘差減少的梯度(Gradient)方向上建立一個新的模型,每個新的模型的建立是使之前模型的殘差往梯度方向減少。 第t輪的第i個樣本的損失函式的負梯度為: $$ \large {r_{mi}} = -\left[\frac{\partial L(y_i,f(x_i))}{\partial f(x_i)} \right]_{f(x)=f_{m-1}(x)} $$ 此時不同的損失函式將會得到不同的負梯度,如果選擇平方損失 $L(y_i,f(x_i)) = \frac{1}{2}(y_i - f(x_i))^2$ 負梯度為$r_{mi} = y_i - f(x_i)$ 此時我們發現GBDT的負梯度就是殘差,所以說對於迴歸問題,我們要擬合的就是殘差。 ![](https://static01.imgkr.com/temp/c6abf8ab07b948ba9008c941bdd8d9cf.png) ### GBDT迴歸演算法 輸入是訓練集樣本$T=\{(x_,y_1),(x_2,y_2), ...(x_m,y_m)\}$, 最大迭代次數T, 損失函式L。 輸出是強學習器$f(x)$ 1) 初始化弱學習器 2) 對迭代輪數t=1,2,...T有: $f_0(x) = \underbrace{arg\; min}_{c}\sum\limits_{i=1}^{m}L(y_i, c)$ a)對樣本$i=1,2,...m$,計算負梯度 $$r_{ti} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}\;\;(x)}$$ b)利用$(x_i,r_{ti})\;\; (i=1,2,..m)$, 擬合一顆CART迴歸樹,得到第t顆迴歸樹,其對應的葉子節點區域為$R_{tj}, j =1,2,..., J$。其中J為迴歸樹t的葉子節點的個數。 c) 對葉子區域$j =1,2,..J$,計算最佳擬合值 $$c_{tj} = \underbrace{arg\; min}_{c}\sum\limits_{x_i \in R_{tj}} L(y_i,f_{t-1}(x_i) +c)$$ d)更新強學習器 $$f_{t}(x) = f_{t-1}(x) + \sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})$$ 3) 得到強學習器f(x)的表示式 $$f(x) = f_T(x) =f_0(x) + \sum\limits_{t=1}^{T}\sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})$$ ### 二元GBDT分類演算法 對於二元GBDT,如果用類似於邏輯迴歸的對數似然損失函式,則損失函式為: $L(y, f(x)) = log(1+ exp(-yf(x)))$ 其中y∈{?1,+1}。則此時的負梯度誤差為 $$r_{ti} = -\bigg[\frac{\partial L(y, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}\;\; (x)} = y_i/(1+exp(y_if(x_i)))$$     對於生成的決策樹,我們各個葉子節點的最佳負梯度擬合值為 $$c_{tj} = \underbrace{arg\; min}_{c}\sum\limits_{x_i \in R_{tj}} log(1+exp(-y_i(f_{t-1}(x_i) +c)))$$     由於上式比較難優化,我們一般使用近似值代替 $$c_{tj} = \sum\limits_{x_i \in R_{tj}}r_{ti}\bigg / \sum\limits_{x_i \in R_{tj}}|r_{ti}|(1-|r_{ti}|)$$ 除了負梯度計算和葉子節點的最佳負梯度擬合的線性搜尋,二元GBDT分類和GBDT迴歸演算法過程相同。 ## 小例子+視覺化理解GBDT 上面對原理進行了分析之後,大致對GBDT有了一定的認識,為了更加形象的解釋GBDT的內部執行過程,這裡引用《統計學習方法》中adaboost一節中的案例資料來進行進一步分析。強烈建議大家對比學習,看一下Adaboost和 GBDT 的區別和聯絡。 資料集如下: ![](https://my-wechat.oss-cn-beijing.aliyuncs.com/image_20200628004945.png) 採用GBDT進行訓練,為了方便,我們採用MSE作為損失函式,並且將樹的深度設為1,決策樹個數設為5,其他引數使用預設值 ``` import numpy as np import pandas as pd from sklearn import tree import matplotlib.pyplot as plt from sklearn.ensemble import GradientBoostingRegressor from sklearn.model_selection import train_test_split X = np.arange(1,11) y = np.array([5.56, 5.70, 5.91, 6.40, 6.80, 7.05, 8.90, 8.70, 9.00, 9.05]) display(X,y) gbdt = GradientBoostingRegressor(n_estimators=5,max_depth=1) gbdt.fit(X.reshape(-1,1),y) ``` 其中GradientBoostingRegressor主要引數如下 ``` GradientBoostingRegressor(alpha=0.9, criterion='friedman_mse', init=None, learning_rate=0.1, loss='ls', max_depth=1, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=5, n_iter_no_change=None, presort='auto', random_state=None, subsample=1.0, tol=0.0001, validation_fraction=0.1, verbose=0, warm_start=False) ``` ![](https://my-wechat.oss-cn-beijing.aliyuncs.com/image_20200629234828.png) 其他引數為決策樹引數,大家應該已經很熟悉了,不再贅述。 下面我們根據GBDT迴歸演算法原理,開始分步硬核拆解: **第一步**:根據初始化公式 $f_0(x) = \underbrace{arg\; min}_{c}\sum\limits_{i=1}^{m}L(y_i, c)$ 可以計算出$F_{0}(x)=7.307$(本例中,恰好為yi均值) **第二步**:計算損失函式的負梯度值: $$r_{ti} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}\;\; (x)}$$ 由於是MSE損失,上式等於$\hat{y}_i = y_i - F_{m-1}(x_i)$,結果如下: ``` #計算殘差 y - y.mean() [out]: array([-1.747, -1.607, -1.397, -0.907, -0.507, -0.257, 1.593, 1.393, 1.693, 1.743]) ``` 第三步:對上面殘差擬合第一棵樹 根據所給的資料,可以考慮的切分點為1.5、2.5、3.5、4.5、5.5、6.5、7.5、8.5、9.5分別計算$y_i - F_{0}(x_i)$的值,並計算出切分後的左右兩側加和MSE最小的切分,最後得到的是6.5 找到最佳的切分點之後,我們可以得到各個葉子節點區域,並計算出$R_{jm}$和$\gamma_{jm}$.此時,$R_{11}$為$x$小於6.5的資料,$R_{21}$為x大於6.5的資料。同時,

$$r_{11} = \frac{1}{6} \sum_{x_i \in R_{11}} y_{i}=-1.0703$$

$$r_{21} = \frac{1}{4} \sum_{x_i \in R_{21}} y_{i}=1.6055$$

``` print((y - y.mean())[:6].mean(), (y - y.mean())[6:10].mean()) [out]:-1.07 1.605 #計算mse print( ((y - y.mean())**2).mean(), ((y[:6] - y[:6].mean())**2).mean(), ((y[6:10] - y[6:10].mean())**2).mean()) [out] 1.911421 0.309689 0.0179686 ``` 第一棵樹的視覺化 ``` tree.plot_tree(gbdt[0,0],filled=True) ``` ![](https://my-wechat.oss-cn-beijing.aliyuncs.com/image_20200630074044.png) **最後**:更新$F_{1}(x_i)$的值 $F_1(x_i)=F_{0}(x_i)+ \rho_m \sum^2_{j=1} \gamma_{j1} I(x_i \in R_{j1})$,其中$\rho_m$為學習率,或稱shrinkage,目的是防止預測結果發生過擬合,預設值是0.1。 **至此第一輪迭代完成**,後面的迭代方式與上面一樣, 本例中我們生成了5棵樹,大家可以用**tree.plot_tree**視覺化其他樹 ![第二棵樹](https://static01.imgkr.com/temp/3a5f5b03c24e40f294cffeb06fbd76a7.png) **課後作業**,大家可以思考一下,第二棵樹中的value是如何計算出來的?其實很簡單哈???? 迭代$m$次後,第$m$次的$F_{m}(x)$即為最終的預測結果。

$$
F_{m}(x) = F_{m-1}(x) + \rho_{m} h(x; a_m)$$

## 參考 https://www.cnblogs.com/pinard/p/6140514.html https://blog.csdn.net/u014168855/article/details/105481881 https://www.csuldw.com/2019/07/12/2019-07-12-an-introduction-to-gbdt/ ![](https://imgkr.cn-bj.ufileos.com/d82ef38c-a77f-4c99-a691-403bd1fbb3