機器學習實戰_集成學習(二)

投票分類

像咱們以前討論的同樣,咱們會在一個項目快結束的時候使用集成算法,一旦你創建了一些好的分類器,就把他們合併爲一個更好的分類器。事實上,在機器學習競賽中得到勝利的算法常常會包含一些集成方法。node

接下來的代碼建立和訓練了在 sklearn 中的投票分類器。這個分類器由三個不一樣的分類器組成(訓練集是第五章中的 moons 數據集):git

>>> from sklearn.ensemble import RandomForestClassifier 
>>> from sklearn.ensemble import VotingClassifier 
>>> from sklearn.linear_model import LogisticRegression 
>>> from sklearn.svm import SVC
>>> log_clf = LogisticRegression() 
>>> rnd_clf = RandomForestClassifier() 
>>> svm_clf = SVC()
>>> voting_clf = VotingClassifier(estimators=[('lr', log_clf), ('rf', rnd_clf), >>> ('svc', svm_clf)],voting='hard') 
>>> voting_clf.fit(X_train, y_train)

讓咱們看一下在測試集上的準確率:github

>>> from sklearn.metrics import accuracy_score 
>>> for clf in (log_clf, rnd_clf, svm_clf, voting_clf): 
>>>     clf.fit(X_train, y_train) 
>>>     y_pred = clf.predict(X_test) 
>>>     print(clf.__class__.__name__, accuracy_score(y_test, y_pred)) 
LogisticRegression 0.864 
RandomForestClassifier 0.872 
SVC 0.888 
VotingClassifier 0.896

你看!投票分類器比其餘單獨的分類器表現的都要好。算法

若是全部的分類器都可以預測類別的機率(例如他們有一個predict_proba()方法),那麼你就可讓 sklearn 以最高的類機率來預測這個類,平均在全部的分類器上。這種方式叫作軟投票他常常比硬投票表現的更好,由於它給予高自信的投票更大的權重。你能夠經過把voting="hard"設置爲voting="soft"來保證分類器能夠預測類別機率。然而這不是 SVC 類的分類器默認的選項,因此你須要把它的probability hyperparameter設置爲True這會使 SVC 使用交叉驗證去預測類別機率,其下降了訓練速度,但會添加predict_proba()方法)。若是你修改了以前的代碼去使用軟投票,你會發現投票分類器正確率高達 91%bootstrap

Bagging 和 Pasting

就像以前講到的,能夠經過使用不一樣的訓練算法去獲得一些不一樣的分類器。另外一種方法就是對每個分類器都使用相同的訓練算法,可是在不一樣的訓練集上去訓練它們。有放回採樣被稱爲裝袋(Bagging,是 bootstrap aggregating 的縮寫)。無放回採樣稱爲粘貼(pasting)dom

換句話說,Bagging 和 Pasting 都容許在多個分類器上對訓練集進行屢次採樣,但只有 Bagging 容許對同一種分類器上對訓練集進行進行屢次採樣。機器學習

當全部的分類器被訓練後,集成能夠經過對全部分類器結果的簡單聚合來對新的實例進行預測。聚合函數一般對分類是統計模式(例如硬投票分類器)或者對迴歸是平均每個單獨的分類器在若是在原始訓練集上都是高誤差,可是聚合下降了誤差和方差。一般狀況下,集成的結果是有一個類似的誤差,可是對比與在原始訓練集上的單一分類器來說有更小的方差函數

在 sklearn 中的 Bagging 和 Pasting

sklearn 爲 Bagging 和 Pasting 提供了一個簡單的API:BaggingClassifier類(或者對於迴歸能夠是BaggingRegressor。接下來的代碼訓練了一個 500 個決策樹分類器的集成,每個都是在數據集上有放回採樣 100 個訓練實例下進行訓練(這是 Bagging 的例子,若是你想嘗試 Pasting,就設置bootstrap=False)。n_jobs參數告訴 sklearn 用於訓練和預測所須要 CPU 核的數量。(-1 表明着 sklearn 會使用全部空閒核):學習

>>>from sklearn.ensemble import BaggingClassifier 
>>>from sklearn.tree import DecisionTreeClassifier
>>>bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,        >>>max_samples=100, bootstrap=True, n_jobs=-1) 
>>>bag_clf.fit(X_train, y_train) 
>>>y_pred = bag_clf.predict(X_test)

若是基分類器能夠預測類別機率(例如它擁有predict_proba()方法),那麼BaggingClassifier會自動的運行軟投票,這是決策樹分類器的狀況。測試

下圖對比了單一決策樹的決策邊界和 Bagging 集成 500 個樹的決策邊界,二者都在 moons 數據集上訓練。正如你所看到的,集成的分類比起單一決策樹的分類產生狀況更好:集成有一個可比較的誤差可是有一個較小的方差(它在訓練集上的錯誤數目大體相同,但決策邊界較不規則)。

clipboard.png

Bootstrap 在每一個預測器被訓練的子集中引入了更多的分集,因此 Bagging 結束時的誤差比 Pasting 更高,但這也意味着預測因子最終變得不相關,從而減小了集合的方差。整體而言,Bagging 一般會致使更好的模型,這就解釋了爲何它一般是首選的。然而,若是你有空閒時間和 CPU 功率,可使用交叉驗證來評估 Bagging 和 Pasting 哪個更好。

Out-of-Bag 評價

對於 Bagging 來講,一些實例可能被一些分類器重複採樣,但其餘的有可能不會被採樣。BaggingClassifier默認採樣。BaggingClassifier默認是有放回的採樣m個實例 (bootstrap=True),其中m是訓練集的大小,這意味着平均下來只有63%的訓練實例被每一個分類器採樣,剩下的37%個沒有被採樣的訓練實例就叫作 Out-of-Bag 實例。注意對於每個的分類器它們的 37% 不是相同的

由於在訓練中分類器歷來沒有看到過 oob 實例,因此它能夠在這些實例上進行評估,而不須要單獨的驗證集或交叉驗證。你能夠拿出每個分類器的 oob 來評估集成自己。

在 sklearn 中,你能夠在訓練後須要建立一個BaggingClassifier來自動評估時設置oob_score=True來自動評估。接下來的代碼展現了這個操做。評估結果經過變量oob_score_來顯示:

>>> bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,bootstrap=True, n_jobs=-1, oob_score=True)
>>> bag_clf.fit(X_train, y_train) 
>>> bag_clf.oob_score_ 
0.93066666666666664

根據這個 obb 評估,BaggingClassifier能夠再測試集上達到93.1%的準確率,讓咱們修改一下:

>>> from sklearn.metrics import accuracy_score 
>>> y_pred = bag_clf.predict(X_test) 
>>> accuracy_score(y_test, y_pred) 
0.93600000000000005

咱們在測試集上獲得了 93.6% 的準確率,足夠接近了!

對於每一個訓練實例 oob 決策函數也可經過oob_decision_function_變量來展現。在這種狀況下(當基決策器有predict_proba()時)決策函數會對每一個訓練實例返回類別機率。例如,oob 評估預測第二個訓練實例有 60.6% 的機率屬於正類(39.4% 屬於負類):

>>> bag_clf.oob_decision_function_ 
array([[ 0.,  1.], [ 0.60588235,  0.39411765],[ 1., 0. ], 
...  [ 1. ,  0. ],[ 0.,  1.],[ 0.48958333,  0.51041667]])

隨機貼片與隨機子空間

BaggingClassifier也支持採樣特徵。它被兩個超參數max_features和bootstrap_features控制。他們的工做方式和max_samples和bootstrap同樣,但這是對於特徵採樣而不是實例採樣。所以,每個分類器都會被在隨機的輸入特徵內進行訓練。

當你在處理高維度輸入下(例如圖片)此方法尤爲有效。對訓練實例和特徵的採樣被叫作隨機貼片。保留了全部的訓練實例(例如bootstrap=False和max_samples=1.0),可是對特徵採樣(bootstrap_features=True而且/或者max_features小於 1.0)叫作隨機子空間。

採樣特徵致使更多的預測多樣性,用高誤差換低方差

隨機森林

正如咱們所討論的,隨機森林是決策樹的一種集成,一般是經過 bagging 方法(有時是 pasting 方法)進行訓練,一般用max_samples設置爲訓練集的大小。與創建一個BaggingClassifier而後把它放入 DecisionTreeClassifier 相反,你可使用更方便的也是對決策樹優化夠的RandomForestClassifier(對於迴歸是RandomForestRegressor)。接下來的代碼訓練了帶有 500 個樹(每一個被限制爲 16 葉子結點)的決策森林,使用全部空閒的 CPU 核:

>>>from sklearn.ensemble import RandomForestClassifier
>>>rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1) 
>>>rnd_clf.fit(X_train, y_train)
>>>y_pred_rf = rnd_clf.predict(X_test)

除了一些例外,RandomForestClassifier使用DecisionTreeClassifier的全部超參數(決定數怎麼生長),把BaggingClassifier的超參數加起來來控制集成自己

隨機森林算法在樹生長時引入了額外的隨機;與在節點分裂時須要找到最好分裂特徵相反,它在一個隨機的特徵集中找最好的特徵。它致使了樹的差別性,而且再一次用高誤差換低方差,總的來講是一個更好的模型。如下是BaggingClassifier大體至關於以前的randomforestclassifier:

>>>bag_clf = BaggingClassifier(DecisionTreeClassifier(splitter="random", max_leaf_nodes=16),n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1)

極端隨機樹

當你在隨機森林上生長樹時,在每一個結點分裂時只考慮隨機特徵集上的特徵(正如以前討論過的同樣)。相比於找到更好的特徵咱們能夠經過使用對特徵使用隨機閾值使樹更加隨機(像規則決策樹同樣)。

這種極端隨機的樹被簡稱爲 Extremely Randomized Trees(極端隨機樹),或者更簡單的稱爲 Extra-Tree。再一次用高誤差換低方差。它還使得 Extra-Tree 比規則的隨機森林更快地訓練,由於在每一個節點上找到每一個特徵的最佳閾值是生長樹最耗時的任務之一。

你可使用 sklearn 的ExtraTreesClassifier來建立一個 Extra-Tree 分類器。他的 API 跟RandomForestClassifier是相同的,類似的, ExtraTreesRegressor 跟RandomForestRegressor也是相同的 API。

咱們很難去分辨ExtraTreesClassifier和RandomForestClassifier到底哪一個更好。一般狀況下是經過交叉驗證來比較它們(使用網格搜索調整超參數)

特徵重要度

最後,若是你觀察一個單一決策樹,重要的特徵會出如今更靠近根部的位置,而不重要的特徵會常常出如今靠近葉子的位置。所以咱們能夠經過計算一個特徵在森林的所有樹中出現的平均深度來預測特徵的重要性。sklearn 在訓練後會自動計算每一個特徵的重要度。你能夠經過feature_importances_變量來查看結果。例如以下代碼在 iris 數據集(第四章介紹)上訓練了一個RandomForestClassifier模型,而後輸出了每一個特徵的重要性。看來,最重要的特徵是花瓣長度(44%)和寬度(42%),而萼片長度和寬度相對比較是不重要的(分別爲 11% 和 2%):

>>> from sklearn.datasets import load_iris 
>>> iris = load_iris() 
>>> rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1) 
>>> rnd_clf.fit(iris["data"], iris["target"]) 
>>> for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_): 
>>>     print(name, score) 
sepal length (cm) 0.112492250999
sepal width (cm) 0.0231192882825 
petal length (cm) 0.441030464364 
petal width (cm) 0.423357996355

類似的,若是你在 MNIST 數據及上訓練隨機森林分類器(在第三章上介紹),而後畫出每一個像素的重要性,你能夠獲得下圖

clipboard.png

隨機森林能夠很是方便快速得了解哪些特徵其實是重要的,特別是你須要進行特徵選擇的時候

提高

下圖顯示連續五次預測的 moons 數據集的決策邊界(在本例中,每個分類器都是高度正則化帶有 RBF 核的 SVM)。第一個分類器誤分類了不少實例,因此它們的權重被提高了。第二個分類器所以對這些誤分類的實例分類效果更好,以此類推。右邊的圖表明瞭除了學習率減半外(誤分類實例權重每次迭代上升一半)相同的預測序列(誤分類的樣本權重提高速率即學習率)。你能夠看出,序列學習技術與梯度降低很類似,除了調整單個預測因子的參數以最小化代價函數以外,AdaBoost 增長了集合的預測器,逐漸使其更好。

clipboard.png

klearn 一般使用 Adaboost 的多分類版本 SAMME(這就表明了 分段加建模使用多類指數損失函數)。若是隻有兩類別,那麼 SAMME 是與 Adaboost 相同的。若是分類器能夠預測類別機率(例如若是它們有predict_proba()),若是 sklearn 可使用 SAMME 叫作SAMME.R的變量(R 表明「REAL」),這種依賴於類別機率的一般比依賴於分類器的更好。

接下來的代碼訓練了使用 sklearn 的AdaBoostClassifier基於 200 個決策樹樁 Adaboost 分類器(正如你說期待的,對於迴歸也有AdaBoostRegressor)。一個決策樹樁是max_depth=1的決策樹-換句話說,是一個單一的決策節點加上兩個葉子結點。這就是AdaBoostClassifier的默認基分類器

>>>from sklearn.ensemble import AdaBoostClassifier
>>>ada_clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=1), n_estimators=200,algorithm="SAMME.R", learning_rate=0.5) 
>>>ada_clf.fit(X_train, y_train)

若是你的 Adaboost 集成過擬合了訓練集,你能夠嘗試減小基分類器的數量或者對基分類器使用更強的正則化。

梯度提高

另外一個很是著名的提高算法是梯度提高。與 Adaboost 同樣,梯度提高也是經過向集成中逐步增長分類器運行的,每個分類器都修正以前的分類結果。然而,它並不像 Adaboost 那樣每一次迭代都更改實例的權重,這個方法是去使用新的分類器去擬合前面分類器預測的殘差 。

讓咱們經過一個使用決策樹當作基分類器的簡單的迴歸例子(迴歸固然也可使用梯度提高)。這被叫作梯度提高迴歸樹(GBRT,Gradient Tree Boosting 或者 Gradient Boosted Regression Trees)。首先咱們用DecisionTreeRegressor去擬合訓練集(例如一個有噪二次訓練集):

>>>from sklearn.tree import DecisionTreeRegressor 
>>>tree_reg1 = DecisionTreeRegressor(max_depth=2) 
>>>tree_reg1.fit(X, y)

如今在第一個分類器的殘差上訓練第二個分類器:

>>>y2 = y - tree_reg1.predict(X) 
>>>tree_reg2 = DecisionTreeRegressor(max_depth=2) 
>>>tree_reg2.fit(X, y2)

隨後在第二個分類器的殘差上訓練第三個分類器:

>>>y3 = y2 - tree_reg1.predict(X) 
>>>tree_reg3 = DecisionTreeRegressor(max_depth=2) 
>>>tree_reg3.fit(X, y3)

如今咱們有了一個包含三個迴歸器的集成。它能夠經過集成全部樹的預測來在一個新的實例上進行預測。

>>>y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))

下圖左欄展現了這三個樹的預測,在右欄展現了集成的預測。在第一行,集成只有一個樹,因此它與第一個樹的預測類似。在第二行,一個新的樹在第一個樹的殘差上進行訓練。在右邊欄能夠看出集成的預測等於前兩個樹預測的和。相同的,在第三行另外一個樹在第二個數的殘差上訓練。你能夠看到集成的預測會變的更好。

clipboard.png

咱們可使用 sklean 中的GradientBoostingRegressor來訓練 GBRT 集成。與RandomForestClassifier類似,它也有超參數去控制決策樹的生長(例如max_depth,min_samples_leaf等等),也有超參數去控制集成訓練,例如基分類器的數量(n_estimators)。接下來的代碼建立了與以前相同的集成:

>>>from sklearn.ensemble import GradientBoostingRegressor
>>>gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0) 
>>>gbrt.fit(X, y)

超參數learning_rate 確立了每一個樹的貢獻。若是你把它設置爲一個很小的樹,例如 0.1,在集成中就須要更多的樹去擬合訓練集,但預測一般會更好。這個正則化技術叫作 shrinkage。下圖 展現了兩個在低學習率上訓練的 GBRT 集成:其中左面是一個沒有足夠樹去擬合訓練集的樹,右面是有過多的樹過擬合訓練集的樹。

clipboard.png

爲了找到樹的最優數量,你可使用早停技術。最簡單使用這個技術的方法就是使用staged_predict():它在訓練的每一個階段(用一棵樹,兩棵樹等)返回一個迭代器。加下來的代碼用 120 個樹訓練了一個 GBRT 集成,而後在訓練的每一個階段驗證錯誤以找到樹的最佳數量,最後使用 GBRT 樹的最優數量訓練另外一個集成

>>>import numpy as np 
>>>from sklearn.model_selection import train_test_split
>>>from sklearn.metrics import mean_squared_error

>>>X_train, X_val, y_train, y_val = train_test_split(X, y)
>>>gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120) 
>>>gbrt.fit(X_train, y_train)
>>>errors = [mean_squared_error(y_val, y_pred)         
     for y_pred in gbrt.staged_predict(X_val)] 
>>>bst_n_estimators = np.argmin(errors)
>>>gbrt_best = GradientBoostingRegressor(max_depth=2,n_estimators=bst_n_estimators) 
>>>gbrt_best.fit(X_train, y_train)

驗證錯誤在圖的左面展現,最優模型預測被展現在右面

clipboard.png

你也能夠早早的中止訓練來實現早停(與先在一大堆樹中訓練,而後再回頭去找最優數目相反)。你能夠經過設置warm_start=True來實現 ,這使得當fit()方法被調用時 sklearn 保留現有樹,並容許增量訓練。接下來的代碼在當一行中的五次迭代驗證錯誤沒有改善時會中止訓練:

>>>gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True)
min_val_error = float("inf") 
error_going_up = 0 
for n_estimators in range(1, 120):    
    gbrt.n_estimators = n_estimators    
    gbrt.fit(X_train, y_train)    
    y_pred = gbrt.predict(X_val)    
    val_error = mean_squared_error(y_val, y_pred)    
    if val_error < min_val_error:        
        min_val_error = val_error        
        error_going_up = 0    
    else:        
        error_going_up += 1        
        if error_going_up == 5:            
            break  # early stopping

GradientBoostingRegressor也支持指定用於訓練每棵樹的訓練實例比例的超參數subsample。例如若是subsample=0.25,那麼每一個樹都會在 25% 隨機選擇的訓練實例上訓練。你如今也能猜出來,這也是個高誤差換低方差的做用。它一樣也加速了訓練。這個技術叫作隨機梯度提高。

也可能對其餘損失函數使用梯度提高。這是由損失超參數控制(見 sklearn 文檔)。

Stacking

本章討論的最後一個集成方法叫作 Stacking(stacked generalization 的縮寫)。這個算法基於一個簡單的想法:不使用瑣碎的函數(如硬投票)來聚合集合中全部分類器的預測,咱們爲何不訓練一個模型來執行這個聚合?圖 展現了這樣一個在新的迴歸實例上預測的集成。底部三個分類器每個都有不一樣的值(3.1,2.7 和 2.9),而後最後一個分類器(叫作 blender 或者 meta learner )把這三個分類器的結果當作輸入而後作出最終決策(3.0)

clipboard.png

爲了訓練這個 blender ,一個通用的方法是採用保持集。讓咱們看看它怎麼工做。首先,訓練集被分爲兩個子集,第一個子集被用做訓練第一層

clipboard.png

接下來,第一層的分類器被用來預測第二個子集(保持集)。這確保了預測結果很「乾淨」,由於這些分類器在訓練的時候沒有使用過這些事例。如今對在保持集中的每個實例都有三個預測值。咱們如今可使用這些預測結果做爲輸入特徵來建立一個新的訓練集(這使得這個訓練集是三維的),而且保持目標數值不變。隨後 blender 在這個新的訓練集上訓練,所以,它學會了預測第一層預測的目標值。

clipboard.png

顯然咱們能夠用這種方法訓練不一樣的 blender (例如一個線性迴歸,另外一個是隨機森林等等):咱們獲得了一層 blender 。訣竅是將訓練集分紅三個子集:第一個子集用來訓練第一層,第二個子集用來建立訓練第二層的訓練集(使用第一層分類器的預測值),第三個子集被用來建立訓練第三層的訓練集(使用第二層分類器的預測值)。以上步驟作完了,咱們能夠經過逐個遍歷每一個層來預測一個新的實例

clipboard.png

然而不幸的是,sklearn 並不直接支持 stacking ,可是你本身組建是很容易的(看接下來的練習)。或者你也可使用開源的項目例如 brew (網址爲 https://github.com/viisar/brew

相關文章
相關標籤/搜索