每一個Kaggle冠軍的獲勝法門:揭祕Python中的模型集成

選自Dataquest
算法

做者:Sebastian Flennerhag編程

機器之心編譯bootstrap

集成方法可將多種機器學習模型的預測結果結合在一塊兒,得到單個模型沒法匹敵的精確結果,它已成爲幾乎全部 Kaggle 競賽冠軍的必選方案。那麼,咱們該如何使用 Python 集成各種模型呢?本文做者,曼徹斯特大學計算機科學與社會統計學院的在讀博士 Sebastian Flennerhag 對此進行了一番簡述。


在 Python 中高效堆疊模型數組

集成(ensemble)正在迅速成爲應用機器學習最熱門和流行的方法。目前,幾乎每個 Kaggle 冠軍的解決方案都使用了集成,不少數據科學 pipeline 也使用集成。bash

簡單來講,集成把不一樣模型的預測結果結合起來,生成最終預測,集成的模型越多,效果就越好。另外,因爲集成結合了不一樣的基線預測,它們的性能至少等同於最優的基線模型。集成使得咱們幾乎免費就得到了性能提高!網絡

集成圖示。輸入數組 X 經過兩個預處理 pipeline 輸入至多個基學習器 f(i)。集成將全部的基學習器的預測結果結合起來,導出最終的預測數組 P。(圖片來源:ml-ensemble.com/app


本文介紹集成的基礎知識:定義及工做原理,並提供構建基礎集成的實踐教程。閱讀本文,你將:dom

  • 理解集成的基礎知識;
  • 瞭解如何編寫集成;
  • 理解集成的主要優缺點。


預測共和黨和民主黨的捐款機器學習

在本文中,咱們將使用美國政治捐款數據集來解釋集成的工做原理。該數據集最初由 FiveThirtyEight 的 Ben Wieder 製做,他查閱美國政府的政治捐款記錄,發現科學家向政治家們捐款時,一般會選擇民主黨。編程語言

該論斷基於向共和黨和民主黨捐款數額的比例。可是,咱們還能夠從中看出更多事情:好比,哪一個學科最可能捐款給共和黨,哪一個州最可能捐款給民主黨。咱們將更進一步,預測捐款更可能的流向。

此處使用的數據通過稍微修改(www.dataquest.io/blog/large_…)。咱們刪除了捐款給共和黨和民主黨以外的其餘黨派的捐款信息,以使過程更加清晰,而且還去除了重複信息和不太有趣的特徵。數據腳本地址:www.dataquest.io/blog/large_…。數據以下:

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
%matplotlib inline

### Import data
# Always good to set a seed for reproducibility
SEED = 222
np.random.seed(SEED)

df = pd.read_csv('input.csv')

### Training and test set
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

def get_train_test(test_size=0.95):
"""Split Data into train and test sets."""
   y = 1 * (df.cand_pty_affiliation == "REP")
   X = df.drop(["cand_pty_affiliation"], axis=1)
   X = pd.get_dummies(X, sparse=True)
   X.drop(X.columns[X.std() == 0], axis=1, inplace=True)
return train_test_split(X, y, test_size=test_size, random_state=SEED)

xtrain, xtest, ytrain, ytest = get_train_test()

# A look at the data
print("\nExample data:")
df.head()
複製代碼
df.cand_pty_affiliation.value_counts(normalize=True).plot(
   kind="bar", title="Share of No. donations")
plt.show()
複製代碼

上圖是 Ben 的論斷的數據依據。確實,75% 的捐款是給民主黨的。咱們來看一下可使用的特徵,咱們具有捐款人、交易詳情和捐款接受者的數據信息:

咱們使用 ROC-AUC 來評估模型性能。若是你以前沒用過該指標,隨機猜想能夠是 0.5 分,完美的召回率和精確率是 1.0。


什麼是集成?

想象一下你在玩常識問答遊戲。一我的玩時,可能會有一些題你徹底不瞭解。若是咱們想得到高分,就須要組建一個團隊來覆蓋全部相關主題。這就是集成的基本概念:結合多個模型的預測,對特異性偏差取平均,從而得到更好的總體預測結果。

一個重要問題是如何結合預測。以常識問答遊戲爲例,咱們很容易想象到團隊成員可能會使用多數投票的方式肯定選擇哪一個答案。機器學習的分類問題也是同樣:做出最多見的類別標籤預測至關於多數投票規則。可是也有不少其餘方式能夠結合預測,一般咱們會使用一個模型來學習如何最好地結合預測結果。

基礎集成的結構。輸入輸送至一系列模型中,元學習器將多個模型的預測結果結合起來。(圖片來源:flennerhag.com/2017-04-18-…


經過決策樹理解集成

咱們用一個簡單的可解釋性模型來解釋集成:使用 if-then 規則的決策樹。決策樹越深,能夠捕捉的模式就越複雜,不過也更有可能出現過擬合。所以,咱們須要另外一種方式來構建決策樹的複雜模型,而不一樣決策樹的集成就是這樣一種方式。

我麼使用下列輔助函數來可視化決策樹:

import pydotplus  # you can install pydotplus with: pip install pydotplus 
from IPython.display import Image
from sklearn.metrics import roc_auc_score
from sklearn.tree import DecisionTreeClassifier, export_graphviz

def print_graph(clf, feature_names):
"""Print decision tree."""
   graph = export_graphviz(
       clf,
       label="root",
       proportion=True,
       impurity=False, 
       out_file=None, 
       feature_names=feature_names,
       class_names={0: "D", 1: "R"},
       filled=True,
       rounded=True
   )
   graph = pydotplus.graph_from_dot_data(graph)  
return Image(graph.create_png())
複製代碼

在訓練數據上用決策樹擬合一個節點(決策規則),查看它在測試集上的性能:

t1 = DecisionTreeClassifier(max_depth=1, random_state=SEED)
t1.fit(xtrain, ytrain)
p = t1.predict_proba(xtest)[:, 1]

print("Decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
print_graph(t1, xtrain.columns)
複製代碼

決策樹的 ROC-AUC 得分:0.672


每一個葉節點記錄它們在訓練樣本中的比例、類別分佈和類別標籤預測。咱們的決策樹根據捐款金額是否超過 101.5 進行預測:它居然做出了一樣的預測!鑑於 75% 的捐款都給了民主黨,這個結果並不使人驚訝。可是這沒有充分利用咱們已有的數據,下面咱們使用三層決策規則,看看會獲得什麼:

t2 = DecisionTreeClassifier(max_depth=3, random_state=SEED)
t2.fit(xtrain, ytrain)
p = t2.predict_proba(xtest)[:, 1]

print("Decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
print_graph(t2, xtrain.columns)
複製代碼


決策樹的 ROC-AUC 得分:0.751

該模型並不比簡單的決策樹好太多:預測到的共和黨捐款金額比例只有 5%,遠遠低於 25%。仔細觀察會發現該決策樹使用了不少不肯定的分割規則(splitting rule)。觀察結果中高達 47.3% 的結果在最左邊的葉節點中,而 35.9% 在右二的葉節點中。所以大量葉節點沒有關聯。使該模型更深只會致使過擬合。

在深度固定的狀況下,決策樹能夠經過增長「寬度」的方式來增長複雜度,即建立多個決策樹,並將其鏈接起來。也就是決策樹的集成。想了解這個集成模型爲何會起做用,先要考慮咱們如何讓決策樹探索出比上層樹更多的其餘模式。最簡單的解決方案就是刪除樹中較早出現的特徵。假如咱們刪除了轉帳金額特徵(transaction_amt),樹的根節點,則新的決策樹以下:

drop = ["transaction_amt"]

xtrain_slim = xtrain.drop(drop, 1)
xtest_slim = xtest.drop(drop, 1)

t3 = DecisionTreeClassifier(max_depth=3, random_state=SEED)
t3.fit(xtrain_slim, ytrain)
p = t3.predict_proba(xtest_slim)[:, 1]


print("Decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
print_graph(t3, xtrain_slim.columns)
複製代碼

決策樹的 ROC-AUC 得分:0.740


ROC-AUC 得分與上樹得分相似,可是共和黨捐款比例增長至 7.3%。仍是很低,但比以前稍高一些。重要的是,與第一個樹相反(第一個樹大部分規則與轉帳自己相關),這棵樹更專一於候選人的居住地。如今咱們有兩個模型,兩者預測能力相近,但基於不一樣的規則運行。所以,它們可能出現不一樣的預測偏差,咱們可使用集成方法取其平均數。


爲何平均預測有做用

假如咱們要基於兩個觀察結果生成預測。第一個觀察結果的真正標籤爲共和黨,第二個是民主黨。在該示例中,假設模型 1 預測結果是民主黨,模型 2 預測結果是共和黨,以下表所示:

若是咱們使用標準的 50% 分割規則(50% cutoff rule)進行類別預測,每一個決策樹的預測結果都是一個正確一個錯誤。咱們對模型的類別機率取平均來建立一個集成。在該示例中,模型 2 對觀察結果 1 的預測是肯定的,而模型 1 相對來講不那麼肯定。集成對兩者的預測進行衡量,而後支持模型 2,正確地預測了共和黨。至於第二個觀察結果,局面扭轉過來,集成正確地預測到民主黨:

若是集成包含兩個以上決策樹,則它根據多數原則進行預測。所以,集成對分類器預測結果取平均又叫做多數投票分類器(majority voting classifier)。當集成基於機率取平均時,咱們稱其爲軟投票,而對類別標籤預測結果取平均被成爲硬投票。

固然,集成不是萬能的。你可能注意到上述示例中,取平均有效的前提是預測偏差必須不相關。若是兩個模型都做出了錯誤的預測,則集成沒法做出進行修正。此外,在軟投票機制中,若是一個模型做出了錯誤的預測,但機率值較高,則集成可能會做出錯誤的判斷。一般,集成沒法使每一個預測都正確,可是預計其性能優於底層模型。


森林是樹的集成

回到咱們的預測問題,看看咱們是否能夠用兩個決策樹構建一個集成。首先檢查偏差關聯性:高度關聯的偏差會形成差的集成。

p1 = t2.predict_proba(xtest)[:, 1]
p2 = t3.predict_proba(xtest_slim)[:, 1]

pd.DataFrame({"full_data": p1,
"red_data": p2}).corr()
複製代碼

有一些關聯性,但不過度:預測方差仍有很大的利用空間。爲了構建該集成,咱們簡單地平均了兩個模型的預測。

p1 = t2.predict_proba(xtest)[:, 1]
p2 = t3.predict_proba(xtest_slim)[:, 1]
p = np.mean([p1, p2], axis=0)
print("Average of decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
複製代碼

決策樹的 ROC-AUC 平均分值:0.783


確實,集成步驟致使分值增長。可是若是咱們有更多不一樣的樹,咱們甚至能夠獲得更大的分值。在設計決策樹時,咱們應該去除哪些特徵?

一個快速有效的實踐方法是隨機地選擇一個特徵子集,在每一個 draw 上擬合一個決策樹並平均其預測。這一過程被稱爲自舉平均(bootstrapped averaging,一般縮寫爲 bagging),它應用於決策樹所產生的模型是隨機森林。讓咱們看看隨機森林能爲咱們作什麼。咱們使用 Scikit-learn 實現構建了 10 個決策樹的集成,每個擬合包含 3 個特徵的子集。

from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(
   n_estimators=10,
   max_features=3,
   random_state=SEED
)

rf.fit(xtrain, ytrain)
p = rf.predict_proba(xtest)[:, 1]
print("Average of decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
複製代碼

決策樹的 ROC-AUC 平均分值:0.844

隨機森林極大改進了咱們以前的模型。可是隻使用決策樹能夠作的事情比較有限。是時候擴展咱們的視野了。


做爲平均預測的集成

目前爲止,咱們看到了集成的兩個重要方面:

1. 預測偏差的關聯性越低,效果越好

2. 模型越多,效果越好

出於這一緣由,儘量使用不一樣模型不失爲一個好方法(只要它們表現良好)。目前爲止咱們一直在依賴簡單的平均,可是稍後咱們將瞭解如何使用更復雜的結合。爲了記錄進程,咱們把集成公式化爲以下:

涵蓋的模型沒有限制:決策樹、線性模型、核模型、非參數模型、神經網絡,或者甚至其餘集成!記住咱們包含的模型越多,集成的速度就會越慢。

爲了構建不一樣模型的集成,咱們首先在數據集上對一組 Scikit-learn 分類器進行基準測試。爲了不代碼重複,咱們使用下面的輔助函數:

# A host of Scikit-learn models
from sklearn.svm import SVC, LinearSVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.kernel_approximation import Nystroem
from sklearn.kernel_approximation import RBFSampler
from sklearn.pipeline import make_pipeline


def get_models():
"""Generate a library of base learners."""
   nb = GaussianNB()
   svc = SVC(C=100, probability=True)
   knn = KNeighborsClassifier(n_neighbors=3)
   lr = LogisticRegression(C=100, random_state=SEED)
   nn = MLPClassifier((80, 10), early_stopping=False, random_state=SEED)
   gb = GradientBoostingClassifier(n_estimators=100, random_state=SEED)
   rf = RandomForestClassifier(n_estimators=10, max_features=3, random_state=SEED)

   models = {'svm': svc,
'knn': knn,
'naive bayes': nb,
'mlp-nn': nn,
'random forest': rf,
'gbm': gb,
'logistic': lr,
             }

return models


def train_predict(model_list):
"""Fit models in list on training set and return preds"""
   P = np.zeros((ytest.shape[0], len(model_list)))
   P = pd.DataFrame(P)

print("Fitting models.")
   cols = list()
for i, (name, m) in enumerate(models.items()):
print("%s..." % name, end=" ", flush=False)
       m.fit(xtrain, ytrain)
       P.iloc[:, i] = m.predict_proba(xtest)[:, 1]
       cols.append(name)
print("done")

   P.columns = cols
print("Done.\n")
return P


def score_models(P, y):
"""Score model in prediction DF"""
print("Scoring models.")
for m in P.columns:
       score = roc_auc_score(y, P.loc[:, m])
print("%-26s: %.3f" % (m, score))
print("Done.\n")
複製代碼

咱們如今正準備建立一個預測矩陣 P,其中每一個特徵對應於由給定模型作出的預測,並根據測試集爲每一個模型評分:

models = get_models()
P = train_predict(models)
score_models(P, ytest)
複製代碼

這是咱們的基線。梯度提高機(Gradient Boosting Machine/GBM)效果最好,其次是簡單的 logistic 迴歸。對於咱們的集成策略來講,預測偏差必須是相對不關聯的。

# You need ML-Ensemble for this figure: you can install it with: pip install mlens
from mlens.visualization import corrmat

corrmat(P.corr(), inflate=False)
plt.show()
複製代碼

偏差明顯關聯,這對於表現良好的模型是能夠預期的,由於它是典型的異常值,很難糾正。然而,大多數關聯性在 50-80%的範圍內,因此還有很大的改進餘地。事實上,若是咱們從類別預測的角度看偏差關聯性,事情看起來會更有但願:

corrmat(P.apply(lambda pred: 1*(pred >= 0.5) - ytest.values).corr(), inflate=False)
plt.show()
複製代碼

爲了建立集成,咱們繼續並進行平均預測,正如咱們所指望的,集成的性能要好於基線。平均化是一個簡單的過程,若是咱們存儲模型預測,咱們能夠從一個簡單的集成開始,並在訓練新模型時隨時增長其大小。

print("Ensemble ROC-AUC score: %.3f" % roc_auc_score(ytest, P.mean(axis=1)))
複製代碼

集成的 ROC-AUC 分值:0.884


可視化集成的工做過程

咱們已經對集成的偏差關聯機制有所瞭解。這意味着集成經過平均謬誤能夠平滑決策邊界。決策邊界向咱們代表評估器如何將特徵空間分割成鄰域,其中全部的觀察結果被預測爲具備相同的分類標籤。經過平均基學習器決策邊界,集成被賦予更平滑的邊界,泛化也更天然。

下圖展現了這一點。這裏的實例是鳶尾花數據集,其中評估者試圖對三種花進行分類。基學習器在其邊界都有一些不良的特性,可是這個集成有一個相對平滑的決策邊界,與觀察結果一致。使人驚訝的是,集成既增長了模型的複雜度,也起到了正則化項的做用!

三個模型及其集成的決策邊界示例


當任務是分類時,另外一種理解集成的方式是檢查 ROC 曲線(Receiver Operator Curve),它向咱們展現了評估者如何進行精確率和召回率之間的權衡。一般,不一樣的基學習器作出不一樣的權衡:一些經過犧牲召回率實現更高的精確率,另外一些則相反。

另外一方面,對於每一個訓練點,非線性元學習器能夠調整其依賴的模型。這意味其能夠極大地減小沒必要要的犧牲,並在增長召回率的同時保持高精確率(反之亦然)。下圖中,集成在精確率上作了一個更小的犧牲,以增長召回率。

from sklearn.metrics import roc_curve

def plot_roc_curve(ytest, P_base_learners, P_ensemble, labels, ens_label):
"""Plot the roc curve for base learners and ensemble."""
   plt.figure(figsize=(10, 8))
   plt.plot([0, 1], [0, 1], 'k--')

   cm = [plt.cm.rainbow(i)
for i in np.linspace(0, 1.0, P_base_learners.shape[1] + 1)]

for i in range(P_base_learners.shape[1]):
       p = P_base_learners[:, i]
       fpr, tpr, _ = roc_curve(ytest, p)
       plt.plot(fpr, tpr, label=labels[i], c=cm[i + 1])

   fpr, tpr, _ = roc_curve(ytest, P_ensemble)
   plt.plot(fpr, tpr, label=ens_label, c=cm[0])

   plt.xlabel('False positive rate')
   plt.ylabel('True positive rate')
   plt.title('ROC curve')
   plt.legend(frameon=False)
   plt.show()


plot_roc_curve(ytest, P.values, P.mean(axis=1), list(P.columns), "ensemble")
複製代碼

超越簡單平均值的集成

可是在預測偏差變更必定的狀況下,你不會指望更多的提高嗎?一些模型表現要比其餘模型相對糟糕,可是其影響同樣大。這對不平衡數據集來講是毀滅性的:若是一個模型作出極端預測(即接近於 0 或 1),則經過軟投票召回,由於其對預測平均值有極大的影響。

對咱們來講,一個重要的因素是模型是否能夠捕捉到共和黨所收捐款的所有比例。一個簡單的檢查代表全部模型對共和黨捐款比例的預測都太低,其中一些相對更糟。

p = P.apply(lambda x: 1*(x >= 0.5).value_counts(normalize=True))
p.index = ["DEM", "REP"]
p.loc["REP", :].sort_values().plot(kind="bar")
plt.axhline(0.25, color="k", linewidth=0.5)
plt.text(0., 0.23, "True share republicans")
plt.show() 
複製代碼

咱們嘗試經過去除最糟糕的來提高集成,好比說多層感知機(MLP):

include = [c for c in P.columns if c not in ["mlp-nn"]]
print("Truncated ensemble ROC-AUC score: %.3f" % roc_auc_score(ytest, P.loc[:, include].mean(axis=1)))
複製代碼

截斷集成的 ROC-AUC 分值:0.883


實際上不算提高:咱們須要一個更聰明的方式來排定模型之間的優先順序。很明顯,從一個集成中刪除模型是至關猛烈的,由於有可能刪除帶有重要信息的模型。咱們真正想要的是學習平均預測時使用的一組合理的權重。這把集成變成了一個須要訓練的參數化模型。


學習結合預測

學習加權平均值意味着對於每一個模型 f_i 都有一個權重參數 ω_i∈(0,1) 將權重分配給該模型的預測。加權平均值須要全部的權重總和爲 1。如今,集成的定義以下:

這與以前的定義有一些小的改變,可是頗有趣的一點是:一旦模型生成預測 p_i=f_i(x),則學習權重就至關於基於預測來擬合線性迴歸:

權重有一些約束項。而後,咱們不用僅擬合線性模型了。假設咱們擬合最近鄰模型。集成會基於給定觀察結果的最近鄰取局部平均值,這樣集成就能夠適應模型性能隨着輸入變化而產生的改變。


實現集成

要構建這種類型的集成,咱們須要:

1. 用於生成預測的基學習器庫;

2. 學習如何最佳結合預測結果的元學習器;

3. 在基學習器和元學習器之間分割訓練數據的方法。

基學習器採用原始輸入,生成一系列預測。若是咱們的原始數據集是形態爲 (n_samples, n_features) 的矩陣 X,那麼基學習器庫輸出新的預測矩陣 P_base,形態爲 (n_samples, n_base_learners),其中每一列表明一個基學習器的預測結果。元學習器在 P_base 上訓練。

這意味着恰當地處理訓練集 X 相當重要。尤爲是,若是咱們在 X 上訓練基學習器,用它們預測 X,則元學習器將在基學習器的訓練偏差上訓練,但在測試時元學習器將面對基學習器的測試偏差。

咱們須要一種策略來生成反映測試偏差的預測矩陣 P。最簡單的策略是將完整的數據集 X 分割成兩部分:在其中一半上訓練基學習器,而後讓它們預測另外一半,而後將預測輸入元學習器。這種方法又簡單又快,不過會丟失一些數據。對於中小型規模的數據集,信息丟失可能會比較嚴重,致使基學習器和元學習器性能很差。

爲了保證覆蓋完整的數據集,咱們可使用交叉驗證法。有不少方式能夠執行交叉驗證,在那以前,咱們先來一步一步地實現集成。


第一步:定義基學習器的庫

它們是處理輸入數據並生成預測的模型,能夠是線性迴歸,也能夠是神經網絡,甚至能夠是另外一個集成。和往常同樣,多樣性是強大的!惟一須要注意的是,咱們加入越多的模型,集成運行的速度就會越慢。在這裏,我會使用此前的模型集合:

base_learners = get_models()
複製代碼

第二步:定義一個元學習器

應該使用哪一個元學習器,人們並無統一見解,但目前流行的選擇是線性模型、基於核的模型(支持向量機和 KNN 算法)以及基於決策樹的模型。你也可使用另外一個集成做爲「元學習器」:在這種特殊狀況下,你最終會獲得一個兩層的集成,這有點相似於前饋神經網絡。

在這裏,咱們會使用一個梯度提高機。爲了確保 GBM 可以探索局部特徵,咱們須要限定每 1000 個決策樹在 4 個基學習器的隨機子集和 50% 的輸入數據上進行訓練。這樣,GBM 就會表達每一個基學習器在不一樣近鄰輸入空間上的預測內容。

meta_learner = GradientBoostingClassifier(
   n_estimators=1000,
   loss="exponential",
   max_features=4,
   max_depth=3,
   subsample=0.5,
   learning_rate=0.005, 
   random_state=SEED
)
複製代碼


第三步:定義步驟,生成訓練和測試集

爲簡單起見,咱們將完整訓練集分爲基學習器的訓練集和預測集。這種方法有時被稱爲「混合(Blending)」。不過,不一樣社區之間的術語是不一樣的,因此知道集成使用了哪一種類型的交叉驗證有時並不容易。

xtrain_base, xpred_base, ytrain_base, ypred_base = train_test_split(
   xtrain, ytrain, test_size=0.5, random_state=SEED)
複製代碼

咱們如今有一個爲基學習器準備的訓練集(X_train_base,y_train_base)和一個預測集(X_pred_base,y_pred_base),準備好爲元學習器生成預測矩陣了。


第四步:在訓練集上訓練基學習器

爲在訓練數據上訓練基學習器,咱們照常運行:

def train_base_learners(base_learners, inp, out, verbose=True):
"""Train all base learners in the library."""
if verbose: print("Fitting models.")
for i, (name, m) in enumerate(base_learners.items()):
if verbose: print("%s..." % name, end=" ", flush=False)
       m.fit(inp, out)
if verbose: print("done")
複製代碼

爲訓練基學習器,咱們須要執行:

train_base_learners(base_learners, xtrain_base, ytrain_base)
複製代碼


第五步:生成基學習器預測

基學習器擬合後,咱們如今能夠生成一系列預測用於訓練元學習器。注意,咱們生成的基於觀測值的預測並不會用於基學習器的訓練,對於每一個觀測:

在基學習器預測集中,咱們生成了基學習器預測結果的集合:

若是你實現本身的集成,請特別注意如何索引預測矩陣的行和列——將數據分紅兩個部分並不難,但對於後來的交叉驗證就頗有挑戰性了。

def predict_base_learners(pred_base_learners, inp, verbose=True):
"""Generate a prediction matrix."""
   P = np.zeros((inp.shape[0], len(pred_base_learners)))

if verbose: print("Generating base learner predictions.")
for i, (name, m) in enumerate(pred_base_learners.items()):
if verbose: print("%s..." % name, end=" ", flush=False)
       p = m.predict_proba(inp)
# With two classes, need only predictions for one class
       P[:, i] = p[:, 1]
if verbose: print("done")

return P
複製代碼

爲生成預測,咱們須要執行:

P_base = predict_base_learners(base_learners, xpred_base)
複製代碼


第六步:訓練元學習器

預測矩陣 P_base 反映了測試時間的性能,可被用於訓練元學習器:

meta_learner.fit(P_base, ypred_base)
複製代碼

就是這樣!咱們如今有了徹底訓練的集成,可用來預測新數據了。爲生成觀測值 x(j) 的預測,咱們先要將其輸入基學習器。它們輸出一系列預測

咱們再將其輸入元學習器。元學習器會給咱們集成的最終預測

如今咱們對於集成學習有了一個明確的認識,是時候看看它能對政治捐款數據集作出怎樣的預測了:

def ensemble_predict(base_learners, meta_learner, inp, verbose=True):
"""Generate predictions from the ensemble."""
   P_pred = predict_base_learners(base_learners, inp, verbose=verbose)
return P_pred, meta_learner.predict_proba(P_pred)[:, 1]
複製代碼

爲生成預測,咱們須要執行:

P_pred, p = ensemble_predict(base_learners, meta_learner, xtest)
print("\nEnsemble ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
複製代碼

集成的 ROC-AUC 分數:0.881

正如咱們所料,集成擊敗了此前基準的最佳估計,但它仍然沒法擊敗簡單的平均集成。這是由於咱們只對一半的數據進行基學習器和元學習器的訓練,因此大量的信息丟失了。爲了防止這點,咱們須要使用交叉驗證策略。


利用交叉驗證訓練

在交叉驗證訓練基學習器時,每一個基學習器的備份都進行了 K-1 fold 的擬合,並進行了剩餘 fold 的預測。這一過程不斷重複,直到每一個 fold 都被預測。咱們指定的 fold 越多,每次訓練過程當中的數據就越少。這使得交叉驗證的預測在測試期間噪聲更小,性能更好。但這顯著增長了訓練時間。經過交叉驗證擬合一個集成常常被稱爲堆疊(stacking),而集成自己會被稱爲超級學習器(Super Learner)。

爲理解交叉驗證是如何運做的,咱們能夠把它想象爲以前集成的一個外循環(outer loop)。外循環在個格不一樣的測試 fold 上迭代,而其他的數據用於訓練;內循環訓練基學習器併產生預測數據。這是一個簡單的堆疊實現:

from sklearn.base import clone

def stacking(base_learners, meta_learner, X, y, generator):
"""Simple training routine for stacking."""

# Train final base learners for test time
print("Fitting final base learners...", end="")
   train_base_learners(base_learners, X, y, verbose=False)
print("done")

# Generate predictions for training meta learners
# Outer loop:
print("Generating cross-validated predictions...")
   cv_preds, cv_y = [], []
for i, (train_idx, test_idx) in enumerate(generator.split(X)):

       fold_xtrain, fold_ytrain = X[train_idx, :], y[train_idx]
       fold_xtest, fold_ytest = X[test_idx, :], y[test_idx]

# Inner loop: step 4 and 5
       fold_base_learners = {name: clone(model)
for name, model in base_learners.items()}
       train_base_learners(
           fold_base_learners, fold_xtrain, fold_ytrain, verbose=False)

       fold_P_base = predict_base_learners(
           fold_base_learners, fold_xtest, verbose=False)

       cv_preds.append(fold_P_base)
       cv_y.append(fold_ytest)
print("Fold %i done" % (i + 1))

print("CV-predictions done")

# Be careful to get rows in the right order
   cv_preds = np.vstack(cv_preds)
   cv_y = np.hstack(cv_y)

# Train meta learner
print("Fitting meta learner...", end="")
   meta_learner.fit(cv_preds, cv_y)
print("done")

return base_learners, meta_learner
複製代碼

讓咱們來看看這裏涉及的步驟。首先,咱們在全部數據上擬合基學習器:這與以前的混合集成相反,基學習器在測試時間內訓練了全部數據。隨後咱們遍歷全部 fold,隨後遍歷全部基學習器生成交叉驗證預測。這些預測堆疊在一塊兒構成了元學習器的訓練集——它也訓練了全部數據。

混合和堆疊的基本區別在於,堆疊容許基學習器和元學習器在所有數據集上進行訓練。使用雙重交叉驗證,咱們能夠測量這種狀況下的差別:

from sklearn.model_selection import KFold

# Train with stacking
cv_base_learners, cv_meta_learner = stacking(
   get_models(), clone(meta_learner), xtrain.values, ytrain.values, KFold(2))

P_pred, p = ensemble_predict(cv_base_learners, cv_meta_learner, xtest, verbose=False)
print("\nEnsemble ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
複製代碼

集成 的 ROC-AUC 分數:0.889


堆疊帶來了可觀的性能提高:事實上,它獲得了目前的最佳分數。對於中小型數據集來講,這種成績是很是典型的,其中混合的影響比重很大。隨着數據集體量的增大,混合與堆疊的表現會逐漸趨同。

堆疊也有本身的缺點,特別是速度。一般,在交叉驗證的狀況下,咱們須要知道這些問題:

1. 計算複雜度

2. 結構複雜度(信息泄露的風險)

3. 內存用量

理解它們對於高效使用集成方法來講很是重要,讓咱們一一道來。


1. 計算複雜度

假設咱們須要使用 10 折堆疊。這須要在 90% 的數據上訓練全部基學習器 10 次,而後在全部數據上再訓練一次。如有 4 個基學習器,集成須要花費的時間大約是最佳基學習器的 40 倍。

但每一個 cv-fit 都是獨立的,因此咱們不須要依次擬合模型。若是咱們可以平行擬合全部 fold,集成就只會比最佳基學習器慢 4 倍——這是一個巨大的提高。集成是並行化的最佳受益者,可以充分利用這一機制對它來講相當重要。爲全部模型擬合全部 fold,集成的時間懲罰就能夠忽略不計了。爲了介紹這一點,下圖是 ML-Ensemble 上的一個基準,它展現了 4 個線程上依次或並行堆疊或混合擬合所花費的時間。

即便有了這種程度的並行性,咱們也能夠減小大量計算時間。然而,並行化與一系列潛在的棘手問題有關,如競態條件、鎖死和內存爆炸。


2. 結構複雜度

當咱們決定在元學習器上使用整個訓練集時,咱們必須關注「信息泄露」問題。當錯誤地預測訓練期間使用的樣本時,就會出現這種現象,例如混合了不一樣的 fold,或使用了錯誤的訓練子集。當元學習器在訓練集上出現信息泄露時,預測錯誤就會產生:garbage in、garbage out。發現這樣的 bug 是很是困難的。


3. 內存用量

並行化的最後一個問題,特別是在 Python 中多任務處理時常常會碰到的問題。在這種狀況下,每一個子進程都有本身的內存,同時須要複製父進程中全部的數據。所以,一個未作優化的實現會複製程序的全部數據,佔用大量內存,浪費數據序列化的時間。爲阻止這種狀況,咱們須要共享數據存儲器,而這反過來又容易致使數據損壞。


結果:使用工具包

這一問題的結果是須要使用一個通過測試、且爲你的機器學習方法構建的軟件包。事實上,一旦你用上了集成工具包,構建集成就會變得很是簡單:你須要作的僅僅是選定基學習器、元學習器,以及訓練集成的方法。

幸運的是,今天每一個流行的編程語言裏都有不少可用的工具包——雖然它們有着不一樣的風格。在本文的末尾我會列舉其中的一些。如今,讓咱們選用其中的一個,看看集成方法是如何處理政治捐款數據集的。在這裏,咱們使用 ML-Ensemble 來構建咱們以前提到的廣義集合,但如今使用 10 折交叉驗證。

from mlens.ensemble import SuperLearner

# Instantiate the ensemble with 10 folds
sl = SuperLearner(
   folds=10,
   random_state=SEED,
   verbose=2,
   backend="multiprocessing"
)

# Add the base learners and the meta learner
sl.add(list(base_learners.values()), proba=True) 
sl.add_meta(meta_learner, proba=True)

# Train the ensemble
sl.fit(xtrain, ytrain)

# Predict the test set
p_sl = sl.predict_proba(xtest)

print("\nSuper Learner ROC-AUC score: %.3f" % roc_auc_score(ytest, p_sl[:, 1]))

Fitting 2 layers
Processing layer-1             done | 00:02:03
Processing layer-2             done | 00:00:03
Fit complete                        | 00:02:08

Predicting 2 layers
Processing layer-1             done | 00:00:50
Processing layer-2             done | 00:00:02
Predict complete                    | 00:00:54

Super Learner ROC-AUC score: 0.890
複製代碼

就是這麼簡單!


觀察超級學習器簡單平均集合的 ROC 曲線,其中展現了超級學習器如何利用所有數據僅犧牲少許召回率便可得到給定精確率。

plot_roc_curve(ytest, p.reshape(-1, 1), P.mean(axis=1), ["Simple average"], "Super Learner") 
複製代碼

繼續進發

除了本文介紹的幾種集成外,目前還有不少其餘的集成方式,不過它們的基礎都是同樣的:一個基學習器庫、一個元學習器,以及一個訓練程序。經過調整這些組件的配合,咱們能夠設計出各類特定的集合形式。有關更高級集合的內容請參閱這篇文章:mlwave.com/kaggle-ense…

當咱們談到軟件時,每一個人都有本身的喜愛。隨着集成方法的流行,集成工具包的數量也愈來愈多。實際上集成方法是先在統計學社區中流行起來的,因此 R 語言中有不少爲此設計的庫。近年來,Python 和其餘語言中也出現了更多相關工具。每一個工具包都能知足不一樣的需求,處於不一樣的成熟階段,因此我建議你們選用前先瀏覽一番再作決策。


下表列出了其中一些工具:

原文連接:www.dataquest.io/blog/introd…

本文爲機器之心編譯,轉載請聯繫本公衆號得到受權。

相關文章
相關標籤/搜索