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

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

通过改变控制每棵树数量的参数来进行比较

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

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

HGBT 使用梯度提升通过将每棵树拟合到损失函数相对于预测值的负梯度来迭代地提高模型的性能。另一方面,RF 基于装袋,并使用多数投票来预测结果。

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

# Author:  Arturo Amor <[email protected]>
# License: 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 实现的许多部分默认情况下是并行化的。

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

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 更 favorable 的速度-精度权衡,无论是使用默认超参数还是包括超参数调整成本。

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

脚本总运行时间:(0 分 56.694 秒)

相关示例

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

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

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

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

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

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

使用树的集合进行特征转换

使用树的集合进行特征转换

由 Sphinx-Gallery 生成的图库