1.11. 集成方法:梯度提升、随机森林、Bagging、投票、堆叠#
**集成方法**结合使用给定学习算法构建的多个基本估计器的预测,以便提高单个估计器的泛化能力/鲁棒性。
更一般地说,集成模型可以应用于树之外的任何基本学习器,例如在平均方法中,如Bagging 方法、模型堆叠或投票,或在提升方法中,如AdaBoost。
1.11.1. 梯度提升树#
梯度树提升或梯度提升决策树 (GBDT) 是将提升推广到任意可微损失函数,请参阅[Friedman2001]的开创性工作。GBDT 是一种优秀的回归和分类模型,特别是对于表格数据。
1.11.1.1. 基于直方图的梯度提升#
Scikit-learn 0.21 引入了两种新的梯度提升树实现,即 HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
,其灵感来自 LightGBM(参见 [LightGBM])。
当样本数量大于数万个时,这些基于直方图的估计器比 GradientBoostingClassifier
和 GradientBoostingRegressor
的速度**快几个数量级**。
它们还内置了对缺失值的处理,从而避免了对插补器的需求。
这些快速估计器首先将输入样本 X
分箱到整数值箱中(通常为 256 个箱),这极大地减少了要考虑的分割点数,并允许算法利用基于整数的数据结构(直方图),而不是在构建树时依赖排序的连续值。 这些估计器的 API 略有不同,并且 GradientBoostingClassifier
和 GradientBoostingRegressor
的某些功能尚不支持,例如某些损失函数。
示例
1.11.1.1.1. 用法#
大多数参数与 GradientBoostingClassifier
和 GradientBoostingRegressor
相同。 一个例外是 max_iter
参数,它取代了 n_estimators
,并控制提升过程的迭代次数。
>>> 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
可用的回归损失函数是“squared_error”、“absolute_error”(对异常值不太敏感)和“poisson”(非常适合对计数和频率进行建模)。 对于分类,“log_loss”是唯一的选择。 对于二元分类,它使用二元对数损失,也称为二项偏差或二元交叉熵。 对于 n_classes >= 3
,它使用多类对数损失函数,其中多项偏差和分类交叉熵是替代名称。 根据传递给 fit 的 y 选择适当的损失版本。
可以通过 max_leaf_nodes
、max_depth
和 min_samples_leaf
参数控制树的大小。
用于对数据进行分箱的箱数由 max_bins
参数控制。 使用较少的箱是一种正则化形式。 通常建议尽可能多地使用箱(255),这是默认值。
l2_regularization
参数充当损失函数的正则化器,对应于以下表达式中的 \(\lambda\)(参见 [XGBoost] 中的公式 (2))
关于 l2 正则化的详细信息#
重要的是要注意,损失项 \(l(\hat{y}_i, y_i)\) 仅描述了实际损失函数的一半,但 pinball 损失和绝对误差除外。
索引 \(k\) 指的是树集合中的第 k 棵树。 在回归和二元分类的情况下,梯度提升模型每次迭代生长一棵树,然后 \(k\) 运行到 max_iter
。 在多类分类问题的情况下,索引 \(k\) 的最大值是 n_classes
\(\times\) max_iter
。
如果 \(T_k\) 表示第 k 棵树中的叶子数,则 \(w_k\) 是长度为 \(T_k\) 的向量,其中包含形式为 w = -sum_gradient / (sum_hessian + l2_regularization)
的叶子值(参见 [XGBoost] 中的公式 (5))。
叶子值 \(w_k\) 是通过将损失函数的梯度总和除以 Hessian 矩阵的总和得出的。 将正则化添加到分母中会惩罚 Hessian 矩阵较小的叶子(平坦区域),从而导致更新较小。 然后,这些 \(w_k\) 值有助于模型对最终位于相应叶子中的给定输入的预测。 最终预测是基本预测和每棵树的贡献之和。 然后,根据损失函数的选择,通过逆链接函数对该总和的结果进行转换(参见 数学公式)。
请注意,原始论文 [XGBoost] 引入了一个术语 \(\gamma\sum_k T_k\),用于惩罚叶子数量(使其成为 max_leaf_nodes
的平滑版本),此处未介绍,因为它未在 scikit-learn 中实现;而 \(\lambda\) 在按学习率重新缩放之前惩罚单个树预测的大小,请参见 通过学习率进行收缩。
请注意,如果样本数量大于 10,000,则默认启用提前停止。提前停止行为由 early_stopping
、scoring
、validation_fraction
、n_iter_no_change
和 tol
参数控制。可以使用任意评分器或仅使用训练或验证损失来提前停止。请注意,出于技术原因,使用可调用对象作为评分器比使用损失要慢得多。默认情况下,如果训练集中至少有 10,000 个样本,则使用验证损失执行提前停止。
1.11.1.1.2. 缺失值支持#
HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
内置了对缺失值(NaN)的支持。
在训练过程中,树生长器会在每个分割点根据潜在增益学习具有缺失值的样本应该进入左子节点还是右子节点。在预测时,具有缺失值的样本会被相应地分配到左子节点或右子节点。
>>> 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.1.1.3. 样本权重支持#
HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
在 拟合 期间支持样本权重。
以下玩具示例演示了样本权重为零的样本将被忽略。
>>> X = [[1, 0],
... [1, 0],
... [1, 0],
... [0, 1]]
>>> y = [0, 0, 1, 0]
>>> # ignore the first 2 training samples by setting their weight to 0
>>> sample_weight = [0, 0, 1, 1]
>>> gb = HistGradientBoostingClassifier(min_samples_leaf=1)
>>> gb.fit(X, y, sample_weight=sample_weight)
HistGradientBoostingClassifier(...)
>>> gb.predict([[1, 0]])
array([1])
>>> gb.predict_proba([[1, 0]])[0, 1]
0.99...
如您所见,[1, 0]
被舒适地分类为 1
,因为前两个样本由于其样本权重而被忽略。
实现细节:考虑样本权重相当于将梯度(和海森矩阵)乘以样本权重。请注意,分箱阶段(特别是分位数计算)不考虑权重。
1.11.1.1.4. 分类特征支持#
HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
本身支持分类特征:它们可以考虑对无序分类数据进行分割。
对于具有分类特征的数据集,使用本机分类支持通常比依赖于独热编码 (OneHotEncoder
) 更好,因为独热编码需要更大的树深度才能实现等效分割。它通常也比将分类特征视为连续(有序)更好,这发生在有序编码的分类数据中,因为类别是名义量,其中顺序无关紧要。
要启用分类支持,可以将布尔掩码传递给 categorical_features
参数,指示哪个特征是分类的。在下文中,第一个特征将被视为分类特征,第二个特征将被视为数值特征。
>>> gbdt = HistGradientBoostingClassifier(categorical_features=[True, False])
等效地,可以传递一个整数列表,指示分类特征的索引。
>>> gbdt = HistGradientBoostingClassifier(categorical_features=[0])
当输入是 DataFrame 时,也可以传递列名列表。
>>> gbdt = HistGradientBoostingClassifier(categorical_features=["site", "manufacturer"])
最后,当输入是 DataFrame 时,我们可以使用 categorical_features="from_dtype"
,在这种情况下,所有具有分类 dtype
的列都将被视为分类特征。
每个分类特征的基数必须小于 max_bins
参数。有关在分类特征上使用基于直方图的梯度提升的示例,请参见 梯度提升中的分类特征支持。
如果在训练期间存在缺失值,则缺失值将被视为一个适当的类别。如果在训练期间没有缺失值,则在预测时,缺失值将映射到样本最多的子节点(就像连续特征一样)。在预测时,拟合期间未看到的类别将被视为缺失值。
使用分类特征查找分割#
在树中考虑分类分割的规范方法是考虑所有 \(2^{K - 1} - 1\) 个分区,其中 \(K\) 是类别的数量。当 \(K\) 很大时,这很快就会变得令人望而却步。幸运的是,由于梯度提升树始终是回归树(即使对于分类问题),因此存在一种可以产生等效分割的更快策略。首先,根据目标的方差对特征的类别进行排序,对于每个类别 k
。对类别进行排序后,可以考虑*连续分区*,即将类别视为有序的连续值(有关正式证明,请参见 Fisher [Fisher1958])。结果,只需要考虑 \(K - 1\) 个分割,而不是 \(2^{K - 1} - 1\) 个。初始排序是一个 \(\mathcal{O}(K \log(K))\) 操作,导致总复杂度为 \(\mathcal{O}(K \log(K) + K)\),而不是 \(\mathcal{O}(2^K)\)。
示例
1.11.1.1.5. 单调约束#
根据手头的问题,您可能有一些先验知识表明给定特征通常应该对目标值产生积极(或消极)的影响。例如,在其他条件相同的情况下,更高的信用评分应该会增加获得贷款的可能性。单调约束允许您将此类先验知识纳入模型中。
对于具有两个特征的预测变量 \(F\)
单调递增约束是以下形式的约束
\[x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2)\]单调递减约束是以下形式的约束
\[x_1 \leq x_1' \implies F(x_1, x_2) \geq F(x_1', x_2)\]
您可以使用 monotonic_cst
参数为每个特征指定一个单调约束。对于每个特征,值 0 表示没有约束,而 1 和 -1 分别表示单调递增和单调递减约束。
>>> from sklearn.ensemble import HistGradientBoostingRegressor
... # monotonic increase, monotonic decrease, and no constraint on the 3 features
>>> gbdt = HistGradientBoostingRegressor(monotonic_cst=[1, -1, 0])
在二元分类上下文中,施加单调递增(递减)约束意味着特征的较高值应该对样本属于正类的概率产生积极(消极)影响。
然而,单调约束只是略微约束了特征对输出的影响。例如,单调递增和递减约束不能用于强制执行以下建模约束。
此外,多类分类不支持单调约束。
注意
由于类别是无序量,因此无法对分类特征强制执行单调约束。
示例
1.11.1.1.6. 交互约束#
先验地,直方图梯度提升树可以使用任何特征将节点拆分为子节点。这会在特征之间创建所谓的交互,即沿着分支使用不同的特征作为拆分。有时,人们希望限制可能的交互,请参阅 [Mayer2022]。这可以通过参数 interaction_cst
来完成,其中可以指定允许交互的特征的索引。例如,对于总共 3 个特征,interaction_cst=[{0}, {1}, {2}]
禁止所有交互。约束条件 [{0, 1}, {1, 2}]
指定了两组可能交互的特征。特征 0 和 1 可以相互交互,特征 1 和 2 也可以相互交互。但请注意,特征 0 和 2 禁止交互。以下描述了一棵树以及该树可能的拆分
1 <- Both constraint groups could be applied from now on
/ \
1 2 <- Left split still fulfills both constraint groups.
/ \ / \ Right split at feature 2 has only group {1, 2} from now on.
LightGBM 对重叠组使用相同的逻辑。
请注意,未在 interaction_cst
中列出的特征会自动分配一个交互组。同样以 3 个特征为例,这意味着 [{0}]
等效于 [{0}, {1, 2}]
。
示例
参考文献
M. Mayer、S.C. Bourassa、M. Hoesli 和 D.F. Scognamiglio。2022 年。《机器学习在土地和结构估值中的应用》。风险与财务管理杂志 15,第 5 期:193
1.11.1.1.7. 低级并行#
HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
通过 Cython 使用 OpenMP 进行并行化。有关如何控制线程数的更多详细信息,请参阅我们的 并行 说明。
以下部分是并行化的
将样本从实数值映射到整数值分箱(但是,查找分箱阈值是顺序的)
构建直方图是在特征上并行化的
在节点处找到最佳拆分点是在特征上并行化的
在拟合期间,将样本映射到左子节点和右子节点是在样本上并行化的
梯度和 Hessian 矩阵计算是在样本上并行化的
预测是在样本上并行化的
1.11.1.1.8. 为什么它更快#
梯度提升过程的瓶颈是构建决策树。构建传统决策树(如在其他 GBDT GradientBoostingClassifier
和 GradientBoostingRegressor
中)需要在每个节点处(针对每个特征)对样本进行排序。需要排序以便可以有效地计算拆分点的潜在增益。因此,拆分单个节点的复杂度为 \(\mathcal{O}(n_\text{特征} \times n \log(n))\),其中 \(n\) 是节点处的样本数。
相比之下,HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
不需要对特征值进行排序,而是使用一种称为直方图的数据结构,其中样本是隐式排序的。构建直方图的复杂度为 \(\mathcal{O}(n)\),因此节点拆分过程的复杂度为 \(\mathcal{O}(n_\text{特征} \times n)\),远小于前者。此外,我们只考虑 max_bins
个拆分点,而不是 \(n\) 个拆分点,这可能会小得多。
为了构建直方图,需要将输入数据 X
分箱到整数值分箱中。此分箱过程确实需要对特征值进行排序,但这仅在提升过程的最开始发生一次(而不是像在 GradientBoostingClassifier
和 GradientBoostingRegressor
中那样在每个节点处进行)。
最后,HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
实现的许多部分都是并行化的。
参考文献
Ke 等人,《LightGBM:一种高效的梯度提升决策树》
Fisher, W.D. (1958)。《关于最大同质性的分组》。美国统计协会杂志,53,789-798。
1.11.1.2. GradientBoostingClassifier
和 GradientBoostingRegressor
#
GradientBoostingClassifier
和 GradientBoostingRegressor
的用法和参数如下所述。这些估计器的两个最重要的参数是 n_estimators
和 learning_rate
。
Classification#
GradientBoostingClassifier
supports both binary and multi-class
classification.
The following example shows how to fit a gradient boosting classifier
with 100 decision stumps as weak learners:
>>> 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...
The number of weak learners (i.e. regression trees) is controlled by the
parameter n_estimators
; The size of each tree can be controlled either by setting the tree
depth via max_depth
or by setting the number of leaf nodes via
max_leaf_nodes
. The learning_rate
is a hyper-parameter in the range
(0.0, 1.0] that controls overfitting via shrinkage .
Note
Classification with more than 2 classes requires the induction
of n_classes
regression trees at each iteration,
thus, the total number of induced trees equals
n_classes * n_estimators
. For datasets with a large number
of classes we strongly recommend to use
HistGradientBoostingClassifier
as an alternative to
GradientBoostingClassifier
.
Regression#
GradientBoostingRegressor
supports a number of
different loss functions
for regression which can be specified via the argument
loss
; the default loss function for regression is squared error
('squared_error'
).
>>> 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='squared_error'
... ).fit(X_train, y_train)
>>> mean_squared_error(y_test, est.predict(X_test))
5.00...
The figure below shows the results of applying GradientBoostingRegressor
with least squares loss and 500 base learners to the diabetes dataset
(sklearn.datasets.load_diabetes
).
The plot shows the train and test error at each iteration.
The train error at each iteration is stored in the
train_score_
attribute of the gradient boosting model.
The test error at each iterations can be obtained
via the staged_predict
method which returns a
generator that yields the predictions at each stage. Plots like these can be used
to determine the optimal number of trees (i.e. n_estimators
) by early stopping.
示例
1.11.1.2.1. 拟合额外的弱学习器#
GradientBoostingRegressor
和 GradientBoostingClassifier
都支持 warm_start=True
,这允许您向已拟合的模型添加更多估计器。
>>> 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='squared_error'
... )
>>> est = est.fit(X_train, y_train) # fit with 100 trees
>>> mean_squared_error(y_test, est.predict(X_test))
5.00...
>>> _ = est.set_params(n_estimators=200, warm_start=True) # set warm_start and increase num 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.1.2.2. 控制树的大小#
回归树基学习器的规模定义了梯度提升模型可以捕获的变量交互级别。通常,深度为 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
对应于 [Friedman2001] 中关于梯度提升的章节中的变量 J
,并且与 R 的 gbm 包中的参数 interaction.depth
相关,其中 max_leaf_nodes == interaction.depth + 1
。
1.11.1.2.3. 数学公式#
我们首先介绍用于回归的 GBRT,然后详细介绍分类情况。
回归#
GBRT 回归器是加性模型,其对给定输入 \(\hat{y}_i\) 的预测 \(x_i\) 具有以下形式:
其中 \(h_m\) 是在提升的上下文中称为*弱学习器*的估计器。梯度提升树使用固定大小的 决策树回归器 作为弱学习器。常数 M 对应于 n_estimators
参数。
与其他提升算法类似,GBRT 以贪婪的方式构建:
其中,新添加的树 \(h_m\) 的拟合是为了在给定先前集成 \(F_{m-1}\) 的情况下,最小化损失 \(L_m\) 的总和:
其中 \(l(y_i, F(x_i))\) 由 loss
参数定义,将在下一节中详细介绍。
默认情况下,初始模型 \(F_{0}\) 选择为最小化损失的常数:对于最小二乘损失,这是目标值的经验均值。初始模型也可以通过 init
参数指定。
使用一阶泰勒展开式,\(l\) 的值可以近似如下:
注意
简而言之,一阶泰勒展开式表示 \(l(z) \approx l(a) + (z - a) \frac{\partial l}{\partial z}(a)\)。这里,\(z\) 对应于 \(F_{m - 1}(x_i) + h_m(x_i)\),而 \(a\) 对应于 \(F_{m-1}(x_i)\)
数量 \(\left[ \frac{\partial l(y_i, F(x_i))}{\partial F(x_i)} \right]_{F=F_{m - 1}}\) 是损失相对于其第二个参数的导数,在 \(F_{m-1}(x)\) 处评估。对于任何给定的 \(F_{m - 1}(x_i)\),它都很容易以封闭形式计算,因为损失是可微的。我们将其表示为 \(g_i\)。
删除常数项,我们有:
如果 \(h(x_i)\) 被拟合来预测与负梯度 \(-g_i\) 成比例的值,则这将最小化。因此,在每次迭代中,**估计器** \(h_m\) **都被拟合来预测样本的负梯度**。梯度在每次迭代时更新。这可以被认为是函数空间中某种梯度下降。
注意
对于某些损失,例如 'absolute_error'
,其中梯度为 \(\pm 1\),拟合的 \(h_m\) 预测的值不够准确:树只能输出整数值。因此,树 \(h_m\) 的叶节点值在树拟合后会被修改,以便叶节点值最小化损失 \(L_m\)。更新取决于损失:对于绝对误差损失,叶节点的值将更新为该叶节点中样本的中位数。
分类#
用于分类的梯度提升与回归情况非常相似。但是,树的总和 \(F_M(x_i) = \sum_m h_m(x_i)\) 与预测不一致:它不能是类,因为树预测的是连续值。
从值 \(F_M(x_i)\) 到类或概率的映射取决于损失。对于对数损失,\(x_i\) 属于正类的概率被建模为 \(p(y_i = 1 | x_i) = \sigma(F_M(x_i))\),其中 \(\sigma\) 是 sigmoid 或 expit 函数。
对于多类分类,在 \(M\) 次迭代中的每次迭代都会构建 K 棵树(对应 K 个类)。\(x_i\) 属于类 k 的概率被建模为 \(F_{M,k}(x_i)\) 值的 softmax。
请注意,即使对于分类任务,\(h_m\) 子估计器仍然是回归器,而不是分类器。这是因为子估计器被训练来预测(负)*梯度*,而梯度始终是连续量。
1.11.1.2.4. 损失函数#
支持以下损失函数,可以使用参数 loss
指定
回归#
平方误差 (
'squared_error'
):由于其优越的计算性能,它是回归的自然选择。初始模型由目标值的平均值给出。绝对误差 (
'absolute_error'
):回归的鲁棒损失函数。初始模型由目标值的中位数给出。Huber (
'huber'
):另一个结合了最小二乘法和最小绝对偏差的鲁棒损失函数;使用alpha
来控制对异常值的敏感度(有关详细信息,请参阅 [Friedman2001])。分位数 (
'quantile'
):分位数回归的损失函数。使用0 < alpha < 1
指定分位数。此损失函数可用于创建预测区间(请参阅 梯度提升回归的预测区间)。
分类#
二元对数损失 (
'log-loss'
):用于二元分类的二项式负对数似然损失函数。它提供概率估计。初始模型由对数几率比给出。多类别对数损失 (
'log-loss'
):用于具有n_classes
个互斥类的多类别分类的多项式负对数似然损失函数。它提供概率估计。初始模型由每个类的先验概率给出。在每次迭代中,必须构建n_classes
个回归树,这使得 GBRT 对于具有大量类别的数据集效率低下。指数损失 (
'exponential'
):与AdaBoostClassifier
相同的损失函数。对错误标记示例的鲁棒性低于'log-loss'
;只能用于二元分类。
1.11.1.2.5. 通过学习率进行收缩#
[Friedman2001] 提出了一种简单的正则化策略,通过常数因子 \(\nu\) 来缩放每个弱学习器的贡献
参数 \(\nu\) 也称为**学习率**,因为它缩放了梯度下降过程的步长;它可以通过 learning_rate
参数设置。
参数 learning_rate
与参数 n_estimators
(要拟合的弱学习器数量)密切相关。learning_rate
的较小值需要更多数量的弱学习器来保持恒定的训练误差。经验证据表明,learning_rate
的较小值有利于更好的测试误差。[HTF] 建议将学习率设置为较小的常数(例如 learning_rate <= 0.1
),并选择足够大的 n_estimators
以便应用提前停止,有关 learning_rate
和 n_estimators
之间交互的更详细讨论,请参阅 梯度提升中的提前停止,另请参阅 [R2007]。
1.11.1.2.6. 子采样#
[Friedman2002] 提出了随机梯度提升,它将梯度提升与自助聚合(装袋)相结合。在每次迭代中,基分类器都在可用训练数据的一部分 subsample
上进行训练。子样本是在没有放回的情况下抽取的。subsample
的典型值为 0.5。
下图说明了收缩和子采样对模型拟合优度的影响。我们可以清楚地看到,收缩优于不收缩。使用收缩进行子采样可以进一步提高模型的准确性。另一方面,不进行收缩的子采样效果不佳。
另一种减少方差的策略是通过对特征进行子采样,类似于 RandomForestClassifier
中的随机分割。子采样特征的数量可以通过 max_features
参数进行控制。
注意
使用较小的 max_features
值可以显着减少运行时间。
随机梯度提升允许通过计算未包含在自助样本(即袋外样本)中的示例的偏差改进量来计算测试偏差的袋外估计。改进存储在属性 oob_improvement_
中。oob_improvement_[i]
保存了在将第 i 阶段添加到当前预测中时,袋外样本损失方面的改进。袋外估计可用于模型选择,例如确定最佳迭代次数。袋外估计通常非常悲观,因此我们建议改用交叉验证,并且仅在交叉验证过于耗时的情况下才使用袋外估计。
示例
1.11.1.2.7. 使用特征重要性进行解释#
可以通过简单地可视化树结构来轻松解释单个决策树。然而,梯度提升模型包含数百个回归树,因此无法通过目视检查单个树来轻松解释它们。幸运的是,已经提出了许多技术来总结和解释梯度提升模型。
通常,特征对预测目标响应的贡献并不相同;在许多情况下,大多数特征实际上是无关紧要的。在解释模型时,第一个问题通常是:哪些是重要特征,以及它们如何有助于预测目标响应?
单个决策树通过选择适当的分割点来本质上执行特征选择。此信息可用于衡量每个特征的重要性;基本思想是:在树的分割点中使用特征的次数越多,该特征就越重要。这种重要性概念可以通过简单地平均每棵树的基于杂质的特征重要性来扩展到决策树集成(有关详细信息,请参阅 特征重要性评估)。
可以通过 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
,后者是基于特征的排列。
示例
参考文献
Friedman, J.H. (2002). 随机梯度提升。计算统计与数据分析,38,367-378。
G. Ridgeway (2006). 广义提升模型:gbm 包指南
1.11.2. 随机森林和其他随机树集成#
sklearn.ensemble
模块包含两种基于随机决策树的平均算法:随机森林算法和极端随机树方法。这两种算法都是专为树设计的扰动和组合技术 [B1998]。这意味着通过在分类器构建中引入随机性来创建一组多样化的分类器。集成的预测是作为单个分类器平均预测的结果给出的。
与其他分类器一样,森林分类器也必须使用两个数组进行拟合:一个形状为 (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. 随机森林#
在随机森林中(参见 RandomForestClassifier
和 RandomForestRegressor
类),集成中的每棵树都是从训练集中进行有放回抽样(即自助法样本)构建的。
此外,在构建树的过程中拆分每个节点时,最佳拆分是通过对所有输入特征或大小为 max_features
的随机子集的特征值进行穷举搜索找到的。(有关更多详细信息,请参阅参数调整指南。)
这两个随机性来源的目的是降低森林估计器的方差。实际上,单个决策树通常表现出高方差,并且容易过拟合。森林中注入的随机性会产生预测误差 somewhat decoupled 的决策树。通过对这些预测取平均值,可以抵消一些错误。随机森林通过组合不同的树来降低方差,有时会以略微增加偏差为代价。在实践中,方差的降低通常是显著的,因此会产生一个总体上更好的模型。
与原始出版物 [B2001] 相反,scikit-learn 实现通过对分类器的概率预测进行平均来组合分类器,而不是让每个分类器对单个类别进行投票。
随机森林的一个竞争替代方案是基于直方图的梯度提升 (HGBT) 模型。
构建树:随机森林通常依赖于深层树(单独过拟合),这会消耗大量计算资源,因为它们需要多次拆分和评估候选拆分。提升模型构建浅层树(单独欠拟合),这些树的拟合和预测速度更快。
顺序提升:在 HGBT 中,决策树是按顺序构建的,其中每棵树都经过训练以纠正前一棵树所犯的错误。这使得它们能够使用相对较少的树迭代地提高模型的性能。相比之下,随机森林使用多数投票来预测结果,这可能需要更多数量的树才能达到相同的精度水平。
高效分箱:HGBT 使用一种高效的分箱算法,可以处理具有大量特征的大型数据集。分箱算法可以预处理数据以加速后续的树构建(请参阅为什么它更快)。相比之下,scikit-learn 实现的随机森林不使用分箱,而是依赖于精确拆分,这在计算上可能很昂贵。
总的来说,HGBT 与 RF 的计算成本取决于数据集的具体特征和建模任务。最好尝试两种模型,并比较它们在特定问题上的性能和计算效率,以确定哪种模型最适合。
示例
1.11.2.2. 极端随机树#
在极端随机树中(参见 ExtraTreesClassifier
和 ExtraTreesRegressor
类),随机性在计算拆分的方式上更进了一步。与随机森林一样,使用候选特征的随机子集,但不是寻找最具判别力的阈值,而是为每个候选特征随机抽取阈值,并选择这些随机生成的阈值中的最佳阈值作为拆分规则。这通常可以进一步降低模型的方差,但代价是略微增加偏差。
>>> 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_estimators
和 max_features
。前者是森林中树的数量。数量越多越好,但计算时间也越长。此外,请注意,超过临界树数后,结果不会再有显著改善。后者是在拆分节点时要考虑的特征随机子集的大小。值越低,方差降低越大,但偏差增加也越大。经验良好的默认值是回归问题的 max_features=1.0
或等效的 max_features=None
(始终考虑所有特征而不是随机子集),以及分类任务的 max_features="sqrt"
(使用大小为 sqrt(n_features)
的随机子集)(其中 n_features
是数据中特征的数量)。max_features=1.0
的默认值等效于装袋树,可以通过设置更小的值来实现更大的随机性(例如,0.3 是文献中的典型默认值)。当设置 max_depth=None
并结合 min_samples_split=2
时(即,在完全开发树时),通常可以获得良好的结果。但请记住,这些值通常不是最佳值,并且可能会导致模型消耗大量 RAM。最佳参数值应始终进行交叉验证。此外,请注意,在随机森林中,默认情况下使用自助法样本(bootstrap=True
),而极端随机树的默认策略是使用整个数据集(bootstrap=False
)。当使用自助法抽样时,可以在遗漏或袋外样本上估计泛化误差。这可以通过设置 oob_score=True
来启用。
注意
使用默认参数的模型大小为 \(O( M * N * log (N) )\),其中 \(M\) 是树的数量,\(N\) 是样本数量。为了减小模型的大小,您可以更改以下参数:min_samples_split
、max_leaf_nodes
、max_depth
和 min_samples_leaf
。
1.11.2.4. 并行化#
最后,此模块还通过 n_jobs
参数实现了树的并行构建和预测的并行计算。如果 n_jobs=k
,则计算将被划分为 k
个作业,并在机器的 k
个内核上运行。如果 n_jobs=-1
,则使用机器上所有可用的内核。请注意,由于进程间通信开销,加速可能不是线性的(即,使用 k
个作业遗憾的是不会快 k
倍)。但在构建大量树时,或者构建单个树需要相当长的时间时(例如,在大型数据集上),仍然可以实现显著的加速。
示例
参考文献
P. Geurts、D. Ernst. 和 L. Wehenkel,“极度随机树”,机器学习,63(1), 3-42, 2006。
1.11.2.5. 特征重要性评估#
在树中用作决策节点的特征的相对等级(即深度)可用于评估该特征相对于目标变量的可预测性的相对重要性。在树的顶部使用的特征有助于更大比例的输入样本的最终预测决策。因此,它们贡献的**样本的预期比例**可以用作**特征的相对重要性**的估计。在 scikit-learn 中,特征贡献的样本比例与其分割带来的杂质减少相结合,以创建该特征的预测能力的标准化估计。
通过对几棵随机树的预测能力估计值进行**平均**,可以**减少**此类估计值的**方差**,并将其用于特征选择。这被称为平均杂质减少或 MDI。有关 MDI 和使用随机森林进行特征重要性评估的更多信息,请参阅 [L2014]。
警告
在基于树的模型上计算的基于杂质的特征重要性存在两个缺陷,可能导致误导性结论。首先,它们是根据从训练数据集中得出的统计数据计算的,因此**不一定能告诉我们哪些特征对于对留出数据集做出良好的预测最重要**。其次,**它们偏向于高基数特征**,即具有许多唯一值的特征。 排列特征重要性 是基于杂质的特征重要性的替代方法,它不存在这些缺陷。这两种获取特征重要性的方法在以下内容中进行了探讨:排列重要性与随机森林特征重要性 (MDI)。
以下示例显示了使用 ExtraTreesClassifier
模型进行人脸识别任务时,每个像素的相对重要性的颜色编码表示。
在实践中,这些估计值存储为拟合模型上名为 feature_importances_
的属性。这是一个形状为 (n_features,)
的数组,其值为正数且总和为 1.0。值越高,匹配特征对预测函数的贡献就越重要。
示例
参考文献
G. Louppe,“理解随机森林:从理论到实践”,列日大学博士论文,2014 年。
1.11.2.6. 完全随机树嵌入#
RandomTreesEmbedding
实现了数据的无监督转换。使用完全随机树的森林,RandomTreesEmbedding
通过数据点最终所在的叶子的索引对数据进行编码。然后以 one-of-K 的方式对该索引进行编码,从而产生高维、稀疏的二进制编码。这种编码可以非常有效地计算,然后可以用作其他学习任务的基础。代码的大小和稀疏性可以通过选择树的数量和每棵树的最大深度来影响。对于集合中的每棵树,编码包含一个 1 的条目。编码的大小最多为 n_estimators * 2 ** max_depth
,即森林中叶子的最大数量。
由于相邻数据点更有可能位于树的同一叶子内,因此转换执行隐式、非参数的密度估计。
示例
手写数字的流形学习:局部线性嵌入、等距映射… 比较了手写数字的非线性降维技术。
使用树的集合进行特征转换 比较了有监督和无监督的基于树的特征转换。
另请参阅
流形学习 技术也可用于推导出特征空间的非线性表示,这些方法也侧重于降维。
1.11.2.7. 拟合额外的树#
RandomForest、Extra-Trees 和 RandomTreesEmbedding
估计器都支持 warm_start=True
,这允许您向已经拟合的模型添加更多树。
>>> from sklearn.datasets import make_classification
>>> from sklearn.ensemble import RandomForestClassifier
>>> X, y = make_classification(n_samples=100, random_state=1)
>>> clf = RandomForestClassifier(n_estimators=10)
>>> clf = clf.fit(X, y) # fit with 10 trees
>>> len(clf.estimators_)
10
>>> # set warm_start and increase num of estimators
>>> _ = clf.set_params(n_estimators=20, warm_start=True)
>>> _ = clf.fit(X, y) # fit additional 10 trees
>>> len(clf.estimators_)
20
当还设置了 random_state
时,内部随机状态也会在 fit
调用之间保留。这意味着使用 n
个估计器训练一次模型与通过多个 fit
调用迭代构建模型相同,其中估计器的最终数量等于 n
。
>>> clf = RandomForestClassifier(n_estimators=20) # set `n_estimators` to 10 + 10
>>> _ = clf.fit(X, y) # fit `estimators_` will be the same as `clf` above
请注意,这与 random_state 的通常行为不同,因为它在不同的调用中*不会*产生相同的结果。
1.11.3. Bagging 元估计器#
在集成算法中,装袋方法构成了一类算法,这些算法在原始训练集的随机子集上构建黑盒估计器的多个实例,然后聚合它们的个体预测以形成最终预测。这些方法通过将随机化引入其构建过程并从中创建集成,以此作为减少基础估计器(例如,决策树)方差的一种方式。在许多情况下,装袋方法构成了一种相对于单个模型进行改进的非常简单的方法,而无需调整底层基础算法。由于它们提供了一种减少过拟合的方法,因此装袋方法最适合与强大而复杂的模型(例如,完全开发的决策树)一起使用,这与通常最适合与弱模型(例如,浅层决策树)一起使用的提升方法形成对比。
装袋方法有多种形式,但主要区别在于它们绘制训练集随机子集的方式
当数据集的随机子集被绘制为样本的随机子集时,该算法称为粘贴 [B1999]。
当样本被替换绘制时,该方法称为装袋 [B1996]。
当数据集的随机子集被绘制为特征的随机子集时,该方法称为随机子空间 [H1998]。
最后,当在样本和特征的子集上构建基础估计器时,该方法称为随机补丁 [LG2012]。
在 scikit-learn 中,装袋方法作为统一的 BaggingClassifier
元估计器(分别是 BaggingRegressor
)提供,它将用户指定的估计器以及指定绘制随机子集策略的参数作为输入。特别是,max_samples
和 max_features
控制子集的大小(就样本和特征而言),而 bootstrap
和 bootstrap_features
控制样本和特征是替换绘制还是不替换绘制。当使用可用样本的子集时,可以通过设置 oob_score=True
使用袋外样本来估计泛化精度。例如,下面的代码片段说明了如何实例化 KNeighborsClassifier
估计器的装袋集成,每个估计器都建立在 50% 的样本和 50% 的特征的随机子集上。
>>> from sklearn.ensemble import BaggingClassifier
>>> from sklearn.neighbors import KNeighborsClassifier
>>> bagging = BaggingClassifier(KNeighborsClassifier(),
... max_samples=0.5, max_features=0.5)
示例
参考文献
L. Breiman,“Pasting small votes for classification in large databases and on-line”,机器学习,36(1),85-103,1999。
L. Breiman,“Bagging predictors”,机器学习,24(2),123-140,1996。
T. Ho,“The random subspace method for constructing decision forests”,模式分析与机器智能,20(8),832-844,1998。
G. Louppe 和 P. Geurts,“Ensembles on Random Patches”,机器学习和数据库中的知识发现,346-361,2012。
1.11.4. 投票分类器#
VotingClassifier
背后的想法是结合概念上不同的机器学习分类器,并使用多数投票或平均预测概率(软投票)来预测类别标签。这种分类器对于一组性能相同的模型很有用,可以平衡它们各自的弱点。
1.11.4.1. 多数类别标签(多数/硬投票)#
在多数投票中,特定样本的预测类别标签是代表每个单独分类器预测的类别标签的多数(众数)的类别标签。
例如,如果给定样本的预测是
分类器 1 -> 类别 1
分类器 2 -> 类别 1
分类器 3 -> 类别 2
则 VotingClassifier(使用 voting='hard'
)将根据多数类别标签将样本分类为“类别 1”。
在平局的情况下,VotingClassifier
将根据升序选择类别。例如,在以下情况下
分类器 1 -> 类别 2
分类器 2 -> 类别 1
类别标签 1 将被分配给样本。
1.11.4.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.4.3. 加权平均概率(软投票)#
与多数投票(硬投票)相比,软投票返回类别标签作为预测概率之和的 argmax。
可以通过 weights
参数为每个分类器分配特定的权重。当提供权重时,将收集每个分类器的预测类别概率,乘以分类器权重,然后取平均值。然后从具有最高平均概率的类别标签导出最终类别标签。
为了用一个简单的例子来说明这一点,让我们假设我们有 3 个分类器和一个 3 类分类问题,我们为所有分类器分配相等的权重:w1=1,w2=1,w3=1。
然后,样本的加权平均概率将按如下方式计算
分类器 |
类别 1 |
类别 2 |
类别 3 |
---|---|---|---|
分类器 1 |
w1 * 0.2 |
w1 * 0.5 |
w1 * 0.3 |
分类器 2 |
w2 * 0.6 |
w2 * 0.3 |
w2 * 0.1 |
分类器 3 |
w3 * 0.3 |
w3 * 0.4 |
w3 * 0.3 |
加权平均 |
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
>>> # Loading some example data
>>> iris = datasets.load_iris()
>>> X = iris.data[:, [0, 2]]
>>> 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=[2, 1, 2])
>>> clf1 = clf1.fit(X, y)
>>> clf2 = clf2.fit(X, y)
>>> clf3 = clf3.fit(X, y)
>>> eclf = eclf.fit(X, y)
1.11.4.4. 用法#
为了根据预测的类别概率预测类别标签(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]
... )
将 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.5. 投票回归器#
VotingRegressor
背后的想法是结合概念上不同的机器学习回归器并返回平均预测值。这种回归器对于一组性能相同的模型很有用,可以平衡它们各自的弱点。
1.11.5.1. 用法#
以下示例显示了如何拟合 VotingRegressor
>>> from sklearn.datasets import load_diabetes
>>> 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_diabetes(return_X_y=True)
>>> # Training classifiers
>>> reg1 = GradientBoostingRegressor(random_state=1)
>>> reg2 = RandomForestRegressor(random_state=1)
>>> reg3 = LinearRegression()
>>> ereg = VotingRegressor(estimators=[('gb', reg1), ('rf', reg2), ('lr', reg3)])
>>> ereg = ereg.fit(X, y)
示例
1.11.6. 堆叠泛化#
堆叠泛化是一种组合估计器以减少其偏差的方法 [W1992] [HTF]。更准确地说,每个单独估计器的预测都被堆叠在一起,并用作最终估计器的输入以计算预测。最终的估计器通过交叉验证进行训练。
StackingClassifier
和 StackingRegressor
提供了可以应用于分类和回归问题的此类策略。
estimators
参数对应于估计器的列表,这些估计器在输入数据上并行堆叠在一起。它应该以名称和估计器的列表形式给出
>>> from sklearn.linear_model import RidgeCV, LassoCV
>>> from sklearn.neighbors import KNeighborsRegressor
>>> estimators = [('ridge', RidgeCV()),
... ('lasso', LassoCV(random_state=42)),
... ('knr', KNeighborsRegressor(n_neighbors=20,
... metric='euclidean'))]
final_estimator
将使用 estimators
的预测作为输入。当使用 StackingClassifier
或 StackingRegressor
时,它分别需要是一个分类器或回归器
>>> from sklearn.ensemble import GradientBoostingRegressor
>>> from sklearn.ensemble import StackingRegressor
>>> final_estimator = GradientBoostingRegressor(
... n_estimators=25, subsample=0.5, min_samples_leaf=25, max_features=1,
... random_state=42)
>>> reg = StackingRegressor(
... estimators=estimators,
... final_estimator=final_estimator)
为了训练 estimators
和 final_estimator
,需要在训练数据上调用 fit
方法
>>> from sklearn.datasets import load_diabetes
>>> X, y = load_diabetes(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
在内部使用 sklearn.model_selection.cross_val_predict
在样本外数据上进行训练。
对于 StackingClassifier
,请注意 estimators
的输出由参数 stack_method
控制,并且它由每个估计器调用。此参数可以是字符串(估计器方法名称)或 'auto'
,后者将根据可用性自动识别可用方法,并按以下优先顺序进行测试:predict_proba
、decision_function
和 predict
。
StackingRegressor
和 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.53
请注意,也可以使用 transform
方法获取堆叠的 estimators
的输出
>>> reg.transform(X_test[:5])
array([[142..., 138..., 146...],
[179..., 182..., 151...],
[139..., 132..., 158...],
[286..., 292..., 225...],
[126..., 124..., 164...]])
在实践中,堆叠预测器的预测效果与基础层中最好的预测器一样好,有时甚至通过结合这些预测器的不同优势来超越它。但是,训练堆叠预测器的计算成本很高。
注意
对于 StackingClassifier
,当使用 stack_method_='predict_proba'
时,如果问题是二元分类问题,则第一列将被删除。实际上,每个估计器预测的两个概率列完全共线。
注意
可以通过将 final_estimator
分配给 StackingClassifier
或 StackingRegressor
来实现多层堆叠
>>> final_layer_rfr = RandomForestRegressor(
... n_estimators=10, max_features=1, max_leaf_nodes=5,random_state=42)
>>> final_layer_gbr = GradientBoostingRegressor(
... n_estimators=10, max_features=1, max_leaf_nodes=5,random_state=42)
>>> final_layer = StackingRegressor(
... estimators=[('rf', final_layer_rfr),
... ('gbrt', final_layer_gbr)],
... final_estimator=RidgeCV()
... )
>>> multi_layer_regressor = StackingRegressor(
... estimators=[('ridge', RidgeCV()),
... ('lasso', LassoCV(random_state=42)),
... ('knr', KNeighborsRegressor(n_neighbors=20,
... metric='euclidean'))],
... 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.53
参考文献
Wolpert, David H. “Stacked generalization.” Neural networks 5.2 (1992): 241-259.
1.11.7. AdaBoost#
模块 sklearn.ensemble
包含流行的 boosting 算法 AdaBoost,由 Freund 和 Schapire 在 1995 年提出 [FS1995]。
AdaBoost 的核心原则是对重复修改的数据版本拟合一系列弱学习器(即,仅比随机猜测略好一点的模型,例如小型决策树)。然后,通过加权多数投票(或求和)组合所有这些预测,以产生最终预测。每个所谓的 boosting 迭代中的数据修改包括对每个训练样本应用权重 \(w_1\)、\(w_2\)、…、\(w_N\)。最初,这些权重都设置为 \(w_i = 1/N\),因此第一步只是在原始数据上训练一个弱学习器。对于每个后续迭代,样本权重都会单独修改,并且学习算法会重新应用于重新加权的数据。在给定步骤中,由先前步骤中生成的 boosting 模型错误预测的训练样本的权重会增加,而正确预测的样本的权重会减少。随着迭代的进行,难以预测的样本会获得越来越大的影响力。因此,每个后续的弱学习器都被迫专注于序列中先前学习器遗漏的样本 [HTF]。
AdaBoost 可以用于分类和回归问题
对于多类别分类,
AdaBoostClassifier
实现了 AdaBoost.SAMME [ZZRH2009]。对于回归,
AdaBoostRegressor
实现了 AdaBoost.R2 [D1997]。
1.11.7.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, algorithm="SAMME",)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.9...
弱学习器的数量由参数 n_estimators
控制。learning_rate
参数控制弱学习器在最终组合中的贡献。默认情况下,弱学习器是决策树桩。可以通过 estimator
参数指定不同的弱学习器。要调整以获得良好结果的主要参数是 n_estimators
和基本估计器的复杂度(例如,其深度 max_depth
或考虑拆分的最小所需样本数 min_samples_split
)。
示例
多类别 AdaBoost 决策树 显示了 AdaBoost 在多类别问题上的性能。
二分类 AdaBoost 展示了使用 AdaBoost-SAMME 对非线性可分二分类问题的决策边界和决策函数值。
使用 AdaBoost 进行决策树回归 演示了使用 AdaBoost.R2 算法进行回归。
参考文献