1.11. 集成方法:梯度提升、隨機森林、套袋法、投票法、堆疊法#

集成方法結合數個使用給定學習演算法建構的基本估計器的預測,以提高單個估計器的泛化能力/穩健性。

兩個非常著名的集成方法範例是梯度提升樹隨機森林

更廣泛地說,集成模型可以應用於樹之外的任何基本學習器,在諸如套袋法模型堆疊投票法的平均方法中,或是在提升法中,如AdaBoost

1.11.1. 梯度提升樹#

梯度樹提升或梯度提升決策樹 (GBDT) 是提升到任意可微損失函數的泛化,請參閱[Friedman2001]的開創性研究。GBDT 是迴歸和分類的絕佳模型,特別是對於表格資料。

1.11.1.1. 基於直方圖的梯度提升#

Scikit-learn 0.21 推出了梯度提升樹的兩個新實作,即HistGradientBoostingClassifierHistGradientBoostingRegressor,其靈感來自LightGBM(請參閱[LightGBM])。

當樣本數量大於數萬個樣本時,這些基於直方圖的估計器可以比GradientBoostingClassifierGradientBoostingRegressor幾個數量級

它們也內建支援遺失值,避免需要填補器。

這些快速估計器首先將輸入樣本X分箱到整數值箱(通常為 256 個箱),這大大減少了要考慮的分割點數量,並允許演算法在建構樹時利用基於整數的資料結構(直方圖),而不是依賴排序的連續值。這些估計器的 API 略有不同,並且尚未支援來自GradientBoostingClassifierGradientBoostingRegressor的一些功能,例如某些損失函數。

範例

1.11.1.1.1. 用法#

大多數參數與GradientBoostingClassifierGradientBoostingRegressor相同。一個例外是max_iter參數取代了n_estimators,並控制提升過程的迭代次數

>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> from sklearn.datasets import make_hastie_10_2

>>> X, y = make_hastie_10_2(random_state=0)
>>> X_train, X_test = X[:2000], X[2000:]
>>> y_train, y_test = y[:2000], y[2000:]

>>> clf = HistGradientBoostingClassifier(max_iter=100).fit(X_train, y_train)
>>> clf.score(X_test, y_test)
0.8965

迴歸可用的損失為

  • ‘squared_error’,這是預設損失;

  • ‘absolute_error’,它對離群值不如平方誤差敏感;

  • ‘gamma’,它非常適合模擬嚴格正向的結果;

  • ‘poisson’,它非常適合模擬計數和頻率;

  • ‘quantile’,它允許估計條件分位數,之後可用於獲得預測區間。

對於分類,唯一選項是 ‘log_loss’。對於二元分類,它使用二元對數損失,也稱為二項偏差或二元交叉熵。對於 n_classes >= 3,它使用多類別對數損失函數,又稱多項偏差和類別交叉熵。會根據傳遞給 fity 來選擇適當的損失版本。

樹的大小可以透過 max_leaf_nodesmax_depthmin_samples_leaf 參數來控制。

用於對資料進行分箱的箱子數量由 max_bins 參數控制。使用較少的箱子數量可作為一種正規化形式。通常建議使用盡可能多的箱子 (255 個),這是預設值。

l2_regularization 參數作為損失函數的正規化器,對應於以下表達式中的 \(\lambda\)(請參閱 [XGBoost] 中的方程式 (2))

\[\mathcal{L}(\phi) = \sum_i l(\hat{y}_i, y_i) + \frac12 \sum_k \lambda ||w_k||^2\]
L2 正規化的詳細資訊#

重要的是要注意,損失項 \(l(\hat{y}_i, y_i)\) 僅描述了實際損失函數的一半,除了 pinball 損失和絕對誤差之外。

索引 \(k\) 指的是樹集成中的第 k 棵樹。在回歸和二元分類的情況下,梯度提升模型每次迭代會生長一棵樹,然後 \(k\) 會運行到 max_iter。在多類別分類問題中,索引 \(k\) 的最大值為 n_classes \(\times\) max_iter

如果 \(T_k\) 表示第 k 棵樹中的葉節點數量,則 \(w_k\) 是一個長度為 \(T_k\) 的向量,其中包含 w = -sum_gradient / (sum_hessian + l2_regularization) 形式的葉節點值 (請參閱 [XGBoost] 中的方程式 (5))。

葉節點值 \(w_k\) 是通過將損失函數的梯度總和除以 hessian 的總和來獲得的。將正規化加入分母會懲罰具有小 hessian 值(平坦區域)的葉節點,從而導致較小的更新。然後,這些 \(w_k\) 值會影響模型對最終落入相應葉節點的給定輸入的預測。最終預測是基本預測和每棵樹的貢獻之和。該總和的結果會根據損失函數的選擇透過逆連結函數進行轉換(請參閱數學公式)。

請注意,原始論文 [XGBoost] 引入了一個 \(\gamma\sum_k T_k\) 項,該項會懲罰葉節點的數量(使其成為 max_leaf_nodes 的平滑版本),此處未呈現,因為它未在 scikit-learn 中實作;而 \(\lambda\) 則會懲罰個別樹預測的大小,然後再透過學習率重新調整,請參閱 透過學習率縮減

請注意,如果樣本數大於 10,000,則預設會啟用提前停止。提前停止行為透過 early_stoppingscoringvalidation_fractionn_iter_no_changetol 參數控制。可以使用任意 評分器,或僅使用訓練或驗證損失來提前停止。請注意,由於技術原因,使用可呼叫物件作為評分器明顯比使用損失慢。預設情況下,如果訓練集中至少有 10,000 個樣本,則會執行提前停止,並使用驗證損失。

1.11.1.1.2. 缺失值支援#

HistGradientBoostingClassifierHistGradientBoostingRegressor 內建支援缺失值 (NaN)。

在訓練期間,樹狀生長器會在每個分割點學習具有缺失值的樣本應該根據潛在增益進入左子節點還是右子節點。在預測時,具有缺失值的樣本會因此被分配到左子節點或右子節點。

>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> import numpy as np

>>> X = np.array([0, 1, 2, np.nan]).reshape(-1, 1)
>>> y = [0, 0, 1, 1]

>>> gbdt = HistGradientBoostingClassifier(min_samples_leaf=1).fit(X, y)
>>> gbdt.predict(X)
array([0, 0, 1, 1])

當缺失模式具有預測性時,可以根據特徵值是否缺失來執行分割

>>> X = np.array([0, np.nan, 1, 2, np.nan]).reshape(-1, 1)
>>> y = [0, 1, 0, 0, 1]
>>> gbdt = HistGradientBoostingClassifier(min_samples_leaf=1,
...                                       max_depth=2,
...                                       learning_rate=1,
...                                       max_iter=1).fit(X, y)
>>> gbdt.predict(X)
array([0, 1, 0, 0, 1])

如果在訓練期間給定特徵沒有遇到任何缺失值,則具有缺失值的樣本會被對應到具有最多樣本的子節點。

範例

1.11.1.1.3. 樣本權重支援#

HistGradientBoostingClassifierHistGradientBoostingRegressorfit 期間支援樣本權重。

以下玩具範例示範了權重為零的樣本會被忽略

>>> X = [[1, 0],
...      [1, 0],
...      [1, 0],
...      [0, 1]]
>>> y = [0, 0, 1, 0]
>>> # ignore the first 2 training samples by setting their weight to 0
>>> sample_weight = [0, 0, 1, 1]
>>> gb = HistGradientBoostingClassifier(min_samples_leaf=1)
>>> gb.fit(X, y, sample_weight=sample_weight)
HistGradientBoostingClassifier(...)
>>> gb.predict([[1, 0]])
array([1])
>>> gb.predict_proba([[1, 0]])[0, 1]
0.99...

如您所見,[1, 0] 會被順利分類為 1,因為前兩個樣本由於其樣本權重而被忽略。

實作細節:將樣本權重納入考量相當於將梯度(和 hessian)乘以樣本權重。請注意,分箱階段(特別是分位數計算)不會將權重納入考量。

1.11.1.1.4. 類別特徵支援#

HistGradientBoostingClassifierHistGradientBoostingRegressor 對類別特徵具有原生支援:它們可以考量非有序類別資料的分割。

對於具有類別特徵的資料集,使用原生類別支援通常比依賴單熱編碼 (OneHotEncoder) 更好,因為單熱編碼需要更大的樹深度才能實現等效分割。依賴原生類別支援通常也比將類別特徵視為連續 (序數) 特徵更好(序數編碼的類別資料會發生這種情況),因為類別是名義數量,其中順序並不重要。

若要啟用類別支援,可以將布林遮罩傳遞至 categorical_features 參數,以指示哪個特徵是類別。在以下範例中,第一個特徵將被視為類別特徵,第二個特徵將被視為數值特徵

>>> gbdt = HistGradientBoostingClassifier(categorical_features=[True, False])

或者,可以傳遞一個整數列表,指示類別特徵的索引

>>> gbdt = HistGradientBoostingClassifier(categorical_features=[0])

當輸入為 DataFrame 時,也可以傳遞欄名稱列表

>>> gbdt = HistGradientBoostingClassifier(categorical_features=["site", "manufacturer"])

最後,當輸入為 DataFrame 時,我們可以使用 categorical_features="from_dtype",在這種情況下,所有具有類別 dtype 的欄都將被視為類別特徵。

每個類別特徵的基數必須小於 max_bins 參數。有關在類別特徵上使用基於直方圖的梯度提升的範例,請參閱梯度提升中的類別特徵支援

如果在訓練期間有缺失值,則缺失值將被視為正確的類別。如果在訓練期間沒有缺失值,那麼在預測時,缺失值會對應到具有最多樣本的子節點(就像連續特徵一樣)。在預測時,在 fit 期間未看到的類別將被視為缺失值。

使用類別特徵尋找分割#

在樹狀結構中考慮類別分割的標準方式是考慮所有 \(2^{K - 1} - 1\) 種分割,其中 \(K\) 是類別的數量。當 \(K\) 很大時,這種做法很快就會變得難以負荷。幸運的是,由於梯度提升樹(gradient boosting trees)始終是迴歸樹(regression trees)(即使是針對分類問題),因此存在一種更快的策略,可以產生等效的分割。首先,根據目標的變異數(variance),針對每個類別 k,對特徵的類別進行排序。一旦類別排序完成,就可以考慮連續分割,即將類別視為已排序的連續值(有關正式證明,請參閱 Fisher [Fisher1958])。如此一來,只需考慮 \(K - 1\) 種分割,而不是 \(2^{K - 1} - 1\) 種。初始排序是一個 \(\mathcal{O}(K \log(K))\) 的操作,導致總體複雜度為 \(\mathcal{O}(K \log(K) + K)\),而不是 \(\mathcal{O}(2^K)\)

範例

1.11.1.1.5. 單調約束#

根據手邊的問題,您可能會有先驗知識,指出給定的特徵通常應該對目標值產生正面(或負面)影響。例如,在其他條件相同的情況下,較高的信用評分應該會增加獲得貸款批准的機率。單調約束允許您將此類先驗知識納入模型中。

對於具有兩個特徵的預測器 \(F\)

  • 單調遞增約束是一種形式為

    \[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2)\]
  • 的約束

    單調遞減約束是一種形式為

\[x_1 \leq x_1' \implies F(x_1, x_2) \geq F(x_1', x_2)\]

>>> from sklearn.ensemble import HistGradientBoostingRegressor

... # monotonic increase, monotonic decrease, and no constraint on the 3 features
>>> gbdt = HistGradientBoostingRegressor(monotonic_cst=[1, -1, 0])

的約束。

您可以使用 monotonic_cst 參數指定每個特徵的單調約束。對於每個特徵,值 0 表示沒有約束,而 1 和 -1 分別表示單調遞增和單調遞減約束。

在二元分類情境中,施加單調遞增(遞減)約束表示特徵的較高值應該對樣本屬於正向類別的機率產生正面(負面)影響。

然而,單調約束僅稍微限制了特徵對輸出的影響。例如,單調遞增和遞減約束不能用於強制執行以下建模約束

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

此外,多類分類不支持單調約束。

範例

由於類別是無序的量,因此無法對類別特徵強制執行單調約束。

單調約束

   1      <- Both constraint groups could be applied from now on
  / \
 1   2    <- Left split still fulfills both constraint groups.
/ \ / \      Right split at feature 2 has only group {1, 2} from now on.

1.11.1.1.6. 交互約束#

先驗而言,直方圖梯度提升樹允許使用任何特徵將節點分割為子節點。這會在特徵之間產生所謂的交互,也就是沿著分支使用不同的特徵作為分割。有時,人們希望限制可能的交互,請參閱 [Mayer2022]。這可以通過參數 interaction_cst 完成,其中可以指定允許交互的特徵索引。例如,總共有 3 個特徵,interaction_cst=[{0}, {1}, {2}] 禁止所有交互。約束 [{0, 1}, {1, 2}] 指定兩組可能交互的特徵。特徵 0 和 1 可以彼此交互,特徵 1 和 2 也可以。但請注意,特徵 0 和 2 被禁止交互。以下描述了一棵樹和樹的可能分割

範例

LightGBM 對於重疊的組使用相同的邏輯。

請注意,未在 interaction_cst 中列出的特徵會自動為自身分配一個交互組。同樣使用 3 個特徵,這表示 [{0}] 等同於 [{0}, {1, 2}]

參考文獻

[Mayer2022]

M. Mayer, S.C. Bourassa, M. Hoesli, and D.F. Scognamiglio. 2022. Machine Learning Applications to Land and Structure Valuation. Journal of Risk and Financial Management 15, no. 5: 193

1.11.1.1.7. 低階並行化#

  • HistGradientBoostingClassifierHistGradientBoostingRegressor 使用 OpenMP 通過 Cython 進行並行化。有關如何控制執行緒數量的更多詳細資訊,請參閱我們的 並行化 說明。

  • 以下部分是並行化的

  • 將樣本從實際值對應到整數值區間(找到區間閾值是循序的)

  • 建構直方圖是針對特徵並行化

  • 在節點尋找最佳分割點是針對特徵並行化

  • 在擬合期間,將樣本對應到左子節點和右子節點是針對樣本並行化

梯度和 Hessian 計算是針對樣本並行化

預測是針對樣本並行化

1.11.1.1.8. 為什麼更快#

梯度提升程序的瓶頸是建構決策樹。建構傳統的決策樹(如同其他 GBDT GradientBoostingClassifierGradientBoostingRegressor 中)需要在每個節點對樣本進行排序(對於每個特徵)。需要排序才能有效地計算分割點的潛在增益。因此,分割單個節點的複雜度為 \(\mathcal{O}(n_\text{features} \times n \log(n))\),其中 \(n\) 是節點的樣本數。

相反地,HistGradientBoostingClassifierHistGradientBoostingRegressor 不需要對特徵值進行排序,而是使用稱為直方圖的資料結構,其中樣本是隱含排序的。建構直方圖的複雜度為 \(\mathcal{O}(n)\),因此節點分割程序的複雜度為 \(\mathcal{O}(n_\text{features} \times n)\),比前一個小得多。此外,我們只考慮 max_bins 個分割點,而不是考慮 \(n\) 個分割點,這可能會小得多。

LightGBM 對於重疊的組使用相同的邏輯。

為了建構直方圖,需要將輸入資料 X 分成整數值區間。這個分區過程確實需要對特徵值進行排序,但它只在提升過程的一開始發生一次(而不是像在 GradientBoostingClassifierGradientBoostingRegressor 中那樣在每個節點發生)。

最後,HistGradientBoostingClassifierHistGradientBoostingRegressor 的許多實作部分都是並行化的。

[XGBoost] (1,2,3)

Tianqi Chen, Carlos Guestrin, “XGBoost: A Scalable Tree Boosting System”

1.11.1.2. GradientBoostingClassifierGradientBoostingRegressor#

以下說明 GradientBoostingClassifierGradientBoostingRegressor 的用法和參數。這些估算器最重要的兩個參數是 n_estimatorslearning_rate

分類#

GradientBoostingClassifier 支援二元和多類別分類。以下範例展示如何使用 100 個決策樹樁作為弱學習器來擬合梯度提升分類器。

>>> from sklearn.datasets import make_hastie_10_2
>>> from sklearn.ensemble import GradientBoostingClassifier

>>> X, y = make_hastie_10_2(random_state=0)
>>> X_train, X_test = X[:2000], X[2000:]
>>> y_train, y_test = y[:2000], y[2000:]

>>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0,
...     max_depth=1, random_state=0).fit(X_train, y_train)
>>> clf.score(X_test, y_test)
0.913...

弱學習器(即迴歸樹)的數量由參數 n_estimators 控制;每棵樹的大小 可以透過 max_depth 設定樹的深度,或透過 max_leaf_nodes 設定葉節點的數量來控制。learning_rate 是一個在 (0.0, 1.0] 範圍內的超參數,可透過 shrinkage 控制過度擬合。

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

具有兩個以上類別的分類需要在每次迭代時歸納 n_classes 個迴歸樹,因此,歸納樹的總數等於 n_classes * n_estimators。對於具有大量類別的數據集,我們強烈建議使用 HistGradientBoostingClassifier 作為 GradientBoostingClassifier 的替代方案。

迴歸#

GradientBoostingRegressor 支援多種用於迴歸的不同損失函數,可以透過參數 loss 指定;迴歸的預設損失函數是平方誤差 ('squared_error')。

>>> import numpy as np
>>> from sklearn.metrics import mean_squared_error
>>> from sklearn.datasets import make_friedman1
>>> from sklearn.ensemble import GradientBoostingRegressor

>>> X, y = make_friedman1(n_samples=1200, random_state=0, noise=1.0)
>>> X_train, X_test = X[:200], X[200:]
>>> y_train, y_test = y[:200], y[200:]
>>> est = GradientBoostingRegressor(
...     n_estimators=100, learning_rate=0.1, max_depth=1, random_state=0,
...     loss='squared_error'
... ).fit(X_train, y_train)
>>> mean_squared_error(y_test, est.predict(X_test))
5.00...

下圖顯示將具有最小二乘損失和 500 個基學習器的 GradientBoostingRegressor 應用於糖尿病數據集 (sklearn.datasets.load_diabetes) 的結果。該圖顯示每次迭代的訓練和測試誤差。每次迭代的訓練誤差儲存在梯度提升模型的 train_score_ 屬性中。每次迭代的測試誤差可以透過 staged_predict 方法獲得,該方法會回傳一個產生器,該產生器會產生每個階段的預測。像這樣的圖可以用來透過提早停止來確定最佳的樹數量(即 n_estimators)。

../_images/sphx_glr_plot_gradient_boosting_regression_001.png

範例

1.11.1.2.1. 擬合額外的弱學習器#

GradientBoostingRegressorGradientBoostingClassifier 都支援 warm_start=True,這允許您向已擬合的模型添加更多估算器。

>>> import numpy as np
>>> from sklearn.metrics import mean_squared_error
>>> from sklearn.datasets import make_friedman1
>>> from sklearn.ensemble import GradientBoostingRegressor

>>> X, y = make_friedman1(n_samples=1200, random_state=0, noise=1.0)
>>> X_train, X_test = X[:200], X[200:]
>>> y_train, y_test = y[:200], y[200:]
>>> est = GradientBoostingRegressor(
...     n_estimators=100, learning_rate=0.1, max_depth=1, random_state=0,
...     loss='squared_error'
... )
>>> est = est.fit(X_train, y_train)  # fit with 100 trees
>>> mean_squared_error(y_test, est.predict(X_test))
5.00...
>>> _ = est.set_params(n_estimators=200, warm_start=True)  # set warm_start and increase num of trees
>>> _ = est.fit(X_train, y_train) # fit additional 100 trees to est
>>> mean_squared_error(y_test, est.predict(X_test))
3.84...

1.11.1.2.2. 控制樹的大小#

迴歸樹基學習器的大小定義梯度提升模型可以捕獲的變數交互作用的程度。一般來說,深度為 h 的樹可以捕獲 h 階的交互作用。有兩種方法可以控制個別迴歸樹的大小。

如果您指定 max_depth=h,則會生長深度為 h 的完整二元樹。這樣的樹(最多)將有 2**h 個葉節點和 2**h - 1 個分裂節點。

或者,您也可以透過參數 max_leaf_nodes 指定葉節點的數量來控制樹的大小。在這種情況下,樹將使用最佳優先搜尋來生長,其中雜質改進最高的節點將首先展開。具有 max_leaf_nodes=k 的樹具有 k - 1 個分裂節點,因此可以模擬最高達 max_leaf_nodes - 1 階的交互作用。

我們發現 max_leaf_nodes=k 給出的結果與 max_depth=k-1 相當,但訓練速度明顯更快,但訓練誤差略高。參數 max_leaf_nodes 對應於 [Friedman2001] 中關於梯度提升章節中的變數 J,並且與 R 的 gbm 套件中的參數 interaction.depth 相關,其中 max_leaf_nodes == interaction.depth + 1

1.11.1.2.3. 數學公式#

我們先介紹 GBRT 用於迴歸,然後詳細說明分類的情況。

迴歸#

GBRT 迴歸器是加法模型,對於給定的輸入 \(x_i\),其預測 \(\hat{y}_i\) 的形式如下

\[\hat{y}_i = F_M(x_i) = \sum_{m=1}^{M} h_m(x_i)\]

其中 \(h_m\) 是在提升的背景下稱為弱學習器的估算器。梯度樹提升使用固定大小的 決策樹迴歸器 作為弱學習器。常數 M 對應於 n_estimators 參數。

與其他提升演算法類似,GBRT 以貪婪的方式構建

\[F_m(x) = F_{m-1}(x) + h_m(x),\]

其中新增的樹 \(h_m\) 經過擬合以最小化損失總和 \(L_m\),給定先前的整體 \(F_{m-1}\)

\[h_m = \arg\min_{h} L_m = \arg\min_{h} \sum_{i=1}^{n} l(y_i, F_{m-1}(x_i) + h(x_i)),\]

其中 \(l(y_i, F(x_i))\)loss 參數定義,詳情請見下一節。

預設情況下,初始模型 \(F_{0}\) 被選為最小化損失的常數:對於最小二乘損失,這是目標值的經驗平均值。也可以透過 init 參數指定初始模型。

使用一階泰勒近似,\(l\) 的值可以近似如下

\[l(y_i, F_{m-1}(x_i) + h_m(x_i)) \approx l(y_i, F_{m-1}(x_i)) + h_m(x_i) \left[ \frac{\partial l(y_i, F(x_i))}{\partial F(x_i)} \right]_{F=F_{m - 1}}.\]

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

簡而言之,一階泰勒近似表示 \(l(z) \approx l(a) + (z - a) \frac{\partial l}{\partial z}(a)\)。這裡,\(z\) 對應到 \(F_{m - 1}(x_i) + h_m(x_i)\),而 \(a\) 對應到 \(F_{m-1}(x_i)\)

數量 \(\left[ \frac{\partial l(y_i, F(x_i))}{\partial F(x_i)} \right]_{F=F_{m - 1}}\) 是損失函數對其第二個參數的導數,在 \(F_{m-1}(x)\) 處評估。由於損失函數是可微分的,因此對於任何給定的 \(F_{m - 1}(x_i)\),都可以用閉合形式輕鬆計算。我們將其表示為 \(g_i\)

移除常數項,我們有

\[h_m \approx \arg\min_{h} \sum_{i=1}^{n} h(x_i) g_i\]

如果 \(h(x_i)\) 被擬合來預測與負梯度 \(-g_i\) 成比例的值,則此值會最小化。因此,在每次迭代中,估計器 \(h_m\) 被擬合來預測樣本的負梯度。梯度會在每次迭代時更新。這可以被視為函數空間中的某種梯度下降。

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

對於某些損失函數,例如梯度為 \(\pm 1\)'absolute_error',擬合的 \(h_m\) 預測的值不夠準確:樹只能輸出整數值。因此,一旦樹被擬合,樹 \(h_m\) 的葉節點值會被修改,使得葉節點值最小化損失 \(L_m\)。更新是與損失相關的:對於絕對誤差損失,葉節點的值會更新為該葉節點中樣本的中位數。

分類#

用於分類的梯度提升與迴歸情況非常相似。然而,樹的總和 \(F_M(x_i) = \sum_m h_m(x_i)\) 與預測並不一致:它不能是一個類別,因為樹預測的是連續值。

從值 \(F_M(x_i)\) 到類別或機率的映射是與損失相關的。對於對數損失,\(x_i\) 屬於正類別的機率建模為 \(p(y_i = 1 | x_i) = \sigma(F_M(x_i))\),其中 \(\sigma\) 是 sigmoid 或 expit 函數。

對於多類別分類,在 \(M\) 次迭代中的每一次迭代都會建立 K 個樹(針對 K 個類別)。\(x_i\) 屬於類別 k 的機率建模為 \(F_{M,k}(x_i)\) 值的 softmax。

請注意,即使對於分類任務,\(h_m\) 子估計器仍然是一個迴歸器,而不是一個分類器。這是因為子估計器被訓練來預測(負)梯度,而梯度始終是連續量。

1.11.1.2.4. 損失函數#

支援下列損失函數,並且可以使用參數 loss 指定

迴歸#
  • 平方誤差('squared_error'):由於其優越的計算特性,是迴歸的自然選擇。初始模型由目標值的平均值給出。

  • 絕對誤差('absolute_error'):用於迴歸的穩健損失函數。初始模型由目標值的中位數給出。

  • Huber('huber'):另一個結合了最小平方法和最小絕對偏差的穩健損失函數;使用 alpha 來控制對離群值的敏感度(詳情請參閱 [Friedman2001])。

  • 分位數('quantile'):用於分位數迴歸的損失函數。使用 0 < alpha < 1 來指定分位數。此損失函數可用於建立預測區間(請參閱 梯度提升迴歸的預測區間)。

分類#
  • 二元對數損失('log-loss'):用於二元分類的二項負對數似然損失函數。它提供機率估計。初始模型由對數勝算比給出。

  • 多類別對數損失('log-loss'):用於具有 n_classes 個互斥類別的多類別分類的多項負對數似然損失函數。它提供機率估計。初始模型由每個類別的先驗機率給出。在每次迭代中,必須建構 n_classes 個迴歸樹,這使得 GBRT 對於具有大量類別的資料集來說相當低效。

  • 指數損失('exponential'):與 AdaBoostClassifier 相同的損失函數。與 'log-loss' 相比,對錯誤標記的範例的穩健性較差;只能用於二元分類。

1.11.1.2.5. 透過學習率進行收縮#

[Friedman2001] 提出了一種簡單的正規化策略,該策略以常數因子 \(\nu\) 縮放每個弱學習器的貢獻

\[F_m(x) = F_{m-1}(x) + \nu h_m(x)\]

參數 \(\nu\) 也稱為學習率,因為它縮放了梯度下降程序的步長;它可以透過 learning_rate 參數設定。

參數 learning_rate 與參數 n_estimators(要擬合的弱學習器數量)強烈地相互作用。learning_rate 的值越小,就需要更多的弱學習器來維持恆定的訓練誤差。經驗證據表明,較小的 learning_rate 值更有利於更好的測試誤差。[HTF] 建議將學習率設定為一個小的常數(例如,learning_rate <= 0.1),並選擇足夠大的 n_estimators,以便應用提前停止,請參閱 梯度提升中的提前停止,以更詳細地討論 learning_raten_estimators 之間的相互作用,請參閱 [R2007]

1.11.1.2.6. 子取樣#

[Friedman2002] 提出了隨機梯度提升,它將梯度提升與 bootstrap 平均(bagging)相結合。在每次迭代中,基本分類器都在可用訓練資料的 subsample 分數上進行訓練。子樣本是在不放回的情況下抽取的。subsample 的典型值為 0.5。

下圖說明了收縮和子取樣對模型擬合優度的影響。我們可以清楚地看到,收縮勝過無收縮。具有收縮的子取樣可以進一步提高模型的準確性。另一方面,無收縮的子取樣效果不佳。

../_images/sphx_glr_plot_gradient_boosting_regularization_001.png

減少變異數的另一種策略是透過子取樣特徵,類似於 RandomForestClassifier 中的隨機分割。子取樣特徵的數量可以透過 max_features 參數控制。

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

使用較小的 max_features 值可以顯著減少執行時間。

隨機梯度提升允許透過計算未包含在自舉樣本中的範例(即袋外範例)的偏差改進,來計算測試偏差的袋外估計值。這些改進儲存在屬性 oob_improvement_ 中。oob_improvement_[i] 保存如果您將第 i 個階段添加到目前預測時,OOB 樣本損失方面的改進。袋外估計值可用於模型選擇,例如確定最佳迭代次數。OOB 估計值通常非常悲觀,因此我們建議改用交叉驗證,並且僅在交叉驗證太耗時時才使用 OOB。

範例

1.11.1.2.7. 使用特徵重要性進行解釋#

個別決策樹可以透過簡單地視覺化樹狀結構來輕鬆解釋。然而,梯度提升模型包含數百個迴歸樹,因此無法透過視覺檢查個別樹來輕鬆解釋。幸運的是,已經提出了許多技術來總結和解釋梯度提升模型。

特徵通常不會對預測目標響應產生相同的貢獻;在許多情況下,大多數特徵實際上是不相關的。在解釋模型時,通常第一個問題是:哪些是重要的特徵,它們如何有助於預測目標響應?

個別決策樹透過選擇適當的分割點來內在地執行特徵選擇。此資訊可用於衡量每個特徵的重要性;基本概念是:在樹的分割點中使用的特徵越多,該特徵就越重要。這種重要性的概念可以透過簡單地平均每棵樹的基於雜質的特徵重要性來擴展到決策樹集成(詳情請參閱特徵重要性評估)。

適合的梯度提升模型的特徵重要性分數可以透過 feature_importances_ 屬性存取

>>> from sklearn.datasets import make_hastie_10_2
>>> from sklearn.ensemble import GradientBoostingClassifier

>>> X, y = make_hastie_10_2(random_state=0)
>>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0,
...     max_depth=1, random_state=0).fit(X, y)
>>> clf.feature_importances_
array([0.10..., 0.10..., 0.11..., ...

請注意,此特徵重要性的計算是基於熵,並且與基於特徵排列的 sklearn.inspection.permutation_importance 不同。

範例

LightGBM 對於重疊的組使用相同的邏輯。

[Friedman2001] (1,2,3,4)

Friedman, J.H. (2001). Greedy function approximation: A gradient boosting machine. Annals of Statistics, 29, 1189-1232.

[Friedman2002]

Friedman, J.H. (2002). Stochastic gradient boosting.. Computational Statistics & Data Analysis, 38, 367-378.

1.11.2. 隨機森林和其他隨機樹集成#

sklearn.ensemble 模組包含兩種基於隨機化 決策樹 的平均演算法:RandomForest 演算法和 Extra-Trees 方法。兩種演算法都是擾動和組合技術 [B1998],專為樹而設計。這表示透過在分類器建構中引入隨機性來建立多樣化的分類器集合。集成的預測由個別分類器的平均預測給出。

與其他分類器一樣,森林分類器必須以兩個陣列進行擬合:一個形狀為 (n_samples, n_features) 的稀疏或密集陣列 X,其中包含訓練樣本;以及一個形狀為 (n_samples,) 的陣列 Y,其中包含訓練樣本的目標值(類別標籤)

>>> from sklearn.ensemble import RandomForestClassifier
>>> X = [[0, 0], [1, 1]]
>>> Y = [0, 1]
>>> clf = RandomForestClassifier(n_estimators=10)
>>> clf = clf.fit(X, Y)

決策樹一樣,樹森林也擴展到多輸出問題(如果 Y 是形狀為 (n_samples, n_outputs) 的陣列)。

1.11.2.1. 隨機森林#

在隨機森林中(參閱RandomForestClassifierRandomForestRegressor類別),集成中的每棵樹都是從訓練集中抽取的可放回取樣(即自舉樣本)建構而成的。

此外,在建構樹時分割每個節點時,最佳分割是透過窮舉搜尋所有輸入特徵或大小為 max_features 的隨機子集的特徵值來找到。(詳情請參閱參數調整指南。)

這兩個隨機性來源的目的是減少森林估計器的變異數。事實上,個別決策樹通常表現出高變異數並且容易過度擬合。森林中注入的隨機性產生具有一定解耦預測誤差的決策樹。透過取這些預測的平均值,可以抵消某些誤差。隨機森林透過結合不同的樹來降低變異數,有時會以輕微增加偏差為代價。實際上,變異數的減少通常很顯著,因此產生整體上更好的模型。

與最初的出版物[B2001]相反,scikit-learn 實作透過平均其機率預測來組合分類器,而不是讓每個分類器對單一類別進行投票。

隨機森林的一個有競爭力的替代方案是基於直方圖的梯度提升(HGBT) 模型

  • 建立樹:隨機森林通常依賴於深度樹(單獨過度擬合),這會消耗大量計算資源,因為它們需要多次分割和候選分割的評估。提升模型建立淺層樹(單獨欠擬合),這些樹更快速地擬合和預測。

  • 循序提升:在 HGBT 中,決策樹是循序建構的,其中每棵樹都經過訓練以校正先前樹所犯的錯誤。這使它們可以使用相對較少的樹來迭代地改進模型的效能。相反地,隨機森林使用多數投票來預測結果,這可能需要更多的樹才能達到相同的準確度。

  • 有效分箱:HGBT 使用有效的分箱演算法,可以處理具有大量特徵的大型資料集。分箱演算法可以預先處理資料以加快後續的樹狀結構建構速度(請參閱為什麼它更快)。相反地,scikit-learn 的隨機森林實作不使用分箱,而是依賴於精確分割,這可能在計算上很耗費成本。

總體而言,HGBT 與 RF 的計算成本取決於資料集的特定特性和建模任務。最好嘗試兩種模型,並比較它們在您的特定問題上的效能和計算效率,以確定哪種模型最適合。

範例

1.11.2.2. 極度隨機樹#

在極度隨機樹中(請參閱ExtraTreesClassifierExtraTreesRegressor類別),隨機性在分割的計算方式中更進一步。與隨機森林一樣,使用候選特徵的隨機子集,但不是尋找最具區別性的閾值,而是針對每個候選特徵隨機抽取閾值,並選擇這些隨機產生的閾值中最佳的閾值作為分割規則。這通常允許稍微降低模型的變異數,但代價是偏差略微增加。

>>> from sklearn.model_selection import cross_val_score
>>> from sklearn.datasets import make_blobs
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.ensemble import ExtraTreesClassifier
>>> from sklearn.tree import DecisionTreeClassifier

>>> X, y = make_blobs(n_samples=10000, n_features=10, centers=100,
...     random_state=0)

>>> clf = DecisionTreeClassifier(max_depth=None, min_samples_split=2,
...     random_state=0)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.98...

>>> clf = RandomForestClassifier(n_estimators=10, max_depth=None,
...     min_samples_split=2, random_state=0)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.999...

>>> clf = ExtraTreesClassifier(n_estimators=10, max_depth=None,
...     min_samples_split=2, random_state=0)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean() > 0.999
True
../_images/sphx_glr_plot_forest_iris_001.png

1.11.2.3. 參數#

使用這些方法時,主要調整的參數是 n_estimatorsmax_features。前者是森林中樹的數量。數量越大越好,但計算時間也會越長。此外,請注意,當樹的數量超過臨界值時,結果將不再顯著改善。後者是分割節點時要考慮的隨機特徵子集的大小。數值越小,變異數的減少幅度越大,但偏差的增加幅度也越大。經驗上良好的預設值是,對於迴歸問題,max_features=1.0 或等效的 max_features=None(總是考慮所有特徵,而不是隨機子集),而對於分類任務,則是 max_features="sqrt"(使用大小為 sqrt(n_features) 的隨機子集),其中 n_features 是資料中特徵的數量。max_features=1.0 的預設值等同於套袋樹,並且可以透過設定較小的值(例如,0.3 是文獻中典型的預設值)來實現更大的隨機性。當 max_depth=Nonemin_samples_split=2 組合時(即完全展開樹時),通常可以獲得良好的結果。但請記住,這些值通常不是最佳的,並且可能會導致模型消耗大量 RAM。最佳參數值應始終進行交叉驗證。此外,請注意,在隨機森林中,預設使用自舉樣本(bootstrap=True),而極端隨機樹的預設策略是使用整個資料集(bootstrap=False)。當使用自舉抽樣時,可以在剩餘的或袋外樣本上估計泛化誤差。可以透過設定 oob_score=True 來啟用此功能。

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

使用預設參數的模型大小為 \(O( M * N * log (N) )\),其中 \(M\) 是樹的數量,而 \(N\) 是樣本的數量。為了減小模型的大小,您可以變更這些參數:min_samples_splitmax_leaf_nodesmax_depthmin_samples_leaf

1.11.2.4. 平行化#

最後,此模組還具有透過 n_jobs 參數進行樹的平行建構和平行計算預測。如果 n_jobs=k,則計算會被分割成 k 個工作,並在機器的 k 個核心上執行。如果 n_jobs=-1,則會使用機器上所有可用的核心。請注意,由於行程間通訊的額外負擔,加速可能不是線性的(也就是說,使用 k 個工作不幸地不會快 k 倍)。但是,當建構大量樹時,或當建構單個樹需要相當長的時間(例如,在大型資料集上)時,仍然可以實現顯著的加速。

範例

LightGBM 對於重疊的組使用相同的邏輯。

[B2001]
  1. Breiman,“隨機森林”,機器學習,45(1),5-32,2001。

[B1998]
  1. Breiman,“Arcking 分類器”,統計年鑑 1998。

  • P. Geurts、D. Ernst 和 L. Wehenkel,“極端隨機樹”,機器學習,63(1),3-42,2006。

1.11.2.5. 特徵重要性評估#

在樹中用作決策節點的特徵的相對排名(即深度)可用於評估該特徵相對於目標變數可預測性的相對重要性。在樹頂使用的特徵有助於更大比例的輸入樣本的最終預測決策。因此,它們所貢獻的 **樣本的預期比例** 可以用作 **特徵相對重要性** 的估計。在 scikit-learn 中,特徵貢獻的樣本比例與分割它們所造成的雜質減少相結合,以產生對該特徵預測能力的標準化估計。

透過 **平均** 幾個隨機樹的預測能力估計,可以 **減少** 此類估計的 **變異數**,並將其用於特徵選擇。這稱為雜質的平均減少量(Mean decrease in impurity,MDI)。有關 MDI 和隨機森林的特徵重要性評估的更多資訊,請參閱 [L2014]

警告

在基於樹的模型上計算的基於雜質的特徵重要性存在兩個缺陷,可能會導致誤導性的結論。首先,它們是在從訓練資料集中導出的統計資料上計算的,因此 **不一定能告訴我們哪些特徵對於對保留資料集做出良好的預測最重要**。其次,**它們偏愛高基數特徵**,也就是具有許多唯一值的特徵。置換特徵重要性 是基於雜質的特徵重要性的替代方法,它沒有這些缺陷。在:置換重要性 vs 隨機森林特徵重要性 (MDI) 中探討了這兩種獲取特徵重要性的方法。

實際上,這些估計值以名稱為 feature_importances_ 的屬性儲存在已擬合模型中。這是一個形狀為 (n_features,) 的陣列,其值為正值,總和為 1.0。值越高,匹配的特徵對預測函數的貢獻就越大。

範例

LightGBM 對於重疊的組使用相同的邏輯。

[L2014]

G. Louppe,“理解隨機森林:從理論到實踐”,博士論文,列日大學,2014 年。

1.11.2.6. 完全隨機樹嵌入#

RandomTreesEmbedding 實作了資料的無監督轉換。使用完全隨機樹的森林,RandomTreesEmbedding 會透過資料點最終所在的葉的索引來編碼資料。然後以一對 K 的方式編碼此索引,從而產生高維、稀疏的二進位編碼。這種編碼可以非常有效地計算,然後可以用作其他學習任務的基礎。程式碼的大小和稀疏性可以透過選擇樹的數量和每棵樹的最大深度來影響。對於集成模型中的每棵樹,編碼包含一個 1 的條目。編碼的大小最多為 n_estimators * 2 ** max_depth,即森林中的最大葉子數。

由於相鄰的資料點更可能位於樹的同一個葉子內,因此轉換會執行隱式、非參數的密度估計。

範例

另請參閱

流形學習 技術也可用於推導特徵空間的非線性表示,此外,這些方法也側重於降維。

1.11.2.7. 擬合其他樹#

RandomForest、Extra-Trees 和 RandomTreesEmbedding 估計器都支援 warm_start=True,這允許您將更多樹新增到已擬合的模型。

>>> from sklearn.datasets import make_classification
>>> from sklearn.ensemble import RandomForestClassifier

>>> X, y = make_classification(n_samples=100, random_state=1)
>>> clf = RandomForestClassifier(n_estimators=10)
>>> clf = clf.fit(X, y)  # fit with 10 trees
>>> len(clf.estimators_)
10
>>> # set warm_start and increase num of estimators
>>> _ = clf.set_params(n_estimators=20, warm_start=True)
>>> _ = clf.fit(X, y) # fit additional 10 trees
>>> len(clf.estimators_)
20

當也設定了 random_state 時,內部隨機狀態也會在 fit 呼叫之間保留。這表示使用 n 個估計器訓練一次模型與透過多個 fit 呼叫以疊代方式建構模型相同,其中估計器的最終數量等於 n

>>> clf = RandomForestClassifier(n_estimators=20)  # set `n_estimators` to 10 + 10
>>> _ = clf.fit(X, y)  # fit `estimators_` will be the same as `clf` above

請注意,這與 random_state 的通常行為不同,因為它 *不會* 在不同的呼叫之間產生相同的結果。

1.11.3. 套袋元估計器#

在集成演算法中,袋裝法(bagging methods)是一類演算法,它會在原始訓練集的隨機子集上建立多個黑箱估計器實例,然後聚合它們各自的預測以形成最終預測。這些方法透過在其建構過程中引入隨機化,然後將其組合成一個集成模型,來減少基本估計器(例如決策樹)的變異數。在許多情況下,袋裝法構成了一種相對於單一模型進行改進的非常簡單的方法,而無需調整底層的基本演算法。由於它們提供了一種減少過擬合的方法,因此袋裝法最適用於強大且複雜的模型(例如,完全展開的決策樹),這與提升法(boosting methods)通常最適用於弱模型(例如,淺層決策樹)形成對比。

袋裝法有很多種變體,但它們之間的主要區別在於如何繪製訓練集的隨機子集。

  • 當資料集的隨機子集是以樣本的隨機子集的方式繪製時,則此演算法稱為「黏貼法」(Pasting)[B1999]

  • 當樣本是以替換方式繪製時,則該方法稱為「袋裝法」(Bagging)[B1996]

  • 當資料集的隨機子集是以特徵的隨機子集的方式繪製時,則該方法稱為「隨機子空間法」(Random Subspaces)[H1998]

  • 最後,當基本估計器建立在樣本和特徵的子集上時,則該方法稱為「隨機區塊法」(Random Patches)[LG2012]

在 scikit-learn 中,袋裝法以統一的 BaggingClassifier 元估計器(resp. BaggingRegressor)的形式提供,它將使用者指定的估計器以及指定繪製隨機子集策略的參數作為輸入。特別地,max_samplesmax_features 控制子集的大小(就樣本和特徵而言),而 bootstrapbootstrap_features 控制樣本和特徵是否以替換或不替換的方式繪製。當使用可用樣本的子集時,可以透過設定 oob_score=True,使用袋外(out-of-bag)樣本來估計泛化準確度。例如,下面的程式碼片段說明了如何實例化 KNeighborsClassifier 估計器的袋裝集成,每個估計器都建立在 50% 樣本和 50% 特徵的隨機子集上。

>>> from sklearn.ensemble import BaggingClassifier
>>> from sklearn.neighbors import KNeighborsClassifier
>>> bagging = BaggingClassifier(KNeighborsClassifier(),
...                             max_samples=0.5, max_features=0.5)

範例

LightGBM 對於重疊的組使用相同的邏輯。

[B1999]

L. Breiman, “Pasting small votes for classification in large databases and on-line”, Machine Learning, 36(1), 85-103, 1999.

[B1996]

L. Breiman, “Bagging predictors”, Machine Learning, 24(2), 123-140, 1996.

[H1998]

T. Ho, “The random subspace method for constructing decision forests”, Pattern Analysis and Machine Intelligence, 20(8), 832-844, 1998.

[LG2012]

G. Louppe and P. Geurts, “Ensembles on Random Patches”, Machine Learning and Knowledge Discovery in Databases, 346-361, 2012.

1.11.4. 投票分類器#

VotingClassifier 背後的概念是結合概念上不同的機器學習分類器,並使用多數投票或平均預測機率(軟投票)來預測類別標籤。這樣的分類器對於一組效能同樣良好的模型來說很有用,以便平衡它們各自的弱點。

1.11.4.1. 多數類別標籤(多數/硬投票)#

在多數投票中,特定樣本的預測類別標籤是代表每個個別分類器預測的類別標籤的多數(眾數)的類別標籤。

例如,如果給定樣本的預測是

  • 分類器 1 -> 類別 1

  • 分類器 2 -> 類別 1

  • 分類器 3 -> 類別 2

則 VotingClassifier(使用 voting='hard')將基於多數類別標籤將樣本分類為「類別 1」。

在平手的情況下,VotingClassifier 將根據遞增排序順序選擇類別。例如,在以下情況中

  • 分類器 1 -> 類別 2

  • 分類器 2 -> 類別 1

類別標籤 1 將被分配給樣本。

1.11.4.2. 用法#

以下範例顯示如何擬合多數規則分類器

>>> from sklearn import datasets
>>> from sklearn.model_selection import cross_val_score
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.naive_bayes import GaussianNB
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.ensemble import VotingClassifier

>>> iris = datasets.load_iris()
>>> X, y = iris.data[:, 1:3], iris.target

>>> clf1 = LogisticRegression(random_state=1)
>>> clf2 = RandomForestClassifier(n_estimators=50, random_state=1)
>>> clf3 = GaussianNB()

>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='hard')

>>> for clf, label in zip([clf1, clf2, clf3, eclf], ['Logistic Regression', 'Random Forest', 'naive Bayes', 'Ensemble']):
...     scores = cross_val_score(clf, X, y, scoring='accuracy', cv=5)
...     print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
Accuracy: 0.95 (+/- 0.04) [Logistic Regression]
Accuracy: 0.94 (+/- 0.04) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [naive Bayes]
Accuracy: 0.95 (+/- 0.04) [Ensemble]

1.11.4.3. 加權平均機率(軟投票)#

與多數投票(硬投票)相反,軟投票返回類別標籤作為預測機率總和的 argmax。

可以使用 weights 參數將特定的權重分配給每個分類器。當提供權重時,會收集每個分類器的預測類別機率,乘以分類器權重,然後求平均值。然後從具有最高平均機率的類別標籤中得出最終類別標籤。

為了用一個簡單的例子來說明,假設我們有 3 個分類器和一個 3 類分類問題,我們為所有分類器分配相等的權重:w1=1、w2=1、w3=1。

然後,樣本的加權平均機率將計算如下

分類器

類別 1

類別 2

類別 3

分類器 1

w1 * 0.2

w1 * 0.5

w1 * 0.3

分類器 2

w2 * 0.6

w2 * 0.3

w2 * 0.1

分類器 3

w3 * 0.3

w3 * 0.4

w3 * 0.3

加權平均值

0.37

0.4

0.23

在此,預測的類別標籤為 2,因為它具有最高的平均機率。

以下範例說明當基於線性支援向量機、決策樹和 K 近鄰分類器使用軟 VotingClassifier 時,決策區域可能會如何變化

>>> from sklearn import datasets
>>> from sklearn.tree import DecisionTreeClassifier
>>> from sklearn.neighbors import KNeighborsClassifier
>>> from sklearn.svm import SVC
>>> from itertools import product
>>> from sklearn.ensemble import VotingClassifier

>>> # Loading some example data
>>> iris = datasets.load_iris()
>>> X = iris.data[:, [0, 2]]
>>> y = iris.target

>>> # Training classifiers
>>> clf1 = DecisionTreeClassifier(max_depth=4)
>>> clf2 = KNeighborsClassifier(n_neighbors=7)
>>> clf3 = SVC(kernel='rbf', probability=True)
>>> eclf = VotingClassifier(estimators=[('dt', clf1), ('knn', clf2), ('svc', clf3)],
...                         voting='soft', weights=[2, 1, 2])

>>> clf1 = clf1.fit(X, y)
>>> clf2 = clf2.fit(X, y)
>>> clf3 = clf3.fit(X, y)
>>> eclf = eclf.fit(X, y)
../_images/sphx_glr_plot_voting_decision_regions_001.png

1.11.4.4. 用法#

為了基於預測的類別機率預測類別標籤(VotingClassifier 中的 scikit-learn 估計器必須支援 predict_proba 方法)

>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft'
... )

(可選)可以為個別分類器提供權重

>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft', weights=[2,5,1]
... )
VotingClassifierGridSearchCV 一起使用#

也可以將 VotingClassifierGridSearchCV 一起使用,以調整個別估計器的超參數

>>> from sklearn.model_selection import GridSearchCV
>>> clf1 = LogisticRegression(random_state=1)
>>> clf2 = RandomForestClassifier(random_state=1)
>>> clf3 = GaussianNB()
>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft'
... )

>>> params = {'lr__C': [1.0, 100.0], 'rf__n_estimators': [20, 200]}

>>> grid = GridSearchCV(estimator=eclf, param_grid=params, cv=5)
>>> grid = grid.fit(iris.data, iris.target)

1.11.5. 投票迴歸器#

VotingRegressor 背後的概念是結合概念上不同的機器學習迴歸器,並傳回平均預測值。這樣的迴歸器對於一組效能同樣良好的模型來說很有用,以便平衡它們各自的弱點。

1.11.5.1. 用法#

以下範例顯示如何擬合 VotingRegressor

>>> from sklearn.datasets import load_diabetes
>>> from sklearn.ensemble import GradientBoostingRegressor
>>> from sklearn.ensemble import RandomForestRegressor
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.ensemble import VotingRegressor

>>> # Loading some example data
>>> X, y = load_diabetes(return_X_y=True)

>>> # Training classifiers
>>> reg1 = GradientBoostingRegressor(random_state=1)
>>> reg2 = RandomForestRegressor(random_state=1)
>>> reg3 = LinearRegression()
>>> ereg = VotingRegressor(estimators=[('gb', reg1), ('rf', reg2), ('lr', reg3)])
>>> ereg = ereg.fit(X, y)
../_images/sphx_glr_plot_voting_regressor_001.png

範例

1.11.6. 堆疊泛化#

堆疊泛化是一種結合估計器以減少它們偏差的方法 [W1992] [HTF]。更精確地說,每個個別估計器的預測堆疊在一起,並用作最終估計器的輸入來計算預測。此最終估計器透過交叉驗證進行訓練。

StackingClassifierStackingRegressor 提供了可以應用於分類和迴歸問題的此類策略。

estimators 參數對應於在輸入數據上並行堆疊在一起的估計器列表。它應該以名稱和估計器的列表形式給出。

>>> from sklearn.linear_model import RidgeCV, LassoCV
>>> from sklearn.neighbors import KNeighborsRegressor
>>> estimators = [('ridge', RidgeCV()),
...               ('lasso', LassoCV(random_state=42)),
...               ('knr', KNeighborsRegressor(n_neighbors=20,
...                                           metric='euclidean'))]

final_estimator 將使用 estimators 的預測作為輸入。當使用 StackingClassifierStackingRegressor 時,它需要分別是一個分類器或迴歸器。

>>> from sklearn.ensemble import GradientBoostingRegressor
>>> from sklearn.ensemble import StackingRegressor
>>> final_estimator = GradientBoostingRegressor(
...     n_estimators=25, subsample=0.5, min_samples_leaf=25, max_features=1,
...     random_state=42)
>>> reg = StackingRegressor(
...     estimators=estimators,
...     final_estimator=final_estimator)

為了訓練 estimatorsfinal_estimator,需要在訓練數據上呼叫 fit 方法。

>>> from sklearn.datasets import load_diabetes
>>> X, y = load_diabetes(return_X_y=True)
>>> from sklearn.model_selection import train_test_split
>>> X_train, X_test, y_train, y_test = train_test_split(X, y,
...                                                     random_state=42)
>>> reg.fit(X_train, y_train)
StackingRegressor(...)

在訓練期間,estimators 會在整個訓練數據 X_train 上進行擬合。當呼叫 predictpredict_proba 時,將會使用它們。為了泛化並避免過度擬合,final_estimator 會使用 sklearn.model_selection.cross_val_predict 在內部對樣本外的數據進行訓練。

對於 StackingClassifier,請注意 estimators 的輸出由參數 stack_method 控制,並由每個估計器呼叫。此參數可以是字串,為估計器的方法名稱,或者為 'auto',它會根據可用性自動識別可用的方法,並按以下優先順序進行測試:predict_probadecision_functionpredict

StackingRegressorStackingClassifier 可以像任何其他迴歸器或分類器一樣使用,公開 predictpredict_probadecision_function 方法,例如:

>>> y_pred = reg.predict(X_test)
>>> from sklearn.metrics import r2_score
>>> print('R2 score: {:.2f}'.format(r2_score(y_test, y_pred)))
R2 score: 0.53

請注意,也可以使用 transform 方法取得堆疊的 estimators 的輸出。

>>> reg.transform(X_test[:5])
array([[142..., 138..., 146...],
       [179..., 182..., 151...],
       [139..., 132..., 158...],
       [286..., 292..., 225...],
       [126..., 124..., 164...]])

實際上,堆疊預測器的預測效果與基礎層的最佳預測器一樣好,甚至有時可以透過結合這些預測器的不同優勢來超越它。然而,訓練堆疊預測器在計算上很耗費資源。

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

對於 StackingClassifier,當使用 stack_method_='predict_proba' 時,如果問題是二元分類問題,則會刪除第一欄。實際上,每個估計器預測的兩個機率欄是完全共線的。

\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2')\]

可以透過將 final_estimator 指定為 StackingClassifierStackingRegressor 來實現多個堆疊層。

>>> final_layer_rfr = RandomForestRegressor(
...     n_estimators=10, max_features=1, max_leaf_nodes=5,random_state=42)
>>> final_layer_gbr = GradientBoostingRegressor(
...     n_estimators=10, max_features=1, max_leaf_nodes=5,random_state=42)
>>> final_layer = StackingRegressor(
...     estimators=[('rf', final_layer_rfr),
...                 ('gbrt', final_layer_gbr)],
...     final_estimator=RidgeCV()
...     )
>>> multi_layer_regressor = StackingRegressor(
...     estimators=[('ridge', RidgeCV()),
...                 ('lasso', LassoCV(random_state=42)),
...                 ('knr', KNeighborsRegressor(n_neighbors=20,
...                                             metric='euclidean'))],
...     final_estimator=final_layer
... )
>>> multi_layer_regressor.fit(X_train, y_train)
StackingRegressor(...)
>>> print('R2 score: {:.2f}'
...       .format(multi_layer_regressor.score(X_test, y_test)))
R2 score: 0.53

LightGBM 對於重疊的組使用相同的邏輯。

[W1992]

Wolpert, David H. “Stacked generalization.” Neural networks 5.2 (1992): 241-259.

1.11.7. AdaBoost#

模組 sklearn.ensemble 包含廣受歡迎的 boosting 演算法 AdaBoost,該演算法於 1995 年由 Freund 和 Schapire 提出 [FS1995]

AdaBoost 的核心原則是在反覆修改的數據版本上擬合一系列弱學習器(即,僅比隨機猜測稍好一點的模型,例如小的決策樹)。然後,將所有這些學習器的預測透過加權多數投票(或總和)組合起來,以產生最終的預測。在每個所謂的 boosting 迭代中的數據修改包括將權重 \(w_1\)\(w_2\)、…、\(w_N\) 應用於每個訓練樣本。最初,這些權重都設定為 \(w_i = 1/N\),因此第一步只是在原始數據上訓練一個弱學習器。對於每個後續迭代,樣本權重會被單獨修改,並且學習演算法會重新應用於重新加權的數據。在給定的步驟中,被前一步驟引導的 boosted 模型錯誤預測的訓練範例的權重會增加,而正確預測的訓練範例的權重會減少。隨著迭代的進行,難以預測的範例會獲得越來越大的影響。因此,每個後續的弱學習器都會被迫專注於序列中先前學習器遺漏的範例 [HTF]

../_images/sphx_glr_plot_adaboost_multiclass_001.png

AdaBoost 可用於分類和迴歸問題。

1.11.7.1. 使用方式#

以下範例說明如何使用 100 個弱學習器來擬合 AdaBoost 分類器。

>>> from sklearn.model_selection import cross_val_score
>>> from sklearn.datasets import load_iris
>>> from sklearn.ensemble import AdaBoostClassifier

>>> X, y = load_iris(return_X_y=True)
>>> clf = AdaBoostClassifier(n_estimators=100)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.9...

弱學習器的數量由參數 n_estimators 控制。learning_rate 參數控制弱學習器在最終組合中的貢獻。預設情況下,弱學習器是決策樹樁。可以透過 estimator 參數指定不同的弱學習器。為了獲得良好結果,需要調整的主要參數是 n_estimators 和基礎估計器的複雜度(例如,其深度 max_depth 或分割時需要考慮的最小樣本數 min_samples_split)。

範例

LightGBM 對於重疊的組使用相同的邏輯。

[FS1995]

Y. Freund, and R. Schapire, “A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting”, 1997.

[ZZRH2009]
  1. Zhu, H. Zou, S. Rosset, T. Hastie. “Multi-class AdaBoost”, 2009.

[D1997]
  1. Drucker. “Improving Regressors using Boosting Techniques”, 1997.

[HTF] (1,2,3)

T. Hastie, R. Tibshirani and J. Friedman, “Elements of Statistical Learning Ed. 2”, Springer, 2009.