.. _ensemble: =========================================================================== 集成方法:梯度提升、随机森林、装袋、投票、堆叠 =========================================================================== .. currentmodule:: sklearn.ensemble **集成方法** 通过结合多个使用给定学习算法构建的基本估计器的预测结果,以提高单个估计器的泛化能力和鲁棒性。 两个非常著名的集成方法例子是 :ref:`梯度提升树 ` 和 :ref:`随机森林 ` 。 更一般地,集成模型可以应用于任何基础学习器,超越树模型,例如在平均方法中如 :ref:`装袋方法 ` 、:ref:`模型堆叠 ` 或 :ref:`投票 ` ,或在提升方法中,如 :ref:`AdaBoost ` 。 .. _gradient_boosting: 梯度提升树 ====================== `梯度树提升 `_ 或梯度提升决策树(GBDT)是对任意可微损失函数的提升方法的推广,参见 [Friedman2001]_ 的经典工作。GBDT 是一个优秀的模型,适用于回归和分类,特别是对于表格数据。 .. topic:: :class:`GradientBoostingClassifier` 与 :class:`HistGradientBoostingClassifier` Scikit-learn 提供了两种梯度提升树的实现::class:`HistGradientBoostingClassifier` 与 :class:`GradientBoostingClassifier` 用于分类,以及相应的回归类。当样本数量大于数万个时,前者可以比后者**快几个数量级**。 缺失值和分类数据在 Hist... 版本中得到原生支持,无需额外的预处理,如插补。 :class:`GradientBoostingClassifier` 和 :class:`GradientBoostingRegressor` ,可能更适用于小样本。 由于分箱可能导致在此设置中过于近似的分割点,因此尺寸可能会有所不同。 .. _histogram_based_gradient_boosting: 基于直方图的梯度提升 ---------------------- Scikit-learn 0.21 引入了两种新的梯度提升树实现,即 :class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` ,灵感来源于 `LightGBM `__(参见 [LightGBM]_)。 这些基于直方图的估计器在样本数量大于数万个时,比 :class:`GradientBoostingClassifier` 和 :class:`GradientBoostingRegressor` **快几个数量级**。 它们还内置了对缺失值的支持,避免了使用填补器的需求。 这些快速估计器首先将输入样本 ``X`` 分箱为整数值(通常为 256 个箱),这大大减少了需要考虑的分割点数量,并允许算法在构建树时利用基于整数的数据结构(直方图),而不是依赖于排序的连续值。这些估计器的 API 略有不同,并且 :class:`GradientBoostingClassifier` 和 :class:`GradientBoostingRegressor` 的一些功能尚未支持,例如某些损失函数。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_inspection_plot_partial_dependence.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_forest_hist_grad_boosting_comparison.py` 使用方法 ^^^^^^^^ 大多数参数与 :class:`GradientBoostingClassifier` 和 :class:`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` 的情况,它使用多类对数损失函数,具有多项偏差 和分类交叉熵等别名。合适的损失版本会根据传递给 :term:`fit` 的 :term:`y` 自动选择。 树的大小可以通过 ``max_leaf_nodes`` 、 ``max_depth`` 和 ``min_samples_leaf`` 参数来控制。 用于对数据进行分箱的箱数由 ``max_bins`` 参数控制。使用较少的箱数可以作为一种正则化手段。通常建议使用尽可能多的箱数(255),这也是默认设置。 ``l2_regularization`` 参数作为损失函数的正则化项, 对应于以下表达式中的 :math:`\lambda` (参见 [XGBoost]_ 中的公式 (2)): .. math:: \mathcal{L}(\phi) = \sum_i l(\hat{y}_i, y_i) + \frac12 \sum_k \lambda ||w_k||^2 .. dropdown:: l2 正则化的详细信息 需要注意的是,损失项 :math:`l(\hat{y}_i, y_i)` 仅描述了实际损失函数的一半,除了分位数损失和绝对误差。 索引 :math:`k` 指的是集成树中的第 k 棵树。在回归和二分类情况下,梯度提升模型每迭代一次生成一棵树,因此 :math:`k` 的范围是 `max_iter` 。在多类分类问题中,索引 :math:`k` 的最大值是 `n_classes` :math:`\times``max_iter ` 。 如果 :math:` T_k `表示第 k 棵树中的叶子数量,那么 :math:` w_k `是一个长度为 :math:` T_k `的向量,包含形式为` w = -sum_gradient / (sum_hessian + l2_regularization) `的叶子值(参见 [XGBoost]_ 中的公式 (5))。 叶子值 :math:` w_k `是通过将损失函数的梯度之和除以海森矩阵之和得到的。在分母中加入正则化项会惩罚具有较小海森矩阵(平坦区域)的叶子,从而导致较小的更新。这些 :math:` w_k `值随后对最终落在相应叶子中的给定输入的模型预测做出贡献。最终预测是基准预测和每棵树贡献的总和。该总和的结果随后根据所选损失函数通过逆链接函数进行转换(参见 :ref:` gradient_boosting_formulation `)。 请注意,原始论文 [XGBoost]_ 引入了一个术语 :math:` \gamma\sum_k T_k ` ,该术语惩罚叶子数量(使其成为` max_leaf_nodes `的平滑版本),这里没有提及,因为它在 scikit-learn 中没有实现;而 :math:` \lambda `在通过学习率重新缩放之前惩罚单个树预测的大小,参见 :ref:` gradient_boosting_shrinkage` 。 请注意,**如果样本数量大于 10,000,默认启用早停机制**。早停行为通过 ``early_stopping`` 、 ``scoring`` 、 ``validation_fraction`` 、 ``n_iter_no_change`` 和 ``tol`` 参数进行控制。可以使用任意 :term:`scorer` ,或者仅使用训练或验证损失进行早停。请注意,由于技术原因,使用可调用对象作为评分器比使用损失显著更慢。默认情况下,如果在训练集中至少有 10,000 个样本,则使用验证损失进行早停。 .. _nan_support_hgbt: 缺失值支持 ^^^^^^^^^^ :class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` 内置支持缺失值(NaNs)。 在训练过程中,树生长器在每个分裂点学习带有缺失值的样本应该分配到左子节点还是右子节点,基于潜在的增益。在预测时,带有缺失值的样本相应地被分配到左子节点或右子节点:: >>> 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]) 如果在训练过程中没有遇到给定特征的缺失值,那么带有缺失值的样本会被映射到具有最多样本的子节点。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_hgbt_regression.py` .. _sw_hgbdt: 样本权重支持 ^^^^^^^^^^^^ :class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` 在 :term:`fit` 过程中支持样本权重。 以下玩具示例演示了样本权重为零的样本被忽略的情况: >>> X = [[1, 0], ... [1, 0], ... [1, 0], ... [0, 1]] >>> y = [0, 0, 1, 0] >>> # 通过将权重设置为0来忽略前2个训练样本 >>> 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` ,因为前两个样本由于其样本权重而被忽略。 实现细节:考虑样本权重相当于将梯度(和海森矩阵)乘以样本权重。请注意,分箱阶段(特别是分位数计算)不考虑权重。 .. _categorical_support_gbdt: 分类特征支持 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` 具有对分类特征的原生支持:它们可以考虑对无序的分类数据进行分割。 对于具有分类特征的数据集,使用原生分类支持通常优于依赖独热编码 (:class:`~sklearn.preprocessing.OneHotEncoder` ),因为独热编码需要更多的树深度才能实现等效分割。通常也更倾向于依赖原生分类支持,而不是将分类特征视为连续(有序),这种情况发生在对分类数据进行有序编码时,因为类别是名义量,顺序并不重要。 要启用分类支持,可以向 `categorical_features` 参数传递一个布尔掩码,指示哪些特征是分类的。在下面的例子中,第一个特征将被视为分类的,第二个特征将被视为数值的:: >>> gbdt = HistGradientBoostingClassifier(categorical_features=[True, False]) 等效地,可以传递一个整数列表,指示分类特征的索引:: >>> gbdt = HistGradientBoostingClassifier(categorical_features=[0]) 当输入是 DataFrame 时,也可以传递一个列名列表:: names:: >>> gbdt = HistGradientBoostingClassifier(categorical_features=["site", "manufacturer"]) 最后,当输入是DataFrame时,我们可以使用 `categorical_features="from_dtype"` ,在这种情况下,所有具有分类 `dtype` 的列都将被视为分类特征。 每个分类特征的基数必须小于 `max_bins` 参数。有关在分类特征上使用基于直方图的梯度提升的示例,请参见 :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_categorical.py` 。 如果在训练期间存在缺失值,则这些缺失值将被视为一个适当的类别。如果在训练期间没有缺失值,则在预测时,缺失值将被映射到具有最多样本的子节点(就像连续特征一样)。在预测时,未在拟合期间看到的类别将被视为缺失值。 .. dropdown:: 分类特征的分裂查找 在树中考虑分类分裂的典型方法是考虑所有 :math:`2^{K - 1} - 1` 个分区,其中 :math:`K` 是类别的数量。当 :math:`K` 很大时,这很快就会变得不可行。幸运的是,由于梯度提升树始终是回归树(即使是分类问题),因此存在一种更快的策略,可以产生等效的分裂。首先,根据每个类别 `k` 的目标方差对特征的类别进行排序。一旦类别排序完成,就可以考虑 *连续分区*,即将类别视为有序的连续值(参见 Fisher [Fisher1958] 的正式证明)。因此,只需要考虑 :math:`K - 1` 个分裂,而不是 :math:`2^{K - 1} - 1` 。初始排序是一个 :math:`\mathcal{O}(K \log(K))` 操作,导致总复杂度为 :math:`\mathcal{O}(K \log(K) + K)` ,而不是 :math:`\mathcal{O}(2^K)` 。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_categorical.py` .. _monotonic_cst_gbdt: 单调约束 ^^^^^^^^^^ 根据手头的问题,您可能拥有先验知识,表明某个给定的特征通常会对目标值产生正向(或负向)影响。例如,在其他条件相同的情况下,更高的信用评分应该会增加获得贷款批准的概率。单调约束允许您将此类先验知识纳入模型中。 对于具有两个特征的预测器 :math:`F` : - **单调递增约束** 是一种形式的约束: .. math:: x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2) - **单调递减约束** 是一种形式的约束: .. math:: 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 ... # 对三个特征分别施加单调递增、单调递减和无约束 >>> gbdt = HistGradientBoostingRegressor(monotonic_cst=[1, -1, 0]) 在二分类上下文中,施加单调递增(递减)约束意味着特征值越高,样本属于正类的概率应该有正向(负向)影响。 然而,单调约束仅对特征对输出的影响施加轻微限制。例如,单调递增和递减约束不能用于强制执行以下建模约束: .. math:: x_1 \leq x_1' \implies F(x_1, x_2) \leq F(x_1', x_2') 此外,单调约束不支持多类分类。 .. note:: 由于类别是无序的量,因此无法对类别特征施加单调约束。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_monotonic_constraints.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_hgbt_regression.py` .. _interaction_cst_hgbt: 交互约束 ^^^^^^^^^^^^^^^^^^^^^^^ 先验地,直方图梯度提升树允许使用任何特征将节点分裂成子节点。这创造了所谓的特征间交互,即沿着分支使用不同的特征进行分裂。有时,我们希望限制可能的交互,参见[Mayer2022]。这可以通过参数 ``interaction_cst`` 来实现,其中可以指定允许交互的特征索引。 例如,总共有3个特征, ``interaction_cst=[{0}, {1}, {2}]`` 禁止所有交互。 约束 ``[{0, 1}, {1, 2}]`` 指定了两个可能交互的特征组。特征0和1可以相互交互,特征1和2也可以相互交互。但请注意,特征0和2禁止交互。 下图描绘了一棵树及其可能的分裂: .. code-block:: none 1 <- 从现在开始可以应用两个约束组 / \ 1 2 <- 左侧分裂仍然满足两个约束组。 / \ / \ 右侧在特征2上的分裂现在只剩下组{1, 2}。 LightGBM对重叠组使用相同的逻辑。 请注意,未在 ``interaction_cst`` 中列出的特征会自动分配给自己的交互组。再次以3个特征为例,这意味着 ``[{0}]`` 等同于 ``[{0}, {1, 2}]`` 。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_inspection_plot_partial_dependence.py` .. rubric:: 参考文献 .. [Mayer2022] M. Mayer, S.C. Bourassa, M. Hoesli, and D.F. Scognamiglio. 2022. :doi:`机器学习在土地和结构估值中的应用 <10.3390/jrfm15050193>` . Journal of Risk and Financial Management 15, no. 5: 193 低级并行化 ^^^^^^^^^^ :class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` 通过 Cython 使用 OpenMP 进行并行化。有关如何控制线程数量的更多详细信息,请参阅我们的 :ref:`parallelism` 笔记。 以下部分是并行化的: - 将样本从实际值映射到整数值的箱子(但查找箱子阈值是顺序的) - 构建直方图在特征上并行化 - 在节点上找到最佳分割点在特征上并行化 - 在拟合过程中,将样本映射到左右子节点在样本上并行化 - 梯度和海森矩阵计算在样本上并行化 - 预测在样本上并行化 .. _Why_it's_faster: 为什么更快 ^^^^^^^^^^ 梯度提升过程的瓶颈是构建决策树。构建传统的决策树(如其他 GBDT 中的 :class:`GradientBoostingClassifier` 和 :class:`GradientBoostingRegressor` ) 需要在每个节点上对样本进行排序(对于每个特征)。排序是必要的,以便可以高效地计算分割点的潜在增益。因此,分割单个节点的复杂度为 :math:`\mathcal{O}(n_\text{features} \times n \log(n))` ,其中 :math:`n` 是节点上的样本数量。 相比之下,:class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` 不需要对特征值进行排序,而是使用称为直方图的数据结构,其中样本是隐式排序的。构建直方图的复杂度为 :math:`\mathcal{O}(n)` ,因此节点分割过程的复杂度为 :math:`\mathcal{O}(n_\text{features} \times n)` ,远小于之前的复杂度。此外,我们只考虑 ``max_bins`` 个分割点,这可能要小得多。 为了构建直方图,输入数据 `X` 需要被分箱到整数值的箱子中。这个分箱过程确实需要对特征值进行排序,但它只在提升过程的最开始发生一次(不像在 :class:`GradientBoostingClassifier` 和 :class:`GradientBoostingRegressor` 中那样在每个节点都发生)。 最后,:class:`HistGradientBoostingClassifier` 和 :class:`HistGradientBoostingRegressor` 的许多实现部分都是并行化的。 .. rubric:: 参考文献 .. [XGBoost] Tianqi Chen, Carlos Guestrin, :arxiv:`"XGBoost: A Scalable Tree Boosting System" <1603.02754>` .. [LightGBM] Ke et. al. `"LightGBM: A Highly Efficient Gradient BoostingDecision Tree" `_ .. [Fisher1958] Fisher, W.D. (1958). `"On Grouping for Maximum Homogeneity" `_ Journal of the American Statistical Association, 53, 789-798. ... max_depth=1, random_state=0).fit(X_train, y_train) >>> clf.score(X_test, y_test) 0.913... 弱学习器(即回归树)的数量由参数 ``n_estimators`` 控制;:ref:`每棵树的大小 ` 可以通过设置树的深度 ``max_depth`` 或通过设置叶节点的数量 ``max_leaf_nodes`` 来控制。 ``learning_rate`` 是一个在 (0.0, 1.0] 范围内的超参数,通过 :ref:`收缩 ` 来控制过拟合。 .. note:: 对于多于2个类别的分类问题,每次迭代需要训练 ``n_classes`` 棵回归树, 因此,总共训练的树的数量等于 ``n_classes * n_estimators`` 。对于类别数量较多的数据集, 我们强烈推荐使用 :class:`HistGradientBoostingClassifier` 作为 :class:`GradientBoostingClassifier` 的替代。 .. dropdown:: 回归 :class:`GradientBoostingRegressor` 支持多种 :ref:`不同的损失函数 ` 用于回归,可以通过参数 ``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... 下图展示了应用 :class:`GradientBoostingRegressor` 的结果 使用最小二乘损失和500个基学习器应用于糖尿病数据集(:func:`sklearn.datasets.load_diabetes` )。图中展示了每次迭代的训练和测试误差。每次迭代的训练误差存储在梯度提升模型的 `train_score_` 属性中。每次迭代的测试误差可以通过:meth:`~GradientBoostingRegressor.staged_predict` 方法获得,该方法返回一个生成器,每次迭代产生预测结果。类似这样的图表可以用来通过提前停止来确定最佳的树的数量(即 ``n_estimators`` )。 .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_gradient_boosting_regression_001.png :target: ../auto_examples/ensemble/plot_gradient_boosting_regression.html :align: center :scale: 75 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_regression.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_oob.py` .. _gradient_boosting_warm_start: 拟合额外的弱学习器 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :class:`GradientBoostingRegressor` 和 :class:`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) # 使用100棵树进行拟合 >>> mean_squared_error(y_test, est.predict(X_test)) 5.00... >>> _ = est.set_params(n_estimators=200, warm_start=True) # 设置warm_start并增加树的数量 .. _gradient_boosting_tree_size: 控制树的大小 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 回归树基学习器的大小定义了梯度提升模型可以捕获的变量交互作用的级别。一般来说,深度为 ``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`` 。 .. _gradient_boosting_formulation: 数学公式 ^^^^^^^^^^^^^^^^^^^^^^^^ 我们首先介绍回归的 GBRT,然后详细说明分类情况。 .. dropdown:: 回归 GBRT 回归器是加法模型,其对于给定输入 :math:`x_i` 的预测 :math:`\hat{y}_i` 具有以下形式: .. math:: \hat{y}_i = F_M(x_i) = \sum_{m=1}^{M} h_m(x_i) 其中 :math:`h_m` 是在梯度提升上下文中称为 *弱学习器* 的估计器。 提升方法中,梯度树提升使用固定大小的 :ref:`决策树回归器 ` 作为弱学习器。常数 M 对应于 `n_estimators` 参数。 与其他提升算法类似,GBRT 以贪婪方式构建: .. math:: F_m(x) = F_{m-1}(x) + h_m(x), 其中新添加的树 :math:`h_m` 拟合以最小化给定先前集合 :math:`F_{m-1}` 的损失和 :math:`L_m` : .. math:: h_m = \arg\min_{h} L_m = \arg\min_{h} \sum_{i=1}^{n} l(y_i, F_{m-1}(x_i) + h(x_i)), 其中 :math:`l(y_i, F(x_i))` 由 `loss` 参数定义,详细内容见下一节。 默认情况下,初始模型 :math:`F_{0}` 选择为最小化损失的常数:对于最小二乘损失,这是目标值的经验均值。初始模型也可以通过 ``init`` 参数指定。 使用一阶泰勒近似,:math:`l` 的值可以近似如下: .. math:: l(y_i, F_{m-1}(x_i) + h_m(x_i)) \approx l(y_i, F_{m-1}(x_i)) + h_m(x_i) \left[ \frac{\partial l(y_i, F(x_i))}{\partial F(x_i)} \right]_{F=F_{m - 1}}. .. note:: 简而言之,一阶泰勒近似表明 :math:`l(z) \approx l(a) + (z - a) \frac{\partial l}{\partial z}(a)` 。这里,:math:`z` 对应于 :math:`F_{m - 1}(x_i) + h_m(x_i)` ,而 :math:`a` 对应于 :math:`F_{m-1}(x_i)` 量 :math:`\left[ \frac{\partial l(y_i, F(x_i))}{\partial F(x_i)} \right]_{F=F_{m - 1}}` 是损失相对于其第二个参数的导数,在 :math:`F_{m-1}(x)` 处求值。由于损失是可微的,对于任何给定的 :math:`F_{m - 1}(x_i)` 都容易以封闭形式计算。我们将其表示为 :math:`g_i` 。 去掉常数项,我们有: .. math:: h_m \approx \arg\min_{h} \sum_{i=1}^{n} h(x_i) g_i 如果 :math:`h(x_i)` 拟合以预测一个值,该值是 与负梯度 :math:`-g_i` 成正比。因此,在每次迭代中,**估计器** :math:`h_m` **被拟合以预测样本的负梯度**。梯度在每次迭代中更新。这可以被视为某种函数空间中的梯度下降。 .. note:: 对于某些损失函数,例如 ``'absolute_error'`` ,其中梯度为 :math:`\pm 1` ,拟合的 :math:`h_m` 预测的值不够准确:树只能输出整数值。因此,树 :math:`h_m` 的叶子值在树拟合后被修改,使得叶子值最小化损失 :math:`L_m` 。更新依赖于损失函数:对于绝对误差损失,叶子值更新为该叶子中样本的中位数。 .. dropdown:: 分类 分类的梯度提升与回归情况非常相似。然而,树的总和 :math:`F_M(x_i) = \sum_m h_m(x_i)` 不是同质的预测:它不能是一个类别,因为树预测的是连续值。 从值 :math:`F_M(x_i)` 到类别或概率的映射依赖于损失函数。对于对数损失,:math:`x_i` 属于正类的概率建模为 :math:`p(y_i = 1 | x_i) = \sigma(F_M(x_i))` ,其中 :math:`\sigma` 是 sigmoid 或 expit 函数。 对于多类别分类,在每次迭代中构建 K 棵树(对于 K 个类别)。:math:`x_i` 属于类别 k 的概率建模为 :math:`F_{M,k}(x_i)` 值的 softmax。 请注意,即使对于分类任务,子估计器 :math:`h_m` 仍然是一个回归器,而不是分类器。这是因为子估计器被训练来预测(负)*梯度*,这些梯度始终是连续量。 .. _gradient_boosting_loss: 损失函数 ^^^^^^^^ 以下损失函数是被支持的,并且可以通过参数 ``loss`` 来指定: .. dropdown:: 回归 * 平方误差( ``'squared_error'`` ):由于其优越的计算特性,是回归的自然选择。初始模型由目标值的均值给出。 * 绝对误差( ``'absolute_error'`` ):一种适用于回归的稳健损失函数。初始模型由目标值的中位数给出。 * Huber( ``'huber'`` ):另一种结合了最小二乘法和最小绝对偏差的稳健损失函数;使用 ``alpha`` 来控制对异常值的敏感度(更多细节请参见 [Friedman2001]_)。 * 分位数( ``'quantile'`` ):用于分位数回归的损失函数。使用 ``0 < alpha < 1`` 来指定分位数。此损失函数可用于创建预测区间(参见 :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_quantile.py` )。 .. dropdown:: 分类 * 二元对数损失( ``'log-loss'`` ):用于二元分类的二项式负对数似然损失函数。它提供概率估计。初始模型由对数几率比给出。 * 多类对数损失( ``'log-loss'`` ):用于具有 ``n_classes`` 个互斥类的多类分类的多项式负对数似然损失函数。它提供概率估计。初始模型由每个类的先验概率给出。在每次迭代中,必须构建 ``n_classes`` 个回归树,这使得GBRT在具有大量类别的数据集上效率较低。 * 指数损失( ``'exponential'`` ):与 :class:`AdaBoostClassifier` 相同的损失函数。相比 ``'log-loss'`` 对错误标记的示例不太稳健;只能用于二元分类。 .. _gradient_boosting_shrinkage: 通过学习率进行收缩 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ [Friedman2001]_ 提出了一种简单的正则化策略,该策略通过一个常数因子 :math:`\nu` 来缩放每个弱学习器的贡献: .. math:: F_m(x) = F_{m-1}(x) + \nu h_m(x) 参数 :math:`\nu` 也被称为 **学习率**,因为它缩放了梯度下降过程的步长;它可以通过 ``learning_rate`` 参数进行设置。 参数 ``learning_rate`` 与参数 ``n_estimators`` (拟合的弱学习器数量)有很强的相互作用。较小的 ``learning_rate`` 值需要更多的弱学习器来维持恒定的训练误差。实证证据表明,较小的 ``learning_rate`` 值有利于更好的测试误差。[HTF]_ 建议将学习率设置为一个较小的常数(例如 ``learning_rate <= 0.1`` ),并选择足够大的 ``n_estimators`` ,以便应用早期停止,有关 ``learning_rate`` 和 ``n_estimators`` 之间相互作用的更详细讨论,请参见 [R2007]_。 子采样 ^^^^^^ [Friedman2002]_ 提出了随机梯度提升,它将梯度提升与自助平均(bagging)相结合。在每次迭代中,基分类器在可用训练数据的一个子集 ``subsample`` 上进行训练。子样本是通过无放回抽样得到的。 ``subsample`` 的典型值为 0.5。 下图展示了收缩和子采样对模型拟合优度的影响。我们可以清楚地看到,收缩优于无收缩。结合收缩的子采样可以进一步提高模型的准确性。另一方面,没有收缩的子采样表现较差。 .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_gradient_boosting_regularization_001.png :target: ../auto_examples/ensemble/plot_gradient_boosting_regularization.html :align: center :scale: 75 另一种减少方差的策略是通过子采样特征 类似于 :class:`RandomForestClassifier` 中的随机分割。 可以通过 ``max_features`` 参数控制子采样特征的数量。 .. note:: 使用较小的 ``max_features`` 值可以显著减少运行时间。 随机梯度提升允许通过计算未包含在自助样本中的样本(即袋外样本)上的偏差改进来计算测试偏差的袋外估计。 改进存储在属性 `oob_improvement_` 中。 ``oob_improvement_[i]`` 表示如果在当前预测中添加第 i 阶段,袋外样本的损失改进。 袋外估计可用于模型选择,例如确定迭代的最佳次数。袋外估计通常非常悲观,因此我们建议使用交叉验证,仅在交叉验证太耗时时才使用袋外估计。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_regularization.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_oob.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_ensemble_oob.py` 特征重要性的解释 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 单个决策树可以通过简单地可视化树结构来轻松解释。然而,梯度提升模型包含数百个回归树,因此不能通过单个树的可视化检查轻松解释。幸运的是,已经提出了多种技术来总结和解释梯度提升模型。 通常,特征对预测目标响应的贡献并不相等;在许多情况下,大多数特征实际上是无关紧要的。 在解释模型时,第一个问题通常是:哪些是重要特征,它们在预测目标响应中的贡献如何? 单个决策树通过选择特征内在地执行特征选择, 合适的分割点。这些信息可以用来衡量每个特征的重要性;基本思想是:一个特征在树的分割点中使用的次数越多,该特征就越重要。这种重要性的概念可以扩展到决策树集成中,只需简单地平均每棵树的基于不纯度的特征重要性(更多细节请参见 :ref:`random_forest_feature_importance` )。 拟合的梯度提升模型的特征重要性分数可以通过 ``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..., ... 请注意,这种特征重要性的计算基于熵,并且与基于特征置换的 :func:`sklearn.inspection.permutation_importance` 不同。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_gradient_boosting_regression.py` .. rubric:: 参考文献 .. [Friedman2001] Friedman, J.H. (2001). :doi:`Greedy function approximation: A gradient boosting machine <10.1214/aos/1013203451>` . Annals of Statistics, 29, 1189-1232. .. [Friedman2002] Friedman, J.H. (2002). `Stochastic gradient boosting. `_ . Computational Statistics & Data Analysis, 38, 367-378. .. [R2007] G. Ridgeway (2006). `Generalized Boosted Models: A guide to the gbm package `_ .. _forest: 随机森林和其他随机化树集成 =================================================== :mod:`sklearn.ensemble` 模块包括两种基于平均的算法 关于随机化 :ref:`决策树 ` :随机森林算法和极端随机树方法。这两种算法都是专门为树设计的扰动-组合技术 [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) 与 :ref:`决策树 ` 类似,树的森林也可以扩展到 :ref:`多输出问题 ` (如果 Y 是一个形状为 ``(n_samples, n_outputs)`` 的数组)。 随机森林 -------- 在随机森林中(参见 :class:`RandomForestClassifier` 和 :class:`RandomForestRegressor` 类),集合中的每棵树都是从训练集中有放回抽样(即,一个自助样本)构建的。 此外,在构建树的过程中,每个节点在进行分裂时,最佳分裂是通过对所有输入特征或大小为 ``max_features`` 的随机子集的特征值进行穷举搜索找到的。(有关更多详细信息,请参阅 :ref:`参数调整指南 ` 。) 这两类随机性的目的是减少森林估计器的方差。实际上,单个决策树通常具有高方差并且容易过拟合。森林中注入的随机性产生了预测误差相对解耦的决策树。通过取这些树的预测平均值,可以减少整体的方差,从而提高模型的泛化能力。 预测中,一些错误可能会相互抵消。随机森林通过组合多样化的树来实现方差的减少,有时可能会以略微增加偏差为代价。在实践中,方差的减少通常是显著的,因此总体上能产生更好的模型。 与原始出版物 [B2001]_ 不同,scikit-learn 实现通过平均其概率预测来组合分类器,而不是让每个分类器对单一类别进行投票。 随机森林的一个有竞争力的替代方案是 :ref:`histogram_based_gradient_boosting` (HGBT) 模型: - 构建树:随机森林通常依赖于深度树(单独来看会过拟合),这会消耗大量计算资源,因为它们需要多次分割和评估候选分割。提升模型构建浅层树(单独来看会欠拟合),这些树的拟合和预测速度更快。 - 顺序提升:在 HGBT 中,决策树是按顺序构建的,每个树都经过训练以纠正前一棵树的错误。这使得它们能够使用相对较少的树迭代地改进模型的性能。相比之下,随机森林使用多数投票来预测结果,这可能需要更多的树才能达到相同的准确度水平。 - 高效分箱:HGBT 使用一种高效的分箱算法,可以处理具有大量特征的大型数据集。分箱算法可以预处理数据以加快后续的树构建(参见 :ref:`为什么更快 ` )。相比之下,scikit-learn 实现的随机森林不使用分箱,而是依赖于精确分割,这可能会在计算上非常昂贵。 总的来说,HGBT 与 RF 的计算成本取决于数据集和建模任务的具体特征。尝试这两种模型并比较它们的性能和计算效率是一个好主意。 针对您的具体问题,确定哪种模型最适合。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_forest_hist_grad_boosting_comparison.py` 极度随机树 -------------------------- 在极度随机树中(参见 :class:`ExtraTreesClassifier` 和 :class:`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 .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_forest_iris_001.png :target: ../auto_examples/ensemble/plot_forest_iris.html :align: center :scale: 75% .. _random_forest_parameters: 参数 ---------- 在使用这些方法时,主要需要调整的参数是 ``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`` (即完全发展树)时可以获得良好的结果。但请记住,这些值通常不是最优的,可能会导致模型消耗大量内存。最佳参数值应始终通过交叉验证来确定。此外,请注意,在随机森林中,默认使用自助样本( ``bootstrap=True`` ),而额外树的默认策略是使用整个数据集( ``bootstrap=False`` )。当使用自助采样时,可以在未包含的样本或袋外样本上估计泛化误差。这可以通过设置 ``oob_score=True`` 来启用。 .. note:: 使用默认参数的模型大小为 :math:`O( M * N * log (N) )` ,其中 :math:`M` 是树的数量,:math:`N` 是样本的数量。 为了减小模型的大小,您可以更改以下参数: ``min_samples_split`` 、 ``max_leaf_nodes`` 、 ``max_depth`` 和 ``min_samples_leaf`` 。 并行化 ------- 最后,该模块还支持通过 ``n_jobs`` 参数进行树的并行构建和预测的并行计算。如果 ``n_jobs=k`` ,则计算被划分为 ``k`` 个任务,并在机器的 ``k`` 个核心上运行。如果 ``n_jobs=-1`` ,则使用机器上所有可用的核心。请注意,由于进程间通信开销,加速可能不是线性的(即,使用 ``k`` 个任务不会不幸地快 ``k`` 倍)。然而,当构建大量树或构建单个树需要相当长的时间(例如,在大数据集上)时,仍然可以实现显著的加速。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_forest_iris.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_forest_importances_faces.py` * :ref:`sphx_glr_auto_examples_miscellaneous_plot_multioutput_face_completion.py` .. rubric:: 参考文献 .. [B2001] L. Breiman, "Random Forests", Machine Learning, 45(1), 5-32, 2001. .. [B1998] L. Breiman, "Arcing Classifiers", Annals of Statistics 1998. * P. Geurts, D. Ernst., and L. Wehenkel, "Extremely randomized trees", Machine Learning, 63(1), 3-42, 2006. .. _random_forest_feature_importance: 特征重要性评估 ---------------- 在树中用作决策节点的特征的相对排名(即深度)可以用来评估该特征相对于目标变量可预测性的相对重要性。在树的顶部使用的特征对输入样本的最终预测决策贡献了更大的一部分。因此,它们贡献的**预期样本比例**可以用来估计特征的**相对重要性**。在 scikit-learn 中,样本比例的 一个特征所贡献的样本数量与从分裂它们中减少的杂质相结合,创建了该特征预测能力的归一化估计。 通过**平均**几个随机化树的预测能力估计,可以**减少这种估计的方差**,并将其用于特征选择。这就是所谓的杂质平均减少,或MDI。有关MDI和使用随机森林进行特征重要性评估的更多信息,请参阅[L2014]_。 .. warning:: 基于树模型的杂质计算的特征重要性存在两个缺陷,可能导致误导性结论。首先,它们是基于从训练数据集派生的统计数据计算的,因此**不一定能告诉我们哪些特征对于在保留数据集上做出良好预测最为重要**。其次,**它们偏爱高基数特征**,即具有许多唯一值的特征。 :ref:`permutation_importance` 是基于杂质特征重要性的替代方法,不会受到这些缺陷的影响。这两种获取特征重要性的方法在以下示例中进行了探讨: :ref:`sphx_glr_auto_examples_inspection_plot_permutation_importance.py` 。 以下示例展示了一个使用 :class:`ExtraTreesClassifier` 模型进行人脸识别任务时,每个像素相对重要性的颜色编码表示。 .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_forest_importances_faces_001.png :target: ../auto_examples/ensemble/plot_forest_importances_faces.html :align: center :scale: 75 在实践中,这些估计值存储为拟合模型的一个名为 ``feature_importances_`` 的属性。这是一个形状为 ``(n_features,)`` 的数组,其值为正且总和为1.0。值越高,匹配特征对预测函数的贡献越重要。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_forest_importances_faces.py` * :ref:`sphx_glr_auto_examples_ensemble_plot_forest_importances.py` .. rubric:: 参考文献 .. [L2014] G. Louppe, :arxiv:`"Understanding Random Forests: From Theory to Practice" <1407.7502>` , PhD Thesis, U. of Liege, 2014. .. _random_trees_embedding: 完全随机树嵌入 ---------------- :class:`RandomTreesEmbedding` 实现了数据的非监督变换。使用完全随机的树森林,:class:`RandomTreesEmbedding` 通过数据点最终落入的叶子的索引对数据进行编码。这个索引然后以一种“一对一”的方式进行编码,导致高维、稀疏的二进制编码。 这种编码可以非常高效地计算,并且可以作为其他学习任务的基础。 编码的大小和稀疏性可以通过选择树的数量和每棵树的最大深度来影响。对于集成中的每棵树,编码包含一个“一”的条目。编码的大小最多为 ``n_estimators * 2 ** max_depth`` ,即森林中叶子的最大数量。 由于邻近的数据点更有可能位于同一棵树的同一片叶子中,这种变换执行了一种隐式的、非参数的密度估计。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_random_forest_embedding.py` * :ref:`sphx_glr_auto_examples_manifold_plot_lle_digits.py` 比较了手写数字上的非线性降维技术。 * :ref:`sphx_glr_auto_examples_ensemble_plot_feature_transformation.py` 比较了基于树的有监督和无监督特征变换。 .. seealso:: :ref:`manifold` 技术也可以用于导出特征空间中的非线性表示,这些方法也关注于降维。 .. _tree_ensemble_warm_start: 拟合额外的树 -------------- 随机森林、Extra-Trees 和 :class:`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) # 用10棵树拟合 >>> len(clf.estimators_) 10 >>> # 设置warm_start并增加估计器数量 >>> _ = clf.set_params(n_estimators=20, warm_start=True) >>> _ = clf.fit(X, y) # 拟合额外的10棵树 >>> len(clf.estimators_) 20 当 ``random_state`` 也被设置时,内部随机状态在 ``fit`` 调用之间也会被保留。这意味着一次性用 ``n`` 个估计器训练模型与通过多次 ``fit`` 调用逐步构建模型是相同的,最终的估计器数量等于 ``n`` 。 :: >>> clf = RandomForestClassifier(n_estimators=20) # 将 `n_estimators` 设置为 10 + 10 >>> _ = clf.fit(X, y) # 拟合的 `estimators_` 将与上面的 `clf` 相同 请注意,这与 :term:`random_state` 的通常行为不同,因为它不会在不同调用中产生相同的结果。 .. _bagging: Bagging 元估计器 ================ 在集成算法中,bagging 方法形成了一类算法,它们在原始训练集的随机子集上构建多个黑盒估计器的实例,然后通过聚合它们的个体预测来形成最终预测。这些方法被用作一种减少基估计器(例如,决策树)方差的方式,通过在其构造过程中引入随机性,然后从中构建一个集成。在许多情况下,bagging 方法构成了一种非常简单的改进方式,相对于单个模型,而不必调整底层基估计器。 算法。由于它们提供了一种减少过拟合的方法,集成方法在强且复杂的模型(例如,完全发展的决策树)上效果最佳,与通常在弱模型(例如,浅层决策树)上效果最佳的提升方法形成对比。 集成方法有多种形式,但主要通过它们绘制训练集随机子集的方式相互区别: * 当数据集的随机子集作为样本的随机子集绘制时,该算法被称为粘贴 [B1999]_。 * 当样本以替换方式绘制时,该方法被称为装袋 [B1996]_。 * 当数据集的随机子集作为特征的随机子集绘制时,该方法被称为随机子空间 [H1998]_。 * 最后,当基估计器建立在样本和特征的子集上时,该方法被称为随机补丁 [LG2012]_。 在 scikit-learn 中,集成方法以统一的 :class:`BaggingClassifier` 元估计器(或 :class:`BaggingRegressor` )提供,接受用户指定的估计器以及指定绘制随机子集策略的参数。特别是, ``max_samples`` 和 ``max_features`` 控制子集的大小(以样本和特征为单位),而 ``bootstrap`` 和 ``bootstrap_features`` 控制样本和特征是按替换方式还是无替换方式绘制。当使用可用样本的子集时,可以通过设置 ``oob_score=True`` 使用袋外样本来估计泛化精度。作为一个例子,下面的代码片段展示了如何实例化一个由 :class:`~sklearn.neighbors.KNeighborsClassifier` 估计器组成的集成,每个估计器建立在50%的样本和50%的特征的随机子集上。 >>> from sklearn.ensemble import BaggingClassifier >>> from sklearn.neighbors import KNeighborsClassifier >>> bagging = BaggingClassifier(KNeighborsClassifier(), ... max_samples=0.5, max_features=0.5) .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_bias_variance.py` .. rubric:: 参考文献 .. [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. >>> 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] 加权平均概率(软投票) ---------------------- 与多数投票(硬投票)相反,软投票返回类别标签作为预测概率之和的 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 近邻分类器使用软投票分类器时,决策区域可能如何变化:: >>> 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) .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_voting_decision_regions_001.png :target: ../auto_examples/ensemble/plot_voting_decision_regions.html :align: center :scale: 75% 用法 ----- 为了基于预测的类别概率来预测类别标签(VotingClassifier 中的 scikit-learn 估计器必须支持 ``predict_proba`` 方法):: rst >>> 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] ... ) .. dropdown:: 使用 :class:`VotingClassifier` 与 :class:`~sklearn.model_selection.GridSearchCV` :class:`VotingClassifier` 也可以与 :class:`~sklearn.model_selection.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) .. _voting_regressor: 投票回归器 ============ :class:`VotingRegressor` 背后的想法是结合概念上不同的机器学习回归器,并返回预测值的平均值。 这样的回归器对于一组表现相当好的模型来说是有用的,以便平衡它们的个别弱点。 用法 ---- 以下示例展示了如何拟合 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 >>> # 加载一些示例数据 >>> X, y = load_diabetes(return_X_y=True) >>> # 训练分类器 >>> 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) .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_voting_regressor_001.png :target: ../auto_examples/ensemble/plot_voting_regressor.html :align: center :scale: 75% .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_voting_regressor.py` .. _stacking: 堆叠泛化 ======== 堆叠泛化是一种组合估计器以减少其偏差的方法 [W1992]_ [HTF]_。更确切地说,每个单独估计器的预测结果被堆叠在一起,并作为最终估计器的输入来计算预测。这个最终估计器是通过交叉验证训练的。 :class:`StackingClassifier` 和 :class:`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` 的预测结果作为输入。在使用 :class:`StackingClassifier` 或 :class:`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` 会在外部样本上进行训练,内部使用 :func:`sklearn.model_selection.cross_val_predict` 。 对于 :class:`StackingClassifier` ,请注意 `estimators` 的输出由参数 `stack_method` 控制,并且每个估计器都会调用它。此参数可以是字符串,即估计器方法名称,或者是 `'auto'` ,它将根据可用性自动识别可用的方法,按偏好顺序测试: `predict_proba` 、 `decision_function` 和 `predict` 。 :class:`StackingRegressor` 和 :class:`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...]] 实际上,一个堆叠预测器预测的效果和基础层中最好的预测器一样好,甚至有时通过结合这些预测器的不同优势而超越它。然而,训练一个堆叠预测器在计算上是昂贵的。 .. note:: 对于 :class:`StackingClassifier` ,当使用 `stack_method_='predict_proba'` 时,如果是二分类问题,第一个概率列会被丢弃。确实,每个估计器预测的两个概率列是完全共线的。 .. note:: 可以通过将 `final_estimator` 赋值给 :class:`StackingClassifier` 或 :class:`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 .. rubric:: 参考文献 .. [W1992] Wolpert, David H. "Stacked generalization." Neural networks 5.2 (1992): 241-259. AdaBoost,由Freund和Schapire在1995年引入 [FS1995]_。 AdaBoost的核心原理是拟合一组弱学习器(即,仅略优于随机猜测的模型,如小型决策树)在反复修改的数据版本上。所有这些学习器的预测结果通过加权多数投票(或求和)来产生最终预测。每个所谓的提升迭代中的数据修改包括对每个训练样本应用权重 :math:`w_1` , :math:`w_2` , ..., :math:`w_N` 。最初,这些权重都设置为 :math:`w_i = 1/N` ,因此第一步简单地在原始数据上训练一个弱学习器。对于每个连续迭代,样本权重被单独修改,并且学习算法被重新应用于重新加权的数据。在给定步骤中,那些被前一步的提升模型错误预测的训练样本的权重增加,而正确预测的样本的权重减少。随着迭代进行,难以预测的样本获得越来越大的影响。因此,每个后续的弱学习器被迫集中于序列中前一个学习器遗漏的样本 [HTF]_。 .. figure:: ../auto_examples/ensemble/images/sphx_glr_plot_adaboost_multiclass_001.png :target: ../auto_examples/ensemble/plot_adaboost_multiclass.html :align: center :scale: 75 AdaBoost既可用于分类问题,也可用于回归问题: - 对于多类分类,:class:`AdaBoostClassifier` 实现了 AdaBoost.SAMME [ZZRH2009]_。 - 对于回归,:class:`AdaBoostRegressor` 实现了 AdaBoost.R2 [D1997]_。 使用方法 -------- 以下示例展示了如何拟合一个包含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`` )。 .. rubric:: 示例 * :ref:`sphx_glr_auto_examples_ensemble_plot_adaboost_multiclass.py` 展示了AdaBoost在多类问题上的性能。 * :ref:`sphx_glr_auto_examples_ensemble_plot_adaboost_twoclass.py` 展示了使用AdaBoost-SAMME解决非线性可分的两类问题的决策边界和决策函数值。 * :ref:`sphx_glr_auto_examples_ensemble_plot_adaboost_regression.py` 演示了使用AdaBoost.R2算法的回归。 .. rubric:: 参考文献 .. [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] H. Drucker. "Improving Regressors using Boosting Techniques", 1997. .. [HTF] T. Hastie, R. Tibshirani and J. Friedman, "Elements of Statistical Learning Ed. 2", Springer, 2009.