1.11 集成算法

集成算法的目的是将几个基估计器的预测与给定的学习算法结合起来,以提高单个估计器的通用性和鲁棒性。

集成方法一般分为两种:

  • 平均法(averaging methods)中,该方法的原理是构建多个独立的估计器,然后取它们的预测结果的平均。一般来说,组合之后的估计器是会比单个估计器要好的,因为它的方差减小了。

    示例: Bagging methods, Forests of randomized trees, …

  • 相反,在提升法(boosting methods)中,基估计器是按顺序建立的,并且试图减小组合估计器的偏差。其动机是将几个弱模型结合起来,形成一个强大的整体。

    示例: AdaBoost, Gradient Tree Boosting, …

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

在集成算法中, bagging方法形成了一类算法,它在原始训练集的随机子集上建立几个黑箱估计器的实例,然后将它们的个体预测聚合起来,形成最终的预测。这些方法通过在基本估计器(例如决策树)的构造过程中引入随机化,然后将其集成起来,从而降低单个基本估计器(如决策树)的方差。在许多情况下,bagging方法是一个非常简单的方法可以用来改进相对单一模型,而不需要调整底层。由于bagging方法提供了一种减少过度拟合的途径,因此对强大模型和复杂模型(例如,充分生长的决策树)最有效,与之对比的提升法在弱模型(例如浅层决策树)上表现最好。

Bagging方法有许多不同的变种(flavours),但主要是因为它们提取训练集的随机子集的方式不同:

  • 如果抽取的数据集的随机子集是样本的随机子集,我们叫做粘贴 (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)
示例
单个估计器 vs bagging:偏差-方差分解

参考

[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 由随机树组成的森林

sklearn.ensemble集成模块包括两种基于随机决策树的平均算法:RandomForest算法和Extra-Trees算法。这两种算法都是专门为树设计的扰动和组合技术(perturb-and-combine techniques)[B1998] 。这意味着在分类器构造过程中引入随机性来创建一组不同的分类器的集合。集成之后的预测是每个分类器的平均。

与其他分类器一样,森林分类器(forest classifiers)必须拟合两个数组:一个稀疏或密集的X数组, 包含训练样本, 形状是[n_samples, n_features], 还有数组Y, 形状是 [n_samples], 包含训练样本的目标值(类标签):

>>> from sklearn.ensemble import RandomForestClassifier
>>> X = [[00], [11]]
>>> Y = [01]
>>> clf = RandomForestClassifier(n_estimators=10)
>>> clf = clf.fit(X, Y)

和决策树 decision trees一样,树的森林也扩展到多输出问题 multi-output problems(如果Y是一个形状为([n_samples, n_outputs])的数组。

1.11.2.1 随机森林

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

另外,在构建树的过程中进行结点分割时,选择的分割点是所有特征的最佳分割点,或特征的大小为 max_features 的随机子集的最佳分割。(更多细节请看parameter tuning guidelines )。

这两种随机的目的是降低森林估计器的方差。事实上,单个决策树通常表现出很高的方差,并且往往会过拟合。在森林中注入随机性产生的决策树具有一定的解耦预测误差(decoupled prediction errors)。通过取这些预测的平均值,可以抵消掉一些误差。随机森林通过组合不同的树来减少方差,有时以增加一点点偏差为代价。在实践中,方差减少通常是值得关注的,因此产生了一个整体更好的模型。

与最初的出版物 [B2001]相比,scikit-learn实现通过平均它们的概率预测来组合分类器,而不是让每个分类器为单个类别进行投票。

1.11.2.2 极端随机树

在极端随机树(参见 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

1.11.2.3 参数

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

注意:默认参数下模型复杂度是:O(M*N*log(N)) , 其中 M 是树的数目, N 是样本数。 可以通过设置以下参数来降低模型复杂度: min_samples_split , max_leaf_nodes , max_depthmin_samples_leaf

1.11.2.4 并行化

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

示例
在iris数据集上绘制决策树的决策面
基于平行树的森林的像素重要性
Face completion with a multi-output estimators

参考

[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 特征重要性评估

特征对目标变量预测的相对重要性可以通过(树中的决策节点的)特征使用的相对顺序(即深度)来进行评估。 决策树顶部使用的特征对输入的大部分样本的最终预测决策做出贡献;因此,可以使用接受每个特征对最终预测的贡献的样本比例来评估该 特征的相对重要性 。scikit-learn通过将特征贡献的样本比例与分割时产生的不纯度减少相结合, 从而创建了对该特征的预测能力的标准化估计。

通过对多个随机树中的 预期贡献率 (expected activity rates) 取平均,可以减少这种估计的 方差 ,并将其用于特征选择。这被称作平均纯度减少,或MDI。关于MDI以及随机森林特征重要性的更多信息,请参考[L2014]。

警告:在基于树的模型上计算的基于不存度的特征重要性存在两个缺陷,可能导致误导性结论。首先,它们是根据从训练数据集派生出来的统计数据来计算的,因此不一定能告诉我们对于更好的预测保留的数据(held-out dataset), 到底哪些特征才是最重要的。其次,他们倾向于高基数特征,即具有许多不同值的特征。置换特征重要性Permutation feature importance是不受这些缺陷影响的基于不存度计算特征重要性的替代选项。这里两种方法对获取特征重要性进行了探讨::置换重要性与随机森林特征重要性(MDI)

下面的示例显示了使用 ExtraTreesClassifier 模型对人脸识别任务的单个像素的相对重要性的颜色编码表示。 实际上,这些估计值存储在一个已经拟合的模型的属性 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 完全随机树嵌入

RandomTreesEmbedding 实现了一个无监督的数据转换。 通过由完全随机树构成的森林,RandomTreesEmbedding 使用数据最终归属的叶子节点的索引值对数据进行编码。 该索引以 one-of-K 方式编码,最终形成一个高维的稀疏二值化编码。 这种编码可以被非常高效地计算出来,并且可以作为其他学习任务的基础。 编码的大小和稀疏度可以通过选择树的数量和每棵树的最大深度来确定。对于集成中的每棵树的,每个样本对应其中的一个叶节点。 编码的大小(维度)最多为 n_estimators * 2 ** max_depth ,即森林中的叶子节点的最大数。

由于相邻数据点更可能位于一颗树的同一叶子中,该变换可以作为一种隐式地非参数密度估计。

示例
基于完全随机树的哈希特征变换
手写数字上的流形学习:局部线性嵌入,Isomap… 比较了手写体数字的非线性降维技术。
树集成的特征转换 比较了基于树的有监督和无监督特征变换.

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

1.11.3. AdaBoost

sklearn.ensemble 模块包含了流行的提升算法 AdaBoost, 这个算法是由 Freund 和 Schapire 在 1995 年提出来的[FS1995]。

AdaBoost 的核心思想是用反复调整的数据来训练一系列的弱学习器(一个弱学习器模型仅仅比随机猜测好一点, 比如一个简单的决策树),由这些弱学习器的预测结果通过加权投票(或加权求和)的方式组合, 产生最终的预测结果。在每一次所谓的提升(boosting)迭代中,数据的修改由应用于每一个训练样本的(新) 的权重 组成。 初始化时,将所有弱学习器的权重都设置为 ,因此第一次迭代仅仅是通过原始数据训练出一个弱学习器。在接下来的连续迭代中,样本的权重逐个地被修改,学习算法也因此要重新应用这些已经修改的权重的数据。在给定的一个迭代中, 那些在上一轮迭代中被预测为错误结果的样本的权重将会被增加,而那些被预测为正确结果的样本的权重将会被降低。随着迭代次数的增加,那些难以预测的样例的影响将会越来越大,每一个随后的弱学习器都将会被强迫更加关注那些在之前被错误预测的样例[HTF]。

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参数控制着弱学习者在最终组合中的贡献。默认情况下,弱学习器是决策树。通过 base_estimator 参数可以指定不同的弱学习器。要获得良好结果的主要参数是n_estimators和基估计器的复杂度(例如,它的深度 max_depth或考虑分裂的最小样本数min_samples_split)。

示例
离散型与真实型AdaBoost 使用 AdaBoost-SAMME 和 AdaBoost-SAMME.R 比较 decision stump, decision tree(决策树)和 boosted decision stump(增强决策树)的分类错误
多类AdaBoost决策树 展示了 AdaBoost-SAMME 和 AdaBoost-SAMME.R 在 multi-class (多类)问题上的性能。
两类AdaBoost 展示了使用 AdaBoost-SAMME 的非线性可分两类问题的决策边界和决策函数值。
基于AdaBoost的决策树回归 使用 AdaBoost.R2 算法证明了回归。

参考

  • [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 Boosted Decision Trees,GBDT)是Booting对任意可微损失函数的推广。GBDT是一种准确有效的现成程序,可用于各种领域的回归和分类问题,包括Web搜索、排名和生态领域。

集成模块 sklearn.ensemble 通过梯度提升树提供了分类和回归的方法。

注意:在LightGBM (参看 [LightGBM])的启发下,Scikit-learn 0.21引入了两种新的梯度提升树的实验实现,即 HistGradientBoostingClassifierHistGradientBoostingRegressor。当样本数大于数万个样本时,这些基于直方图的估计可以比GradientBoostingClassifierGradientBoostingRegressor 快几个数量级。他们还内置了对缺失值的支持,从而避免了计算的需要。这些估计器将在下面基于直方图的梯度提升Histogram-Based Gradient Boosting中更详细地描述。

下面的指南重点介绍 GradientBoostingClassifierGradientBoostingRegressor,它们可能是小样本大小的首选,因为在这个设置中,装箱可能会导致分割点过于接近。

下面介绍了 GradientBoostingClassifierGradientBoostingRegressor的用法和参数。这些估计器最重要的两个参数是n_estimatorslearning_rate

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控制,每棵树的大小The size of each tree可以通过设置树的深度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方法获得,该方法返回在每个阶段生成预测的生成器。这样画图可以通过早期停止来确定树的最优数目(即 n_estimators)。右边的图显示了基于不存度的特征重要性,它可以通过feature_importances_属性获得。

示例
梯度提升回归
梯度提升袋外估计

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指定叶节点的数目来控制树的大小。在这种情况下,树将使用最佳优先搜索来生长,这种搜索方式是通过每次选取对不纯度提升最大的节点来首先展开。一棵 max_leaf_nodes=k 的树拥有 k - 1 个切分节点,因此可以模拟max_leaf_nodes - 1阶的交互作用。

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

1.11.4.5 数学公式

我们首先说GBRT的回归,然后详细介绍分类案例。

1.11.4.5.1 回归

GBRT回归器是一种加法模型,其对给定输入的预测由以下形式给出:

其中,是在boosting中被称为弱学习器的估计器。梯度树提升采用固定大小的决策树回归器作为弱学习器。常数对应n_estimators参数。

与其他提升算法类似,GBRT是以贪婪的方式构建的:

其中新添加的树中的是为了最小化损失的总和而拟合的,给定前面的集成器为

其中loss参数定义,详见下一节。

默认情况下,初始模型选择使损失最小化的常数:对于最小二乘损失,这是目标值的经验平均值。初始模型也可以通过init参数指定。

使用一阶泰勒(Taylor)近似,的值可以近似为如下:

注意:简要地说, 一阶泰勒近似是:, 这里对应, 对应

是损失相对于第二个参数的导数, 这里按计算。对于任何给定的,由于损失都是可微的,因此很容易计算出它的闭合形式。我们用表示。

除去常数项,我们有:

如果被拟合来预测的值与负梯度成正比,则这是最小化的。因此,在每次迭代时,被拟合的估计器用来预测样本的负梯度。梯度在每次迭代中都会被更新。这可以看作是函数空间中的某种梯度下降。

注意:注意,对于一些损失,例如最小绝对偏差(LAD),其梯度为±1,拟合的预测的值不够精确:这样树只能输出整数值。因此,一旦树被拟合,树的叶子上的值就会被修改,使叶子上的能能最小化损失。更新是依赖于损失的:对于LAD损失,叶子的值将被更新为该叶中样本的中值。

1.11.4.5.2 分类

梯度提升的分类非常类似于回归的情况。然而,树的总和 与预测不是同质的:它不能是一个类,因为树预测的是连续值。

到类或概率的映射依赖于损失。对于偏差(或对数损失), 属于正类的概率被建模为,其中是sigmoid函数。

对于多类分类,K树(对于K类)是在每一个迭代中构建的。属于k类的概率被建模为值的一个Softmax。

请注意,即使对于分类任务,基估计器仍然是一个回归器,而不是分类器。这是因为基估计器被训练来预测(负)梯度,这些梯度总是连续的。

1.11.4.6 损失函数

支持下面这些损失函数,并可以使用参数 loss指定这些函数:

  • 回归
    • 最小二乘('ls'):回归的自然选择,因为它具有优越的计算特性。用目标值的平均值给出了初始模型。
    • 最小绝对偏差('lad'):一个稳健的回归损失函数。初始模型由目标值的中值给出。
    • Huber(‘Huber’):另一个结合最小二乘和最小绝对偏差的稳健损失函数;使用alpha控制异常值的灵敏度(详见[F2001])。
    • Quantile ('quantile'):分位数回归的损失函数。使用 0 < alpha < 1指定分位数。此损失函数可用于创建预测间隔 (参见梯度提升回归的预测间隔 Prediction Intervals for Gradient Boosting Regression)
  • 分类
    • 二项偏差('deviance'):二分类的负二项式对数似然损失函数(提供概率估计)。用对数比率给出了初始模型。
    • 多项偏差('deviance'):对于多分类问题的负的多项对数似然损失函数具有 n_classes个互斥的类。提供概率估计。 初始模型由每个类的先验概率给出.在每一次迭代中 n_classes 回归树被构建,这使得 GBRT 在处理多类别数据集时相当低效。
    • 指数损失('exponential'):与 AdaBoostClassifier 具有相同的损失函数。与 'deviance' 相比,对被错误标记的样本的鲁棒性较差,仅用于在二分类问题。

1.11.4.7 学习收缩率

[F2001] 提出了一种简单的正则化策略,通过常数因子 来衡量每个弱学习器的贡献:

参数也称为学习速率,因为它可以缩放步长、梯度下降过程;它可以通过 learning_rate参数来设置。

参数learning_raten_estimators(弱学习器的个数)有很强的相互作用。较小的learning_rate值要求较多的弱学习者以保持一个恒定的训练误差。经验证据表明,较小的learning_rate有利于更好的测试误差。[HTF]建议将学习速率设置为一个小常数(例如,learning_rate <= 0.1),并通过早期的停止选择n_estimators。关于learning_raten_estimators 之间相互作用的更详细的讨论,见 [R2007]。

1.11.4.8 子采样(Subsampling)

[F1999] 提出了随机梯度提升,这种方法将梯度提升(gradient boosting)和 bootstrap averaging(bagging) 相结合。在每次迭代中,基分类器是通过抽取所有可利用训练集中一小部分的 subsample 训练得到的子样本采用无放回的方式采样。 subsample 参数的值一般设置为 0.5 。

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

另一种降低方差的策略是对随机森林分类器中类似随机分裂的特征进行二次采样。通过max_features参数可以控制子采样特征的数量。

注意:使用一个较小的max_Feature值可以显著减少运行时。

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

例子
梯度提升正则
梯度提升袋外估计
随机森林的OOB

1.11.4.9 特征重要性的解释

通过简单的可视化树结构,可以很容易地解释单个决策树。然而,梯度提升模型包含了数百棵回归树,因此它们很难通过对单个树的可视化来进行解释。幸运的是,已经提出了一些技术来总结和解释梯度提升模型。

通常,特性对预测目标响应的贡献并不相同;在许多情况下,大多数特征实际上是无关的。在解释模型时,第一个问题通常是:重要的特征是什么,并且它们在预测目标响应方面有何贡献?

单个决策树本质上通过选择合适的分割点来进行特征选择。这些信息可以用来度量每个特征的重要性;基本思想是:在树的分割点中使用某特征越频繁,该特性就越重要。这种重要性的概念可以通过简单地平均每个树的基于不存度的特征重要性来扩展到决策树集成器上(更多细节请参见特征重要性评估Feature importance evaluation)。

一个拟合的梯度提升模型的特征重要性分数可以通过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..., ...

请注意,这种特征重要性的计算是基于熵的,并且它不同于sklearn.inspection.permutation_importance,后者是基于特征的排列。

示例
梯度提升回归

1.11.5 基于直方图的梯度提升

Scikit-Leard0.21介绍了两种新的梯度提升树的实验实现,即基于LightGBM(见[LightGBM])的HistGradientBoostingClassifierHistGradientBoostingRegressor

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

他们还内置了对缺失值的支持,从而避免了计算机的需要。

这些快速估计器首先将输入样本X放入整数值箱(通常为256个),这极大地减少了要考虑的分裂点数,并允许算法在构建树时利用基于整数的数据结构(直方图)而不是依赖排序的连续值。这些估计器的API略有不同,对一些损失函数的 GradientBoostingClassifierGradientBoostingRegressor的实例将不再支持一些特性。

这些估计器仍然是实验性的:它们的预测和API可能在没有任何反对周期的情况下发生变化。要使用它们,您需要显式地导入enable_hist_gradient_boosting

>>> # explicitly require this experimental feature
>>> from sklearn.experimental import enable_hist_gradient_boosting  # noqa
>>> # now you can import normally from ensemble
>>> from sklearn.ensemble import HistGradientBoostingClassifier
示例
部分依赖图

1.11.5.1 使用方法

大多数参数来自 GradientBoostingClassifierGradientBoostingRegressor。一个例外是max_iter参数,该参数取代 n_estimators,并控制提升过程的迭代次数:

>>> from sklearn.experimental import enable_hist_gradient_boosting
>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> from sklearn.datasets import make_hastie_10_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’(对异常值不太敏感)和 ‘poisson’(非常适合模型计数和频率)。在分类方面,‘binary_crossentropy’ 用于二分类, ‘categorical_crossentropy’用于多分类。默认情况下,损失函数是 ‘auto’,并将更具传入的y选择合适的损失函数进行fit

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

用于存储数据的回收箱数量由max_bins参数控制。使用较少的垃圾箱作为一种形式的正规化。通常建议使用尽可能多的回收箱,这是默认的。

用于装数据的箱子数量由max_bins参数控制。使用较少的箱子可以作为一种正则化的形式。通常建议使用尽可能多的箱子,这是默认的。

l2_regularization参数是损失函数上的正则化参数,对应于[XGBoost]方程(2)中的

注意,如果样本数大于10,000,则默认启用早期停止。早期停止行为通过 early-stopping, scoring, validation_fraction, n_iter_no_change, and tol参数来控制.使用一个随意的scorer,或仅仅是训练或验证的损失是有可能早期停止的。注意,由于技术原因,使用 scorer比使用损失要慢得多。默认情况下,如果训练集中至少有10,000个样本,则使用验证损失执行早期停止。

1.11.5.2 缺失值支持

HistGradientBoostingClassifierHistGradientBoostingRegressor内建了缺失值支持。

在训练过程中,生长的树根据潜在的增益,在每次分割时无论样本是否含有缺失值都应该被划分到右边或者左边。因此,因此, 在预测时,有缺失值的样本也会被分配给左或右的子节点:

>>> from sklearn.experimental import enable_hist_gradient_boosting  # noqa
>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> import numpy as np

>>> X = np.array([012, np.nan]).reshape(-11)
>>> y = [0011]

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

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

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

如果在训练期间给定特征下没有碰见缺失值,则会将有缺少值的样本映射到具有最多样本的那个子节点上。

1.11.5.3 样本加权支持

HistGradientBoostingClassifierHistGradientBoostingRegressorfit的时候可以对样本进行加权。

下面的小示例演示了模型如何忽略样本权重为零的样本:

>>> X = [[10],
...      [10],
...      [10],
...      [01]]
>>> y = [0010]
>>> # ignore the first 2 training samples by setting their weight to 0
>>> sample_weight = [0011]
>>> gb = HistGradientBoostingClassifier(min_samples_leaf=1)
>>> gb.fit(X, y, sample_weight=sample_weight)
HistGradientBoostingClassifier(...)
>>> gb.predict([[10]])
array([1])
>>> gb.predict_proba([[10]])[01]
0.99...

正如您所看到的,[1, 0]被轻松地归为1,因为前两个样本由于其样本权重而被忽略。

实现细节:考虑样本权重等于将梯度(和Hessians)乘以样本权重。注意,二进制阶段(特别是分位数计算)没有考虑权重。

1.11.5.4 单调约束

根据手头的问题,您可能事先了解到,给定的特征通常会对目标值产生积极(或负面)影响。例如,在其他条件相同的情况下,较高的信用评分应该会增加获得贷款批准的可能性。单调约束允许您将这些先验知识集成到模型中。

正单调约束是形式的约束:

, 其中是具有两个特征的预测器。

同样,负单调约束的形式如下:

请注意,单调约束只约束输出“所有其他条件相同”。事实上,下列关系并不是通过积极的约束而得到加强的:

您可以使用 monotonic_cst参数为每个特征指定一个单调约束。对于每个特征,值0表示没有约束,而-1和1分别表示负约束和正约束:

>>> from sklearn.experimental import enable_hist_gradient_boosting  # noqa
>>> from sklearn.ensemble import HistGradientBoostingRegressor

... # positive, negative, and no constraint on the 3 features
>>> gbdt = HistGradientBoostingRegressor(monotonic_cst=[1-10])

在二分类中,施加单调约束意味着特征对属于正类的概率有正/负的影响。对于多类分类,不支持单调约束。

示例
单调约束

1.11.5.5 低级并行

HistGradientBoostingClassifier and HistGradientBoostingRegressor通过Cython使用使用OpenMP并行化。有关如何控制线程数量的详细信息,请参阅我们的 Parallelism说明。

以下部分并行化:

  • 将样本从真实值映射到整数值箱(然而,找到bin阈值是连续的)
  • 构建直方图在特征上并行化
  • 在节点上找到最佳分割点的方法是在特征上并行化
  • 在fit期间,将样本映射到左、右两个子节点,并在样本上并行化
  • 梯度和Hessians计算在样本上并行化
  • 预测在样本上并行化

1.11.5.6 为什么它更快

梯度提升过程的瓶颈是决策树的建立。构建一个传统的决策树(如在其他GBDTs、GradientBoostingClassifierGradientBoostingRegressor中)需要在每个节点(针对每个特征)对样本进行排序。排序是必要的,以便能够有效地计算一个分裂点的潜在增益。因此,分割单个节点的复杂度为,其中是节点上的样本数。

相反,HistGradientBoostingClassifierHistGradientBoostingRegressor不需要对特征值进行排序,而是使用名为histogram的数据结构,其中样本是隐式排序的。构造histogram的复杂度为,因此节点分裂过程的复杂度为,远远小于以前的算法。另外,我们不考虑个分裂点,而是只考虑max_bins分裂点,它要小得多。

为了建立histograms,输入数据X需要被绑定到整数值的箱子中。这个装箱过程确实需要对特征值进行排序,但它只在提升过程开始时发生一次(而不是在每个节点,不像GradientBoostingClassifierGradientBoostingRegressor

最后,HistGradientBoostingClassifierHistGradientBoostingRegressor的实现了许多并行化。

参考

[F1999] Friedmann, Jerome H., 2007, “Stochastic Gradient Boosting”

[R2007] G. Ridgeway, “Generalized Boosted Models: A guide to the gbm package”, 2007

[XGBoost] Tianqi Chen, Carlos Guestrin, “XGBoost: A Scalable Tree Boosting System”

LightGBM(1,2) Ke et. al. “LightGBM: A Highly Efficient Gradient BoostingDecision Tree”

1.11.6 投票分类器

VotingClassifier (投票分类器)的原理是结合了多个不同的机器学习分类器,并且采用多数表决(majority vote)(硬投票) 或者平均预测概率(软投票)的方式来预测分类标签。 这样的分类器可以用于一组同样表现良好的模型,以便平衡它们各自的弱点。

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

在多数投票中,对于每个特定样本的预测类别标签是所有单独分类器预测的类别标签中票数占据多数(模式)的类别标签。

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

  • classifier 1 -> class 1
  • classifier 2 -> class 1
  • classifier 3 -> class 2

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

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

  • classifier 1 -> class 2
  • classifier 2 -> class 1

这种情况下, class 1 将会被指定为该样本的类标签。

1.11.6.2 使用方法

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

>>> 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.3 加权平均概率(软投票)

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

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

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

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

这里,预测的类标签是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

>>> # Loading some example data
>>> iris = datasets.load_iris()
>>> X = iris.data[:, [02]]
>>> y = iris.target

>>> # Training classifiers
>>> 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=[212])

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

1.11.6.4 投票分类器(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.0100.0], 'rf__n_estimators': [20200]}

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

1.11.6.5 使用方法

为了通过预测的类别概率来预测类别标签(投票分类器中的 scikit-learn estimators 必须支持 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 投票回归器

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

>>> # Loading some example data
>>> X, y = load_boston(return_X_y=True)

>>> # Training classifiers
>>> 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)
示例
绘制单独与投票回归预测

1.11.8 叠加泛化

叠加泛化是一种组合估计器减少偏差的方法 [W1992] [HTF]。更准确地说,每一个估计器的预测被堆叠在一起,并作为最终估计器的输入来计算预测。这个最终的估计器是通过交叉验证来训练的。

StackingClassifier and StackingRegressor 提供了适用于分类和回归问题的策略。

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的预测作为输入。当使用StackingClassifier 或者StackingRegressor, 时,它需要是一个分类器或回归器:

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

在训练过程中,estimators对整个训练数据X_train进行拟合。它们将在调用 predict或“预测 predict_proba时被调用。为了推广和避免过拟合,final_estimator在out-samples上被训练, 内部使用 sklearn.model_selection.cross_val_predict

对于 StackingClassifier,请注意, estimators的输出由参数stack_method控制,并由每个估计器调用。该参数要么是字符串,即估计器方法名称,要么是'auto',它将根据可用性自动识别可用的方法,并按偏好顺序进行测试:predict_proba, decision_function and predict

StackingRegressor and StackingClassifier可以用作任何其他的回归者或分类器,通用方法predict, predict_proba, 和 decision_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'时,当问题是二分类问题时,将删除第一列。实际上,每个估计器预测的两个概率列都是完全共线的。

注意:可以通过向StackingClassifier 或者 StackingRegressor分配final_estimator来实现多个堆叠层:

>>> 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.83

参考:

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