---
# System prepended metadata

title: 機器學習 05：整體學習
tags: [Ensemble learning, ML model]

---

# 機器學習 05：整體學習
###### tags: `ML model`, `Ensemble learning`
## 硬投票分類器

建立不同模型，彙總這些模型的**預測結果**進行多數決投票。此用方法必須讓各個模型盡可能獨立，且產生的錯誤都是不相關的才會有最佳效果。

以 sklearn 實現：

```python=
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

log_clf = LogisticRegression(solver="lbfgs", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
svm_clf = SVC(gamma="scale", random_state=42)

# 硬投票須將 voting 設為 hard
voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='hard')
voting_clf.fit(X_train, y_train)
```

## 軟投票分類器

軟投票則會計算各模型的**機率平均**，須確保所有分類器都可以估計類別機率，如 SVC 把超參數 probability 設為 True 讓它估計機率。

---
## Bagging \ Pasting

取得多樣化分類器除了使用不同模型，還可以用同一種模型、不同子資料集合訓練，**如果取完之後會被放回去（replacement），這種作法稱為 Bagging，反之若不會放回去則稱為 Pasting**。

每一個預測器的偏差都比用原始資料訓練高，但彙總後就可以降低偏差及變異度。

偏差：bagging $\geq$ pasting
變異度：bagging $<$ pasting

以 sklearn 實現：
```python=
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

# Bagging：bootstrap = True
# Pasting：bootstrap = False
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), n_estimators=500,
    max_samples=100, bootstrap=True, random_state=42)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
```

### <font color="#f00">**Out of bag**</font>

使用 Bagging 時大約只會抽出 63% 的實例訓練，剩下未抽出的 37% 被稱為 **out-of-bag（oob）** 實例。

$$(1- \frac{1}{N})^N = \frac{1}{(\frac{N}{N-1}\space)^N} = \frac{1}{(1 +\frac{1}{N-1}\space)^N} = \frac{1}{e} = 37\%$$

有研究指出 out-of-bag 估計和使用與訓練集大小一致的測試集所得到的錯誤率一樣精確，所以使用out-of-bag error 估計可以不用另外建立一個測試集。

在 sklearn 使用 BaggingClassifier 時可以設定 oob_score = True 在訓練後自動進行 oob 評估：

```python=
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(random_state=42), n_estimators=500,
    bootstrap=True, oob_score=True, random_state=40)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
```

### 隨機子空間與隨機補丁

隨機子空間（Random Suspace）：從訓練集中隨機抽取選定數量的特徵進行替換稱為隨機子空間

隨機補丁（Random Patches）：當隨機子空間和 Bagging 或 Pasting 一起使用時稱為隨機補丁（隨機抽特徵和資料）

---
## 隨機森林

Bagging + DecisionTree = Random forest

透過隨機抽取特徵及資料建立多棵樹，最後將每棵樹的結果進行投票。

![](https://i.imgur.com/VnO8Auk.png)

以 sklearn 實現：

```python=
from sklearn.ensemble import RandomForestClassifier

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, random_state=42)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)
```

---
## Boosting

將一堆弱學習器組合起來變強學習器的方法。

- ### AdaBoost

    讓新的預測器修正它上一個的預測器。演算法會訓練一個基本分類器（如決策樹），再讓他預測訓練組，接著演算法會提升被分類錯誤的訓練實例權重，然後再使用更新過的權重訓練第二個分類器，再次預測訓練組，再更新權重，以此類推。最後根據每個預測器做加權平均投票，準確度高的弱分類器有較高的權重。

    ![](https://i.imgur.com/ahASmiA.png)

    AdaBoost 最大的缺點為無法平行化，因為每個預測器只能在上一個預測器訓練好且評估完後才可訓練。

    sklearn 使用多類別的 AdaBoost 版本，稱為 **SAMME**，用預測器預測結果投票；如果預測器可以估計類別機率（如 predict_proba()），sklearn 可以使用 **SAMME.R**，以機率預測投票，通常有更好的表現。
    
    ```python=
    from sklearn.ensemble import AdaBoostClassifier

    # max_depth = 1 弱預測器
    ada_clf = AdaBoostClassifier(
        DecisionTreeClassifier(max_depth=1), n_estimators=200,
        algorithm="SAMME.R", learning_rate=0.5, random_state=42)
    ada_clf.fit(X_train, y_train)
    ```
- ### 梯度增強（GRBT, Gradient Boosting）

    試著讓**新預測器擬合上一個預測器的殘差（residual error）**。

    舉例來說 A、B、C、D 四人，他們的年齡分別是 14、16、24、26。如果是用一棵傳統的回歸決策樹來訓練，會得到如下圖1所示結果：

    ![](https://i.imgur.com/5GR1qvK.png)

    這是一棵普通的回歸樹，我們通過年齡平均值將少年和青年分開，再用上網時長將每個分支繼續細分到不能分割或者達到要求為止。接下來看 GBDT 實現：

    ![](https://i.imgur.com/tZO1yzX.png)

    在第一棵樹分枝和上圖一樣，由於 A、B 年齡較為相近，C、D年齡較為相近，他們被分為兩群，每群用平均年齡作為預測值。此時得到 A、B、C、D 的殘差分別為 -1、1、-1、1。接著我們拿殘差替代 A、B、C、D 的原值，到第二棵樹去學習，如果我們的預測值和它們的殘差相等，則只需把第二棵樹的結論累加到第一棵樹上就能得到真實年齡了。此時所有人的殘差都是0，即每個人都得到了真實的預測值。

    - 14歲高一學生；購物較少，經常問學長問題；預測年齡 A = 15 – 1 = 14
    - 16歲高三學生；購物較少，經常被學弟問問題；預測年齡 B = 15 + 1 = 16
    - 24歲應屆畢業生；購物較多，經常問師兄問題；預測年齡 C = 25 – 1 = 24
    - 26歲工作兩年員工；購物較多，經常被師弟問問題；預測年齡 D = 25 + 1 = 26

    **註：無論 loss function 為何，殘差向量（-1, 1, -1, 1）都是全局最優方向，也就是 Gradient**


    以 sklearn 實現：

    ```python=
    from sklearn.tree import DecisionTreeRegressor
    # 先訓練一個決策樹
    tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
    tree_reg1.fit(X, y)

    # 用第一個預測器的殘差當作目標
    y2 = y - tree_reg1.predict(X)
    tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
    tree_reg2.fit(X, y2)

    # 用第二個預測器的殘差當作目標
    y3 = y2 - tree_reg2.predict(X)
    tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
    tree_reg3.fit(X, y3)

    # 加總三個預測器就是預測值
    y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
    ```

    更簡單的做法：
    ```python=
    from sklearn.ensemble import GradientBoostingRegressor

    gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
    gbrt.fit(X, y)
    ```
---
## XGBoost

建很多樹去擬合上一棵樹的殘差，跟梯度提升法只差在loss function 有加正規化項。
    
## LGBM

跟XGBoost相比，他的樹是依照葉節點的方式分割，他會先看哪個節點亂度最高而分割。

---
## Stacking

與其用簡單的函數（如硬投票）來彙總整體的預測器，不如訓練一個模型來彙總。

首先將資料分兩組，將 A 組分別用三個模型建模，再以 B 組當作測試集預測，預測出來的三個值當作該資料的特徵，並以這些特徵訓練一個最終模型。



---
## reference
1. 精通機器學習，使用 Scikit-Learn, Keras 與 Tensorflow－Aurelien Greon

2. [Random patches and random subspaces.](https://machinelearningjourney.com/index.php/2020/03/24/random-patches-and-random-subspaces/)
3. [機器學習: Ensemble learning之Bagging、Boosting和AdaBoost](https://machinelearningjourney.com/index.php/2020/03/24/random-patches-and-random-subspaces/)

4. [GBDT算法](https://zhuanlan.zhihu.com/p/39354380)