Введение
В этом лабораторном занятии мы научимся использовать TransformedTargetRegressor из библиотеки scikit-learn. Мы применим его к двум различным датасетам, чтобы изучить преимущества преобразования целевых значений перед обучением модели линейной регрессии. Мы будем использовать синтетические данные и датасет по недвижимости в Амсе, чтобы проиллюстрировать влияние преобразования целевых значений.
Советы по работе с ВМ
После запуска ВМ нажмите в левом верхнем углу, чтобы переключиться на вкладку Ноутбук и получить доступ к 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("Линейная регрессия на исходных целевых переменных:")
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("\nЛинейная регрессия на преобразованных целевых переменных:")
for key, val in score.items():
print(f"{key}: {val}")
Построим графики фактических значений versus предсказанных значений для обеих моделей
Построим графики фактических значений versus предсказанных значений для обеих моделей и добавим оценку в легенду каждого графика.
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")
## Keep only numeric columns
X = ames.data.select_dtypes(np.number)
## Remove columns with NaN or Inf values
X = X.drop(columns=["LotFrontage", "GarageYrBlt", "MasVnrArea"])
## Let the price be in k$
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("Ridge regression \n without target transformation")
ax0[1].set_title("Ridge regression \n with target transformation")
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 regression \n without target transformation")
ax1[1].set_title("Ridge regression \n with target transformation")
f.suptitle("Ames housing data: selling price", y=1.05)
plt.tight_layout()
plt.show()
Резюме
В этом практическом занятии мы узнали, как использовать TransformedTargetRegressor из библиотеки scikit - learn. Мы применяли его к двум различным наборам данных, чтобы наблюдать за выгодами преобразования целевых значений перед обучением модели линейной регрессии. Мы использовали синтетические данные и набор данных о недвижимости в Амесе, чтобы проиллюстрировать влияние преобразования целевых значений. Мы наблюдали, что логарифмическая функция линей化了 целевые переменные, что позволило получить более точные прогнозы даже с использованием похожей линейной модели, как это показывает медианная абсолютная ошибка (MedAE). Мы также заметили, что эффект трансформера был менее выражен для набора данных о недвижимости в Амесе, но по-прежнему привел к увеличению R2 и значительному уменьшению MedAE.