# 機器學習 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)