注意
跳转至页面底部下载完整示例代码,或通过 JupyterLite 或 Binder 在浏览器中运行此示例。
使用堆叠组合预测器#
堆叠是一种集成方法。在该策略中,多个基估计器的“袋外”(out-of-fold)预测结果被用于训练一个元模型,该元模型在推理时整合这些输出。与通过固定(或用户指定)权重对预测结果进行平均的 VotingRegressor 不同,StackingRegressor 通过其 final_estimator(最终估计器)来学习这种组合方式。
在本示例中,我们将展示将不同的回归器堆叠在一起,并使用一个最终的正则化线性回归器来输出预测结果的用例。我们将比较各个独立回归器与堆叠策略的性能。在此案例中,堆叠策略略微提升了整体性能。
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
生成数据#
我们使用由正弦波加上线性趋势及异方差高斯噪声生成的合成数据。数据中引入了一个突变点,因为该突变无法用线性模型描述,但基于树的模型可以自然地处理它。
import numpy as np
import pandas as pd
rng = np.random.RandomState(42)
X = rng.uniform(-3, 3, size=500)
trend = 2.4 * X
seasonal = 3.1 * np.sin(3.2 * X)
drop = 10.0 * (X > 2).astype(float)
sigma = 0.75 + 0.75 * X**2
y = trend + seasonal - drop + rng.normal(loc=0.0, scale=np.sqrt(sigma))
df = pd.DataFrame({"X": X, "y": y})
_ = df.plot.scatter(x="X", y="y")

在单一数据集上堆叠预测器#
有时很难判断哪种模型更适合特定任务,因为不同的模型家族可能达到相似的性能,同时表现出各自的优势和劣势。堆叠通过整合它们的输出来利用这些互补行为,并能纠正单个模型自身无法修复的系统性误差。通过在 final_estimator 中进行适当的正则化,StackingRegressor 通常能达到最强基模型的水平,当基学习器的误差仅部分相关时,它甚至能超越基模型,从而降低个体的偏差/方差。
在这里,我们组合了 3 个学习器(线性与非线性),并使用默认的 RidgeCV 回归器来整合它们的输出。
注意
尽管一些基学习器包含预处理(例如 StandardScaler),但当使用默认的 passthrough=False 时,final_estimator 不需要额外的预处理,因为它只接收基学习器的预测结果。如果设置 passthrough=True,则 final_estimator 应为一个包含适当预处理步骤的流水线(Pipeline)。
from sklearn.ensemble import HistGradientBoostingRegressor, StackingRegressor
from sklearn.linear_model import RidgeCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, SplineTransformer, StandardScaler
linear_ridge = make_pipeline(StandardScaler(), RidgeCV())
spline_ridge = make_pipeline(
SplineTransformer(n_knots=6, degree=3),
PolynomialFeatures(interaction_only=True),
RidgeCV(),
)
hgbt = HistGradientBoostingRegressor(random_state=0)
estimators = [
("Linear Ridge", linear_ridge),
("Spline Ridge", spline_ridge),
("HGBT", hgbt),
]
stacking_regressor = StackingRegressor(estimators=estimators, final_estimator=RidgeCV())
stacking_regressor
测量并绘制结果#
我们可以直接绘制预测结果。的确,突变点被 HistGradientBoostingRegressor 模型 (HGBT) 正确描述,但样条模型更平滑且过拟合程度更低。堆叠回归器最终成为了 HGBT 的平滑版本。
import matplotlib.pyplot as plt
X = X.reshape(-1, 1)
linear_ridge.fit(X, y)
spline_ridge.fit(X, y)
hgbt.fit(X, y)
stacking_regressor.fit(X, y)
x_plot = np.linspace(X.min() - 0.1, X.max() + 0.1, 500).reshape(-1, 1)
preds = {
"Linear Ridge": linear_ridge.predict(x_plot),
"Spline Ridge": spline_ridge.predict(x_plot),
"HGBT": hgbt.predict(x_plot),
"Stacking (Ridge final estimator)": stacking_regressor.predict(x_plot),
}
fig, axes = plt.subplots(2, 2, figsize=(10, 8), sharex=True, sharey=True)
axes = axes.ravel()
for ax, (name, y_pred) in zip(axes, preds.items()):
ax.scatter(
X[:, 0],
y,
s=6,
alpha=0.35,
linewidths=0,
label="observed (sample)",
)
ax.plot(x_plot.ravel(), y_pred, linewidth=2, alpha=0.9, label=name)
ax.set_title(name)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.legend(loc="lower right")
plt.suptitle("Base Models Predictions versus Stacked Predictions", y=1)
plt.tight_layout()
plt.show()

我们还可以绘制预测误差,并评估各个预测器及回归器堆叠的性能。
import time
from sklearn.metrics import PredictionErrorDisplay
from sklearn.model_selection import cross_val_predict, cross_validate
fig, axs = plt.subplots(2, 2, figsize=(9, 7))
axs = np.ravel(axs)
for ax, (name, est) in zip(
axs, estimators + [("Stacking Regressor", stacking_regressor)]
):
scorers = {r"$R^2$": "r2", "MAE": "neg_mean_absolute_error"}
start_time = time.time()
scores = cross_validate(est, X, y, scoring=list(scorers.values()), n_jobs=-1)
elapsed_time = time.time() - start_time
y_pred = cross_val_predict(est, X, y, n_jobs=-1)
scores = {
key: (
f"{np.abs(np.mean(scores[f'test_{value}'])):.2f}"
r" $\pm$ "
f"{np.std(scores[f'test_{value}']):.2f}"
)
for key, value in scorers.items()
}
display = PredictionErrorDisplay.from_predictions(
y_true=y,
y_pred=y_pred,
kind="actual_vs_predicted",
ax=ax,
scatter_kwargs={"alpha": 0.2, "color": "tab:blue"},
line_kwargs={"color": "tab:red"},
)
ax.set_title(f"{name}\nEvaluation in {elapsed_time:.2f} seconds")
for name, score in scores.items():
ax.plot([], [], " ", label=f"{name}: {score}")
ax.legend(loc="upper left")
plt.suptitle("Prediction Errors of Base versus Stacked Predictors", y=1)
plt.tight_layout()
plt.subplots_adjust(top=0.9)
plt.show()

即使在交叉验证后得分有很大重叠,堆叠回归器的预测结果仍然略胜一筹。
一旦拟合,我们就可以检查训练好的 final_estimator_ 的系数(或元权重,前提是它是一个线性模型)。它们揭示了各个估计器对堆叠回归器的贡献程度。
stacking_regressor.fit(X, y)
stacking_regressor.final_estimator_.coef_
array([-0.00446216, 0.44878552, 0.54762418])
我们看到在这种情况下,HGBT 模型占主导地位,样条岭回归(Spline Ridge)也做出了有意义的贡献。普通的线性模型在加入这两个模型后并未添加有用的信号;由于使用了 RidgeCV 作为 final_estimator,它没有被舍弃,但被分配了一个小的负权重来修正其残差偏差。
如果我们使用 LassoCV 作为 final_estimator,那种微小且无益的贡献会被直接设置为零,从而产生样条岭回归和 HGBT 模型的更简洁组合。
from sklearn.linear_model import LassoCV
stacking_regressor = StackingRegressor(estimators=estimators, final_estimator=LassoCV())
stacking_regressor.fit(X, y)
stacking_regressor.final_estimator_.coef_
array([0. , 0.41148006, 0.56187293])
如何在 scikit-learn 中模拟 SuperLearner#
`SuperLearner` [Polley2010] 是一种作为 R 包实现的堆叠策略,在 Python 中并非现成可用。它与 StackingRegressor 密切相关,因为两者都是基于基估计器的袋外预测结果来训练元模型。
关键区别在于 `SuperLearner` 估计的是一组凸元权重(非负且总和为 1)并忽略截距;相比之下,StackingRegressor 默认使用带有截距的无约束元学习器(并且可以通过 passthrough 选择性地包含原始特征)。
没有截距时,元权重可以直接解释为对最终预测的分数贡献。
from sklearn.linear_model import LinearRegression
linear_reg = LinearRegression(fit_intercept=False, positive=True)
super_learner_like = StackingRegressor(
estimators=estimators, final_estimator=linear_reg
)
super_learner_like.fit(X, y)
super_learner_like.final_estimator_.coef_
array([2.41599723e-04, 4.48129539e-01, 5.49327451e-01])
堆叠回归器中元权重的总和接近 1.0,但并不严格等于 1。
super_learner_like.final_estimator_.coef_.sum()
np.float64(0.9976985896404182)
除了可解释性之外,`SuperLearner` 中归一化为 1.0 的约束还具有以下优点:
保持共识:如果所有基模型在某一点输出相同的值,则集成模型返回相同的值(不会产生人为的放大或衰减)。
平移等变性:向每个基预测结果添加常数,集成模型也会发生相同的平移。
移除一个自由度:避免与常数项冗余,并在共线性下适度稳定权重。
在 scikit-learn 中强制执行系数归一化的最干净方法是定义一个自定义估计器,但这超出了本教程的范围。
结论#
堆叠回归器结合了不同回归器的优势。然而,请注意训练堆叠回归器的计算成本远高于选择性能最好的单个模型。
References
Polley, E. C. and van der Laan, M. J., Super Learner In Prediction, 2010.
脚本总运行时间: (0 分钟 6.766 秒)
相关示例