In [1]:
from __future__ import division, print_function, unicode_literals

import numpy as np
import os

np.random.seed(42)

%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12

如果我们合并了一组分类器的预测,我们就会得到一个比单一分类器更好的预测结果。这一组分类器就叫做集成,这一技术叫做集成学习,而一个集成学习算法就叫做集成方法。

一般,在项目快结束的时候,我们使用集成算法,一旦建立了一些好的分类器,就合并它们成为一个更好的分类器。

投票分类

In [3]:
heads_proba = 0.51
coin_tosses = (np.random.rand(10000, 10) < heads_proba).astype(np.int32)
# 每行代表一次投币,每一列代表一种投币的情景
cumulative_heads_ratio = np.cumsum(coin_tosses, axis=0) / np.arange(1, 10001).reshape(-1, 1)
plt.plot(cumulative_heads_ratio)
plt.axis([0, 10000, 0.42, 0.58])
Out[3]:
[0, 10000, 0.42, 0.58]
In [20]:
# 以 moons 数据集为例,我们使用三种不同的分类器来进行分类
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
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)

log_clf = LogisticRegression(random_state=42)
rnd_clf = RandomForestClassifier(random_state=42, n_estimators=100)
svm_clf = SVC(random_state=42, gamma='scale')
voting_clf = VotingClassifier(
    estimators=[
        ('lr', log_clf),
        ('rf', rnd_clf),
        ('svc', svm_clf)
    ],
    voting='hard'
)
voting_clf.fit(X_train, y_train)
Out[20]:
VotingClassifier(estimators=[('lr', LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=42, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)), ('rf', RandomFor...f',
  max_iter=-1, probability=False, random_state=42, shrinking=True,
  tol=0.001, verbose=False))],
         flatten_transform=None, n_jobs=1, voting='hard', weights=None)
In [21]:
# 验证一下训练集上的准确率
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.896
SVC 0.888
VotingClassifier 0.904

在正常使用中,我们可以以最高的类概率来预测这个类,平均在所有的分类器上,这样的投票叫做软投票,然而 SVC 没有默认的 predict_proba,需要一些设置。

In [36]:
log_clf = LogisticRegression(random_state=42)
rnd_clf = RandomForestClassifier(random_state=42, n_estimators=100)
# 令 svm 使用交叉验证去预测类别概率。
svm_clf = SVC(probability=True, random_state=42, gamma='scale')

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='soft')
voting_clf.fit(X_train, y_train)
Out[36]:
VotingClassifier(estimators=[('lr', LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=42, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)), ('rf', RandomFor...bf',
  max_iter=-1, probability=True, random_state=42, shrinking=True,
  tol=0.001, verbose=False))],
         flatten_transform=None, n_jobs=1, voting='soft', weights=None)
In [37]:
# 软投票结果
# 它通常比硬投票表现更好,因为它给予高自信的投票更大的权重
# 使用的是 predict_proba ,然后以最高的类概率来预测这个类
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.896
SVC 0.888
VotingClassifier 0.912

bagging 和 pasting

我们可以通过使用不同算法去得到一些不同的分类器。

另一种是对每一个分类器都是用相同的训练算法,但是在不同的训练集上训练他们,有放回被称为装袋 bagging ,无放回别成为黏贴 pasting 。

bagging 和 pasting 都允许在多个分类器上对训练集进行多次采样,但是只有 bagging 允许在同一种分类器上对训练集进行多次采样。

当所有的分类器被训练完成,集成可以对所有的分类器结果进行简单聚合来对新的实例进行预测,聚合函数通常对分类是统计模式,对回归是平均模式。通常,集成结果有一个相似的偏差,但是比原来有一个更小的方差。

sklearn 中的 bagging 和 pasting

In [40]:
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
# 它训练了一个500个决策树分类器的集成,每一个都是在数据集上有放回采样 100 个训练实例下进行训练
# 注意 BaggindClassifier 如果是对于拥有 predict_proba 的分类器,将优先使用软投票
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), 
    n_estimators=500,
    max_samples=100, 
    bootstrap=True,  # 设置 false 将使用 pasting 模式
    n_jobs=-1,  # 使用空闲核
    random_state=42
)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
In [41]:
# 集成学习的效果:
from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
0.904
In [42]:
# 普通决策树的效果:
tree_clf = DecisionTreeClassifier(random_state=42, n)
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
In [63]:
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()]
    custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
    # 边界
    y_pred = clf.predict(X_new).reshape(x1.shape)
    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)
#     # 显示分割线
#     y_proba = clf.predict_proba(X_new)[:, 1].reshape(x1.shape)
#     contour = plt.contour(x1, x2, y_proba, alpha=0.3, cmap=plt.cm.brg)
    
    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)
In [64]:
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 默认是有放回的采样 m 个实例,其中 m 是训练集的大小,比如平均 63% 的训练样例被每个分类器采样,剩下 37% 没有被采样的训练实例就是 out-of-bag 实例。

换个角度看,因为我们没有用到过 oob 实例,所以可以在这些实例上进行评估,而不需要单独的验证集或交叉验证,可以用 oob 来作为测试集。

In [65]:
# 使用 oob 作为测试集
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(),
    n_estimators=500,
    bootstrap=True,
    n_jobs=-1,
    oob_score=True  # 自动评估
)
bag_clf.fit(X_train, y_train)
# 使用 oob_score_ 来查看评估结果
bag_clf.oob_score_
Out[65]:
0.9013333333333333

随机贴片与随机子空间

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

对训练实例和特征的采样叫做随机贴片。

如果保留所有的训练实例,但是对特征采样,叫做随机子空间。这将导致趋向欠拟合。

随机森林

In [66]:
# 随机森林是决策树的一种集成,通常使用 bagging 方法。
# 下面设置的随机森林带有 500 颗树,每个被限制为 16 个叶子节点,使用空闲核。
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)
In [67]:
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred_rf)
Out[67]:
0.92

随机森林在树生长的时候引入了额外的随机,与节点分裂找最好的分裂特征不同,它在随机的特征集中找最好的特征,导致树的差异性,同时向欠拟合方向进发,生成一个更好的模型。

In [70]:
# 这里的 bagging 大致相当于上面的 random_forest
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_bf = bag_clf.predict(X_test)
In [71]:
accuracy_score(y_test, y_pred_bf)
Out[71]:
0.912

极端随机树

在随机森林上生长树时,在每个节点分裂时只考虑随机特征集上的特征,通过对特征使用随机阈值,这让树更加随机。

这种极端随机的树称为 extremely randomized trees 极端随机树,或者叫 extra-tree 。再一次用高偏差换低方差,向欠拟合方向移动。

ExtraTreesClassifier 和 RandomForestClassifier 的 api 基本相同,我们很难判断哪个更好,通常使用交叉验证比较它们,使用网格搜索调整超参数。

特征重要度

非常显然的事情是,在决策树中,重要的特征在靠近根部的地方,而因此我们可以通过计算一个特征在森林的全部树种出现的平均深度来预测一个特征的重要性。

sklearn 在训练后会自动计算每个特征的重要度,可以通过 featureimportances 变量来查看结果。

In [78]:
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
In [80]:
# 配置代理
import socket
import socks
SOCKS5_PROXY_HOST = '127.0.0.1' 
SOCKS5_PROXY_PORT = 1086
default_socket = socket.socket
socks.set_default_proxy(socks.SOCKS5, SOCKS5_PROXY_HOST, SOCKS5_PROXY_PORT) 
socket.socket = socks.socksocket
In [81]:
# 在 mnist 上训练随机森林,然后画出每个像素的重要性
# 获取 mnist
from six.moves import urllib
from sklearn.datasets import fetch_mldata
try:
    mnist = fetch_mldata('MNIST original')
except urllib.error.HTTPError as ex:
    print("Could not download MNIST data from mldata.org, trying alternative...")
    from scipy.io import loadmat
    mnist_alternative_url = "https://github.com/amplab/datascience-sp14/raw/master/lab7/mldata/mnist-original.mat"
    mnist_path = "./mnist-original.mat"
    response = urllib.request.urlopen(mnist_alternative_url)
    with open(mnist_path, "wb") as f:
        content = response.read()
        f.write(content)
    mnist_raw = loadmat(mnist_path)
    mnist = {
        "data": mnist_raw["data"].T,
        "target": mnist_raw["label"][0],
        "COL_NAMES": ["label", "data"],
        "DESCR": "mldata.org dataset: mnist-original",
    }
    print("Success!")
Could not download MNIST data from mldata.org, trying alternative...
Success!
In [83]:
rnd_clf = RandomForestClassifier(random_state=42)
rnd_clf.fit(mnist["data"], mnist["target"])
def plot_digit(data):
    image = data.reshape(28, 28)
    plt.imshow(image, cmap = matplotlib.cm.hot,
               interpolation="nearest")
    plt.axis("off")

plot_digit(rnd_clf.feature_importances_)
cbar = plt.colorbar(ticks=[rnd_clf.feature_importances_.min(), 
                           rnd_clf.feature_importances_.max()])
cbar.ax.set_yticklabels(['Not important', 'Very important'])
plt.show()
/Users/ronnie/.pyenv/versions/3.6.4rc1/envs/chatbot/lib/python3.6/site-packages/sklearn/ensemble/forest.py:248: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)

提升

boosting ,指的是可以将几个弱学习者组合成强学习者的集成方法,对于大多数的提升方法的思想是按顺序去训练分类器,每一个都要尝试修正前面的分类,现如今已经有许多的提升方法了,但最著名的就是 Adaboost。

Adaboost

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

比如:第一个是决策树,被训练后在训练集上做预测,在误分类的训练实例上的权重就增加了;第二个分类器使用之前更新过得权重然后再一次更新,权重更新,以此类推。

In [86]:
from sklearn.ensemble import AdaBoostClassifier
ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1),
    n_estimators=200,
    learning_rate=0.5,
    random_state=42
)
ada_clf.fit(X_train, y_train)
Out[86]:
AdaBoostClassifier(algorithm='SAMME.R',
          base_estimator=DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=1,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best'),
          learning_rate=0.5, n_estimators=200, random_state=42)
In [87]:
plot_decision_boundary(ada_clf, X, y)
In [94]:
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, gamma='scale')
        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()

序列学习技术的一个重要缺点是:它不能被并行,只能按步骤执行。每个分类器只能根据前面的分类器来继续训练。因此,它不像 bagging 和 pasting 一样。

$$r_{j}=\frac{\sum_{i=1,\widehat{y}_{j}^{i}\neq y^{(i)}}^{m}w^{(i)}}{\sum_{i=1}^{m}w^{(i)}}$$

这表示第 j 个分类器的权重误差率,其中 hat y_j^i 是第 j 个分类器对于第 i 个实例的预测。

分类器的权重 j 随后用下面的公式表示, 其中 n 是超参数学习率,默认是 1 。分类器准确率越高,它的权重就越高,如果它只是瞎猜,那么它的权重会趋向于 0 ,然而,如果它总是错误,甚至会出现负数。

$$\alpha _{j}=\eta log\frac{1-r_{j}}{r_{j}}$$

接下来实例的权重会根据下面的公式更新,误分类的实例权重会被提升。对于 i = 1,2,3,...

$$w^{(i)}=\left\{\begin{matrix}w^{(i)}if \widehat{y}_{j}^{(i)}=y^{(i)} \\ w^{(i)}exp(\alpha _{j})if \widehat{y}_{j}^{(i)}\neq y^{(i)} \end{matrix}\right.$$

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

为了进行预测, Adaboost 通过分类器权重 j 简单的计算了所有的分类器和权重,预测类别会是权重投票中主要的类别。

$$\widehat{y}(x)=argmax_{k}\sum _{j=1,\widehat{y}_{j}=k}^{N}\alpha _{j}$$

sklearn 通常使用 Adaboost 的多分类版本 SAMME ,如果只有两类,那么 SAMME 和 Adaboost 是相同的。如果分类器可以预测类别概率,使用 SAMME.R 的变量,R 代表 Real 。依赖于类别概率通常比依赖于分类器好。

梯度提升

它不像 Adaboost 那样每一次迭代都更改实例的权重,这个方法是去使用新的分类器去拟合前面分类器预测的残差。

我们可以通过一个使用决策树单做基分类器的简单的回归例子,这被称作梯度提升回归树 GBRT Gradient Tree Boosting 或者 Gradient Boosted Regression Trees 。首先用 DecisionTreeRegressor 去拟合训练集。

In [97]:
from sklearn.tree import DecisionTreeRegressor
np.random.seed(42)
X = np.random.rand(100, 1) - 0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)
tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)
Out[97]:
DecisionTreeRegressor(criterion='mse', max_depth=2, max_features=None,
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None, min_samples_leaf=1,
           min_samples_split=2, min_weight_fraction_leaf=0.0,
           presort=False, random_state=42, splitter='best')
In [99]:
# 现在在第一个分类器的残差上训练第二个分类器
y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2)
Out[99]:
DecisionTreeRegressor(criterion='mse', max_depth=2, max_features=None,
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None, min_samples_leaf=1,
           min_samples_split=2, min_weight_fraction_leaf=0.0,
           presort=False, random_state=42, splitter='best')
In [100]:
# 随后在第二个分类器的残差上训练第三个分类器
y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3)
Out[100]:
DecisionTreeRegressor(criterion='mse', max_depth=2, max_features=None,
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None, min_samples_leaf=1,
           min_samples_split=2, min_weight_fraction_leaf=0.0,
           presort=False, random_state=42, splitter='best')
In [101]:
X_new = np.array([[0.8]])
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
y_pred
Out[101]:
array([0.75026781])
In [102]:
# 使用下面的 GBRT 就是上面的决策树
from sklearn.ensemble import GradientBoostingRegressor
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)
print(gbrt.predict(X_new))
[0.75026781]
In [103]:
# learning_rete 确立了每个树的贡献,如果设置一个很小的树,那么需要更多的树去拟合。
# 但是通常,这样的拟合效果更好,这个正则化技术叫做 shrinkage
gbrt_slow = GradientBoostingRegressor(max_depth=2, n_estimators=200, learning_rate=0.1, random_state=42)
gbrt_slow.fit(X, y)
print(gbrt_slow.predict(X_new))
[0.74398943]
In [104]:
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)
In [105]:
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()

早停技术

最简单使用这个技术的方法是使用 staged_predict() ,它在训练的每个阶段返回一个迭代器,下面的代码用 120 个树来训练一个 GBRT 集成,然后在训练的每个阶段验证错误以找到最佳数量,最后使用 GBRT 的最优数量训练另一个集成:

In [106]:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# 仍然是 moons 数据集
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)]
best_n_estimators = np.argmin(errors)
gbrt_best = GradientBoostingRegressor(max_depth=2, n_estimators=best_n_estimators, random_state=42)
gbrt_best.fit(X_train, y_train)
Out[106]:
GradientBoostingRegressor(alpha=0.9, criterion='friedman_mse', init=None,
             learning_rate=0.1, loss='ls', max_depth=2, max_features=None,
             max_leaf_nodes=None, min_impurity_decrease=0.0,
             min_impurity_split=None, min_samples_leaf=1,
             min_samples_split=2, min_weight_fraction_leaf=0.0,
             n_estimators=55, n_iter_no_change=None, presort='auto',
             random_state=42, subsample=1.0, tol=0.0001,
             validation_fraction=0.1, verbose=0, warm_start=False)
In [110]:
plt.figure(figsize=(11, 4))
plt.subplot(121)
plt.plot(errors, "b.-")
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)" % best_n_estimators, fontsize=14)

plt.show()

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

In [114]:
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
print(gbrt.n_estimators)
print(min_val_error)
61
0.002712853325235463

GBRT 可以设置 subsample ,如果 0.25 ,那么每个树在 25% 的随机实例上训练,这也是个高偏差换低方差的作用,这个技术叫做随机梯度提升。

Stacking

最后的一个集成方法是 Stacking ,是 stacked generalization 的缩写。这个算法基于一个简单的想法,不使用琐碎的函数来聚合集合中所有分类器的预测,我们为什么不训练一个模型来这行这个聚合?

我们使用一个分类器,叫做 blender 或者 meta learner 来把三个分类器的结果当做输入做出最后的决策。

要训练这个 blender ,我们需要采用保持集,首先,训练集被分成两个自己,第一个子集被训练第一层,第一层的分类器被用来预测第二个子集(保持集)。这确保了预测结果是干净的,因为这些分类器在训练的时候没有使用过这些事例。现在对保持集中每一个实例都有三个预测值。

现在可以使用这些预测结果作为输入特征来创建一个新的训练集,这使得这个训练集是三维的,当然保持目标数值不变。随后 blender 在这个新的训练集上训练,因此,它学会了预测第一层预测的目标。

因此层数可以叠加,如果我们需要预测,可以遍历每层来得到答案。