機器學習演算法系列(二十)-梯度提升決策樹演算法(Gradient Boosted Decision Trees / GBDT)

Saisimon發表於2022-04-04

閱讀本文需要的背景知識點:自適應增強演算法、泰勒公式、One-Hot編碼、一丟丟程式設計知識

一、引言

  前面一節我們學習了自適應增強演算法(Adaptive Boosting / AdaBoost Algorithm),是一種提升演算法 (Boosting Algorithm),而該演算法家族中還有另一種重要的演算法——梯度提升決策樹1 (Gradient Boosted Decision Trees / GBDT),GBDT 及其變體演算法在傳統機器學習中有著廣泛的應用,瞭解其背後的思想與原理對於以後的學習有很大的幫助。

二、模型介紹

  梯度提升決策樹同自適應增強演算法一樣是一種提升演算法,也可以解釋為一種加法模型,第 k 輪後的強估計器為第 k - 1 輪後的強估計器加上帶係數的弱估計器 h(x):

$$ H_k(x) = H_{k - 1}(x) + \alpha_k h_k(x) $$

<center>式2-1</center>

  假設第 k - 1 輪對應的代價函式為$Cost(y, H_{k - 1}(X))$, 第 k 輪對應的代價函式為$Cost(y, H_k(X))$,我們的目的是使得每次迭代後,其代價函式都會逐漸變小。

  由於每個不同的代價函式對應的優化方式不同,Jerome H. Friedman 在其原始演算法的論文2 中給出了一個統一處理的方法,可以使用代價函式的負梯度來擬合該輪迭代代價函式的減小,這也是最速下降法的一種近似,其本質是使用一階泰勒公式近似其代價函式。當基礎估計器使用的是決策迴歸樹時,該演算法被稱為梯度提升決策樹(GBDT)。

$$ \hat{y}_{k, i} = -\left[\frac{\partial Cost(y_i, H(X_i))}{\partial H(X_i)} \right]_{H(x) = H_{k-1}(x)} $$

<center>式2-2</center>

  下面還是同 AdaBoost 演算法一樣,分別考慮迴歸、二分類、多分類這三個問題,一一介紹每個問題對應的演算法。由於 GBDT 演算法迴歸比分類簡單,所以這次將回歸問題放在前面介紹。

迴歸

  對於迴歸問題的代價函式,我們先使用平方誤差來作為代價函式:

$$ Cost(y, H(x)) = (y - H(x))^2 $$

<center>式2-3</center>

  第 k 輪的代價函式:

(1)帶入式 2-1

(2)帶入平方誤差的代價函式

(3)改變括號

(4)將 y 與第 k - 1 輪的結果之差記為 $\hat{y}_k$

$$ \begin{aligned} Cost(y, H_{k}(x)) & = Cost(y, H_{k - 1}(x) + \alpha_kh_k(x)) & (1) \\ & = (y - (H_{k - 1}(x) + \alpha_kh_k(x) ))^2 & (2) \\ &= ((y - H_{k - 1}(x)) - \alpha_kh_k(x))^2 & (3) \\ &= (\hat{y}_k - \alpha_kh_k(x))^2 & (4) \end{aligned} $$

<center>式2-4</center>

  觀察式 2-4 中的(4)式會發現這就是前面章節中介紹的決策迴歸樹的代價函式,只是該回歸樹不再是使用訓練集中的 y,而是去擬合上式中的 $\hat{y}$,也可以稱為殘差。得到迴歸樹後更新 H(x) ,然後進行新的迭代。

  這樣就得到了最簡單的最小二乘迴歸的 GBDT 演算法實現,具體步驟可以參考第三節的演算法步驟中的迴歸部分,可以看到其中的係數 $\alpha$ 可以理解為融入到了迴歸樹內部。

  在論文中作者還介紹了其他的幾種代價函式,分別是最小絕對偏差(LDA)、Huber 損失3 等,感興趣的讀者可以閱讀論文中對應的章節。

  由於 GBDT 需要計算負梯度是個連續值,所以對於分類問題, 其基礎估計器沒有使用分類樹,而是依然使用的迴歸樹。

二分類

  對於分類問題的代價函式,可以使用指數函式與對數函式。當使用指數函式時,GBDT 將等同於前面一節中介紹的 AdaBoost 演算法,這裡我們使用對數函式作為代價函式:

$$ Cost(y, H(x)) = \log (1 + e^{-2yH(x)}) $$

<center>式2-5</center>

  按照前面模型介紹的計算負梯度的方法,帶入代價函式計算出 $\hat{y}$ :

$$ \begin{aligned} \hat{y}_{k, i} &= -\left[\frac{\partial Cost(y_i, H(X_i))}{\partial H(X_i)} \right]_{H(x) = H_{k-1}(x)} & (1)\\ &= \frac{2y_i}{1 + e^{2y_iH_{k-1}(X_i)}} & (2) \end{aligned} $$

<center>式2-6</center>

  計算出 $\hat{y}$ 後我們可以擬合出一顆迴歸樹估計器 h(x) ,這時我們要求的是每輪迭代後的估計器的係數 $\alpha$:

$$ \alpha_k = \underset{\alpha}{argmin} \sum_{i = 1}^{N} \log (1 + e^{-2y_i(H_{k - 1}(X_i) + \alpha h_k(X_i))}) $$

<center>式2-7</center>

  我們先來看下這個迴歸樹估計器 h(x) ,其表示式可以寫成下式,其中迴歸樹一共有 J 個葉子結點,$R_j$ 、$\beta_j$分別代表迴歸樹第 j 個葉子結點包含的訓練集合和取值, $I(x)$ 為前面章節中提到過的指示函式:

$$ h(x) = \sum_{j = 1}^{J} \beta_j I(x \in R_j) $$

<center>式2-8</center>

  這時將式 2-8 帶入式 2-7 中改寫一下,這時就不再是求估計器的係數 alpha,轉而直接求 $\gamma$,其中$\gamma_{k,j} = \alpha_k * \beta _{k,j}$:

$$ \gamma_{k, j} = \underset{\gamma}{argmin} \sum_{X_i \in R_{k, j}}^{} \log (1 + e^{-2y_i(H_{k - 1}(X_i) + \gamma )}) $$

<center>式2-9</center>

  由於式 2-9 中包含了對數指數函式,導致其沒有閉式解,這時可以使用二階泰勒公式對其進行近似處理得到如下結果:

$$ \gamma_{k,j} = \frac{\sum_{X_i \in R_{k,j}} \hat{y}_{k,i}}{\sum_{X_i \in R_{k,j}} |\hat{y}_{k,i}| (2 - |\hat{y}_{k,i}|)} $$

<center>式2-10</center>

  得到 gamma 後更新 H(x) ,然後進行新的迭代。

  這樣就得到了二分類的 GBDT 演算法實現,具體步驟可以參考第三節的演算法步驟中的二分類部分,具體的公式的來由可以參考第四節的原理證明。

多分類

  多分類相對二分類更復雜一些,同樣是使用對數函式作為其代價函式,還用到了前面多分類對數機率迴歸演算法中介紹的 Softmax 函式,同時還需對輸入的標籤值 y 進行 One-Hot 編碼4 操作,其代價函式如下:

$$ \begin{aligned} Cost(y, H(x)) &= \sum_{m = 1}^M y_m \log p_m(x) & (1) \\ p_{m}(x) &= \frac{e^{H_{m}(x)}}{\sum_{l=1}^M e^{H_{l}(x)}} & (2) \\ \end{aligned} $$

<center>式2-11</center>

  同樣按照前面模型介紹的計算負梯度的方法,帶入代價函式計算出 $\hat{y}$ :

$$ \begin{aligned} \hat{y}_{k, m, i} &= -\left[\frac{\partial Cost(y_i, H(X_i))}{\partial H(X_i)} \right]_{H(x) = H_{k-1}(x)} & (1)\\ &= y_{m, i} - p_{k-1, m}(X_i) & (2) \\ \end{aligned} $$

<center>式2-12</center>

  同二分類一樣,擬合迴歸樹,同時將其轉化為求對應的 $\gamma$,不同的是需要對每一個分類都擬合一個迴歸樹,所以多分類一共需要擬合 K * M 個決策迴歸樹:

$$ \gamma_{k, m, j} = \underset{\gamma}{argmin} \sum_{i = 1}^{N} \sum_{m = 1}^{M} Cost\left(y_{m,i}, H_{k - 1, m}(X_i) + \sum_{j = 1}^{J} \gamma I(X_i \in R_{k, m, j})\right) $$

<center>式2-13</center>

  同樣使用泰勒公式對其進行近似處理得到如下結果:

$$ \gamma_{k, m, j} = \frac{M - 1}{M} \frac{\sum_{X_i \in R_{k, m, j}} \hat{y}_{k, m, i}}{\sum_{X_i \in R_{k, m, j}} |\hat{y}_{k, m, i}| (1 - |\hat{y}_{k, m, i}|)} $$

<center>式2-14</center>

  得到 $\gamma$ 後更新對應分類的 H(x) ,然後進行新的迭代。

  這樣就得到了多分類的 GBDT 演算法實現,具體步驟可以參考第三節的演算法步驟中的多分類部分。

三、演算法步驟

迴歸

  假設訓練集 T = { $X_i$, $y_i$ },i = 1,...,N,h(x) 為估計器,估計器的數量為 K。

梯度提升決策樹迴歸演算法步驟如下:

初始化 $H_0(x)$,令其等於 y 的平均值 $\bar{y}$

$$ H_0(X_i) = \bar{y} $$

遍歷估計器的數量 K 次:

  計算第 k 輪的殘差 $\hat{y}_{k}$

$$ \hat{y}_{k, i} = y_i - H_{k-1}(X_i) $$

  將第 k 輪 $\hat{y}_{k}$ 當作標籤值擬合訓練集,得到決策迴歸樹估計器 $h_k(x)$

  更新 $H_k(x)$

$$ H_k(X_i) = H_{k-1}(X_i) + h_k(X_i) $$

結束迴圈

最後的預測策略:

  輸入 x ,K 個決策迴歸樹估計器依次預測後相加,然後加上初始值 $H_0$,得到最後的預測結果

$$ H(x) = H_{0} + \sum_{k = 1}^K h_k(x) $$

二分類

  假設訓練集 T = { $X_i$, $y_i$ },i = 1,...,N,$y_i \in \{ -1, +1 \}$ ,h(x) 為估計器,估計器的數量為 K。

梯度提升決策樹二分類演算法步驟如下:

初始化 $H_0(x)$,其中 $\bar{y}$ 為 y 的平均值

$$ H_0(X_i) = \frac{1}{2} \log \frac{1 + \bar{y}}{1 - \bar{y}} $$

遍歷估計器的數量 K 次:

  計算第 k 輪的 $\hat{y}_{k}$

$$ \hat{y}_{k,i} = \frac{2y_i}{1 + e^{2y_iH_{k-1}(X_i)}} $$

  將第 k 輪 $\hat{y}_{k}$ 當作標籤值擬合訓練集,得到決策迴歸樹估計器 $h_k(x)$,其中 $h_k(x)$ 包含 J 個葉子結點

  計算第 k 輪第 j 個葉子結點的係數 $\gamma$,其中 $R_{kj}$ 代表第 k 輪擬合出的決策迴歸樹估計器 $h_k(x)$ 第 j 個葉子結點包含的訓練集合

$$ \gamma_{k,j} = \frac{\sum_{X_i \in R_{k,j}} \hat{y}_{k,i}}{\sum_{X_i \in R_{k,j}} |\hat{y}_{k,i}| (2 - |\hat{y}_{k,i}|)} $$

  更新 $H_k(x)$,其中 $I(x)$ 為前面章節中提到過的指示函式

$$ H_k(X_i) = H_{k - 1}(X_i) + \sum_{j = 1}^{J} \gamma_{k,j} I(X_i \in R_{k,j}) $$

結束迴圈

最後的預測策略:

  輸入 x ,K 個決策迴歸樹估計器依次判斷所屬葉子結點,將葉子結點對應的係數 $\gamma$ 累加,然後加上初始值 $H_0$,得到 H(x)

$$ H(x) = H_0 + \sum_{k = 1}^{K}\sum_{j = 1}^{J} \gamma_{k,j} I(x \in R_{k,j}) $$

  分別計算正類和負類的概率

$$ \left\{ \space \begin{aligned} p_+(x) &= \frac{1}{1 + e^{-2H(x)}} \\ p_-(x) &= \frac{1}{1 + e^{2H(x)}} \end{aligned} \right. $$

  取正類和負類概率中最大的分類,作為最後的分類結果

$$ \underset{m}{argmax} \space p_m(x) \quad (m \in \{+ , -\}) $$

多分類

  假設訓練集 T = { $X_i, y_i$ },i = 1,...,N,y 的取值有 M 種可能,h(x) 為估計器,估計器的數量為 K。

梯度提升決策樹多分類演算法步驟如下:

對訓練集中的標籤值 y 進行 One-Hot 編碼

初始化 $H_{0,m}(x)$,其中 m 為第 m 個分類,在原始論文中賦值為 0 ,而 scikit-learn 中的實現為各個分類的先驗

$$ H_{0, m}(X_i) = \frac{\sum_{i=1}^{N}I(y_i = m)}{N} 或 0 $$

遍歷估計器的數量 K 次:

  遍歷分類的數量 M 次:

    計算第 k - 1 輪第 m 個分類的概率 p(x)

$$ p_{k-1, m}(X_i) = \frac{e^{H_{k-1, m}(X_i)}}{\sum_{l=1}^M e^{H_{k-1, l}(X_i)}} $$

    計算第 k 輪第 m 個分類的 $\hat{y}_{k, m}$

$$ \hat{y}_{k, m, i} = y_{m, i} - p_{k-1, m}(X_i) $$

    將第 k 輪第 m 個分類的 $\hat{y}_{k, m}$ 當作標籤值擬合訓練集,得到決策迴歸樹估計器 $h_{k,m}(x)$,其中 $h_{k,m}(x)$ 包含 J 個葉子結點

    計算第 k 輪第 m 個分類第 j 個葉子結點的係數 $\gamma$,其中 $R_{k,m,j}$ 代表第 k 輪第 m 個分類擬合出的決策迴歸樹估計器 $h_{k,m}(x)$ 第 j 個葉子結點包含的訓練集合

$$ \gamma_{k, m, j} = \frac{M - 1}{M} \frac{\sum_{X_i \in R_{k, m, j}} \hat{y}_{k, m, i}}{\sum_{X_i \in R_{k, m, j}} |\hat{y}_{k, m, i}| (1 - |\hat{y}_{k, m, i}|)} $$

    更新 $H_{k,m}(x)$,其中 $I(x)$ 為前面章節中提到過的指示函式

$$ H_{k, m}(X_i) = H_{k - 1, m}(X_i) + \sum_{j = 1}^{J} \gamma_{k, m, j} I(X_i \in R_{k, m, j}) $$

  結束迴圈

結束迴圈

最後的預測策略:

  輸入 x ,第 m 個分類對應的 K 個決策迴歸樹估計器依次判斷所屬葉子結點,將葉子結點對應的係數 $\gamma$ 累加,然後加上第 m 個分類的初始值 $H_0$,得到第 m 個分類的 H(x)

$$ H_{m}(x) = H_{0,m} + \sum_{k = 1}^{K} \sum_{j = 1}^{J} \gamma_{k, m, j} I(x \in R_{k, m, j}) $$

  依次計算每個分類的概率

$$ p_m(x) = \frac{e^{H_m(x)}}{\sum_{l = 1}^M e^{H_l(x)}} $$

  取每個分類的概率中最大的分類,作為最後的分類結果

$$ \underset{m}{argmax} \space p_m(x) \quad (m = 1,2,\dots,M) $$

四、原理證明

迴歸問題初始值

  對於用平方誤差作為代價函式的最小二乘迴歸,其初始值為 y 的均值:

(1)迴歸問題的代價函式

(2)對代價函式求導數並令其等於零

(3)可以算出其初始值為 y 的均值

$$ \begin{aligned} Cost(H(x)) &= \sum_{i = 1}^{N} (y_i - H(x))^2 & (1) \\ \frac{\partial Cost(H(x))}{\partial H(x)} &= 2\sum_{i = 1}^{N} (H(x) - y_i) = 0 & (2) \\ H(x) &= \frac{\sum_{i = 1}^{N} y_i}{N} = \bar{y} & (3) \\ \end{aligned} $$

<center>式4-1</center>

  即得證

二分類問題初始值

  對於二分類問題,$y \in \{ -1, +1 \}$:

(1)$y = +1$ 的個數

(2)$y = -1$ 的個數

(3)兩式之和為 N

$$ \begin{aligned} n_{+} &= \sum_{i = 1}^N I(y_i = +1) & (1) \\ n_{-} &= \sum_{i = 1}^N I(y_i = -1) & (2) \\ N &= n_{+} + n_{-} & (3) \\ \end{aligned} $$

<center>式4-2</center>

(1)y 的平均值的表示式
(2)化簡得到

$$ \begin{aligned} \bar{y} &= \frac{1 * n_{+} + (-1) * n_{-}}{N} & (1) \\ &= \frac{n_{+} - n_{-}}{N} & (2) \\ \end{aligned} $$

<center>式4-3</center>

  由式 4-2 中的(3)式和式 4-3 中的(2)式,可以分別求得如下結果:

$$ \begin{aligned} n_{+} &= \frac{N(1 + \bar{y})}{2} & (1) \\ n_{-} &= \frac{N(1 - \bar{y})}{2} & (2) \end{aligned} $$

<center>式4-4</center>

(1)二分類問題的代價函式

(2)對代價函式求導數

(3)將(2)式的結果拆分為兩個連加的和

(4)帶入式 4-4 的結果,將連加符號去除

(5)化簡後令其等於零

(6)最後可以算出其初始值

$$ \begin{aligned} Cost(H(x)) & = \sum_{i = 1}^N \log (1 + e^{-2y_iH(x)}) & (1) \\ \frac{\partial Cost(H(x))}{\partial H(x)} &= -\sum_{i = 1}^N \frac{2y_i}{1 + e^{2y_iH(x)}} & (2) \\ &= -\sum_{y_i = +1} \frac{2y_i}{1 + e^{2y_iH(x)}} - \sum_{y_i = -1} \frac{2y_i}{1 + e^{2y_iH(x)}} & (3) \\ &= - \frac{N(1 + \bar{y})}{2} * \frac{2}{1 + e^{2H(x)}} - \frac{N(1 - \bar{y})}{2} * \frac{-2}{1 + e^{-2H(x)}} & (4) \\ &= - \frac{N(1 + \bar{y})}{1 + e^{2H(x)}} + \frac{N(1 - \bar{y})}{1 + e^{-2H(x)}} = 0 & (5) \\ H(x) &= \frac{1}{2} \log \frac{1 + \bar{y}}{1 - \bar{y}} & (6) \end{aligned} $$

<center>式4-5</center>

  即得證

二分類問題係數 $\gamma$

  我們先來看下 $\gamma$ 的優化目標函式:

(1)二分類問題的代價函式

(2)式 2-9 得到的 $\gamma$ 的優化目標

(3)對(2)式在 $H_{k - 1}(x)$ 處進行二階泰勒展開近似

(4)可以看到 $H(x) - H_{k - 1}(x)$ 等於 $\gamma$

$$ \begin{aligned} Cost(H(x)) &= \sum_{X_i \in R_{k, j}}^{} \log (1 + e^{-2y_iH(X_i)}) & (1) \\ \gamma_{k, j} &= \underset{\gamma}{argmin} \sum_{X_i \in R_{k, j}}^{} \log (1 + e^{-2y_i(H_{k - 1}(X_i) + \gamma )}) & (2)\\ &= \underset{\gamma}{argmin} \sum_{X_i \in R_{k, j}}^{} Cost(H_{k - 1}(X_i)) + Cost^{'}(H_{k - 1}(X_i)) (H(X_i) - H_{k - 1}(X_i)) + \frac{1}{2} Cost^{''}(H_{k - 1}(X_i)) (H(X_i) - H_{k - 1}(X_i))^2 & (3) \\ &= \underset{\gamma}{argmin} \sum_{X_i \in R_{k, j}}^{} Cost(H_{k - 1}(X_i)) + Cost^{'}(H_{k - 1}(X_i)) \gamma + \frac{1}{2} Cost^{''}(H_{k - 1}(X_i)) \gamma^2 & (4) \end{aligned} $$

<center>式4-6</center>

  對其近似進行求解:

(1)式 4-6 得到的 $\gamma$ 的泰勒展開近似

(2)對函式求導並令其為零

(3)得到 $\gamma$ 的結果

$$ \begin{aligned} \phi (\gamma ) &= \sum_{X_i \in R_{k, j}}^{} Cost(H_{k - 1}(X_i)) + Cost^{'}(H_{k - 1}(X_i)) \gamma + \frac{1}{2} Cost^{''}(H_{k - 1}(X_i)) \gamma^2 & (1) \\ \frac{\partial \phi (\gamma )}{\partial \gamma } &= \sum_{X_i \in R_{k, j}}^{} + Cost^{'}(H_{k - 1}(X_i)) + Cost^{''}(H_{k - 1}(X_i)) \gamma = 0 & (2) \\ \gamma &= -\frac{\sum_{X_i \in R_{k, j}}^{} Cost^{'}(H_{k - 1}(X_i))}{\sum_{X_i \in R_{k, j}}^{} Cost^{''}(H_{k - 1}(X_i))} & (3) \\ \end{aligned} $$

<center>式4-7</center>

  下面依次求代價函式的一階導數和二階導數:

$$ \begin{aligned} Cost^{'}(H_{k - 1}(X_i)) &= -\frac{2y_i}{1 + e^{2y_iH_{k-1}(X_i)}} = -\hat{y_i} & (1) \\ Cost^{''}(H_{k - 1}(X_i)) &= \frac{4y_i^2e^{2y_iH_{k-1}(X_i)}}{(1 + e^{2y_iH_{k-1}(X_i)})^2} = 2\hat{y_i}y_i - \hat{y}_i^2 & (2) \\ \end{aligned} $$

<center>式4-8</center>

(1)式 4-7 中得到的 $\gamma$ 的表示式

(2)帶入式 4-8 得到

(3)當 $y = +1$ 時,$\hat{y} \in (0, 2)$,當 $y = -1$ 時,$\hat{y} \in (-2, 0)$,所以 $\hat{y} * y = |\hat{y}|$

(4)分母提出 $|\hat{y}|$

$$ \begin{aligned} \gamma &= -\frac{\sum_{X_i \in R_{k, j}}^{} Cost^{'}(H_{k - 1}(X_i))}{\sum_{X_i \in R_{k, j}}^{} Cost^{''}(H_{k - 1}(X_i))} & (1) \\ &= \frac{\sum_{X_i \in R_{k, j}}^{} \hat{y}_i}{\sum_{X_i \in R_{k, j}}^{} (2\hat{y}_iy_i - \hat{y}_i^2) } & (2) \\ &= \frac{\sum_{X_i \in R_{k, j}}^{} \hat{y}_i}{\sum_{X_i \in R_{k, j}}^{} (2|\hat{y}_i| - \hat{y}_i^2) } & (3) \\ &= \frac{\sum_{X_i \in R_{k, j}} \hat{y}_{i}}{\sum_{X_i \in R_{k,j}} |\hat{y}_{i}| (2 - |\hat{y}_{i}|)} & (4) \\ \end{aligned} $$

<center>式4-9</center>

  即得證

多分類問題係數 $\gamma$

  多分類問題的係數 $\gamma$ 由於存在多棵樹重疊,涉及到黑塞矩陣,無法像二分類一樣單獨求解,論文中是使用對角近似其黑塞矩陣,直接給出了結果,由於筆者能力有限,如有知道如何推匯出結果的讀者請留言或私信。

五、正則化

  梯度提升樹同樣也需要進行正則化操作來防止過擬合的情況,其正則化的方法一般有如下幾種方式:

學習速率

  在每次迭代更新 H(x) 時增加一個學習速率 $\eta$ 的超引數,下式中分別展示了學習速率 $\eta$ 在不同問題中的使用方法:

$$ \begin{aligned} H_k(x) &= H_{k - 1}(x) + \eta \alpha_k h_k(x) & (1) \\ H_k(x) &= H_{k - 1}(x) + \eta \sum_{j = 1}^{J} \gamma_{k,j} I(x \in R_{k,j}) & (2) \\ H_{k, m}(x) &= H_{k - 1, m}(x) + \eta \sum_{j = 1}^{J} \gamma_{k, m ,j} I(x \in R_{k, m, j}) & (3) \\ \end{aligned} $$

<center>式5-1</center>

  其中學習速率 $\eta \in (0,1]$ ,當學習速率 $\eta$ 過小時,需要增加迭代次數才能達到好的學習效果,所以我們需要綜合考慮該超引數的使用。在 scikit-learn 中使用 learning_rate 引數來控制。

子取樣

  子取樣類似於隨機梯度下降的方法,每次只取一部分的訓練集來學習,可以減小方差,但是同時也會增加偏差。在 scikit-learn 中使用 subsample 引數來控制,同樣為大於 0 小於等於 1 的小數。

決策樹枝剪

  決策樹枝剪同前面在決策樹章節中介紹的方法一樣,通過對基估計器的控制來達到正則化的目的。在 scikit-learn 中使用決策樹相關的引數來控制。

  下面程式碼實現中只實現了利用學習速率來正則化的操作,其他的方法可以參考 scikit-learn 的原始碼實現。

六、程式碼實現

使用 Python 實現梯度提升樹迴歸演算法:

import numpy as np
from sklearn.tree import DecisionTreeRegressor

class gbdtr:
    """
    梯度提升樹迴歸演算法
    """
    
    def __init__(self, n_estimators = 100, learning_rate = 0.1):
        # 梯度提升樹弱學習器數量
        self.n_estimators = n_estimators
        # 學習速率
        self.learning_rate = learning_rate
        
    def fit(self, X, y):
        """
        梯度提升樹迴歸演算法擬合
        """
        # 初始化 H0
        self.H0 = np.average(y)
        # 初始化預測值
        H = np.ones(X.shape[0]) * self.H0
        # 估計器陣列
        estimators = []
        # 遍歷 n_estimators 次
        for k in range(self.n_estimators):
            # 計算殘差 y_hat
            y_hat = y - H
            # 初始化決策迴歸樹估計器
            estimator = DecisionTreeRegressor(max_depth = 3)
            # 用 y_hat 擬合訓練集
            estimator.fit(X, y_hat)
            # 使用迴歸樹的預測值
            y_predict = estimator.predict(X)
            # 更新預測值
            H += self.learning_rate * y_predict
            estimators.append(estimator)
        self.estimators = np.array(estimators)
        
    def predict(self, X):
        """
        梯度提升樹迴歸演算法預測
        """
        # 初始化預測值
        H = np.ones(X.shape[0]) * self.H0
        # 遍歷估計器
        for k in range(self.n_estimators):
            estimator = self.estimators[k]
            y_predict = estimator.predict(X)
            # 計算預測值
            H += self.learning_rate * y_predict
        return H

使用 Python 實現梯度提升樹二分類演算法:

import numpy as np
from sklearn.tree import DecisionTreeRegressor

class gbdtc:
    """
    梯度提升樹二分類演算法
    """
    
    def __init__(self, n_estimators = 100, learning_rate = 0.1):
        # 梯度提升樹弱學習器數量
        self.n_estimators = n_estimators
        # 學習速率
        self.learning_rate = learning_rate
        
    def fit(self, X, y):
        """
        梯度提升樹二分類演算法擬合
        """
        # 標籤類
        self.y_classes = np.unique(y)
        # 標籤類數量
        self.n_classes = len(self.y_classes)
        # 標籤的平均值
        y_avg = np.average(y)
        # 初始化H0
        self.H0 = np.log((1 + y_avg) / (1 - y_avg)) / 2
        # 初始化預測值
        H = np.ones(X.shape[0]) * self.H0
        # 估計器陣列
        estimators = []
        # 葉子結點取值陣列
        gammas = []
        for k in range(self.n_estimators):
            # 計算 y_hat
            y_hat = 2 * np.multiply(y, 1 / (1 + np.exp(2 * np.multiply(y, H))))
            # 初始化決策迴歸樹估計器
            estimator = DecisionTreeRegressor(max_depth = 3, criterion="friedman_mse")
            # 將 y_hat 當作標籤值擬合訓練集
            estimator.fit(X, y_hat)
            # 計算訓練集在當前決策迴歸樹的葉子結點
            leaf_ids = estimator.apply(X)
            # 每個葉子結點下包含的訓練資料序號
            node_ids_dict = self.get_leaf_nodes(leaf_ids)
            # 葉子結點取值字典表
            gamma_dict = {}
            # 計算葉子結點取值
            for leaf_id, node_ids in node_ids_dict.items():
                # 當前葉子結點包含的 y_hat
                y_hat_sub = y_hat[node_ids]
                y_hat_sub_abs = np.abs(y_hat_sub)
                # 計算葉子結點取值
                gamma = np.sum(y_hat_sub) / np.sum(np.multiply(y_hat_sub_abs, 2 - y_hat_sub_abs))
                gamma_dict[leaf_id] = gamma
                # 更新預測值
                H[node_ids] += self.learning_rate * gamma
            estimators.append(estimator)
            gammas.append(gamma_dict)
        self.estimators = estimators
        self.gammas = gammas
        
    def predict(self, X):
        """
        梯度提升樹二分類演算法預測
        """
        # 初始化預測值
        H = np.ones(X.shape[0]) * self.H0
        # 遍歷估計器
        for k in range(self.n_estimators):
            estimator = self.estimators[k]
            # 計算在當前決策迴歸樹的葉子結點
            leaf_ids = estimator.apply(X)
            # 每個葉子結點下包含的資料序號
            node_ids_dict = self.get_leaf_nodes(leaf_ids)
            # 葉子結點取值字典表
            gamma_dict = self.gammas[k]
            # 計算預測值
            for leaf_id, node_ids in node_ids_dict.items():
                gamma = gamma_dict[leaf_id]
                H[node_ids] += self.learning_rate * gamma
        # 計算概率
        probs = np.zeros((X.shape[0], self.n_classes))
        probs[:, 0] = 1 / (1 + np.exp(2 * H))
        probs[:, 1] = 1 / (1 + np.exp(-2 * H))
        return self.y_classes.take(np.argmax(probs, axis=1), axis = 0)
    
    def get_leaf_nodes(self, leaf_ids):
        """
        每個葉子結點下包含的資料序號
        """
        node_ids_dict = {}
        for j in range(len(leaf_ids)):
            leaf_id = leaf_ids[j]
            node_ids = node_ids_dict.setdefault(leaf_id, [])
            node_ids.append(j)
        return node_ids_dict

使用 Python 實現梯度提升樹多分類演算法:

import numpy as np
from sklearn.tree import DecisionTreeRegressor

class gbdtmc:
    """
    梯度提升樹多分類演算法
    """
    
    def __init__(self, n_estimators = 100, learning_rate = 0.1):
        # 梯度提升樹弱學習器數量
        self.n_estimators = n_estimators
        # 學習速率
        self.learning_rate = learning_rate
        
    def fit(self, X, y):
        """
        梯度提升樹多分類演算法擬合
        """
        # 標籤類,對應標籤的數量
        self.y_classes, y_counts = np.unique(y, return_counts = True)
        # 標籤類數量
        self.n_classes = len(self.y_classes)
        # 對標籤進行One-Hot編碼
        y_onehot = np.zeros((y.size, y.max() + 1))
        y_onehot[np.arange(y.size), y] = 1
        # 初始化 H0
        self.H0 = y_counts / X.shape[0]
        # 初始化預測值
        H = np.ones((X.shape[0], 1)).dot(self.H0.reshape(1, -1))
        # 估計器陣列
        estimators = []
        # 葉子結點取值陣列
        gammas = []
        # 遍歷 n_estimators 次
        for k in range(self.n_estimators):
            H_exp = np.exp(H)
            H_exp_sum = np.sum(H_exp, axis = 1)
            # 估計器
            sub_estimators = []
            # 葉子結點取值
            sub_gammas = []
            # 遍歷 n_classes 次
            for m in range(self.n_classes):
                p_m = H_exp[:, m] / H_exp_sum
                # 計算第 m 個 y_hat
                y_hat_m = y_onehot[:, m] - p_m
                # 初始化決策迴歸樹估計器
                estimator = DecisionTreeRegressor(max_depth = 3, criterion="friedman_mse")
                # 將第 m 個 y_hat 當作標籤值擬合訓練集
                estimator.fit(X, y_hat_m)
                # 計算訓練集在當前決策迴歸樹的葉子結點
                leaf_ids = estimator.apply(X)
                # 每個葉子結點下包含的訓練資料序號
                node_ids_dict = self.get_leaf_nodes(leaf_ids)
                # 葉子結點取值字典表
                gamma_dict = {}
                # 計算葉子結點取值
                for leaf_id, node_ids in node_ids_dict.items():
                    # 當前葉子結點包含的 y_hat
                    y_hat_sub = y_hat_m[node_ids]
                    y_hat_sub_abs = np.abs(y_hat_sub)
                    # 計算葉子結點取值
                    gamma = np.sum(y_hat_sub) / np.sum(np.multiply(y_hat_sub_abs, 1 - y_hat_sub_abs)) * (self.n_classes - 1) / self.n_classes
                    gamma_dict[leaf_id] = gamma
                    # 更新預測值
                    H[node_ids, m] += self.learning_rate * gamma
                sub_estimators.append(estimator)
                sub_gammas.append(gamma_dict)
            estimators.append(sub_estimators)
            gammas.append(sub_gammas)
        self.estimators = estimators
        self.gammas = gammas
        
    def predict(self, X):
        """
        梯度提升樹多分類演算法預測
        """
        # 初始化預測值
        H = np.ones((X.shape[0], 1)).dot(self.H0.reshape(1, -1))
        # 遍歷估計器
        for k in range(self.n_estimators):
            sub_estimators = self.estimators[k]
            sub_gammas = self.gammas[k]
            # 遍歷分類數
            for m in range(self.n_classes):
                estimator = sub_estimators[m]
                # 計算在當前決策迴歸樹的葉子結點
                leaf_ids = estimator.apply(X)
                # 每個葉子結點下包含的訓練資料序號
                node_ids_dict = self.get_leaf_nodes(leaf_ids)
                # 葉子結點取值字典表
                gamma_dict = sub_gammas[m]
                # 計算預測值
                for leaf_id, node_ids in node_ids_dict.items():
                    gamma = gamma_dict[leaf_id]
                    H[node_ids, m] += self.learning_rate * gamma
        H_exp = np.exp(H)
        H_exp_sum = np.sum(H_exp, axis = 1)
        # 計算概率
        probs = np.zeros((X.shape[0], self.n_classes))
        for m in range(self.n_classes):
            probs[:, m] = H_exp[:, m] / H_exp_sum
        return self.y_classes.take(np.argmax(probs, axis=1), axis = 0)
    
    def get_leaf_nodes(self, leaf_ids):
        """
        每個葉子結點下包含的資料序號
        """
        node_ids_dict = {}
        for j in range(len(leaf_ids)):
            leaf_id = leaf_ids[j]
            node_ids = node_ids_dict.setdefault(leaf_id, [])
            node_ids.append(j)
        return node_ids_dict

七、第三方庫實現

scikit-learn5 實現:

from sklearn.ensemble import GradientBoostingClassifier

# 梯度提升樹分類器
clf = GradientBoostingClassifier(n_estimators = 100)
# 擬合資料集
clf = clf.fit(X, y)

scikit-learn6 實現:

from sklearn.ensemble import GradientBoostingRegressor

# 梯度提升樹迴歸器
reg = GradientBoostingRegressor(n_estimators = 100, max_depth = 3, random_state = 0, loss = 'ls')
# 擬合資料集
reg = reg.fit(X, y)

八、示例演示

  下面三張圖片分別展示了使用梯度提升演算法進行二分類,多分類與迴歸的結果

0.png
<center>圖8-1</center>

1.png
<center>圖8-2</center>

2.png
<center>圖8-3</center>

九、思維導圖

3.jpeg
<center>圖9-1</center>

十、參考文獻

  1. https://en.wikipedia.org/wiki...
  2. https://www.cse.cuhk.edu.hk/i...
  3. https://en.wikipedia.org/wiki...
  4. https://en.wikipedia.org/wiki...
  5. https://scikit-learn.org/stab...
  6. https://scikit-learn.org/stab...

完整演示請點選這裡

注:本文力求準確並通俗易懂,但由於筆者也是初學者,水平有限,如文中存在錯誤或遺漏之處,懇請讀者通過留言的方式批評指正

本文首發於——AI導圖,歡迎關注

相關文章