Skip to content

1.11. 集成学习方法(Ensemble methods)

集成方法的目标是把使用多个给定学习算法构建的基本估计器的预测结果结合起来,从而获得比单个基本估计器更好的泛化能力/鲁棒性。

集成学习方法分通常分为两种:

  • 平均方法(averaging methods)中,驱动原则是首先独立地构建若干个估计器,然后对它们的预测结果取平均。在平均意义上,组合得到的估计器通常优于任意一个基本估计器,因为它的方差被减小了。

例如: Bagging方法由随机树组成的森林(Forests of randomized trees)

  • 相比之下,在增强方法(boosting methods)中,基本估计器是按顺序构建的,其中每一个基本估计器都致力于减小组合估计器的偏差。这种方法的动机是通过组合若干个弱模型来产生一个强大的集成模型。

例如: AdaBoost梯度提升树(Gradient Tree Boosting)

1.11.1. Bagging元估计器(meta-estimator)

在集成算法中,bagging方法会在原始训练集的随机子集上构建黑盒估计器(black-box estimator)的多个实例,然后把这些估计器的预测结果结合起来形成最终的预测结果。该方法通过在构建模型的过程中引入随机性,来减少基本估计器的方差(例如,减小决策树的方差)。在多数情况下,bagging方法提供了一种非常简单的方式来对单一模型进行改进,而无需修改底层算法。因为bagging方法可以减小过拟合,所以通常在强分类器和复杂模型上使用时表现的很好(例如,完全决策树(fully developed decision trees)),相比之下boosting方法则在弱模型上表现更好(例如,浅层决策树(shallow decision trees))。

Bagging方法有多种风格,但它们之间的区别主要在于它们抽取训练集的随机子集的方法:

  • 如果抽取数据集的随机子集作为样本的随机子集,该方法称为粘贴(Pasting)[B1999]
  • 如果抽取样本是有放回的,该方法称为Bagging[B1996]
  • 如果抽取数据集的随机子集作为特征的随机子集,该方法称为“随机子空间(Random Subspaces)” [H1998]
  • 最后,如果是用样本和特征抽取的子集来构建基本估计器,该方法称为随机补丁(Random Patches)[LG2012]

在scikit-learn中,bagging方法是由统一的BaggingClassifier元估计器(resp.BaggingRegressor)提供的,用户指定的基本估计器以及随机子集的选择策略的参数作为输入。特别需要指出的是,max_samplesmax_features 控制着子集的大小(对于样本和特征),而bootstrapbootstrap_features控制着样本和特征的抽取是有放回还是无放回的。当使用样本子集时,通过设置oob_score=True可以使用袋外(out-of-bag)样本来评估泛化精度。下面代码说明了如何构造一个KNeighborsClassifier估计器的bagging集成实例,每一个基本估计器都建立在50%的样本随机子集和50%的特征随机子集上。

>>> from sklearn.ensemble import BaggingClassifier
>>> from sklearn.neighbors import KNeighborsClassifier
>>> bagging = BaggingClassifier(KNeighborsClassifier(),
...                             max_samples=0.5, max_features=0.5)

案例:

参考文献:

[B1999] L. Breiman, “Pasting small votes for classification in large databases and on-line”, Machine Learning, 36(1), 85-103, 1999.

[B1996] L. Breiman, “Bagging predictors”, Machine Learning, 24(2), 123-140, 1996.

[H1998] T. Ho, “The random subspace method for constructing decision forests”, Pattern Analysis and Machine Intelligence, 20(8), 832-844, 1998.

[LG2012] G. Louppe and P. Geurts, “Ensembles on Random Patches”, Machine Learning and Knowledge Discovery in Databases, 346-361, 2012.

1.11.2. 由随机树组成的森林(Forests of randomized trees)

sklearn.ensemble模块包括两种基于随机决策树的平均算法:RandomForest算法和Extra-Trees算法。两种算法都是专为树设计的扰动与组合技术(perturb-and-combine techniques)[B1998]。这意味着通过在分类器构造中引入随机性来创建多样化的分类器集。集成分类器的预测结果就是所有单个分类器预测结果的平均值。

与其他分类器一样,森林分类器(forest classifiers)必须要在两个数组上进行拟合:一个是用于训练样本的形状为[n_samples, n_features]的稠密或稀疏的X数组,另一个是与训练数据对应的目标变量(如类标签)的形状为[n_samples]的Y数组:

>>> from sklearn.ensemble import RandomForestClassifier
>>> X = [[0, 0], [1, 1]]
>>> Y = [0, 1]
>>> clf = RandomForestClassifier(n_estimators=10)
>>> clf = clf.fit(X, Y)

决策树一样,由树构成的森林也扩展到支持多输出问题 (如果Y是形状为[n_samples, n_outputs]数组)。

1.11.2.1. 随机森林(Random Forests)

在随机森林中(参见RandomForestClassifierRandomForestRegressor类),每棵树构建时的样本都是由训练集经过有放回抽样得来的(例如,自助采样法(bootstrap sample))。

此外,在树的构造过程中拆分每个节点时,可以从所有输入特征或大小为max_features的随机子集中找到最佳拆分。(有关更多详细信息,请参见参数调整准则)。

这两个随机性的目的是减少森林估计器的方差。实际上,单个决策树通常表现出较高的方差并且倾向于过拟合。在森林中注入随机性来产生决策树,让其预测误差有些解耦。通过取这些预测结果的平均值,可以减少一些误差。随机森林通过组合不同的树来减少方差,有时会以略微增加偏差(bias)为代价。在实践中,由于方差的减小通常是很明显的,因此在总体上产生了更好的模型。

与原始出版的[B2001]相比,scikit-learn的实现是取每个分类器预测出的概率的平均,而不是让每个分类器对单个类别进行投票。

1.11.2.2. 极大随机树(Extremely Randomized Trees)

在极大随机树方法中(请参见ExtraTreesClassifierExtraTreesRegressor类),随机性更进一步的体现在计算划分的方式上。极大随机树也和随机森林一样,使用了候选特征的随机子集,但是不同之处在于:随机森林为每个特征寻找最具分辨性的阈值,而在极大随机树里面每个特征的阈值也是随机抽取的,并且这些随机生成的阈值里面最好的阈值会被用来分割节点。这种更随机的做法通常能够使得模型的方差减小一点但是会使得模型的偏差稍微的增加一点:

>>> from sklearn.model_selection import cross_val_score
>>> from sklearn.datasets import make_blobs
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.ensemble import ExtraTreesClassifier
>>> from sklearn.tree import DecisionTreeClassifier

>>> X, y = make_blobs(n_samples=10000, n_features=10, centers=100,
...     random_state=0)

>>> clf = DecisionTreeClassifier(max_depth=None, min_samples_split=2,
...     random_state=0)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.98...

>>> clf = RandomForestClassifier(n_estimators=10, max_depth=None,
...     min_samples_split=2, random_state=0)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.999...

>>> clf = ExtraTreesClassifier(n_estimators=10, max_depth=None,
...     min_samples_split=2, random_state=0)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean() > 0.999
True

https://scikit-learn.org/stable/_images/sphx_glr_plot_forest_iris_0011.png

1.11.2.3. 参数(Parameters)

使用这些方法时,要调整的参数主要是n_estimatorsmax_featuresn_estimators是森林里的树的数量,通常数量越大,效果越好,但是计算时间也会随之增加。此外要注意,当树的数量超过一个临界值之后,算法的效果并不会很显著增加。max_features是分割节点时特征的随机子集的大小。这个值越低,方差减小得越多,但是偏差的增加也越多。根据经验,回归问题中使用max_features=n_features,分类问题使用max_features=sqrt(n_features),( 特征的个数n_features是比较好的默认值)。max_depth=Nonemin_samples_split=2的参数组合通常会有不错的效果(即生成完全的树)。请记住,这些(默认)值通常不是最佳的,同时还可能消耗大量的内存,最佳参数值应由交叉验证获得。另外,请注意,在随机森林中,默认使用自助采样法(bootstrap=True),然而极大随机树(extra-trees)的默认策略是使用整个数据集(bootstrap=False)。当使用自助采样法方法抽样时,泛化精度是可以通过剩余的或者袋外的样本来估算的,可以通过设置oob_score=True来实现。

注意:

具有默认参数的模型复杂度为$O( M * N * log (N) )$,其中$M$是树的数目,$N$是样本数。可以通过设置以下参数来降低模型复杂度:min_samples_splitmax_leaf_nodesmax_depthmin_samples_leaf

1.11.2.4. 并行化(Parallelization)

最后,该模块还支持树的并行构建和预测结果的并行计算,这可以通过n_jobs参数实现。如果设置n_jobs=k,则计算被划分为k 个作业,并运行在机器的k 个核上。如果设置n_jobs = -1,则使用机器的所有核进行工作。注意由于进程间通信具有一定的开销,这里的提速并不是线性的(即,使用 k个作业并不意味着快k倍)。当然,在建立大量的树或者构建单个树需要相当长的时间(例如,在大数据集上)的情况下,(通过并行化)仍然可以实现显著的加速。

案例:

参考文献:

  • [B2001] Breiman, “Random Forests”, Machine Learning, 45(1), 5-32, 2001.
  • [B1998] Breiman, “Arcing Classifiers”, Annals of Statistics 1998.

  • P. Geurts, D. Ernst., and L. Wehenkel, “Extremely randomized trees”, Machine Learning, 63(1), 3-42, 2006.

1.11.2.5. 特征重要性评估

特征对目标变量预测的相对重要性可以通过(树中的决策节点的)特征使用的相对顺序(即深度)来进行评估。决策树顶部使用的特征对更大一部分输入样本的最终预测决策做出贡献;因此,树顶部的那些特征所贡献的期望样本比例(expected fraction of the samples) 可以作为特征的相对重要性(relative importance of the features)的一个估计。在scikit-learn中,一个特征所贡献的样本的比例与分解它们所产生的不纯度的减少相结合,从而创建了对该特征的预测能力的归一化估计。

通过对多个随机树的预测能力的估计进行平均化(averaging),可以减小该估计的方差,并将其用于特征选择。这种方法被称为不纯度平均减小(mean decrease in impurity)或MDI。请参考[L2014]获得更多关于MDI和用随机森林进行特征重要行评估的信息。

以下例子显示了使用ExtraTreesClassifier模型用在一个面部识别任务中显示每个像素的相对重要性,其中重要性由颜色的深浅来表示:

https://scikit-learn.org/stable/_images/sphx_glr_plot_forest_importances_faces_0011.png

实际上,对于训练完成的模型的估计值存储在feature_importances_属性中。这是一个形状为(n_features,)的数组,其中每个元素值为正,并且总和为 1.0。一个元素的值越高,其对应的特征对预测函数的贡献越大。

案例:

参考文献:

[L2014] G. Louppe, “Understanding Random Forests: From Theory to Practice”, PhD Thesis, U. of Liege, 2014.

1.11.2.6. 完全随机树嵌入(Totally Random Trees Embedding)

RandomTreesEmbedding实现数据的无监督转换。使用由完全随机树构造的森林,RandomTreesEmbedding 通过从数据点到树的叶子的索引对数据进行编码。然后,该索引以K种方式中的任意一种方式进行编码,从而实现了高维,稀疏的二进制编码。该编码可以非常有效地计算,然后可以作为其他学习任务的基础。通过选择树的数量和每棵树的最大深度,可以影响代码的大小和稀疏性。对于集成树中的每一棵树都有对应的编码。编码的大小最大为n_estimators * 2 ** max_depth,该大小是森林中最大的叶子数。

由于相邻数据点更有可能位于某一棵树的同一片叶子内,因此该转换将执行隐式,非参数密度估计。

案例:

也可以看看:流形学习技术也可以用于导出特征空间的非线性表示,这些方法也可以用于降维。

1.11.3. 自适应推举算法(AdaBoost)

sklearn.ensemble模块包括了一种流行的增强算法(AdaBoost),该算法是在1995年由Freund和Schapire[FS1995]引入。

AdaBoost的核心是在被重复采样的数据上拟合一个弱学习器(weak learner)序列。(弱学习器指的是那些比随机猜测稍好一点的模型,比如小决策树)。这些弱学习器的预测结果会通过加权投票的方式被组合起来产生最终的预测。在每一次推举迭代(boosting iteration)中,数据修改(data modifications)就是把权重$w_1$,$w_2$,…,$w_N$分配给每一个训练样本。在迭代开始的时候,所有的样本权重都被设置为$w_i = 1/N$,这样第一步迭代就是在原始数据上简单训练一个弱分类器 。在后续的迭代步骤中,样本权重会被单独修改,然后学习算法被重新用到被重新加权的样本数据上。在某个给定的迭代步骤中,那些没有被上一步得到的增强模型正确预测的训练样本的权重就会增加,而那些已经被上一步得到的增强模型正确预测的样本的权重会被降低。随着迭代过程的推进,那些比较难预测的样本会获得不断增加的权重。每一个后继的弱学习器就会被强制聚焦到这些被以前的学习器序列错分的权重较高的难分样本上[HTF].。

https://scikit-learn.org/stable/_images/sphx_glr_plot_adaboost_hastie_10_2_0011.png

AdaBoost可以用于分类和回归问题:

1.11.3.1. 用法

以下例子显示了如何拟合一个用100个弱学习器构建的AdaBoost分类器:

>>> from sklearn.model_selection import cross_val_score
>>> from sklearn.datasets import load_iris
>>> from sklearn.ensemble import AdaBoostClassifier

>>> X, y = load_iris(return_X_y=True)
>>> clf = AdaBoostClassifier(n_estimators=100)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.9...

弱学习器的数量由参数n_estimators控制。参数learning_rate控制着在最终的集成学习器中每个弱学习器的贡献量。默认情况下,弱学习器都是决策树桩(decision stumps)。弱学习器可以通过参数base_estimator指定。为了获得好的结果需要调节的主要参数有n_estimators和基本学习器的复杂度参数(比如树的最大深度max_depth和单个划分上需要的最小样本量min_samples_split)。

例子:

参考文献:

[FS1995] Y. Freund, and R. Schapire, “A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting”, 1997.

[ZZRH2009] J. Zhu, H. Zou, S. Rosset, T. Hastie. “Multi-class AdaBoost”, 2009.

[D1997] Drucker. “Improving Regressors using Boosting Techniques”, 1997.

HTF(1,2,3) T. Hastie, R. Tibshirani and J. Friedman, “Elements of Statistical Learning Ed. 2”, Springer, 2009.

1.11.4. 梯度提升树(Gradient Tree Boosting)

梯度提升树(Gradient Tree Boosting) 或梯度提升决策树(Gradient Boosted Decision Trees)(GBDT)是增强(boosting)在任意可微损失函数上的推广。GBRT是一个准确高效的现成的程序,可用于回归和分类问题。梯度提升树模型被广泛用于各个领域包括网站搜索排序和生态学。

sklearn.ensemble模块提供了通过梯度提升决策树来进行分类和回归的方法。

注意: Scikit-learn 0.21版本引入了两个新的梯度提升树的实验实现,分别是HistGradientBoostingClassifierHistGradientBoostingRegressor,受LightGBM启发 (请参见[LightGBM])。当样本数量大于成千上万时,这些基于直方图的估计器可能比GradientBoostingClassifierGradientBoostingRegressor还要快上好几个数量级 。它们还内置了对缺失值的支持,从而避免了麻烦。这些估计器将在下面的基于直方图的梯度提升中详细介绍 。以下教程着重于介绍GradientBoostingClassifierGradientBoostingRegressor,它们这对于小样本量来说可能是首选,因为在此设置中合并可能会导致分裂点过于类似。

1.11.4.1. 分类

GradientBoostingClassifier支持二分类和多分类任务。下面的例子展示了如何拟合一个带有100个决策树桩作为弱学习器的梯度上升分类器:

>>> from sklearn.datasets import make_hastie_10_2
>>> from sklearn.ensemble import GradientBoostingClassifier

>>> X, y = make_hastie_10_2(random_state=0)
>>> X_train, X_test = X[:2000], X[2000:]
>>> y_train, y_test = y[:2000], y[2000:]

>>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0,
...     max_depth=1, random_state=0).fit(X_train, y_train)
>>> clf.score(X_test, y_test)
0.913...

弱学习器的数量(即回归树)的数量由参数n_estimators控制;每棵树的大小可以通过设置树的深度max_depth或通过设置max_leaf_nodes参数来控制叶节点的数量。学习率(learning_rate)是一个介于(0.0, 1.0]之间的超参数, 它通过收缩(shrinkage)来控制过拟合。

注意: 超过两类的分类问题在每一次迭代时需要归纳 n_classes 个回归树。因此,所有的需要归纳的树数量等于 n_classes * n_estimators 。 对于拥有大量类别的数据集我们强烈推荐使用HistGradientBoostingClassifier来代替GradientBoostingClassifier

1.11.4.2. 回归

GradientBoostingRegressor支持使用多种 不同的损失函数 进行回归,可以通过参数 loss来指定回归时使用默认损失函数是最小二乘损失('ls')。

>>> import numpy as np
>>> from sklearn.metrics import mean_squared_error
>>> from sklearn.datasets import make_friedman1
>>> from sklearn.ensemble import GradientBoostingRegressor

>>> X, y = make_friedman1(n_samples=1200, random_state=0, noise=1.0)
>>> X_train, X_test = X[:200], X[200:]
>>> y_train, y_test = y[:200], y[200:]
>>> est = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1,
...     max_depth=1, random_state=0, loss='ls').fit(X_train, y_train)
>>> mean_squared_error(y_test, est.predict(X_test))
5.00...

下图显示了使用最小二乘损失函数和500个基本学习器的GradientBoostingRegressor来处理波士顿房价数据集(sklearn.datasets.load_boston)的结果。左图显示了每次迭代时的训练误差和测试误差。每次迭代的训练误差保存在 梯度提升模型的train_score_属性中,每一次迭代的测试误差能够通过staged_predict方法获取,该方法返回一个生成器,用来产生每一步迭代的预测结果。类似下面这样的图表,可以用于决定最优的树的数量,从而进行提前停止。右图表示每个特征的重要性,它可以通过feature_importances_属性来获取。

https://scikit-learn.org/stable/_images/sphx_glr_plot_gradient_boosting_regression_0011.png

例子:

1.11.4.3. 拟合附加的弱学习器

GradientBoostingRegressorGradientBoostingClassifier 都支持warm_start=True,它允许您添加更多的估计器到一个已经训练好的模型上。

>>> _ = est.set_params(n_estimators=200, warm_start=True)  # set warm_start and new nr of trees
>>> _ = est.fit(X_train, y_train) # fit additional 100 trees to est
>>> mean_squared_error(y_test, est.predict(X_test))
3.84...

1.11.4.4. 控制树的大小

回归树的基本学习器的大小定义了能够被梯度提升模型捕捉的变量(即特征)相互作用(即多个特征共同对预测产生影响)的程度。总的来讲,一棵深度为h的树能够捕捉h阶的相互作用。有两种方法用来控制单个回归树的大小:

如果您指定max_depth=h,那么将会产生一个深度为h的完全二叉树。这棵树将会(至多)有2**h个叶子节点和2**h - 1个切分节点。

另外,您能通过参数max_leaf_nodes 指定叶子节点的数量来控制树的大小。在这种情况下,树将会使用最优优先搜索(best-first search)来生成,这种搜索方式是通过每次选取对不纯度提升最大的节点来展开。一棵max_leaf_nodes=k的树拥有k - 1个切分节点,因此可以建模阶数最高达到max_leaf_nodes - 1阶的相互作用(即max_leaf_nodes - 1个特征共同决定预测值)。

我们发现,max_leaf_nodes=k 可以给出与max_depth=k-1相当的结果,但是其训练速度明显更快,同时也会以多一点的训练误差作为代价。参数 max_leaf_nodes对应于[F2001]中梯度提升(gradient boosting)章节中的变量J,同时与 R 语言z中的gbm包的参数interaction.depth相关,两者间的关系是max_leaf_nodes == interaction.depth + 1

1.11.4.5. 数学公式

GBRT是一种具有以下形式的加性模型(additive models):

$$ F(x) = \sum_{m=1}^{M} \gamma_m h_m(x) $$ 其中$h_m(x)$是基础函数,通常在增强(Boosting)算法中通常被称为弱学习器 (weak learners) 。梯度提升树使用固定大小的决策树作为弱学习器。决策树具有许多有价值的提升能力,即处理混合类型数据的能力和建立复杂函数模型的能力。

与其他增强算法类似,GBRT以贪婪的方式构建加性模型:

$$ F_m(x) = F_{m-1}(x) + \gamma_m h_m(x), $$ 当给定上一步产生的集成模型$F_{m−1}$时,新添加的树$h_m$尝试最小化损失$L$:

$$ h_m = \arg\min_{h} \sum_{i=1}^{n} L(y_i, F_{m-1}(x_i) + h(x_i)). $$ 初始模型$F_0$是与某个问题相关的,对于最小二乘回归,通常选择目标值的均值。

注意: 初始模型也可以通过init参数指定。传入的对象必须实现fitpredict方法。

梯度提升(Gradient Boosting)尝试通过最陡下降(steepest descent)方法来数值化地求解上述最小化问题:最陡下降方向是在当前模型$F_{m−1}$上计算出的损失函数的负梯度方向,这可以为任何可微损失函数计算: $$ F_m(x) = F_{m-1}(x) - \gamma_m \sum_{i=1}^{n} \nabla_F L(y_i, F_{m-1}(x_i)) $$ 其中,步长$γ_m$是使用线性搜索来选择的:

$$ \gamma_m = \arg\min_{\gamma} \sum_{i=1}^{n} L(y_i, F_{m-1}(x_i)-\gamma \frac{\partial L(y_i, F_{m-1}(x_i))}{\partial F_{m-1}(x_i)}) $$ 回归和分类算法仅在所使用的具体损失函数上有所不同。

1.11.4.5.1. 损失函数

支持以下损失函数,可以使用参数loss来指定:

  • 回归
  • 最小二乘(Least squares)('ls'):由于其优越的计算性能,是回归问题的自然选择。初始模型由目标变量的平均值给出。
  • 最小绝对偏差(Least absolute deviation)('lad'):一个用于回归的鲁棒的损失函数。初始模型由目标变量的中值 给出。
  • Huber('huber'):另一个用于回归的鲁棒的损失函数,它组合了最小二乘和最小绝对偏差;使用alpha参数来控制损失函数对异常点的敏感度(有关更多详细信息,请参见[F2001])。
  • 分位数(Quantile)('quantile'):一个用于分位数回归(quantile regression)的损失函数。使用0 < alpha < 1来指定分位数。这个损失函数可以用来创造预测区间(prediction intervals)(请参阅“梯度提升回归的预测间隔”)。
  • 分类
  • 二项式偏差(Binomial deviance)('deviance'):用于二分类的负二项式对数似然损失函数(提供概率估计)。初始模型由对数比值比(log odds-ratio)给出。
  • 多项式偏差(Multinomial deviance)('deviance'):用于多元分类(有n_classes个互斥类)的负多项式对数似然损失函数(也提供概率估计)。初始模型由每个类的先验概率给出。在每一次迭代中必须构建n_classes棵回归树使得GBRT在具有大量类的数据集上相当低效。
  • 指数损失(Exponential loss)('exponential'):与AdaBoostClassifier的损失函数相同。与'deviance'相比,对于误标记的样本的鲁棒性较差。只能用于二分类问题。

1.11.4.6. 正则化

1.11.4.6.1. 收缩(Shrinkage)

[F2001]提出了一种简单的正则化策略,可以通过$\nu$来衡量每个弱学习器的贡献: $$ F_m(x) = F_{m-1}(x) + \nu \gamma_m h_m(x) $$ 参数$\nu$之所以称为学习率,是因为它可以缩放梯度下降过程的步长;可以通过learning_rate参数来设置。

参数learning_rate与参数n_estimators之间有很强的制约关系(要拟合的弱学习器的数量) 。较小的learning_rate需要大量的弱分类器才能维持训练误差的稳定。经验表明数值较小的 learning_rate 将会得到更好的测试误差。[HTF]建议将学习率设置为较小的常数(例如learning_rate <= 0.1)同时通过提前停止策略来选择合适的n_estimators。有关learning_raten_estimators之间的详细讨论,请参见[R2007]

1.11.4.6.2.子采样(Subsampling)

[F1999]提出了一种将梯度增强和自举平均(bootstrap averaging (bagging))相结合的随机梯度增强方法。在每次迭代中,基本分类器在可用训练数据的一部分子样本subsample上被训练。子采样(subsample)是通过无放回采样获得的。subsample的典型值为0.5。

下图展示了收缩和子采样对于模型拟合好坏的影响。我们可以明显看到指定收缩率比没有收缩拥有更好的表现。而将子采样和收缩率相结合能进一步的提高模型的准确率。相反,使用子采样而不使用收缩的结果十分糟糕。

https://scikit-learn.org/stable/_images/sphx_glr_plot_gradient_boosting_regularization_0011.png

减少方差的另一种策略是通过对特征进行子采样,这种类似于RandomForestClassifier中的随机分割。可以通过max_features参数来控制子采样特征的数量。

注意: 使用较小的max_features值可以大大减少运行时间。

随机梯度提升允许计算测试偏差的袋外(Out-of-bag)估计值,方法是计算那些不在自助采样之内的样本偏差的改进 (即袋外示例)。这个改进保存在属性oob_improvement_中。如果将第i个阶段添加到当前预测中,oob_improvement_[i]可以改善OOB样本的损失。袋外估计可以用于模型选择中,例如决定最优迭代次数。OOB估计通常都很悲观,因此我们推荐使用交叉验证来代替它,而当交叉验证太耗时时我们就只能使用OOB了。

案例:

1.11.4.7.解释

通过简单地可视化树结构可以很容易地解释单个决策树,然而对于梯度提升模型来说,一般拥有数百棵回归树,将每一棵树都可视化来解释整个模型是很困难的。幸运的是,有很多关于总结和解释梯度提升模型的技术。

1.11.4.7.1. 特征重要性

通常,各个特征分量对目标响应的预测所做的贡献是不一样的。在很多情形中,大多数特征分量之间是不相关的。当我们要解释一个模型的时候,遇到的第一个问题通常是:最重要的特征是哪些?它们对目标响应的预测做了怎样的贡献?

单个决策树本质上就是通过选择适当的分割点来进行特征选择的一种模型。这些信息可以用来度量每个特征的重要性,其基本的思想是:如果一个特征在树的分割节点中用的越频繁,则这个特征的重要性就越高。这种特征重要性的概念可以通过简单的平均一下每棵树上的特征重要性扩展到决策树集合(有关更多详细信息,请参见 特征重要性评估)。

一个经过拟合得的梯度提升模型的特征重要性的分数可以通过参数feature_importances_获得:

>>> from sklearn.datasets import make_hastie_10_2
>>> from sklearn.ensemble import GradientBoostingClassifier

>>> X, y = make_hastie_10_2(random_state=0)
>>> clf = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0,
...     max_depth=1, random_state=0).fit(X, y)
>>> clf.feature_importances_
array([0.10..., 0.10..., 0.11..., ...

案例:

1.11.5. 基于直方图的梯度增强

Scikit-learn 0.21 引入了两种新的梯度增强树(gradient boosting trees)的实现,即HistGradientBoostingClassifierHistGradientBoostingRegressor,其灵感来自LightGBM(参见LightGBM)。

当样本数大于数万个时,这些基于直方图的估计器可以比GradientBoostingClassifierGradientBoostingRegressor 快几个数量级

它们还内置了对缺失值的支持,从而避免了对输入的需要。

这些快速估计器首先将输入样本“X”放入整数值箱(通常为256箱),这大大减少了要考虑的分割点的数量,并且允许算法在构建树时利用基于整数的数据结构(直方图),而不是依赖排好序的连续值。这些估计器的API略有不同,GradientBoostingClassifierGradientBoostingRegressor的一些特性还不受支持:特别是样本权重和一些损失函数。

这些估算器仍然是“实验性的”:它们的预测和API可能会发生变化,但不会发生任何弃用周期。 要使用它们,您需要显式导入 enable_hist_gradient_boosting:

>>> # 明确要求此experimental feature
>>> from sklearn.experimental import enable_hist_gradient_boosting  # noqa
>>> # 现在您可以正常地从集成(ensemble)导入
>>> from sklearn.ensemble import HistGradientBoostingClassier

案例:

1.11.5.1.用法

GradientBoostingClassifierGradientBoostingRegressor的大部分参数没有变化。一个例外是max_iter参数,它代替 n_estimators,并控制boosting过程的迭代次数:

>>> from sklearn.experimental import enable_hist_gradient_boosting
>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> from sklearn.datasets import make_hastie_1_2
>>> X, y = make_hastie_10_2(random_state=0)
>>> X_train, X_test = X[:2000], X[2000:]
>>> y_train, y_test = y[:2000], y[2000:]

>>> clf = HistGradientBoostingClassifier(max_iter=100).fit(X_train, y_train)
>>> clf.score(X_test, y_test)
0.8965

回归的可用损失是‘least_squares’ 和 ‘least_absolute_deviation’,这对异常值不太敏感。对于分类,‘binary_crossentropy’ 用于二元分类,‘categorical_crossentropy’用于多元分类。默认情况下,损失为'自动'(‘auto’ ),并将根据传递给 fity 选择适当的loss。

树的大小可以通过max_leaf_nodes, max_depth, 和 min_samples_leaf这三个参数来控制。

用于对数据进行装箱的箱子数量由max_bins参数控制。使用较少的箱子作为一种规范化的形式。通常建议使用尽可能多的箱子,这是默认设置。

l2_regularization参数是损失函数的正则化器,对应于XGBoost的方程(2)中的$\lambda$。

提前停止训练是通过 scoring, validation_fraction, n_iter_no_change, 和 tol 参数控制。有可能使用任意的 scorer,或者只是训练或验证损失(training or validation loss)来提前停止训练。默认情况下,使用验证集上估计器的默认 scorer 执行提前停止(early-stopping),但也可以基于损失值( loss value)执行提前停止,这明显更快。

1.11.5.2. 支持缺失值

HistGradientBoostingClassifier and HistGradientBoostingRegressor 内置了对缺失值(NaNs)的支持。

在训练过程中,学习器(the tree grower)在每个分割点学习:根据潜在收益决定将缺失值的样本分配给左侧或右侧的子代。预测时,会将缺失值的样本分配给左或右子项,从而:

>>> from sklearn.experimental import enable_hist_gradient_boosting  # noqa
>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> import numpy as np
>>> X = np.array([0, 1, 2, np.nan]).reshape(-1, 1)
>>> y = [0, 0, 1, 1]

>>> gbdt = HistGradientBoostingClassifier(min_samples_leaf=1).fit(X, y)
>>> gbdt.predict(X)
array([0, 0, 1, 1])

当缺失模式是可预测的时,可以根据特征值是否缺失进行分割:

>>> X = np.array([0, np.nan, 1, 2, np.nan]).reshape(-1, 1)
>>> y = [0, 1, 0, 0, 1]
>>> gbdt = HistGradientBoostingClassifier(min_samples_leaf=1,
...                                       max_depth=2,
...                                       learning_rate=1,
...                                       max_iter=1).fit(X, y)
>>> gbdt.predict(X)
array([0, 1, 0 0, 1])

如果在训练期间未遇到给定特征的缺失值,则具有缺失值的样本将映射到具有最多样本的子级。

1.11.5.3.低级并行

HistGradientBoostingClassifierHistGradientBoostingRegressor 通过Cython使用OpenMP实现了并行化。有关如何控制线程数的更多详细信息,请参阅我们的Parallelism说明。

以下部分是并行的:

  • 将样本从实值映射到整数值箱(但查找箱阈值是连续的)

  • 构建直方图是在特征上进行并行化的

  • 在节点上找到最佳分割点是在特征上进行并行化的

  • 在拟合过程中,将样本映射到左、右子级是在样本上进行并行的

  • 梯度和hessians计算是在样本上进行并行化的

  • 预测是在样本上进行并行化的

1.11.5.4. 为什么速度更快

梯度增强过程的瓶颈是决策树的建立。构建传统的决策树(与其他GBDTsGradientBoostingClassifier 和[GradientBoostingRegressor]一样)需要对每个节点上的样本进行排序(对每个特征)。为了有效地计算分裂点潜在的增益,需要对每个节点上的样本进行排序。因此,拆分单个节点的复杂性为$\mathcal{O}(n_\text{features} \times n \log(n))$,其中$n$是节点上的样本数。

相比之下,HistGradientBoostingClassifierHistGradientBoostingRegressor不需要对特征值进行排序,而是使用一种称为直方图的数据结构,其中的样本是隐式排序的。构建直方图复杂度为$\mathcal{O}(n)$,因此节点分割过程复杂度比前者更小,为$\mathcal{O}(n_\text{features} \times n)$。此外,我们这里不考虑$n$分割点,而只考虑max_bins分割点,max_binsn小得多。

为了构建直方图,需要将输入数据“X”放入整数值容器中。这个binning过程确实需要对特征值进行排序,但它只在boosting过程的一开始执行一次(而不是在每个节点上,如在GradientBoostingClassifierGradientBoostingRegressor) 中)。

最后,HistGradientBoostingClassifierHistGradientBoostingRegressor 实现的许多部分是并行的。

参考文献

1.11.6. 投票分类器

VotingClassifier(投票分类器)的思想是结合多个概念上不同的机器学习分类器,使用多数票或平均预测概率(软投票)来预测类标签。这样的分类器可以用于一组性能同样良好的模型,以平衡它们各自的弱点。

1.11.6.1. 多数类标签(多数/硬投票)

在多数投票中,特定样本的预测类标签是表示每个单独分类器预测的类标签中占多数(模式)的类标签。

例如, 如果给定样本的预测是

  • classifier 1 -> class 1

  • classifier 2 -> class 1

  • classifier 3 -> class 2

VotingClassifier (with voting='hard') 将根据多数类标签将样本分类为“class 1”。

在平局的情况下,VotingClassifier (投票分类器)将根据升序排序顺序选择类。例如, 在下面的场景中

  • classifier 1 -> class 2

  • classifier 2 -> class 1

此时,将class 1作为样本类标签。

1.11.6.1.1.用法

下面的示例演示如何训练多数规则分类器:

>>> from sklearn import datasets
>>> from sklearn.model_selection import cross_val_score
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.naive_bayes import GaussianNB
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.ensemble import VotingClassifier
>>> iris = datasets.load_iris()
>>> X, y = iris.data[:, 1:3], iris.target

>>> clf1 = LogisticRegression(random_state=1)
>>> clf2 = RandomForestClassifier(n_estimators=50, random_state=1)
>>> clf3 = GaussianNB()

>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='hard')

>>> for clf, label in zip([clf1, clf2, clf3, eclf], ['Logistic Regression', 'Random Forest', 'naive Bayes', 'Ensemble']):
...     scores = cross_val_score(clf, X, y, scoring='accuracy', cv=5)
...     print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
Accuracy: 0.95 (+/- 0.04) [Logistic Regression]
Accuracy: 0.94 (+/- 0.04) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [naive Bayes]
Accuracy: 0.95 (+/- 0.04) [Ensemble]

1.11.6.2. 加权平均概率(软投票)

与多数投票(硬投票)不同,软投票返回的类标签为预测概率之和的argmax。

可以通过weights(权重)参数为每个分类器分配特定的权重。当提供权重参数时,收集每个分类器的预测类概率,乘以分类器权重,然后取平均值。最后平均概率最高的类标签最为最终类标签。

为了用一个简单的例子来说明这一点,假设我们有3个分类器和一个3类分类问题,其中我们为所有分类器分配相等的权重:w1=1,w2=1,w3=1。

样本的加权平均概率计算如下:

classifier class 1 class 2 class 3
classifier 1 w1 * 0.2 w1 * 0.5 w1 * 0.3
classifier 2 w2 * 0.6 w2 * 0.3 w2 * 0.1
classifier 3 w3 * 0.3 w3 * 0.4 w3 * 0.3
weighted average 0.37 0.4 0.23

这里,预测的类标签是2,因为它具有最高的平均概率。

下面的示例说明了当使用基于线性支持向量机、决策树和K近邻分类器的软投票分类器VotingClassifier时,决策区域如何变化:

>>> from sklearn import datasets
>>> from sklearn.tree import DecisionTreeClassifier
>>> from sklearn.neighbors import KNeighborsClassifier
>>> from sklearn.svm import SVC
>>> from itertools import product
>>> from sklearn.ensemble import VotingClassifier
>>> # 加载一些示例数据
>>> iris = datasets.load_iris()
>>> X = iris.data[:, [0, 2]]
>>> y = iris.target

>>> # 训练分类器
>>> clf1 = DecisionTreeClassifier(max_depth=4)
>>> clf2 = KNeighborsClassifier(n_neighbors=7)
>>> clf3 = SVC(kernel='rbf', probability=True)
>>> eclf = VotingClassifier(estimators=[('dt', clf1), ('knn', clf2), ('svc', clf3)],
...                         voting='soft', weights=[2, 1, 2])

>>> clf1 = clf1.fit(X, y)
>>> clf2 = clf2.fit(X, y)
>>> clf3 = clf3.fit(X, y)
>>> eclf = eclf.fit(X, y)

https://scikit-learn.org/stable/_images/sphx_glr_plot_voting_decision_regions_0011.png

1.11.6.3. 投票分类器(VotingClassifier)与网格搜索(GridSearchCV)一起使用

VotingClassifier (投票分类器)还可以与GridSearchCV(网格搜索)一起使用,以便调整各个估计器的超参数:

>>> from sklearn.model_selection import GridSearchCV
>>> clf1 = LogisticRegression(random_state=1)
>>> clf2 = RandomForestClassifier(random_state=1)
>>> clf3 = GaussianNB()
>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft'
... )
>>> params = {'lr__C': [1.0, 100.0], 'rf__n_estimators': [20, 200]}

>>> grid = GridSearchCV(estimator=eclf, param_grid=params, cv=5)
>>> grid = grid.fit(iris.data, iris.target)

1.11.6.3.1. 用法

通过预测的类概率预测类标签(VotingClassifier中的scikit-learn估计器必须支持predict_proba 方法):

>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft'
... )

可选地,可以为各个分类器提供权重:

>>> eclf = VotingClassifier(
...     estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft', weights=[2,5,1]
... )

1.11.7. 投票回归器(Voting Regressor)

VotingRegressor的主要想法是组合概念上不同的机器学习回归变量并返回平均的预测值。这样的回归变量可用于一组性能良好的模型,以平衡其各自的弱点。

1.11.7.1. 用法

以下例子显示了如何训练VotingRegressor:

>>> from sklearn.datasets import load_boston
>>> from sklearn.ensemble import GradientBoostingRegressor
>>> from sklearn.ensemble import RandomForestRegressor
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.ensemble import VotingRegressor

>>> # 加载一些样本数据
>>> X, y = load_boston(return_X_y=True)

>>> # 训练分类器
>>> reg1 = GradientBoostingRegressor(random_state=1, n_estimators=10)
>>> reg2 = RandomForestRegressor(random_state=1, n_estimators=10)
>>> reg3 = LinearRegression()
>>> ereg = VotingRegressor(estimators=[('gb', reg1), ('rf', reg2), ('lr', reg3)])
>>> ereg = ereg.fit(X, y)

https://scikit-learn.org/stable/_images/sphx_glr_plot_voting_regressor_0011.png

案例:

1.11.8. 堆叠泛化(Stacked generalization)

堆叠泛化是一种组合估计器以减少其偏差的方法[W1992][HTF]。更精确来说,每个单独的估计器的预测结果都被堆叠在一起,并且作为最终估计器的输入来进行预测。该最终估计器通过交叉验证进行训练。

StackingClassifierStackingRegressor提供可应用于分类和回归等问题的策略。

estimators参数对应于在输入数据上并行堆叠在一起的估计器列表。它接收名称和估计器的列表:

>>> from sklearn.linear_model import RidgeCV, LassoCV
>>> from sklearn.svm import SVR
>>> estimators = [('ridge', RidgeCV()),
...               ('lasso', LassoCV(random_state=42)),
...               ('svr', SVR(C=1, gamma=1e-6))]

final_estimator将使用estimators的预测结果作为输入。当使用StackingClassifierStackingRegressor时,final_estimator必须是分类器或回归器:

>>> from sklearn.ensemble import GradientBoostingRegressor
>>> from sklearn.ensemble import StackingRegressor
>>> reg = StackingRegressor(
...     estimators=estimators,
...     final_estimator=GradientBoostingRegressor(random_state=42))

要训练estimatorsfinal_estimator,需要把训练数据传入fit方法:

>>> from sklearn.datasets import load_boston
>>> X, y = load_boston(return_X_y=True)
>>> from sklearn.model_selection import train_test_split
>>> X_train, X_test, y_train, y_test = train_test_split(X, y,
...                                                     random_state=42)
>>> reg.fit(X_train, y_train)
StackingRegressor(...)

在训练期间,将用整个训练数据 X_train用于训练estimators。调用predictpredict_proba时将使用它们(估计器)。为了泛化和避免过拟合,在内部使用sklearn.model_selection.cross_val_predictfinal_estimator在样本上进行训练。

请注意,对于StackingClassifier来说,estimators的输出由参数stack_method控制,并由每个估计器调用。此参数接收一个估计器名称的字符串,或者是'auto''auto'这将自动识别可用的方法,以优先顺序进行测试:predict_probadecision_functionpredict

StackingRegressorStackingClassifier可以被用作任何其他回归器或分类器。它们提供predictpredict_probadecision_function方法,例如:

>>> y_pred = reg.predict(X_test)
>>> from sklearn.metrics import r2_score
>>> print('R2 score: {:.2f}'.format(r2_score(y_test, y_pred)))
R2 score: 0.81

注意,也可以使用transform方法来获得estimators的堆叠输出 :

>>> reg.transform(X_test[:5])
array([[28.78..., 28.43...  , 22.62...],
       [35.96..., 32.58..., 23.68...],
       [14.97..., 14.05..., 16.45...],
       [25.19..., 25.54..., 22.92...],
       [18.93..., 19.26..., 17.03... ]])

实际上,堆叠预测器的预测结果与基础层的最佳预测器一样好,甚至有时可以通过组合这些预测器的不同强度来进行预测。然而,训练堆叠预测器在计算上是昂贵的。

注意: 对于StackingClassifier来说,当使用stack_method_='predict_proba'并且是二分类问题时,第一列将被删除。每个估计器预测的两个概率列都是完全共线的。

注意:

final_estimator参数传递StackingClassifierStackingRegressor类的对象可以实现多个堆叠层:

>>> final_layer = StackingRegressor(
...     estimators=[('rf', RandomForestRegressor(random_state=42)),
...                 ('gbrt', GradientBoostingRegressor(random_state=42))],
...     final_estimator=RidgeCV()
...     )
>>> multi_layer_regressor = StackingRegressor(
...     estimators=[('ridge', RidgeCV()),
...                 ('lasso', LassoCV(random_state=42)),
...                 ('svr', SVR(C=1, gamma=1e-6, kernel='rbf'))],
...     final_estimator=final_layer
... )
>>> multi_layer_regressor.fit(X_train, y_train)
StackingRegressor(...)
>>> print('R2 score: {:.2f}'
...       .format(multi_layer_regressor.score(X_test, y_test)))
R2 score: 0.82

参考文献:

[W1992] Wolpert, David H. “Stacked generalization.” Neural networks 5.2 (1992): 241-259.

©2007-2019,scikit-learn开发人员(BSD许可证)。 显示此页面源码