使用堆叠组合预测器#

堆叠是一种集成方法。在该策略中,多个基估计器的“袋外”(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")
plot stack predictors

在单一数据集上堆叠预测器#

有时很难判断哪种模型更适合特定任务,因为不同的模型家族可能达到相似的性能,同时表现出各自的优势和劣势。堆叠通过整合它们的输出来利用这些互补行为,并能纠正单个模型自身无法修复的系统性误差。通过在 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
StackingRegressor(estimators=[('Linear Ridge',
                               Pipeline(steps=[('standardscaler',
                                                StandardScaler()),
                                               ('ridgecv', RidgeCV())])),
                              ('Spline Ridge',
                               Pipeline(steps=[('splinetransformer',
                                                SplineTransformer(n_knots=6)),
                                               ('polynomialfeatures',
                                                PolynomialFeatures(interaction_only=True)),
                                               ('ridgecv', RidgeCV())])),
                              ('HGBT',
                               HistGradientBoostingRegressor(random_state=0))],
                  final_estimator=RidgeCV())
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任 notebook。
在 GitHub 上,HTML 表示形式无法渲染,请尝试使用 nbviewer.org 加载此页面。


测量并绘制结果#

我们可以直接绘制预测结果。的确,突变点被 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()
Base Models Predictions versus Stacked Predictions, Linear Ridge, Spline Ridge, HGBT, Stacking (Ridge final estimator)

我们还可以绘制预测误差,并评估各个预测器及回归器堆叠的性能。

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()
Prediction Errors of Base versus Stacked Predictors, Linear Ridge Evaluation in 0.02 seconds, Spline Ridge Evaluation in 0.03 seconds, HGBT Evaluation in 0.32 seconds, Stacking Regressor Evaluation in 1.48 seconds

即使在交叉验证后得分有很大重叠,堆叠回归器的预测结果仍然略胜一筹。

一旦拟合,我们就可以检查训练好的 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

[Polley2010]

Polley, E. C. and van der Laan, M. J., Super Learner In Prediction, 2010.

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

相关示例

绘制个体和投票回归预测

绘制个体和投票回归预测

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

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

带 AdaBoost 的决策树回归

带 AdaBoost 的决策树回归

scikit-learn 0.22 发布亮点

scikit-learn 0.22 发布亮点

由 Sphinx-Gallery 生成的图库