Introdução
Neste laboratório, aprenderemos a utilizar o TransformedTargetRegressor da biblioteca scikit-learn. Aplicaremos-o a dois conjuntos de dados diferentes para observar os benefícios de transformar os valores-alvo antes de treinar um modelo de regressão linear. Utilizaremos dados sintéticos e o conjunto de dados de habitação de Ames para ilustrar o impacto da transformação dos valores-alvo.
Dicas da Máquina Virtual
Após o arranque da máquina virtual, clique no canto superior esquerdo para mudar para a aba Notebook para aceder ao Jupyter Notebook para a prática.
Por vezes, pode ser necessário esperar alguns segundos para o Jupyter Notebook terminar de carregar. A validação das operações não pode ser automatizada devido a limitações no Jupyter Notebook.
Se tiver problemas durante o aprendizado, não hesite em contactar o Labby. Forneça feedback após a sessão e resolveremos o problema rapidamente para si.
Importação de bibliotecas necessárias e carregamento de dados sintéticos
Começamos importando as bibliotecas necessárias e carregando dados sintéticos. Geramos um conjunto de dados de regressão aleatório sintético e modificamos os alvos, traduzindo todos os alvos para que todas as entradas sejam não negativas e aplicando uma função exponencial para obter alvos não lineares que não podem ser ajustados usando um modelo linear simples. Em seguida, usamos uma função logarítmica (np.log1p) e uma função exponencial (np.expm1) para transformar os alvos antes de treinar um modelo de regressão linear e usá-lo para previsão.
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
## Gerar dados sintéticos
X, y = make_regression(n_samples=10_000, noise=100, random_state=0)
## Modificar os alvos
y = np.expm1((y + abs(y.min())) / 200)
y_trans = np.log1p(y)
Plotar as distribuições dos alvos
Plotamos as funções de densidade de probabilidade do alvo antes e depois de aplicar as funções logarítmicas.
f, (ax0, ax1) = plt.subplots(1, 2)
ax0.hist(y, bins=100, density=True)
ax0.set_xlim([0, 2000])
ax0.set_ylabel("Probabilidade")
ax0.set_xlabel("Alvo")
ax0.set_title("Distribuição do alvo")
ax1.hist(y_trans, bins=100, density=True)
ax1.set_ylabel("Probabilidade")
ax1.set_xlabel("Alvo")
ax1.set_title("Distribuição do alvo transformado")
f.suptitle("Dados sintéticos", y=1.05)
plt.tight_layout()
Treinar e avaliar um modelo de regressão linear nos alvos originais
Treinamos e avaliamos um modelo de regressão linear nos alvos originais. Devido à não-linearidade, o modelo treinado não será preciso durante a previsão.
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("Regressão Linear nos alvos originais:")
for key, val in score.items():
print(f"{key}: {val}")
Treinar e avaliar um modelo de regressão linear nos alvos transformados
Treinamos e avaliamos um modelo de regressão linear nos alvos transformados usando TransformedTargetRegressor. A função logarítmica lineariza os alvos, permitindo uma melhor previsão mesmo com um modelo linear semelhante, conforme demonstrado pelo erro absoluto mediano (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("\nRegressão Linear nos alvos transformados:")
for key, val in score.items():
print(f"{key}: {val}")
Plotar valores reais vs. valores preditos para ambos os modelos
Plotamos os valores reais vs. valores preditos para ambos os modelos e adicionamos a pontuação na legenda de cada eixo.
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("Regressão Ridge \n sem transformação de alvo")
ax1.set_title("Regressão Ridge \n com transformação de alvo")
f.suptitle("Dados sintéticos", y=1.05)
plt.tight_layout()
Carregar e pré-processar os dados de habitação de Ames
Carregamos o conjunto de dados de habitação de Ames e pré-processamos-o, mantendo apenas as colunas numéricas e removendo colunas com valores NaN ou Inf. O alvo a ser previsto é o preço de venda de cada casa.
from sklearn.datasets import fetch_openml
from sklearn.preprocessing import quantile_transform
ames = fetch_openml(name="house_prices", as_frame=True, parser="pandas")
## Manter apenas colunas numéricas
X = ames.data.select_dtypes(np.number)
## Remover colunas com valores NaN ou Inf
X = X.drop(columns=["LotFrontage", "GarageYrBlt", "MasVnrArea"])
## Deixe o preço em k$
y = ames.target / 1000
y_trans = quantile_transform(
y.to_frame(), n_quantiles=900, output_distribution="normal", copy=True
).squeeze()
Plotar distribuições de destino para dados de habitação de Ames
Plotamos as funções de densidade de probabilidade do alvo antes e depois de aplicar o QuantileTransformer.
f, (ax0, ax1) = plt.subplots(1, 2)
ax0.hist(y, bins=100, density=True)
ax0.set_ylabel("Probabilidade")
ax0.set_xlabel("Alvo")
ax0.set_title("Distribuição do alvo")
ax1.hist(y_trans, bins=100, density=True)
ax1.set_ylabel("Probabilidade")
ax1.set_xlabel("Alvo")
ax1.set_title("Distribuição do alvo transformada")
f.suptitle("Dados de habitação de Ames: preço de venda", y=1.05)
plt.tight_layout()
Treinar e avaliar um modelo de regressão linear nos alvos originais para dados de habitação de Ames
Treinamos e avaliamos um modelo de regressão linear nos alvos originais para os dados de habitação de Ames.
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("\nRegressão Linear nos alvos originais:")
for key, val in score.items():
print(f"{key}: {val}")
Treinar e avaliar um modelo de regressão linear nos alvos transformados para dados de habitação de Ames
Treinamos e avaliamos um modelo de regressão linear nos alvos transformados usando TransformedTargetRegressor para os dados de habitação de Ames.
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("\nRegressão Linear nos alvos transformados:")
for key, val in score.items():
print(f"{key}: {val}")
Plotar valores reais vs. valores preditos e resíduos vs. valores preditos para ambos os modelos
Plotamos os valores reais vs. valores preditos e os resíduos vs. valores preditos para ambos os modelos e adicionamos a pontuação na legenda de cada eixo.
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("Regressão Ridge \n sem transformação de alvo")
ax0[1].set_title("Regressão Ridge \n com transformação de alvo")
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("Regressão Ridge \n sem transformação de alvo")
ax1[1].set_title("Regressão Ridge \n com transformação de alvo")
f.suptitle("Dados de habitação de Ames: preço de venda", y=1.05)
plt.tight_layout()
plt.show()
Resumo
Neste laboratório, aprendemos a utilizar o TransformedTargetRegressor da biblioteca scikit-learn. Aplicamos-no a dois conjuntos de dados diferentes para observar os benefícios de transformar os valores-alvo antes de treinar um modelo de regressão linear. Utilizamos dados sintéticos e o conjunto de dados de habitação de Ames para ilustrar o impacto da transformação dos valores-alvo. Observámos que a função logarítmica linearizou os alvos, permitindo uma melhor previsão mesmo com um modelo linear semelhante, como reportado pelo erro absoluto mediano (MedAE). Também observámos que o efeito do transformador foi mais fraco para o conjunto de dados de habitação de Ames, mas ainda resultou num aumento no R2 e numa grande diminuição do MedAE.