线性回归中的目标变换

Beginner

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

简介

在本实验中,我们将学习如何使用 scikit-learn 库中的 TransformedTargetRegressor。我们将把它应用于两个不同的数据集,以观察在训练线性回归模型之前对目标值进行转换的好处。我们将使用合成数据和艾姆斯房屋数据集来说明转换目标值的影响。

虚拟机提示

虚拟机启动完成后,点击左上角切换到“笔记本”标签,以访问 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("岭回归 \n 无目标转换")
ax1.set_title("岭回归 \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)。我们将其应用于两个不同的数据集,以观察在训练线性回归模型之前变换目标值的好处。我们使用合成数据和艾姆斯房屋数据集来说明变换目标值的影响。我们观察到对数函数使目标线性化,即使使用与中位数绝对误差(MedAE)报告的类似线性模型,也能实现更好的预测。我们还观察到,对于艾姆斯房屋数据集,变换器的效果较弱,但仍导致 R2 增加和 MedAE 大幅下降。