Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.
Объединение предикторов с помощью стекинга
Стекинг — это ансамблевый метод. В этой стратегии внеблочные предсказания нескольких базовых оценщиков используются для обучения метамодели, которая объединяет их выходные данные во время инференса. В отличие от 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), final_estimator не требует дополнительной предварительной обработки при использовании passthrough=False по умолчанию, поскольку получает только предсказания базовых алгоритмов. Если passthrough=True, final_estimator должен быть конвейером с соответствующей предварительной обработкой.
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, а сплайновая ridge-модель также вносит существенный вклад. Обычная линейная модель не добавляет полезной информации после включения этих двух моделей; если в качестве final_estimator используется RidgeCV, она не исключается, но получает небольшой отрицательный вес, корректирующий остаточное смещение.
Если использовать LassoCV в качестве final_estimator, этот небольшой бесполезный вклад становится равным нулю, и получается более простой ансамбль из сплайновой ridge-модели и 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])
Как воспроизвести SuperLearner с помощью scikit-learn
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, но не равна ей в точности:
super_learner_like.final_estimator_.coef_.sum()
np.float64(0.9976985896404182)
Помимо интерпретируемости, ограничение нормировки до 1.0 в SuperLearner дает следующие преимущества:
- Сохранение согласованности: если все базовые модели выдают в некоторой точке одинаковое значение, ансамбль возвращает то же значение (без искусственного усиления или ослабления).
- Эквивариантность относительно сдвига: прибавление константы к каждому базовому прогнозу сдвигает ансамбль на ту же константу.
- Устранение одной степени свободы: исключение избыточности со свободным членом и небольшая стабилизация весов при мультиколлинеарности.
Наиболее простой способ обеспечить нормировку коэффициентов в scikit-learn — определить пользовательский оценщик, однако это выходит за рамки данного руководства.
Выводы
Стекинговый регрессор объединяет сильные стороны разных регрессоров. Однако обратите внимание, что обучение стекингового регрессора требует гораздо больше вычислительных ресурсов, чем выбор наиболее эффективной модели.
Литература
Polley, E. C. и van der Laan, M. J., Супер-обучение в прогнозировании, 2010.
Общее время выполнения скрипта: (0 минут 6.766 секунд)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/ensemble/plot_stack_predictors.html