Note
Go to the end to download the full example code. or to run this example in your browser via Binder
比较随机森林和直方图梯度提升模型#
在这个示例中,我们比较了随机森林(RF)和直方图梯度提升(HGBT)模型在回归数据集上的得分和计算时间表现,尽管 这里介绍的所有概念同样适用于分类。
通过改变控制每个估计器中树木数量的参数进行比较:
n_estimators
控制森林中的树木数量。它是一个固定的数值。max_iter
是基于梯度提升模型的最大迭代次数。迭代次数对应于回归和二分类问题中的树木数量。此外,模型所需的实际树木数量取决于停止准则。
HGBT 使用梯度提升通过将每棵树拟合到损失函数相对于预测值的负梯度来迭代地提高模型的性能。另一方面,RF 基于袋装法并使用多数投票来预测结果。
有关集成模型的更多信息,请参见 用户指南 或查看 直方图梯度提升树的特性 以了解展示 HGBT 模型其他特性示例。
# 作者:scikit-learn 开发者
# SPDX 许可证标识符: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
计算分数和计算时间#
请注意,HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
的许多实现部分默认是并行化的。
RandomForestRegressor
和 RandomForestClassifier
的实现也可以通过使用 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: 6
与随机森林(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 提供更有利的速度-准确性权衡,无论是使用默认超参数还是包括超参数调优成本。
不过,这条经验法则有一个例外:当训练一个具有大量可能类别的多类分类模型时,HGBDT 在每次提升迭代中会在内部为每个类别拟合一棵树,而 RF 模型使用的树本质上是多类的,这应该会在这种情况下改善 RF 模型的速度和准确性权衡。
Total running time of the script: (0 minutes 45.653 seconds)
Related examples