梯度提升迴歸的預測區間#

此範例展示如何使用分位數迴歸來建立預測區間。請參閱 直方圖梯度提升樹狀結構中的特徵,以取得展示 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)

請注意,HistGradientBoostingRegressorGradientBoostingRegressor 快得多,從中繼資料集 (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()
plot gradient boosting quantile

比較預測中位數和預測平均值,我們注意到中位數平均低於平均值,因為雜訊偏向高值(大型離群值)。中位數估計值似乎也更平滑,因為它自然對離群值具有穩健性。

另請注意,梯度提升樹狀結構的誘導偏差不幸地阻止我們的 0.05 分位數完全捕獲訊號的弦波形狀,尤其是在 x=8 附近。如本筆記本的最後一部分所示,調整超參數可以減少此效應。

錯誤指標分析#

使用 mean_squared_errormean_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)
  pbl=0.05 pbl=0.50 pbl=0.95 MSE
模型        
mse 0.715413 0.715413 0.715413 7.750348
q 0.05 0.127128 1.253445 2.379763 18.933253
q 0.50 0.305438 0.622811 0.940184 9.827917
q 0.95 3.909909 2.145957 0.382005 28.667219


一欄顯示由相同指標評估的所有模型。當模型使用相同指標訓練和測量時,應該獲得一欄的最小值。如果訓練收斂,訓練集上應該始終是這種情況。

請注意,由於目標分佈是非對稱的,因此預期的條件平均值和條件中位數差異很大,因此不能使用平方誤差模型來很好地估計條件中位數,反之亦然。

如果目標分佈是對稱的且沒有離群值(例如,具有高斯雜訊),則中位數估計值和最小平方估計值將產生相似的預測。

然後我們在測試集上執行相同的操作。

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)
  pbl=0.05 pbl=0.50 pbl=0.95 MSE
模型        
mse 0.917281 0.767498 0.617715 6.692901
q 0.05 0.144204 1.245961 2.347717 15.648026
q 0.50 0.412021 0.607752 0.803483 5.874771
q 0.95 4.354394 2.355445 0.356497 34.852774


錯誤較高表示模型略微過擬合資料。它仍然顯示,當模型透過最小化此相同指標進行訓練時,會獲得最佳測試指標。

請注意,條件中位數估計值在測試集上的 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 個百分位數迴歸器。

為了確認此假設,我們透過交叉驗證在 alpha=0.05 的 Pinball 損失上選擇最佳模型參數,來調整第 5 個百分位數的新迴歸器的超參數

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()
Prediction with tuned hyper-parameters

與未調整的模型相比,該圖在質量上看起來更好,尤其是對於較低分位數的形狀。

我們現在定量評估這對估計器的聯合校準情況。

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 分鐘 12.002 秒)

相關範例

分位數迴歸

分位數迴歸

用於時間序列預測的滯後特徵

用於時間序列預測的滯後特徵

SGD:凸損失函數

SGD:凸損失函數

高斯過程迴歸:基本入門範例

高斯過程迴歸:基本入門範例

由 Sphinx-Gallery 產生的圖庫