目标变换在回归分析中的应用

在机器学习中,线性回归模型是一种常用的预测模型。然而,面对非线性目标变量时,直接应用线性模型可能会导致预测精度不足。为了解决这一问题,可以在训练线性回归模型之前,对目标变量进行变换。本文将通过两个例子来展示目标变换如何提高线性回归模型的预测精度。第一个例子使用合成数据,而第二个例子基于艾姆斯房价数据集。

合成数据示例

首先,生成了一个合成的随机回归数据集。目标变量y通过以下方式进行修改:将所有目标变量平移,使得所有条目都是非负的(通过加上最低y的绝对值),并应用指数函数以获得非线性目标,这些目标无法使用简单的线性模型拟合。因此,在训练线性回归模型并用于预测之前,将使用对数(np.log1p)和指数函数(np.expm1)来变换目标。

import numpy as np from sklearn.datasets import make_regression X, y = make_regression(n_samples=10000, noise=100, random_state=0) y = np.expm1((y + abs(y.min())) / 200) y_trans = np.log1p(y)

接下来,绘制了目标变量在应用对数函数前后的概率密度函数。

import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split 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()

首先在原始目标上应用线性模型。由于非线性,训练得到的模型在预测时将不够精确。随后,使用对数函数来线性化目标,即使使用类似的线性模型,也能实现更好的预测,这一点通过中位数绝对误差(MedAE)来报告。

from sklearn.metrics import median_absolute_error, r2_score def compute_score(y_true, y_pred): return { "R2": f"{r2_score(y_true, y_pred):.3f}", "MedAE": f"{median_absolute_error(y_true, y_pred):.3f}", } from sklearn.compose import TransformedTargetRegressor from sklearn.linear_model import RidgeCV from sklearn.metrics import PredictionErrorDisplay f, (ax0, ax1) = plt.subplots(1, 2, sharey=True) ridge_cv = RidgeCV().fit(X_train, y_train) y_pred_ridge = ridge_cv.predict(X_test) 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) 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}) # Add the score in the legend of each axis for ax, y_pred in zip([ax0, ax1], [y_pred_ridge, y_pred_ridge_with_trans_target]): for name, score in compute_score(y_test, y_pred).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()

在合成数据的例子中,可以看到目标变换显著提高了模型的预测精度。接下来,将探讨在真实世界数据集上应用目标变换的效果。

艾姆斯房价数据集

艾姆斯房价数据集被用来展示在模型学习之前变换目标的影响。在这个例子中,要预测的目标是每栋房子的售价。

from sklearn.datasets import fetch_openml from sklearn.preprocessing import quantile_transform ames = fetch_openml(name="house_prices", as_frame=True) # 只保留数值列 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来规范化目标分布,然后应用RidgeCV模型。

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()

在艾姆斯房价数据集上,变换器的效果比在合成数据上要弱。然而,变换导致R^2的增加和MedAE的大幅下降。在没有目标变换的情况下,残差图(预测目标 - 真实目标 vs 预测目标)呈现出曲线形的“倒微笑”形状,这是由于残差值随预测目标值的变化而变化。使用目标变换后,形状更加线性,表明模型拟合得更好。

from sklearn.preprocessing import QuantileTransformer f, (ax0, ax1) = plt.subplots(2, 2, sharey="row", figsize=(6.5, 8)) ridge_cv = RidgeCV().fit(X_train, y_train) y_pred_ridge = ridge_cv.predict(X_test) 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) # 绘制实际值与预测值 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 compute_score(y_test, y_pred).items(): ax.plot([], [], " ", label=f"{name}={score}") ax.legend(loc="upper left") ax0[0].set_title("Ridge回归\n无目标变换") ax0[1].set_title("Ridge回归\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("Ridge回归\n无目标变换") ax1[1].set_title("Ridge回归\n有目标变换") f.suptitle("艾姆斯房价数据:售价", y=1.05) plt.tight_layout() plt.show()
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485