如果你合并了一組分類器的預測(像分類或者回歸),會得到一個比單一分類器更好的預測結果。這一組分類器就叫做集成。因此,這個技術就叫做集成學習,一個集成學習算法就叫做集成方法。
以訓練一組決策樹分類器,每一個都在一個隨機的訓練集上。為了去做預測,必須得到所有單一樹的預測值,然后通過投票來預測類別。例如一種決策樹的集成就叫做隨機森林。
1.投票分類

from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
log_clf = LogisticRegression(random_state=42)
rnd_clf = RandomForestClassifier(random_state=42)
svm_clf = SVC(random_state=42)
voting_clf = VotingClassifier(
estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
voting='hard')
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() 方法)。如果修改了之前的代碼去使用軟投票,會發(fā)現投票分類器正確率高達 91%
2. Bagging, Pasting
對每一個分類器都使用相同的訓練算法,但是在不同的訓練集上去訓練它們。有放回采樣被稱為裝袋(Bagging,是 bootstrap aggregating 的縮寫)。無放回采樣稱為粘貼(pasting)。

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
# 如果想嘗試Pasting,就設置 bootstrap=False
bag_clf = BaggingClassifier(
DecisionTreeClassifier(random_state=42), n_estimators=500,
max_samples=100, bootstrap=True, n_jobs=-1, random_state=42)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
# 0.904
tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
y_pred_tree = tree_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_tree))
# 0.856
from matplotlib.colors import ListedColormap
def plot_decision_boundary(clf, X, y, axes=[-1.5, 2.5, -1, 1.5], alpha=0.5, contour=True):
x1s = np.linspace(axes[0], axes[1], 100)
x2s = np.linspace(axes[2], axes[3], 100)
x1, x2 = np.meshgrid(x1s, x2s)
X_new = np.c_[x1.ravel(), x2.ravel()]
y_pred = clf.predict(X_new).reshape(x1.shape)
custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
plt.contourf(x1, x2, y_pred, alpha=0.3, cmap=custom_cmap)
if contour:
custom_cmap2 = ListedColormap(['#7d7d58','#4c4c7f','#507d50'])
plt.contour(x1, x2, y_pred, cmap=custom_cmap2, alpha=0.8)
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", alpha=alpha)
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", alpha=alpha)
plt.axis(axes)
plt.xlabel(r"$x_1$", fontsize=18)
plt.ylabel(r"$x_2$", fontsize=18, rotation=0)
plt.figure(figsize=(11,4))
plt.subplot(121)
plot_decision_boundary(tree_clf, X, y)
plt.title("Decision Tree", fontsize=14)
plt.subplot(122)
plot_decision_boundary(bag_clf, X, y)
plt.title("Decision Trees with Bagging", fontsize=14)
plt.show()

out of bag
對于 Bagging 來說,一些實例可能被一些分類器重復采樣,但其他的有可能不會被采樣。 BaggingClassifier 默認采樣。 BaggingClassifier 默認是有放回的采樣 m 個實例( bootstrap=True ),其中 m 是訓練集的大小,這意味著平均下來只有63%的訓練實例被每個分類器采樣,剩下的37%個沒有被采樣的訓練實例就叫做 Out-of-Bag 實例。
bag_clf = BaggingClassifier(
DecisionTreeClassifier(random_state=42), n_estimators=500,
max_samples=100, bootstrap=True, n_jobs=-1, random_state=42, oob_score=True)
bag_clf.fit(X_train, y_train)
print(bag_clf.oob_score_)
# 0.925333
3. 隨機森林
隨機森林是決策樹的一種集成。
bagging方式
bag_clf = BaggingClassifier(
DecisionTreeClassifier(splitter="random", max_leaf_nodes=16, random_state=42),
n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1, random_state=42)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
'''
[0 0 0 1 1 1 0 0 0 0 1 0 1 1 1 0 0 1 1 0 0 1 1 0 0 0 1 0 1 0 1 1 0 0 1 0 0
1 1 1 1 1 0 0 0 0 1 0 1 0 1 1 0 0 1 0 1 1 0 1 0 1 1 0 1 0 0 0 0 1 0 0 1 1
0 0 1 1 0 0 1 1 1 0 1 1 1 0 1 1 1 0 0 0 0 1 0 1 0 1 0 1 1 0 0 0 0 0 1 1 1
0 0 1 0 0 0 0 0 1 1 1 0 0 0]
'''
RandomForestClassifier
from sklearn.ensemble import RandomForestClassifier
# 訓練了帶有 500 個樹(每個被限制為 16 葉子結點)的決策森林
rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1, random_state=42)
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
'''
[0 0 0 1 1 1 0 0 0 0 1 0 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 0 1 0 1 1 0 0 1 0 0
1 1 1 1 1 0 0 0 0 1 0 1 1 1 1 0 0 1 0 1 1 0 1 0 1 1 0 1 0 0 0 0 1 0 0 1 1
0 0 1 1 0 0 1 1 1 0 1 1 1 0 1 1 1 0 0 0 0 1 0 1 0 1 0 1 1 0 0 0 0 0 1 1 1
0 0 1 1 0 0 0 0 1 1 1 0 0 0]
'''
np.sum(y_pred == y_pred_rf) / len(y_pred)
# 0.976
特征重要度
觀察一個單一決策樹,重要的特征會出現在更靠近根部的位置,而不重要的特征會經常出現在靠近葉子的位置。因此我們可以通過計算一個特征在森林的全部樹中出現的平均深度來預測特征的重要性。
sklearn 在訓練后會自動計算每個特征的重要度??梢酝ㄟ^ feature_importances_ 變量來查看結果。
from sklearn.datasets import load_iris
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1, random_state=42)
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.11249225099876374
sepal width (cm) 0.023119288282510326
petal length (cm) 0.44103046436395765
petal width (cm) 0.4233579963547681
'''
rnd_clf.feature_importances_
# array([0.11249225, 0.02311929, 0.44103046, 0.423358 ])
4. Boosting(提升)
指的是可以將幾個弱學習者組合成強學習者的集成方法。對于大多數的提升方法的思想就是按順序去訓練分類器,每一個都要嘗試修正前面的分類。
Adaboost
適應性提升,Adaptive Boosting。使一個新的分類器去修正之前分類結果的方法就是對之前分類結果不對的訓練實例多加關注。這導致新的預測因子越來越多地聚焦于這些之前分類錯誤的實例。(增加權重)

from sklearn.ensemble import AdaBoostClassifier
ada_clf = AdaBoostClassifier(
# 200次迭代
DecisionTreeClassifier(max_depth=1), n_estimators=200,
algorithm="SAMME.R", learning_rate=0.5, random_state=42)
ada_clf.fit(X_train, y_train)
plot_decision_boundary(ada_clf, X, y)

五次SVM
m = len(X_train)
plt.figure(figsize=(11, 4))
for subplot, learning_rate in ((121, 1), (122, 0.5)):
sample_weights = np.ones(m)
plt.subplot(subplot)
for i in range(5):
svm_clf = SVC(kernel="rbf", C=0.05, random_state=42)
svm_clf.fit(X_train, y_train, sample_weight=sample_weights)
y_pred = svm_clf.predict(X_train)
sample_weights[y_pred != y_train] *= (1 + learning_rate)
plot_decision_boundary(svm_clf, X, y, alpha=0.2)
plt.title("learning_rate = {}".format(learning_rate), fontsize=16)
if subplot == 121:
plt.text(-0.7, -0.65, "1", fontsize=14)
plt.text(-0.6, -0.10, "2", fontsize=14)
plt.text(-0.5, 0.10, "3", fontsize=14)
plt.text(-0.4, 0.55, "4", fontsize=14)
plt.text(-0.3, 0.90, "5", fontsize=14)
plt.show()

梯度提升 Gradient Boosting
使用新的分類器去擬合前面分類器預測的殘差 。
算法過程實踐
np.random.seed(42)
X = np.random.rand(100, 1) - 0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)
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)
X_new = np.array([[0.8]])
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
# 0.75
def plot_predictions(regressors, X, y, axes, label=None, style="r-", data_style="b.", data_label=None):
x1 = np.linspace(axes[0], axes[1], 500)
y_pred = sum(regressor.predict(x1.reshape(-1, 1)) for regressor in regressors)
plt.plot(X[:, 0], y, data_style, label=data_label)
plt.plot(x1, y_pred, style, linewidth=2, label=label)
if label or data_label:
plt.legend(loc="upper center", fontsize=16)
plt.axis(axes)
plt.figure(figsize=(11,11))
plt.subplot(321)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h_1(x_1)$", style="g-", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Residuals and tree predictions", fontsize=16)
plt.subplot(322)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1)$", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Ensemble predictions", fontsize=16)
plt.subplot(323)
plot_predictions([tree_reg2], X, y2, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_2(x_1)$", style="g-", data_style="k+", data_label="Residuals")
plt.ylabel("$y - h_1(x_1)$", fontsize=16)
plt.subplot(324)
plot_predictions([tree_reg1, tree_reg2], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1)$")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.subplot(325)
plot_predictions([tree_reg3], X, y3, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_3(x_1)$", style="g-", data_style="k+")
plt.ylabel("$y - h_1(x_1) - h_2(x_1)$", fontsize=16)
plt.xlabel("$x_1$", fontsize=16)
plt.subplot(326)
plot_predictions([tree_reg1, tree_reg2, tree_reg3], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1) + h_3(x_1)$")
plt.xlabel("$x_1$", fontsize=16)
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.show()

sklearn中的梯度提升
from sklearn.ensemble import GradientBoostingRegressor
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)
gbrt_slow = GradientBoostingRegressor(max_depth=2, n_estimators=200, learning_rate=0.1, random_state=42)
gbrt_slow.fit(X, y)
plt.figure(figsize=(11,4))
plt.subplot(121)
plot_predictions([gbrt], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="Ensemble predictions")
plt.title("learning_rate={}, n_estimators={}".format(gbrt.learning_rate, gbrt.n_estimators), fontsize=14)
plt.subplot(122)
plot_predictions([gbrt_slow], X, y, axes=[-0.5, 0.5, -0.1, 0.8])
plt.title("learning_rate={}, n_estimators={}".format(gbrt_slow.learning_rate, gbrt_slow.n_estimators), fontsize=14)
plt.show()

超參數 learning_rate 確立了每個樹的貢獻。如果你把它設置為一個很小的樹,例如 0.1,在集成中就需要更多的樹去擬合訓練集,但預測通常會更好。
運用早停技術
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, random_state=49)
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120, random_state=42)
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, random_state=42)
gbrt_best.fit(X_train, y_train)
min_error = np.min(errors)
plt.figure(figsize=(11, 4))
plt.subplot(121)
plt.plot(errors, "b.-")
plt.plot([bst_n_estimators, bst_n_estimators], [0, min_error], "k--")
plt.plot([0, 120], [min_error, min_error], "k--")
plt.plot(bst_n_estimators, min_error, "ko")
plt.text(bst_n_estimators, min_error*1.2, "Minimum", ha="center", fontsize=14)
plt.axis([0, 120, 0, 0.01])
plt.xlabel("Number of trees")
plt.title("Validation error", fontsize=14)
plt.subplot(122)
plot_predictions([gbrt_best], X, y, axes=[-0.5, 0.5, -0.1, 0.8])
plt.title("Best model (%d trees)" % bst_n_estimators, fontsize=14)
plt.show()

5. Stacking
訓練一個模型來執(zhí)行所有分類器輸出的聚合,這個模型稱為blender或者meta learner

如何訓練這個blender?
首先,訓練集被分為兩個子集,第一個子集被用作訓練第一層。

接下來,第一層的分類器被用來預測第二個子集(被稱為保持集hold-on set)?,F在對在保持集中的每一個實例都有三個預測值。
現在使用這些預測結果作為輸入特征來創(chuàng)建一個新的訓練集(這使得這個訓練集是三維的)。隨后 blender 在這個新的訓練集上訓練,因此,它學會了預測第一層預測的目標值。

顯然可以用這種方法訓練不同的 blender (例如一個線性回歸,另一個是隨機森林等等):我們得到了一層 blender 。訣竅是將訓練集分成三個子集:第一個子集用來訓練第一層,第二個子集用來創(chuàng)建訓練第二層的訓練集(使用第一層分類器的預測值),第三個子集被用來創(chuàng)建訓練第三層的訓練集(使用第二層分類器的預測值)。以上步驟做完了,我們可以通過逐個遍歷每個層來預測一個新的實例。

練習
針對MNIST數據,把它切分進一個訓練集,一個驗證集,和一個測試集(例如 40000 個實例進行訓練,10000 個進行驗證,10000 個進行測試)。然后訓練多個分類器,例如一個隨機森林分類器,一個 Extra-Tree 分類器和一個 SVM。接下來,嘗試將它們組合成集成,使用軟或硬投票分類器來勝過驗證集上的所有集合。
MNIST數據加載參考這篇簡書
X_train_val, X_test, y_train_val, y_test = train_test_split(
X_train, y_train, test_size=10000, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(
X_train_val, y_train_val, test_size=10000, random_state=42)
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.svm import LinearSVC
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import VotingClassifier
random_forest_clf = RandomForestClassifier(random_state=42)
extra_trees_clf = ExtraTreesClassifier(random_state=42)
svm_clf = LinearSVC(random_state=42)
mlp_clf = MLPClassifier(random_state=42)
voting_clf = VotingClassifier(named_estimators)
voting_clf.fit(X_train, y_train)
voting_clf.score(X_val, y_val)
# 0.9576
[estimator.score(X_val, y_val) for estimator in voting_clf.estimators_]
# [0.9393, 0.9463, 0.864, 0.9604]
# remove svm classifier
del voting_clf.estimators_[2]
voting_clf.score(X_val, y_val)
# 0.9614
voting_clf.voting = "soft"
voting_clf.score(X_val, y_val)
# 0.9688
voting_clf.score(X_test, y_test)
# 0.967
上面每一個單獨的分類器來對驗證集進行預測,并合成創(chuàng)建一個新的訓練集。用一個新的分類器對其進行訓練(訓練了一個 blender) 用這個分類器來評估測試集。
X_val_predictions = np.empty((len(X_val), len(estimators)), dtype=np.float32)
for index, estimator in enumerate(estimators):
X_val_predictions[:, index] = estimator.predict(X_val)
'''
array([[2., 2., 2., 2.],
[8., 3., 3., 3.],
[8., 8., 8., 8.],
...,
[2., 8., 2., 2.],
[3., 8., 8., 8.],
[7., 7., 7., 7.]], dtype=float32)
'''
rnd_forest_blender = RandomForestClassifier(n_estimators=200, oob_score=True, random_state=42)
rnd_forest_blender.fit(X_val_predictions, y_val)
rnd_forest_blender.oob_score_
# 0.9621
X_test_predictions = np.empty((len(X_test), len(estimators)), dtype=np.float32)
for index, estimator in enumerate(estimators):
X_test_predictions[:, index] = estimator.predict(X_test)
y_pred = rnd_forest_blender.predict(X_test_predictions)
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
# 0.9586