線形回帰のためのターゲット変換

Beginner

This tutorial is from open-source community. Access the source code

はじめに

この実験では、scikit-learn ライブラリの TransformedTargetRegressor をどのように使用するか学びます。線形回帰モデルを学習する前に目的変数を変換する利点を観察するために、2 つの異なるデータセットに適用します。合成データとエイムズ住宅データセットを使用して、目的変数を変換する影響を示します。

VM のヒント

VM の起動が完了したら、左上隅をクリックしてノートブックタブに切り替え、Jupyter Notebook を使って練習しましょう。

時々、Jupyter Notebook が読み込み終了するまで数秒待つ必要がある場合があります。Jupyter Notebook の制限により、操作の検証を自動化することはできません。

学習中に問題に遭遇した場合は、Labby にお問い合わせください。セッション後にフィードバックを提供してください。すぐに問題を解決いたします。

必要なライブラリをインポートして合成データを読み込む

必要なライブラリをインポートして合成データを読み込むことから始めます。合成乱数回帰データセットを生成し、すべてのエントリが非負になるようにすべてのターゲットを変換することでターゲットを変更し、単純な線形モデルではフィットさせることができない非線形ターゲットを取得するために指数関数を適用します。その後、線形回帰モデルを学習して予測に使用する前に、対数関数(np.log1p)と指数関数(np.expm1)を使ってターゲットを変換します。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.compose import TransformedTargetRegressor
from sklearn.linear_model import RidgeCV
from sklearn.metrics import median_absolute_error, r2_score, PredictionErrorDisplay

## Generate synthetic data
X, y = make_regression(n_samples=10_000, noise=100, random_state=0)

## Modify the targets
y = np.expm1((y + abs(y.min())) / 200)
y_trans = np.log1p(y)

ターゲットの分布をプロットする

対数関数を適用する前後のターゲットの確率密度関数をプロットします。

f, (ax0, ax1) = plt.subplots(1, 2)

ax0.hist(y, bins=100, density=True)
ax0.set_xlim([0, 2000])
ax0.set_ylabel("確率")
ax0.set_xlabel("ターゲット")
ax0.set_title("ターゲット分布")

ax1.hist(y_trans, bins=100, density=True)
ax1.set_ylabel("確率")
ax1.set_xlabel("ターゲット")
ax1.set_title("変換後のターゲット分布")

f.suptitle("合成データ", y=1.05)
plt.tight_layout()

元のターゲットに対して線形回帰モデルを学習して評価する

元のターゲットに対して線形回帰モデルを学習して評価します。非線形性のため、学習したモデルは予測時に正確ではなくなります。

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

ridge_cv = RidgeCV().fit(X_train, y_train)
y_pred_ridge = ridge_cv.predict(X_test)

score = {
    "R2": f"{r2_score(y_test, y_pred_ridge):.3f}",
    "MedAE": f"{median_absolute_error(y_test, y_pred_ridge):.3f}",
}

print("Linear Regression on original targets:")
for key, val in score.items():
    print(f"{key}: {val}")

変換後のターゲットに対して線形回帰モデルを学習して評価する

TransformedTargetRegressor を使って、変換後のターゲットに対して線形回帰モデルを学習して評価します。対数関数はターゲットを線形化し、中央絶対誤差(MedAE)によって報告されるように、同様の線形モデルでもより良い予測が可能になります。

ridge_cv_with_trans_target = TransformedTargetRegressor(
    regressor=RidgeCV(), func=np.log1p, inverse_func=np.expm1
).fit(X_train, y_train)
y_pred_ridge_with_trans_target = ridge_cv_with_trans_target.predict(X_test)

score = {
    "R2": f"{r2_score(y_test, y_pred_ridge_with_trans_target):.3f}",
    "MedAE": f"{median_absolute_error(y_test, y_pred_ridge_with_trans_target):.3f}",
}

print("\nLinear Regression on transformed targets:")
for key, val in score.items():
    print(f"{key}: {val}")

両モデルの実際値と予測値をプロットする

両モデルの実際値と予測値をプロットし、各軸の凡例にスコアを追加します。

f, (ax0, ax1) = plt.subplots(1, 2, sharey=True)

PredictionErrorDisplay.from_predictions(
    y_test,
    y_pred_ridge,
    kind="actual_vs_predicted",
    ax=ax0,
    scatter_kwargs={"alpha": 0.5},
)
PredictionErrorDisplay.from_predictions(
    y_test,
    y_pred_ridge_with_trans_target,
    kind="actual_vs_predicted",
    ax=ax1,
    scatter_kwargs={"alpha": 0.5},
)

for ax, y_pred in zip([ax0, ax1], [y_pred_ridge, y_pred_ridge_with_trans_target]):
    for name, score in score.items():
        ax.plot([], [], " ", label=f"{name}={score}")
    ax.legend(loc="upper left")

ax0.set_title("Ridge 回帰 \n ターゲット変換なし")
ax1.set_title("Ridge 回帰 \n ターゲット変換あり")
f.suptitle("合成データ", y=1.05)
plt.tight_layout()

エイムズ住宅データを読み込んで前処理する

エイムズ住宅データセットを読み込み、数値列のみを残し、NaN または Inf 値を持つ列を削除することで前処理します。予測対象のターゲットは、各住宅の販売価格です。

from sklearn.datasets import fetch_openml
from sklearn.preprocessing import quantile_transform

ames = fetch_openml(name="house_prices", as_frame=True, parser="pandas")

## 数値列のみを残す
X = ames.data.select_dtypes(np.number)

## NaN または Inf 値を持つ列を削除する
X = X.drop(columns=["LotFrontage", "GarageYrBlt", "MasVnrArea"])

## 価格を千ドル単位にする
y = ames.target / 1000
y_trans = quantile_transform(
    y.to_frame(), n_quantiles=900, output_distribution="normal", copy=True
).squeeze()

エイムズ住宅データのターゲット分布をプロットする

QuantileTransformer を適用する前後のターゲットの確率密度関数をプロットします。

f, (ax0, ax1) = plt.subplots(1, 2)

ax0.hist(y, bins=100, density=True)
ax0.set_ylabel("確率")
ax0.set_xlabel("ターゲット")
ax0.set_title("ターゲット分布")

ax1.hist(y_trans, bins=100, density=True)
ax1.set_ylabel("確率")
ax1.set_xlabel("ターゲット")
ax1.set_title("変換後のターゲット分布")

f.suptitle("エイムズ住宅データ:販売価格", y=1.05)
plt.tight_layout()

エイムズ住宅データの元のターゲットに対して線形回帰モデルを学習して評価する

エイムズ住宅データの元のターゲットに対して線形回帰モデルを学習して評価します。

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)

ridge_cv = RidgeCV().fit(X_train, y_train)
y_pred_ridge = ridge_cv.predict(X_test)

score = {
    "R2": f"{r2_score(y_test, y_pred_ridge):.3f}",
    "MedAE": f"{median_absolute_error(y_test, y_pred_ridge):.3f}",
}

print("\n元のターゲットに対する線形回帰:")
for key, val in score.items():
    print(f"{key}: {val}")

エイムズ住宅データの変換後のターゲットに対して線形回帰モデルを学習して評価する

エイムズ住宅データに対して、変換後のターゲットを使って線形回帰モデルを学習して評価します。ここでは、TransformedTargetRegressor を使います。

ridge_cv_with_trans_target = TransformedTargetRegressor(
    regressor=RidgeCV(),
    transformer=QuantileTransformer(n_quantiles=900, output_distribution="normal"),
).fit(X_train, y_train)
y_pred_ridge_with_trans_target = ridge_cv_with_trans_target.predict(X_test)

score = {
    "R2": f"{r2_score(y_test, y_pred_ridge_with_trans_target):.3f}",
    "MedAE": f"{median_absolute_error(y_test, y_pred_ridge_with_trans_target):.3f}",
}

print("\n変換後のターゲットに対する線形回帰:")
for key, val in score.items():
    print(f"{key}: {val}")

両モデルについて、実際値と予測値、および残差と予測値をプロットする

両モデルについて、実際値と予測値、および残差と予測値をプロットし、各軸の凡例にスコアを追加します。

f, (ax0, ax1) = plt.subplots(2, 2, sharey="row", figsize=(6.5, 8))

PredictionErrorDisplay.from_predictions(
    y_test,
    y_pred_ridge,
    kind="actual_vs_predicted",
    ax=ax0[0],
    scatter_kwargs={"alpha": 0.5},
)
PredictionErrorDisplay.from_predictions(
    y_test,
    y_pred_ridge_with_trans_target,
    kind="actual_vs_predicted",
    ax=ax0[1],
    scatter_kwargs={"alpha": 0.5},
)

for ax, y_pred in zip([ax0[0], ax0[1]], [y_pred_ridge, y_pred_ridge_with_trans_target]):
    for name, score in score.items():
        ax.plot([], [], " ", label=f"{name}={score}")
    ax.legend(loc="upper left")

ax0[0].set_title("ターゲット変換なしのリッジ回帰 \n")
ax0[1].set_title("ターゲット変換ありのリッジ回帰 \n")

PredictionErrorDisplay.from_predictions(
    y_test,
    y_pred_ridge,
    kind="residual_vs_predicted",
    ax=ax1[0],
    scatter_kwargs={"alpha": 0.5},
)
PredictionErrorDisplay.from_predictions(
    y_test,
    y_pred_ridge_with_trans_target,
    kind="residual_vs_predicted",
    ax=ax1[1],
    scatter_kwargs={"alpha": 0.5},
)
ax1[0].set_title("ターゲット変換なしのリッジ回帰 \n")
ax1[1].set_title("ターゲット変換ありのリッジ回帰 \n")

f.suptitle("エイムズ住宅データ:販売価格", y=1.05)
plt.tight_layout()
plt.show()

まとめ

この実験では、scikit-learn ライブラリの TransformedTargetRegressor をどのように使用するかを学びました。線形回帰モデルを学習する前にターゲット値を変換する利点を観察するために、それを 2 つの異なるデータセットに適用しました。合成データとエイムズ住宅データセットを使って、ターゲット値を変換する影響を示しました。対数関数がターゲットを線形化し、中央絶対誤差 (MedAE) によって報告されるように、同様の線形モデルでもより良い予測が可能になることを観察しました。また、エイムズ住宅データセットでは、変換器の効果が弱かったものの、R2 の増加と MedAE の大幅な減少がもたらされることも観察されました。