1.11. 集成方法:梯度提升、随机森林、Bagging、投票、Stacking#
集成方法组合多个基于给定学习算法构建的基础估计器的预测,以提高单个估计器的泛化能力/鲁棒性。
更一般地,集成模型可以应用于树之外的任何基础学习器,在平均方法中,如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’,它比平方误差对异常值不太敏感;
‘gamma’,非常适合模拟严格为正的结果;
‘poisson’,非常适合模拟计数和频率;
‘quantile’,允许估计条件分位数,之后可用于获得预测区间。
对于分类,’log_loss’是唯一的选择。对于二元分类,它使用二元对数损失,也称为二项式偏差或二元交叉熵。对于n_classes >= 3
,它使用多类对数损失函数,多项式偏差和分类交叉熵作为替代名称。合适的损失版本是根据传递给y的fit选择的。
可以通过max_leaf_nodes
、max_depth
和min_samples_leaf
参数控制树的大小。
用于对数据进行分箱的分箱数由max_bins
参数控制。使用较少的分箱是一种正则化形式。通常建议尽可能多地使用分箱(255),这是默认值。
l2_regularization
参数充当损失函数的正则化器,对应于以下表达式中的\(\lambda\)(参见[XGBoost]中的公式(2))
L2正则化的细节#
需要注意的是,损失项\(l(\hat{y}_i, y_i)\)只描述了实际损失函数的一半,除了弹球损失和绝对误差。
索引\(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, and D.F. Scognamiglio. 2022. Machine Learning Applications to Land and Structure Valuation. Journal of Risk and Financial Management 15, no. 5: 193
1.11.1.1.7. 低级并行化#
HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
使用 OpenMP 通过 Cython 进行并行化。有关如何控制线程数的更多详细信息,请参阅我们的 并行性 说明。
并行化的部分包括:
将样本从实数值映射到整数值箱(但是查找箱阈值是顺序的)
构建直方图在特征上并行化
在节点处找到最佳分割点在特征上并行化
在拟合过程中,将样本映射到左子节点和右子节点在样本上并行化
梯度和 Hessian 计算在样本上并行化
预测在样本上并行化
1.11.1.1.8. 为什么它更快#
梯度提升过程的瓶颈是构建决策树。构建传统的决策树(如其他 GBDT GradientBoostingClassifier
和 GradientBoostingRegressor
)需要在每个节点(对于每个特征)对样本进行排序。需要排序以便能够有效地计算分割点的潜在增益。因此,分割单个节点的复杂度为 \(\mathcal{O}(n_\text{features} \times n \log(n))\),其中 \(n\) 是节点处样本的数量。
相比之下,HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
不需要对特征值进行排序,而是使用一种称为直方图的数据结构,其中样本被隐式排序。构建直方图的复杂度为 \(\mathcal{O}(n)\),因此节点分裂过程的复杂度为 \(\mathcal{O}(n_\text{features} \times n)\),远小于之前的复杂度。此外,我们只需要考虑 max_bins
个分割点,而不是 \(n\) 个分割点,这可能要小得多。
为了构建直方图,需要将输入数据 X
分箱为整数值的箱。这个分箱过程确实需要对特征值进行排序,但它只在提升过程的开始发生一次(不像在 GradientBoostingClassifier
和 GradientBoostingRegressor
中那样,在每个节点都进行排序)。
最后,HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
的许多实现部分都是并行化的。
参考文献
Fisher, W.D. (1958). “On Grouping for Maximum Homogeneity” 美国统计协会杂志,53, 789-798。
1.11.1.2. GradientBoostingClassifier
和 GradientBoostingRegressor
#
GradientBoostingClassifier
和 GradientBoostingRegressor
的使用方法和参数如下所述。这两个估计器的两个最重要的参数是 n_estimators
和 learning_rate
。
分类#
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] 内的超参数,它通过 收缩 来控制过拟合。
注意
超过 2 个类别的分类需要在每次迭代中引入 n_classes
个回归树,因此,引入的树的总数等于 n_classes * n_estimators
。对于具有大量类别的数据集,我们强烈建议使用 HistGradientBoostingClassifier
作为 GradientBoostingClassifier
的替代方案。
回归#
GradientBoostingRegressor
支持多种用于回归的 不同的损失函数,可以通过参数 loss
指定;回归的默认损失函数是平方误差 ('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...
下图显示了将GradientBoostingRegressor
(使用最小二乘损失和500个基学习器)应用于糖尿病数据集(sklearn.datasets.load_diabetes
)的结果。该图显示了每次迭代的训练误差和测试误差。每次迭代的训练误差存储在梯度提升模型的train_score_
属性中。每次迭代的测试误差可以通过staged_predict
方法获得,该方法返回一个生成器,生成每个阶段的预测结果。此类图表可用于通过提前停止来确定最佳树木数量(即n_estimators
)。
示例
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\)是为了最小化损失之和\(L_m\)而拟合的,给定之前的集成\(F_{m-1}\)
其中\(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]提出了随机梯度提升,它结合了梯度提升和自助聚合(bagging)。在每次迭代中,基本分类器都使用可用训练数据的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). 随机梯度提升。. Computational Statistics & Data Analysis, 38, 367-378.
G. Ridgeway (2006). 广义提升模型:gbm 包指南
1.11.2. 随机森林和其他随机树集成#
sklearn.ensemble
模块包含两个基于随机决策树的平均算法:随机森林算法和Extra-Trees方法。这两种算法都是扰动和组合技术[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
类),集成中的每棵树都是从训练集中有放回地抽取样本(即 bootstrap 样本)构建的。
此外,在构建树的过程中分割每个节点时,最佳分割是通过对所有输入特征或大小为 max_features
的随机子集的特征值进行穷举搜索来找到的。(更多详情请参见参数调整指南)。
这两种随机性的目的是降低森林估计器的方差。实际上,单个决策树通常表现出高方差,并倾向于过拟合。森林中注入的随机性会产生预测误差有些解耦的决策树。通过对这些预测取平均值,一些误差可以抵消。随机森林通过组合不同的树来降低方差,有时是以略微增加偏差为代价的。在实践中,方差减少通常非常显著,因此产生了更好的整体模型。
与原始论文[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
的默认值等效于bagging树,可以通过设置较小的值来获得更大的随机性(例如,文献中的典型默认值为0.3)。当结合min_samples_split=2
(即完全展开树时)设置max_depth=None
时,通常可以获得良好的结果。但请记住,这些值通常并非最佳值,并且可能导致消耗大量RAM的模型。最佳参数值应始终进行交叉验证。此外,请注意,在随机森林中,默认情况下使用bootstrap样本(bootstrap=True
),而Extra-Trees的默认策略是使用整个数据集(bootstrap=False
)。使用bootstrap采样时,可以在留出的或袋外样本上估计泛化误差。这可以通过设置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)。
在实践中,这些估计值存储为拟合模型上的名为feature_importances_
的属性。这是一个形状为(n_features,)
的数组,其值为正数且总和为1.0。值越高,匹配特征对预测函数的贡献就越重要。
示例
参考文献
G. Louppe, “理解随机森林:从理论到实践”,列日大学博士论文,2014年。
1.11.2.6. 完全随机树嵌入#
RandomTreesEmbedding
实现了一种数据的无监督转换。使用完全随机树的森林,RandomTreesEmbedding
通过数据点最终所在的叶节点索引对数据进行编码。然后以one-of-K的方式对该索引进行编码,从而产生高维稀疏二进制编码。这种编码可以非常高效地计算,然后可以用作其他学习任务的基础。编码的大小和稀疏性可以通过选择树的数量和每棵树的最大深度来影响。对于集合中的每棵树,编码包含一个值为1的条目。编码的大小最多为n_estimators * 2 ** max_depth
,即森林中叶节点的最大数量。
由于相邻数据点更有可能位于树的同一叶节点中,因此该转换执行隐式非参数密度估计。
示例
手写数字的流形学习:局部线性嵌入、Isomap… 将非线性降维技术应用于手写数字进行比较。
基于树的集成特征转换 比较了监督和无监督的基于树的特征转换。
另请参见
流形学习 技术也可用于导出特征空间的非线性表示,这些方法也侧重于降维。
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 元估计器#
在集成算法中,Bagging 方法构成一类算法,这些算法在原始训练集的随机子集上构建多个黑盒估计器的实例,然后聚合它们的个体预测以形成最终预测。这些方法被用作减少基本估计器(例如,决策树)方差的一种方法,方法是在其构建过程中引入随机性,然后从中构建集成。在许多情况下,Bagging 方法构成了一种非常简单的方法来改进单个模型,而无需调整底层基本算法。由于它们提供了一种减少过拟合的方法,因此 Bagging 方法最适合强大的复杂模型(例如,完全展开的决策树),这与通常最适合弱模型(例如,浅层决策树)的 Boosting 方法形成对比。
Bagging 方法有很多变体,但它们主要区别在于它们绘制训练集随机子集的方式。
当数据集的随机子集被绘制为样本的随机子集时,该算法被称为 Pasting [B1999]。
当样本有放回地抽取时,该方法被称为 Bagging [B1996]。
当数据集的随机子集被绘制为特征的随机子集时,该方法被称为随机子空间 [H1998]。
最后,当基本估计器建立在样本和特征的子集上时,该方法被称为随机块 [LG2012]。
在 scikit-learn 中,Bagging 方法作为统一的 BaggingClassifier
元估计器(或 BaggingRegressor
)提供,它以用户指定的估计器以及指定绘制随机子集策略的参数作为输入。特别是,max_samples
和 max_features
控制子集的大小(就样本和特征而言),而 bootstrap
和 bootstrap_features
控制样本和特征是有放回还是无放回地抽取。当使用可用样本的子集时,可以通过设置 oob_score=True
来使用包外样本估计泛化精度。例如,下面的代码片段说明了如何实例化 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)
示例
参考文献
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
包含流行的提升算法AdaBoost,该算法由Freund和Schapire于1995年提出 [FS1995]。
AdaBoost的核心原理是,在一个反复修改的数据版本上拟合一系列弱学习器(即,比随机猜测略好一些的模型,例如小的决策树)。然后,通过加权多数投票(或求和)组合所有弱学习器的预测,以产生最终预测。每次所谓的提升迭代中的数据修改包括将权重\(w_1\)、\(w_2\)、…、\(w_N\)应用于每个训练样本。最初,这些权重都设置为\(w_i = 1/N\),因此第一步只是在原始数据上训练一个弱学习器。对于每个后续迭代,样本权重会分别修改,并且学习算法会重新应用于重新加权的数据。在给定的步骤中,那些被前一步生成的增强模型错误预测的训练示例的权重会增加,而那些被正确预测的示例的权重会降低。随着迭代的进行,难以预测的示例会获得越来越大的影响。因此,每个后续的弱学习器都将被迫关注先前序列中错过的示例 [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)
>>> 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
)。
示例
多类 AdaBoosted 决策树 显示了 AdaBoost 在多类问题上的性能。
二类 AdaBoost 使用 AdaBoost-SAMME 显示了非线性可分离二类问题的决策边界和决策函数值。
使用 AdaBoost 的决策树回归 演示了 AdaBoost.R2 算法的回归。
参考文献