随机森林与直方图梯度提升模型的比较#

在这个例子中,我们比较了随机森林 (RF) 和直方图梯度提升 (HGBT) 模型在回归数据集上的分数和计算时间性能,尽管**此处介绍的所有概念也适用于分类**。

比较是通过改变控制每个估计器树数量的参数来进行的。

  • n_estimators 控制森林中树的数量。这是一个固定数字。

  • max_iter 是基于梯度提升的模型的最大迭代次数。对于回归和二元分类问题,迭代次数对应于树的个数。此外,模型所需的实际树木数量取决于停止标准。

HGBT 使用梯度提升法迭代地改进模型的性能,方法是将每棵树拟合到损失函数关于预测值的负梯度。另一方面,RF 基于 Bagging,并使用多数投票来预测结果。

有关集成模型的更多信息,请参见用户指南,或参见直方图梯度提升树中的特性,了解 HGBT 模型的其他一些特性的示例。

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

加载数据集#

from sklearn.datasets import fetch_california_housing

X, y = fetch_california_housing(return_X_y=True, as_frame=True)
n_samples, n_features = X.shape

HGBT 对分箱后的特征值使用基于直方图的算法,可以有效地处理大型数据集(数万个样本或更多),并且具有大量的特征(参见为什么它更快)。scikit-learn 中 RF 的实现不使用分箱,而是依赖于精确分割,这在计算上可能代价很高。

print(f"The dataset consists of {n_samples} samples and {n_features} features")
The dataset consists of 20640 samples and 8 features

计算分数和计算时间#

请注意,HistGradientBoostingClassifierHistGradientBoostingRegressor 的实现的许多部分默认情况下都是并行化的。

RandomForestRegressorRandomForestClassifier 的实现也可以通过使用 n_jobs 参数在多个核心上运行,此处设置为与主机上的物理核心数匹配。有关更多信息,请参见并行化

import joblib

N_CORES = joblib.cpu_count(only_physical_cores=True)
print(f"Number of physical cores: {N_CORES}")
Number of physical cores: 2

与 RF 不同,HGBT 模型提供了一种提前停止选项(参见梯度提升中的提前停止),以避免添加新的不必要的树。在内部,算法使用样本外集合来计算每次添加树时模型的泛化性能。因此,如果泛化性能在超过 n_iter_no_change 次迭代后没有改进,则停止添加树。

两个模型的其他参数都进行了调整,但为了使示例保持简单,此处未显示该过程。

import pandas as pd

from sklearn.ensemble import HistGradientBoostingRegressor, RandomForestRegressor
from sklearn.model_selection import GridSearchCV, KFold

models = {
    "Random Forest": RandomForestRegressor(
        min_samples_leaf=5, random_state=0, n_jobs=N_CORES
    ),
    "Hist Gradient Boosting": HistGradientBoostingRegressor(
        max_leaf_nodes=15, random_state=0, early_stopping=False
    ),
}
param_grids = {
    "Random Forest": {"n_estimators": [10, 20, 50, 100]},
    "Hist Gradient Boosting": {"max_iter": [10, 20, 50, 100, 300, 500]},
}
cv = KFold(n_splits=4, shuffle=True, random_state=0)

results = []
for name, model in models.items():
    grid_search = GridSearchCV(
        estimator=model,
        param_grid=param_grids[name],
        return_train_score=True,
        cv=cv,
    ).fit(X, y)
    result = {"model": name, "cv_results": pd.DataFrame(grid_search.cv_results_)}
    results.append(result)

注意

调整 RF 的 n_estimators 通常会导致浪费计算机资源。实际上,只需要确保它足够大,以至于将其值加倍不会导致测试分数的显著提高。

绘制结果#

我们可以使用 plotly.express.scatter 来可视化计算时间和平均测试分数之间的权衡。将光标悬停在给定点上会显示相应的参数。误差条对应于交叉验证的不同折叠中计算出的一个标准差。

import plotly.colors as colors
import plotly.express as px
from plotly.subplots import make_subplots

fig = make_subplots(
    rows=1,
    cols=2,
    shared_yaxes=True,
    subplot_titles=["Train time vs score", "Predict time vs score"],
)
model_names = [result["model"] for result in results]
colors_list = colors.qualitative.Plotly * (
    len(model_names) // len(colors.qualitative.Plotly) + 1
)

for idx, result in enumerate(results):
    cv_results = result["cv_results"].round(3)
    model_name = result["model"]
    param_name = list(param_grids[model_name].keys())[0]
    cv_results[param_name] = cv_results["param_" + param_name]
    cv_results["model"] = model_name

    scatter_fig = px.scatter(
        cv_results,
        x="mean_fit_time",
        y="mean_test_score",
        error_x="std_fit_time",
        error_y="std_test_score",
        hover_data=param_name,
        color="model",
    )
    line_fig = px.line(
        cv_results,
        x="mean_fit_time",
        y="mean_test_score",
    )

    scatter_trace = scatter_fig["data"][0]
    line_trace = line_fig["data"][0]
    scatter_trace.update(marker=dict(color=colors_list[idx]))
    line_trace.update(line=dict(color=colors_list[idx]))
    fig.add_trace(scatter_trace, row=1, col=1)
    fig.add_trace(line_trace, row=1, col=1)

    scatter_fig = px.scatter(
        cv_results,
        x="mean_score_time",
        y="mean_test_score",
        error_x="std_score_time",
        error_y="std_test_score",
        hover_data=param_name,
    )
    line_fig = px.line(
        cv_results,
        x="mean_score_time",
        y="mean_test_score",
    )

    scatter_trace = scatter_fig["data"][0]
    line_trace = line_fig["data"][0]
    scatter_trace.update(marker=dict(color=colors_list[idx]))
    line_trace.update(line=dict(color=colors_list[idx]))
    fig.add_trace(scatter_trace, row=1, col=2)
    fig.add_trace(line_trace, row=1, col=2)

fig.update_layout(
    xaxis=dict(title="Train time (s) - lower is better"),
    yaxis=dict(title="Test R2 score - higher is better"),
    xaxis2=dict(title="Predict time (s) - lower is better"),
    legend=dict(x=0.72, y=0.05, traceorder="normal", borderwidth=1),
    title=dict(x=0.5, text="Speed-score trade-off of tree-based ensembles"),
)


随着集成中树木数量的增加,HGBT 和 RF 模型都得到了改进。但是,分数达到平台期,添加新的树木只会使拟合和评分变慢。RF 模型较早达到此平台期,并且永远无法达到最大 HGBDT 模型的测试分数。

请注意,上述图中显示的结果在不同运行之间可能会略有变化,在其他机器上运行时变化更大:尝试在您自己的本地机器上运行此示例。

总的来说,通常应该观察到,基于直方图的梯度提升模型在“测试分数与训练速度权衡”方面始终优于随机森林模型(HGBDT 曲线应该位于 RF 曲线的左上方,且永不相交)。“测试分数与预测速度”权衡也可能更有争议,但通常更有利于 HGBDT。始终建议检查这两种模型(进行超参数调整)并比较它们在特定问题上的性能,以确定哪个模型最合适,但**HGBT 几乎总是比 RF 提供更有利的速度-精度权衡**,无论使用默认超参数还是包括超参数调整成本。

不过,此经验法则有一个例外:当训练具有大量可能类别的多类分类模型时,HGBT 在每次提升迭代中内部拟合每个类别一棵树,而 RF 模型使用的树是自然的多类树,这应该在这种情况下提高 RF 模型的速度精度权衡。

脚本的总运行时间:(0 分钟 58.300 秒)

相关示例

用于文本特征提取和评估的示例管道

用于文本特征提取和评估的示例管道

平衡模型复杂度和交叉验证分数

平衡模型复杂度和交叉验证分数

直方图梯度提升树中的特征

直方图梯度提升树中的特征

使用树集成进行特征转换

使用树集成进行特征转换

由 Sphinx-Gallery 生成的图库