《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第七章 集成学习

假设你去随机问很多人一个很复杂的问题,然后把它们的答案合并起来。通常情况下你会发现这个合并的答案比一个专家的答案要好。这就叫做群体智慧。同样的,如果你合并了一组分类器的预测(像分类或者回归),你也会得到一个比单一分类器更好的预测结果。这一组分类器就叫做集成;因此,这个技术就叫做集成学习,一个集成学习算法就叫做集成方法。

例如,你可以训练一组决策树分类器,每一个都在一个随机的训练集上。为了去做预测,你必须得到所有单一树的预测值,然后通过投票来预测类别。例如一种决策树的集成就叫做随机森林,它除了简单之外也是现今存在的最强大的机器学习算法之一。

在本章中我们会讨论一下特别著名的集成方法,包括 bagging, boosting, stacking,和其他一些算法。我们也会讨论随机森林。


Voting Classifiers

假设你已经训练了一些分类器,每一个都有 80% 的准确率。你可能有了一个逻辑斯蒂回归、或一个 SVM、或一个随机森林,或者一个 KNN。
这里写图片描述

一个非常简单去创建一个更好的分类器的方法就是去整合每一个分类器的预测然后经过投票去预测分类。这种分类器就叫做硬投票分类器。

这里写图片描述

这种投票分类器得出的结果经常会比集成中最好的一个分类器结果更好。事实上,即使每一个分类器都是一个弱学习器(意味着它们也就比瞎猜好点),集成后仍然是一个强学习器(高准确率),只要有足够数量的弱学习者,他们就足够多样化。

这怎么可能?接下来的分析将帮助你解决这个疑问。假设你有一个有偏差的硬币,他有 51% 的几率为正面,49% 的几率为背面。如果你实验 1000 次,你会得到差不多 510 次正面,490 次背面,因此大多数都是正面。如果你用数学计算,你会发现在实验 1000 次后,正面概率为 51% 的人比例为 75%。你实验的次数越多,正面的比例越大(例如你试验了 10000 次,总体比例可能性就会达到 97%)。这是因为大数定律 :当你一直用硬币实验时,正面的比例会越来越接近 51%。下图展示了始终有偏差的硬币实验。你可以看到当实验次数上升时,正面的概率接近于 51%。最终所有 10 种实验都会收敛到 51%,它们都大于 50%。
这里写图片描述

同样的,假设你创建了一个包含 1000 个分类器的集成模型,其中每个分类器的正确率只有 51%(仅比瞎猜好一点点)。如果你用投票去预测类别,你可能得到 75% 的准确率!然而,这仅仅在所有的分类器都独立运行的很好、不会发生有相关性的错误的情况下才会这样,然而每一个分类器都在同一个数据集上训练,导致其很可能会发生这样的错误。他们可能会犯同一种错误,所以也会有很多票投给了错误类别导致集成的准确率下降。

当预测变量尽可能彼此独立时,集成学习方法效果最好。获得不同分类器的一种方法是使用非常不同的算法来训练它们。这增加了他们会犯很多不同类型错误的机会,从而提高了整体的准确性。

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' ) 
voting_clf.fit(X_train, y_train)

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

如果所有分类器都能够估计类概率(即,它们具有pre dict_proba 方法),那么您可以告诉Scikit-Learn预测具有最高类概率的类,在所有单个分类器上进行平均。 这被称为软投票。 它通常比硬性投票取得更高的绩效,因为它给予了高度信任的投票更多的权重。

上述代码 estimators 中 voting = ‘soft’ 后,准确率可以提升至 0.912


Bagging 和 Pasting

获得多种分类器的一种方式是使用非常不同的训练算法。 另一种方法是对每个预测器使用相同的训练算法,但是训练集在不同的随机子集上进行训练。 当采样进行替换时,这种方法被称为bagging.

这里写图片描述
当采样是有放回时,这种方法被称为bagging (bootstrap aggregating的缩写)
当采样没有放回时,被称为 pasting。

在 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 ) 
    # 想使用pasting,只需设置bootstrap = False     
    # n_jobs = -1 表示Scikit-Learn使用所有可用的内核
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.92

如果基础分类器可以估计类别概率(即,如果它具有predict_proba()方法),则BaggingClassifier将自动执行软投票而不是硬投票,这与Decision Trees分类器的情况是一样的。

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import numpy as np

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()

这里写图片描述

Bootstrap 在每个预测器被训练的子集中引入了更多的分集,所以 Bagging 结束时的偏差比 Pasting 更高,但这也意味着预测因子最终变得不相关,从而减少了集合的方差。总体而言,Bagging 通常会导致更好的模型,这就解释了为什么它通常是首选的。


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,random_state=42)
bag_clf.fit(X_train, y_train)

>>>print(bag_clf.oob_score_)
0.898666666667

from sklearn.metrics import accuracy_score

y_pred = bag_clf.predict(X_test)
>>>print(accuracy_score(y_test, y_pred))
0.912

对于每个训练实例 oob 决策函数也可通过oob_decision_function_变量来展示。在这种情况下(当基决策器有predict_proba()时)决策函数会对每个训练实例返回类别概率。例如,oob 评估预测第二个训练实例有 60.6% 的概率属于正类(39.4% 属于负类):

>>>bag_clf.oob_decision_function_
array([[ 0.32352941,  0.67647059],
       [ 0.35625   ,  0.64375   ],
       [ 1.        ,  0.        ],
       [ 0.        ,  1.        ],
       [ 0.        ,  1.        ],
       ......

随机贴片与随机子空间(Random Patches and Random Subspaces)

BaggingClassifier也支持采样特征。它被两个超参数max_features和bootstrap_features控制。他们的工作方式和max_samples和bootstrap一样,但这是对于特征采样而不是实例采样。因此,每一个分类器都会被在随机的输入特征内进行训练。

bootstrap_features=True and/or max_features smaller than 1.0 时可以对特征采样
Random Patches method: 对特征和训练实例都采样,
Random Subspaces method:只对特征采样

bag_clf = BaggingClassifier(DecisionTreeClassifier(), 
                            n_estimators=500,bootstrap=False,bootstrap_features=True, max_features=0.5,
                            n_jobs=-1,random_state=42)
bag_clf.fit(X_train, y_train)

y_pred = bag_clf.predict(X_test)
>>>print(accuracy_score(y_test, y_pred))
0.784

随机森林

正如我们所讨论的,随机森林是决策树的一种集成,通常是通过 bagging 方法(有时是 pasting 方法)进行训练,通常用max_samples设置为训练集的大小。与建立一个BaggingClassifier然后把它放入 DecisionTreeClassifier 相反,你可以使用更方便的也是对决策树优化够的RandomForestClassifier(对于回归是RandomForestRegressor)。

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)
print(accuracy_score(y_test, y_pred_rf))

近似于以下这个 bagging 分类器

bag_clf = BaggingClassifier(

DecisionTreeClassifier(splitter="random", max_leaf_nodes=16),
    n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1 )
bag_clf.fit(X_train, y_train)

y_pred = bag_clf.predict(X_test)
print(accuracy_score(y_test, y_pred))
Extremely Randomized Trees 极度随机树

当您在随机森林中生成一棵树时,每个节点只有一个随机的特征子集被考虑用于分裂。通过对每个特征使用随机阈值,而不是搜索最佳可能的阈值(像常规决策树那样),可以使树更加随机。 这种非常随机的森林被简称为极度随机树(简称Extra-Trees)。这种方式更偏向于较低的方差。它还使Extra-Tree比常规的随机森林快得多,因为在每个节点上为每个特征找到最佳的阈值是生成决策树中最耗时的任务之一。

你可以使用 sklearn 的ExtraTreesClassifier来创建一个 Extra-Tree 分类器。他的 API 跟RandomForestClassifier是相同的,相似的, ExtraTreesRegressor 跟RandomForestRegressor也是相同的 API。

我们很难去分辨ExtraTreesClassifier和RandomForestClassifier到底哪个更好。通常情况下是通过交叉验证来比较它们(使用网格搜索调整超参数)。

from sklearn.ensemble import ExtraTreesClassifier

et_clf = ExtraTreesClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1) 
et_clf.fit(X_train, y_train)

y_pred_et = et_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_et))
feature importance

如果你看一个决策树,重要的特征可能会更接近树的根,而不重要的特征往往会更接近树叶(或根本没有)。 因此可以通过计算它在森林中所有树木上出现的平均深度来估计一个特征的重要性。 Scikit-Learn在训练后为每个功能自动计算。 可以使用feature importances变量来访问结果。

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.10134619538
sepal width (cm) 0.0257244435546
petal length (cm) 0.438985740896
petal width (cm) 0.43394362017

Boosting

Boosting,最初称为假设增强,指的是可以将几个弱学习者组合成强学习者的集成方法。对于大多数的提升方法的思想就是按顺序去训练分类器,每一个都要尝试修正前面的分类。现如今已经有很多的提升方法了,但最著名的就是 Adaboost(适应性提升,是 Adaptive Boosting 的简称) 和 Gradient Boosting(梯度提升)

Adaboost

使一个新的分类器去修正之前分类结果的方法就是对之前分类结果不对的训练实例多加关注。这导致新的预测因子越来越多地聚焦于这种情况。这是 Adaboost 使用的技术。

举个例子,去构建一个 Adaboost 分类器,第一个基分类器(例如一个决策树)被训练然后在训练集上做预测,在误分类训练实例上的权重就增加了。第二个分类机使用更新过的权重然后再一次训练,权重更新,以此类推。
这里写图片描述

一旦所有的分类器都被训练后,除了分类器根据整个训练集上的准确率被赋予的权重外,集成预测就非常像Bagging和Pasting了。

序列学习技术的一个重要的缺点就是:它不能被并行化(只能按步骤),因为每个分类器只能在之前的分类器已经被训练和评价后再进行训练。因此,它不像Bagging和Pasting一样。

让我们详细看一下 Adaboost 算法。每一个实例的权重wi初始都被设为1/m第一个分类器被训练,然后他的权重误差率r1在训练集上算出,详见公式 7-1。

公式7-1:第j个分类器的权重误差率

这里写图片描述
其中 yj˜i y j ~ i 是第j个分类器对于第i实例的预测。

分类器的权重 αj α j 随后用公式 7-2 计算出来。其中 η η 是超参数学习率(默认为 1)。分类器准确率越高,它的权重就越高。如果它只是瞎猜,那么它的权重会趋近于 0。然而,如果它总是出错(比瞎猜的几率都低),它的权重会是负数。
这里写图片描述

第 j 个预测器在最终总学习器中的权重,可以看出,当一个预测器准确率<0.5 时,权值为 负数

接下来实例的权重会按照公式 7-3 更新:误分类的实例权重会被提升。

公式7-3 权重更新规则
这里写图片描述

随后所有实例的权重都被归一化(例如被 i=1mwi整除)

最后,一个新的分类器通过更新过的权重训练,整个过程被重复(新的分类器权重被计算,实例的权重被更新,随后另一个分类器被训练,以此类推)。当规定的分类器数量达到或者最好的分类器被找到后算法就会停止。

为了进行预测,Adaboost 通过分类器权重 αj α j 简单的计算了所有的分类器和权重。预测类别会是权重投票中主要的类别。(详见公式 7-4)

公式7-4: Adaboost 分类器
这里写图片描述

Scikit-Learn实际上使用称为SAMME的多类型AdaBoost(代表使用 多指数损失函数的分段加法建模 )。 当单个预测器预测结果只有两个类时,SAMME等同于AdaBoost。 此外,如果预测器可以估计类概率(即,如果它们具有predict_proba()方法),则Scikit-Learn可以使用称为SAMME.R(R代表“Real”)的SAMME变体,其依赖于类概率,通常表现更好。

from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier( 
    DecisionTreeClassifier(max_depth=1), n_estimators=200, algorithm="SAMME", learning_rate=0.5 ) 
ada_clf.fit(X_train, y_train)

y_pred_ada = ada_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_ada))

当单个预测器预测结果只有两个类时,SAMME等同于AdaBoost。 此外,如果预测器可以估计类概率(即,如果它们具有predict_proba()方法),则Scikit-Learn可以使用称为SAMME.R(R代表“Real”)的SAMME变体,其依赖于类概率,通常表现更好。

from sklearn.ensemble import AdaBoostClassifier

adar_clf = AdaBoostClassifier( 
    DecisionTreeClassifier(max_depth=1), n_estimators=400, algorithm="SAMME.R", learning_rate=0.1 ) 
adar_clf.fit(X_train, y_train)

y_pred_adar = adar_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_adar))

梯度提升

与 Adaboost 一样,梯度提升也是通过向集成中逐步增加分类器运行的,每一个分类器都修正之前的分类结果。然而,它并不像 Adaboost 那样每一次迭代都更改实例的权重,这个方法是去使用新的分类器去拟合前面分类器预测的残差 。

让我们通过一个使用决策树当做基分类器的简单的回归例子(回归当然也可以使用梯度提升)。这被叫做梯度提升回归树(GBRT,Gradient Tree Boosting 或者 Gradient Boosted Regression Trees)。首先我们用DecisionTreeRegressor去拟合训练集(例如一个有噪二次训练集):

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) 
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_reg2.predict(X) 
tree_reg3 = DecisionTreeRegressor(max_depth=2) 
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))
>>>y_pred
array([ 0.75026781])
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=(16,16))

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()

这里写图片描述

使用 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 集成:其中左面是一个没有足够树去拟合训练集的树,右面是有过多的树过拟合训练集的树。
这里写图片描述

为了找到树的最优数量,你可以使用early-stop。最简单使用这个技术的方法就是使用staged_predict():它在训练的每个阶段(用一棵树,两棵树等)返回一个迭代器。加下来的代码用 120 个树训练了一个 GBRT 集成,然后在训练的每个阶段验证错误以找到树的最佳数量,最后使用 GBRT 树的最优数量训练另一个集成:

import numpy as np 
from sklearn.model_selection import train_test_split 
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import GradientBoostingRegressor

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)

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()

这里写图片描述

可以通过设置warm_start=True来实现 ,这使得当fit()方法被调用时 sklearn 保留现有树,并允许增量训练。接下来的代码在当一行中的五次迭代验证错误没有改善时会停止训练:

gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True, random_state=42)

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% 随机选择的训练实例上训练。你现在也能猜出来,这也是个高偏差换低方差的作用。它同样也加速了训练。这个技术叫做随机梯度提升。


Stacking

Stacking(stacked generalization 的缩写)。这个算法基于一个简单的想法:不使用琐碎的函数(如硬投票)来聚合集合中所有分类器的预测,我们为什么不训练一个模型来执行这个聚合?下图展示了这样一个在新的回归实例上预测的集成。底部三个分类器每一个都有不同的值(3.1,2.7 和 2.9),然后最后一个分类器(叫做 blender 或者 meta learner )把这三个分类器的结果当做输入然后做出最终决策(3.0)。

这里写图片描述

为了训练这个 blender ,一个通用的方法是采用保持集。让我们看看它怎么工作。首先,训练集被分为两个子集,第一个子集被用作训练第一层。
这里写图片描述

接下来,第一层的分类器被用来预测第二个子集(保持集)(详见 7-14)。这确保了预测结果很“干净”,因为这些分类器在训练的时候没有使用过这些事例。现在对在保持集中的每一个实例都有三个预测值。我们现在可以使用这些预测结果作为输入特征来创建一个新的训练集(这使得这个训练集是三维的),并且保持目标数值不变。随后 blender 在这个新的训练集上训练,因此,它学会了预测第一层预测的目标值。

这里写图片描述

显然我们可以用这种方法训练不同的 blender (例如一个线性回归,另一个是随机森林等等):我们得到了一层 blender 。诀窍是将训练集分成三个子集:第一个子集用来训练第一层,第二个子集用来创建训练第二层的训练集(使用第一层分类器的预测值),第三个子集被用来创建训练第三层的训练集(使用第二层分类器的预测值)。以上步骤做完了,我们可以通过逐个遍历每个层来预测一个新的实例。
这里写图片描述

sklearn 并不直接支持 stacking ,但是你自己组建是很容易的(看接下来的练习)。或者你也可以使用开源的项目例如 brew (网址为 https://github.com/viisar/brew


Q&A

1.如果您在完全相同的训练数据上训练了五个不同的模型,并且都达到了95%的精度,那么您是否有机会将这些模型结合起来以获得更好的结果? 如果是这样,怎么样? 如果没有,为什么?
答:有,当元学习器之间存在差异时,使用集成学习仍能取得比单个学习器更好的效果。如果模型差别很大(例如,SVM分类器,决策树分类器,Logistic回归分类器等),则效果更好。

2.硬投票和软投票分类器有什么区别?
答:硬投票,每个元学习器的投票权值相等;软投票软投票分类器计算每个类别的平均估计类别概率,并以最高概率挑选类别。这给予了高置信度的投票更多的权重,往往表现更好,但只有每个分类器能够估计类概率(例如,对于Scikit-Learn中的SVM分类器,您必须设置probability = True),它才有效。

3.是否有可能通过分布在多台服务器上来加快对bagging的训练? pasting , boosting,随机森林, 堆栈泛化呢?
答:除了boosting,其余方法均可以使用分布式计算来提升训练速度,因为它们每个元学习器的训练都是独立的(堆栈泛化中处于同一层的学习器时独立的),是可以并行的。而 boosting 中,下一个元学习器需要使用上一个学习器训练并evaluation之后,调整权值才可以开始训练,时间上是串行的,因此很难用分布式计算提升速度。

4.包外评估有什么好处?
答:无需额外划分测试集,之间使用包外数据作为测试集。

5.什么使Extra-Trees比常规的随机森林更随机? 这种多余的随机性如何帮助? 额外的树比常规的随机森林慢吗?
答:extra-trees 通过对每个特征使用随机阈值,而不是搜索最佳可能的阈值(像常规决策树那样)。

6.如果你的AdaBoost对训练数据欠拟合了,你应该调整哪些超参数?
答:可以尝试增加估算器的数量或减少基本估算器的正则化超参数,也可以尝试稍微提高学习率。

7.如果您的gradient boosting 对数据集过拟合了,您是否应该增加或减少学习速率?
答:降低学习率或者使用early-stop。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值