Transformando o Alvo para Regressão Linear

Beginner

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

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.