Примечание
Перейти к концу для скачивания полного кода примера. Или запустите этот пример в вашем браузере через JupyterLite или Binder
Прогнозные интервалы для регрессии градиентного бустинга
В этом примере показано, как регрессия квантилей может быть использована для создания прогнозных интервалов. См. Функции в деревьях градиентного бустинга с гистограммами для примера, демонстрирующего некоторые другие функции HistGradientBoostingRegressor.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Сгенерируйте некоторые данные для синтетической регрессионной задачи, применив функцию f к равномерно сгенерированным случайным входным данным.
import numpy as np
from sklearn.model_selection import train_test_split
def f(x):
"""The function to predict."""
return x * np.sin(x)
rng = np.random.RandomState(42)
X = np.atleast_2d(rng.uniform(0, 10.0, size=1000)).T
expected_y = f(X).ravel()
Чтобы сделать задачу интересной, мы генерируем наблюдения целевой переменной y как сумму детерминированного члена, вычисленного функцией f, и случайного члена шума, который следует логнормальному распределению с центром в нуле. Чтобы сделать это еще более интересным, мы рассмотрим случай, когда амплитуда шума зависит от входной переменной x (гетероскедастический шум).
Логнормальное распределение несимметрично и имеет длинные хвосты: наблюдение больших выбросов вероятно, но наблюдение малых выбросов невозможно.
sigma = 0.5 + X.ravel() / 10 noise = rng.lognormal(sigma=sigma) - np.exp(sigma**2 / 2) y = expected_y + noise
Разделить на обучающую и тестовую выборки:
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
Построение нелинейных регрессоров квантилей и наименьших квадратов
Обучите модели градиентного бустинга, обученные с потерей квантиля и alpha=0.05, 0.5, 0.95.
Полученные модели для alpha=0.05 и alpha=0.95 создают 90%-ный доверительный интервал (95% - 5% = 90%).
Модель, обученная с alpha=0.5, создаёт регрессию медианы: в среднем должно быть одинаковое количество целевых наблюдений выше и ниже предсказанных значений.
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_pinball_loss, mean_squared_error
all_models = {}
common_params = dict(
learning_rate=0.05,
n_estimators=200,
max_depth=2,
min_samples_leaf=9,
min_samples_split=9,
)
for alpha in [0.05, 0.5, 0.95]:
gbr = GradientBoostingRegressor(loss="quantile", alpha=alpha, **common_params)
all_models["q %1.2f" % alpha] = gbr.fit(X_train, y_train)
Обратите внимание, что HistGradientBoostingRegressor намного быстрее, чем GradientBoostingRegressor начиная со средних наборов данных (n_samples >= 10_000), что не наблюдается в данном примере.
Для сравнения, мы также обучили базовая модель, обученная с помощью обычной (среднеквадратической) ошибки (MSE).
gbr_ls = GradientBoostingRegressor(loss="squared_error", **common_params) all_models["mse"] = gbr_ls.fit(X_train, y_train)
Создайте равномерно распределённый набор оценочных значений входных данных, охватывающий диапазон [0, 10].
xx = np.atleast_2d(np.linspace(0, 10, 1000)).T
Постройте истинную условную среднюю функцию f, прогнозы условной средней (потеря равна квадратичной ошибке), условную медиану и условный 90%-ный интервал (от 5-го до 95-го условных перцентилей).
import matplotlib.pyplot as plt
y_pred = all_models["mse"].predict(xx)
y_lower = all_models["q 0.05"].predict(xx)
y_upper = all_models["q 0.95"].predict(xx)
y_med = all_models["q 0.50"].predict(xx)
fig = plt.figure(figsize=(10, 10))
plt.plot(xx, f(xx), "g:", linewidth=3, label=r"$f(x) = x\,\sin(x)$")
plt.plot(X_test, y_test, "b.", markersize=10, label="Test observations")
plt.plot(xx, y_med, "r-", label="Predicted median")
plt.plot(xx, y_pred, "r-", label="Predicted mean")
plt.plot(xx, y_upper, "k-")
plt.plot(xx, y_lower, "k-")
plt.fill_between(
xx.ravel(), y_lower, y_upper, alpha=0.4, label="Predicted 90% interval"
)
plt.xlabel("$x$")
plt.ylabel("$f(x)$")
plt.ylim(-10, 25)
plt.legend(loc="upper left")
plt.show()

Сравнивая предсказанную медиану с предсказанной средней, мы отмечаем, что медиана в среднем ниже средней, поскольку шум смещён в сторону высоких значений (больших выбросов). Оценка медианы также кажется более гладкой из-за её естественной устойчивости к выбросам.
Также обратите внимание, что индуцированный предвзятость деревьев градиентного бустинга, к сожалению, мешает нашему 0.05 квантилю полностью захватить синусоидальную форму сигнала, особенно около x=8. Настройка гиперпараметров может уменьшить этот эффект, как показано в последней части этого блокнота.
Анализ метрик ошибок
Измерьте модели с помощью mean_squared_error и mean_pinball_loss метрик на обучающей выборке.
import pandas as pd
def highlight_min(x):
x_min = x.min()
return ["font-weight: bold" if v == x_min else "" for v in x]
results = []
for name, gbr in sorted(all_models.items()):
metrics = {"model": name}
y_pred = gbr.predict(X_train)
for alpha in [0.05, 0.5, 0.95]:
metrics["pbl=%1.2f" % alpha] = mean_pinball_loss(y_train, y_pred, alpha=alpha)
metrics["MSE"] = mean_squared_error(y_train, y_pred)
results.append(metrics)
pd.DataFrame(results).set_index("model").style.apply(highlight_min)
Один столбец показывает все модели, оцененные по одной метрике. Минимальное значение в столбце должно быть получено, когда модель обучена и измерена с помощью той же метрики. Это всегда должно происходить на обучающей выборке, если обучение сошлось.
Обратите внимание, что из-за асимметрии распределения целевой переменной ожидаемые условные среднее и условное медианное значения существенно отличаются, и поэтому модель с квадратичной ошибкой не может дать хорошую оценку условной медианы и наоборот.
Если распределение целевой переменной было бы симметричным и не имело выбросов (например, с нормальным шумом), то оценка медианы и оценка методом наименьших квадратов дали бы похожие предсказания.
Затем мы делаем то же самое на тестовой выборке.
results = []
for name, gbr in sorted(all_models.items()):
metrics = {"model": name}
y_pred = gbr.predict(X_test)
for alpha in [0.05, 0.5, 0.95]:
metrics["pbl=%1.2f" % alpha] = mean_pinball_loss(y_test, y_pred, alpha=alpha)
metrics["MSE"] = mean_squared_error(y_test, y_pred)
results.append(metrics)
pd.DataFrame(results).set_index("model").style.apply(highlight_min)
Ошибки выше, что означает, что модели немного переобучились. Это всё ещё показывает, что лучшая тестовая метрика получается, когда модель обучается с минимизацией этой же метрики.
Обратите внимание, что оценка условной медианы конкурирует с оценкой методом наименьших квадратов по метрике MSE на тестовой выборке: это можно объяснить тем, что оценка методом наименьших квадратов очень чувствительна к большим выбросам, что может привести к значительному переобучению. Это видно на правой стороне предыдущего графика. Оценка условной медианы смещена (недооценка для этого асимметричного шума), но также естественно устойчива к выбросам и переобучается меньше.
Калибровка доверительного интервала
Мы также можем оценить способность двух крайних оценок квантилей к созданию хорошо откалиброванного условного 90%-ного доверительного интервала.
Для этого мы можем вычислить долю наблюдений, которые попадают между прогнозами:
def coverage_fraction(y, y_low, y_high):
return np.mean(np.logical_and(y >= y_low, y <= y_high))
coverage_fraction(
y_train,
all_models["q 0.05"].predict(X_train),
all_models["q 0.95"].predict(X_train),
)
np.float64(0.9)
На обучающей выборке калибровка очень близка к ожидаемому значению охвата для 90%-ного доверительного интервала.
coverage_fraction(
y_test, all_models["q 0.05"].predict(X_test), all_models["q 0.95"].predict(X_test)
)
np.float64(0.868)
На тестовой выборке оценочный доверительный интервал немного слишком узкий. Тем не менее, необходимо будет заключить эти метрики в цикл перекрестной проверки, чтобы оценить их изменчивость при повторном применении данных.
Настройка гиперпараметров квантильных регрессоров
На графике выше мы заметили, что регрессор 5-го процентиля, похоже, недообучен и не смог адаптироваться к синусоидальной форме сигнала.
Гиперпараметры модели были приблизительно настраивались вручную для медианного регрессора, и нет оснований полагать, что те же гиперпараметры подходят для регрессора 5-го процентиля.
Чтобы подтвердить эту гипотезу, мы настраиваем гиперпараметры нового регрессора 5-го процентиля, выбирая лучшие параметры модели с помощью перекрестной проверки по потере пинбола с alpha=0.05:
from sklearn.experimental import enable_halving_search_cv # noqa
from sklearn.model_selection import HalvingRandomSearchCV
from sklearn.metrics import make_scorer
from pprint import pprint
param_grid = dict(
learning_rate=[0.05, 0.1, 0.2],
max_depth=[2, 5, 10],
min_samples_leaf=[1, 5, 10, 20],
min_samples_split=[5, 10, 20, 30, 50],
)
alpha = 0.05
neg_mean_pinball_loss_05p_scorer = make_scorer(
mean_pinball_loss,
alpha=alpha,
greater_is_better=False, # maximize the negative loss
)
gbr = GradientBoostingRegressor(loss="quantile", alpha=alpha, random_state=0)
search_05p = HalvingRandomSearchCV(
gbr,
param_grid,
resource="n_estimators",
max_resources=250,
min_resources=50,
scoring=neg_mean_pinball_loss_05p_scorer,
n_jobs=2,
random_state=0,
).fit(X_train, y_train)
pprint(search_05p.best_params_)
{'learning_rate': 0.2,
'max_depth': 2,
'min_samples_leaf': 20,
'min_samples_split': 10,
'n_estimators': 150}
Мы наблюдаем, что гиперпараметры, которые были настроены вручную для медианного регрессора, находятся в том же диапазоне, что и гиперпараметры, подходящие для регрессора 5-го процентиля.
Теперь давайте настроим гиперпараметры для регрессора 95-го процентиля. Нам нужно переопределить scoring метрику, используемую для выбора лучшей модели, а также настроить параметр alpha внутреннего градиентного бустинга-оценщика:
from sklearn.base import clone
alpha = 0.95
neg_mean_pinball_loss_95p_scorer = make_scorer(
mean_pinball_loss,
alpha=alpha,
greater_is_better=False, # maximize the negative loss
)
search_95p = clone(search_05p).set_params(
estimator__alpha=alpha,
scoring=neg_mean_pinball_loss_95p_scorer,
)
search_95p.fit(X_train, y_train)
pprint(search_95p.best_params_)
{'learning_rate': 0.05,
'max_depth': 2,
'min_samples_leaf': 5,
'min_samples_split': 20,
'n_estimators': 150}
Результат показывает, что гиперпараметры для регрессора 95-го процентиля, определенные процедурой поиска, примерно в том же диапазоне, что и гиперпараметры, настроенные вручную для медианного регрессора, и гиперпараметры, определенные процедурой поиска для регрессора 5-го процентиля. Однако поиск гиперпараметров привел к улучшению 90%-ного доверительного интервала, который охватывается прогнозами этих двух настроенных квантильных регрессоров. Обратите внимание, что прогноз верхнего 95-го процентиля имеет гораздо более грубую форму, чем прогноз нижнего 5-го процентиля из-за выбросов:
y_lower = search_05p.predict(xx)
y_upper = search_95p.predict(xx)
fig = plt.figure(figsize=(10, 10))
plt.plot(xx, f(xx), "g:", linewidth=3, label=r"$f(x) = x\,\sin(x)$")
plt.plot(X_test, y_test, "b.", markersize=10, label="Test observations")
plt.plot(xx, y_upper, "k-")
plt.plot(xx, y_lower, "k-")
plt.fill_between(
xx.ravel(), y_lower, y_upper, alpha=0.4, label="Predicted 90% interval"
)
plt.xlabel("$x$")
plt.ylabel("$f(x)$")
plt.ylim(-10, 25)
plt.legend(loc="upper left")
plt.title("Prediction with tuned hyper-parameters")
plt.show()

График выглядит качественно лучше, чем для ненастроенных моделей, особенно для формы нижнего квантиля.
Теперь давайте количественно оценим совместную калибровку пары оценок:
coverage_fraction(y_train, search_05p.predict(X_train), search_95p.predict(X_train))
np.float64(0.9026666666666666)
coverage_fraction(y_test, search_05p.predict(X_test), search_95p.predict(X_test))
np.float64(0.796)
К сожалению, калибровка настроенной пары не лучше на тестовом наборе: ширина оценочного доверительного интервала по-прежнему слишком узкая.
Опять же, нам нужно было бы заключить это исследование в цикл перекрестной проверки, чтобы лучше оценить изменчивость этих оценок.
Общее время выполнения скрипта: (0 минут 10,117 секунд)
Связанные примеры
© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/ensemble/plot_gradient_boosting_quantile.html