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