本文参考了机器学习实战:基于Scikit-Learn和Tensorflow一书。

集成学习:聚合一组预测器的预测,得到的预测结果会比单个预测器要好。这样的一组预测称为集成,这种技术也被称为集成学习,相应的学习算法称为集成方法。
随机森林:基于训练集的不同随机子集进行训练一组决策树分类器,预测时,根据获得的所有树的各自预测,给票数最多的类别作为预测结果。这样的一组决策树称为随机森林。

投票分类器

假设已经训练好多种分类器:
要创建出一个更好的分类器,最简单的办法就是聚合每个 分类器的预测,然后将得票最多的结果作为预测类别。这种大多数投 票分类器被称为硬投票分类器
从上图可以看出,通过多个学习器的集成可以获得较好的结果。当预测器尽可能互相独立时,集成方法的效果最优。获得多 种分类器的方法之一就是使用不同的算法进行训练。这会增加它们犯不同类型错误的机会,从而提升集成的准确率。

from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

log_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC()
voting_clf = VotingClassifier(
    estimators=[
        ('lr', log_clf),
        ('rf', rnd_clf),
        ('sf', svm_clf)
    ],
    voting='hard'
)
X, y = (make_moons())
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
voting_clf.fit(X_train, y_train)

# 查看每个分类器在测试集上的准确率
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.95
RandomForestClassifier 1.0
SVC 1.0
VotingClassifier 1.0

软投票法:如果分类器可以估算出类别的概率(predict_proba()方法),将概率在所有单个分类器上平 均,然后给出平均概率最高的类别作为预测。它比硬投票法的表现更优,因为它给予那些高 度自信的投票更高的权重。修改为voting=“hard”,SVC类则需要将其超参数probability设置为 True。

bagging和pasting

获取不同的分类器的另一种方法是在不同的训练集随机子集进行训练。如果采样时样本放回,这种方法叫做bagging(bootstrap aggregating,自举汇聚法),采样是放回,这种方法叫做pasting。
聚合函数通常是统计法用于分类,平均法用于回归。每个预测器单独的偏差都高于原始训练集的偏差,通过聚合,同时降低了方差与偏差。与直接在原始训练集上的单个预测器相比,集成的偏差相近,但是方差更低。

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,
                            max_samples=0.1, bootstrap=True, n_jobs=-1)
# 500个决策树分类器,每次选10%个实例,
# 有放回,n_job=-1表示可以使用所有CPU内核

bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

如果基础分类器能够估算类别概率(predict_proba()方法),比如决策树分类器,那么BaggingClassifier 自动执行的就是软投票法而不是硬投票法。
由上图可以看出,集成预测的泛化效果很可能会比单独的决策 树要好一些:二者偏差相近,但是集成的方差更小(两边训练集上的 错误数量差不多,但是集成的决策边界更规则)。
由于自助法给每个预测器的训练子集引入了更高的多样性,所以 最后bagging比pasting的偏差略高,但这也意味着预测器之间的关联 度更低,所以集成的方差降低。

包外评估

由于使用bagging,有些实例可能被多次采样,有些则根本不采样。BaggingClassifier默认采样m 个训练实例,然后放回样本,m是训练集的大小。这意味着,平均只有63%的训练实例被被采样。其他37%未被采样的称为包外实例。对所有训练器,这37%则是不同的。
因此,我们可以用包外实例对每个预测器进行评估,而不需单独的验证集或交叉验证。对评估结果平均,就可以得到对集成的评估。

bag_clf=BaggingClassifier(DecisionTreeClassifier(),n_estimators=500,
                          bootstrap=True,n_jobs=-1,oob_score=True
)
bag_clf.fit(X_train,y_train)  # 软间隔
print(bag_clf.oob_score_)   #0.925 评估分数

from sklearn.metrics import  accuracy_score

y_pred=bag_clf.predict(X_test)
print(accuracy_score(y_test,y_pred))  #0.85

print(bag_clf.oob_decision_function_)   # 决策函数返回每个实例的类别概率
[[0.03684211 0.96315789]
 [0.         1.        ]
 [0.         1.        ]
 [0.         1.        ]
.......
 [0.39366516 0.60633484]
 [0.07792208 0.92207792]
 [1.         0.        ]]

随机森林

随机森林是决策树的集成,可以使用RandomForestClassifier或个RandomForestRegressorRandomForestClassifier具有DecisionTreeClassifier 的所有超参数,以及BaggingClassifier的所有超参数,前者用来控制 树的生长,后者用来控制集成本身。
随机森林在树的生长上引入了更多的随机性:分裂节点时不再是 搜索最好的特征,而是在一个随机生成的特征子集里 搜索最好的特征。这导致决策树具有更大的多样性,用更 高的偏差换取更低的方差,总之,还是产生了一个整体性能更优的模 型。

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)


bag_clf=BaggingClassifier(
    DecisionTreeClassifier(splitter='random',max_leaf_nodes=16),
    n_estimators=500,n_jobs=-1,max_samples=1.0,bootstrap=True
)
极端随机森林

随机森林在单棵树的生长中,每个节点在分裂时仅考虑到一个随机子集所包含的特征,如果我们对每个特征使用随机阈值,而不是搜索得到的最佳阈值,可能让决策树生长的更随机。这种极端随机的决策树组成的森林称为极端随机树的集成。它也是,以更高大的偏差换取更低的方差。在训练时,极端随机树比常规随机森林要快很多,因为在每个节点上找到每个特征的最佳阈值是决策树生长中最耗时的任务之一。
ExtraTreesClassifier可以创建一个极端随机树分类器。和RandomForestClassifie比较,可以使用交叉验证进行比较。

特征的重要性

在决策树中,重要的特征更可能出现在靠近根节点的位置,而不重要的特征则出现在更靠近叶节点的位置。计算一个特征在森林中的所有树的平均长度,可以估算一个特惠总能的重要性。以鸢尾花数据集,

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.09864227855299668
sepal width (cm) 0.023630157588240004
petal length (cm) 0.44677436540285886
petal width (cm) 0.4309531984559042

当执行特征选择时,可以使用随机森林来选择。

提升法

提升法(Boosting,最初被称为假设提升)是指可以将几个弱学 习器结合成一个强学习器的任意集成方法。大多数提升法的总体思路是循环训练预测器,每一次对其前序做出一些改正。

AdaBoost

新预测器对其前序进行纠正的方法之一,就是更多的关注前序拟合不足的训练实例。
要构建一个AdaBoost分类器,首先需要训练一个基础分类 器(比如决策树),用它对训练集进行预测。然后对错误分类的训练 实例增加其相对权重,接着,使用这个最新的权重对第二个分类器进 行训练,然后再次对训练集进行预测,继续更新权重,并不断循环向 前。
以卫星数据集,下图是5个连续的预测器的决策边界,每个预测器都是要了RBF核函数的高度正则化的SVM分类器。右图学习率减半,以每次迭代仅提升一般错误分类的实例权重。
每个实例初始权重w(i)为1/m。

  1. 第j个预测器的加权误差率
    r j = <munderover> i = 1 <mover> y Λ </mover> ( i ) y ( i ) m </munderover> w ( i ) <munderover> i = 1 m </munderover> w ( i ) <mover> y Λ </mover> ( i ) i {{\rm{r}}_j} = {{\sum\limits_{i = 1,{{\mathop y\limits^\Lambda }^{(i)}} \ne {y^{(i)}}}^m {{w^{(i)}}} } \over {\sum\limits_{i = 1}^m {{w^{(i)}}} }},其中{{\mathop y\limits^\Lambda }^{(i)}}是第就个预测器对第i个实例的预测 rj=i=1mw(i)i=1yΛ(i)=y(i)mw(i)yΛ(i)i
  2. 预测器权重
    α j = η log 1 r j r j {\alpha _{\rm{j}}} = \eta \log {{1 - {r_j}} \over {{r_j}}} αj=ηlogrj1rj
    预测器准确率越高,其权重就也高。如果只是随机猜测,其权重接近于0。如果大部分情况下都是错的(准确率比随机猜测还低),那么权重为负。
  3. 权重更新规则
    然后将所有实例的权重归一化,即除以 <munderover> i = 1 m </munderover> w ( i ) {\sum\limits_{i = 1}^m {{w^{(i)}}} } i=1mw(i)
    使用更新后的权重训练一个新的预测器,然后重复整个过 程。当到达所需数量的预测器,或得到完美的预测器时, 算法停止。
  4. AdaBoost预测
    即简单计算所有预测器的预测结果,并使用预测器权重重αj进行加权。得到大多数加权投票的 类别就是预测器给出的预测类别。
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)

Scikit-Learn使用的其实是AdaBoost的一个多分类版本,叫作 SAMME(基于多类指数损失函数的逐步添 加模型)。当只有两个类别时,SAMME即等同于AdaBoost。此外, 如果预测器可以估算类别概率(即具有predict_proba()方法), Scikit-Learn会使用一种SAMME的变体,称为SAMME.R(R代 表“Real”),它依赖的是类别概率而不是类别预测,通常表现更好。
如果你的AdaBoost集成过度拟合训练集,你可以试试减少估 算器数量,或是提高基础估算器的正则化程度。

梯度提升

它不是 像AdaBoost那样在每个迭代中调整实例权重,而是让新的预测器针对 前一个预测器的残差进行拟合。

from sklearn.tree import DecisionTreeClassifier

tree_reg1 = DecisionTreeClassifier(max_depth=2)
tree_reg1.fit(X_train, y_train)

y2 = y - tree_reg1.predict(X_train)
tree_reg2 = DecisionTreeClassifier(max_depth=2)
tree_reg2.fit(X_train, y2)

y3 = y2 - tree_reg2.predict(X_train)
tree_reg3 = DecisionTreeClassifier(max_depth=2)
tree_reg3.fit(X_train, y3)

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

左侧表示这三棵树单独的预测,右侧表示集成的预测。
相同的方式在Scikit-Learn中:

from sklearn.ensemble import GradientBoostingClassifier

grbt = GradientBoostingClassifier(max_depth=2, n_estimators=3, learning_rate=1.0)
grbt.fit(X_train, y_train)

超参数learning_rate对每棵树的贡献进行缩放。如果你将其设置 为低值,比如0.1,则需要更多的树来拟合训练集,但是预测的泛化 效果通常更好。这是一种被称为收缩的正则化技术。下图中左侧拟合不足,右侧则过度训练。
要找到树的最佳数量,可以使用早期停止法。简单的实现方法就是使用staged_predict()方法:在训练的每个阶段都对集成的预测放回一个迭代器。

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_test,y_train,y_test=train_test_split(X,y)

gbrt=GradientBoostingRegressor(max_depth=2,n_estimators=120)
grbt.fit(X_train,y_train)

errors=[mean_squared_error(y_test,y_pred) for y_pred in grbt.staged_predict(X_test)]
bst_n_estimators=np.argmin(errors)

gbrt_best= GradientBoostingRegressor(max_depth=2, n_estimators=bst_n_estimators)
gbrt_best.fit(X_train, y_train)

还可以不训练大量的树,设置warm_start=True,当fit方法被调用时,Scikit会保留现有的树,从而允许增量训练。

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

min_val_error = float('inf')
error_going_up = 0
for n_estimators in range(1, 120):
    grbt.n_estimators = n_estimators
    gbrt.fit(X_train, y_train)
    y_pred = gbrt.predict(X_test)
    val_error = mean_squared_error(y_test, 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

GradientBoostingRegressor类还可以支持超参数subsample,指定 用于训练每棵树的实例的比例。例如,如果subsample=0.25,则每棵 树用25%的随机选择的实例进行训练。现在你可以猜到,这也是用更 高的偏差换取了更低的方差,同时在相当大的程度上加速了训练过程。这种技术被称为随机梯度提升

堆叠法

又称层叠泛化法:训练一个模型来执行聚合,而不是简单的函数来聚合集成中所有预测器的预测。
训练混合器的常用方法是使用留存集
用第一层的预测器在第二个(留存)子集上进行预测。对于留存集中的每个实例都有三个预测值。使用这些预测值作为输入特征,创建一个新的训练集,并保留目标值。在这个新训练集上训练混合器。
事实上,通过这种方法可以训练多种不同的混合器(例如,一个 使用线性回归,另一个使用随机森林回归,等等):于是我们可以得 到一个混合器层。诀窍在于将训练集分为三个子集:第一个用来训练 第一层,第二个用来创造训练第二层的新训练集(使用第一层的预 测),而第三个用来创造训练第三层的新训练集(使用第二层的预 测)。一旦训练完成,我们可以按照顺序遍历每层来对新实例进行预 测,如图7-15所示。
Scikit-Learn不直接支持堆叠,你可以直接堆叠。或者,你也可以使用开源 的实现方案,例如brew