Introdução
Este tutorial demonstrará como realizar regressão por quantis usando o scikit-learn. Geraremos dois conjuntos de dados sintéticos para ilustrar como a regressão por quantis pode prever quantis condicionais não triviais. Usaremos a classe QuantileRegressor para estimar a mediana, bem como um quantil baixo e um quantil alto fixados em 5% e 95%, respectivamente. Compararemos QuantileRegressor com LinearRegression e avaliaremos seu desempenho usando o erro absoluto médio (MAE) e o erro quadrático médio (MSE).
Dicas da Máquina Virtual
Após o término da inicialização da VM, clique no canto superior esquerdo para mudar para a aba Notebook para acessar o Jupyter Notebook para praticar.
Às vezes, pode ser necessário aguardar alguns segundos para que o Jupyter Notebook termine de carregar. A validação das operações não pode ser automatizada devido a limitações no Jupyter Notebook.
Se você enfrentar problemas durante o aprendizado, sinta-se à vontade para perguntar ao Labby. Forneça feedback após a sessão e resolveremos o problema rapidamente para você.
Geração de Conjuntos de Dados
Vamos gerar dois conjuntos de dados sintéticos com o mesmo valor esperado usando uma relação linear com um único recurso x. Adicionaremos ruído normal heterocedástico e ruído de Pareto assimétrico aos conjuntos de dados.
import numpy as np
rng = np.random.RandomState(42)
x = np.linspace(start=0, stop=10, num=100)
X = x[:, np.newaxis]
y_true_mean = 10 + 0.5 * x
## Ruído Normal Heterocedástico
y_normal = y_true_mean + rng.normal(loc=0, scale=0.5 + 0.5 * x, size=x.shape[0])
## Ruído de Pareto Assimétrico
a = 5
y_pareto = y_true_mean + 10 * (rng.pareto(a, size=x.shape[0]) - 1 / (a - 1))
Visualização dos Conjuntos de Dados
Vamos visualizar os conjuntos de dados e a distribuição dos resíduos y - média(y).
import matplotlib.pyplot as plt
_, axs = plt.subplots(nrows=2, ncols=2, figsize=(15, 11), sharex="row", sharey="row")
axs[0, 0].plot(x, y_true_mean, label="Média Verdadeira")
axs[0, 0].scatter(x, y_normal, color="black", alpha=0.5, label="Observações")
axs[1, 0].hist(y_true_mean - y_normal, edgecolor="black")
axs[0, 1].plot(x, y_true_mean, label="Média Verdadeira")
axs[0, 1].scatter(x, y_pareto, color="black", alpha=0.5, label="Observações")
axs[1, 1].hist(y_true_mean - y_pareto, edgecolor="black")
axs[0, 0].set_title("Conjunto de dados com alvos distribuídos normalmente heterocedásticos")
axs[0, 1].set_title("Conjunto de dados com alvo distribuído de forma assimétrica de Pareto")
axs[1, 0].set_title(
"Distribuição dos resíduos para alvos distribuídos normalmente heterocedásticos"
)
axs[1, 1].set_title("Distribuição dos resíduos para alvo distribuído assimétricamente de Pareto")
axs[0, 0].legend()
axs[0, 1].legend()
axs[0, 0].set_ylabel("y")
axs[1, 0].set_ylabel("Contagens")
axs[0, 1].set_xlabel("x")
axs[0, 0].set_xlabel("x")
axs[1, 0].set_xlabel("Resíduos")
_ = axs[1, 1].set_xlabel("Resíduos")
Regressão de Quantil
Usaremos a classe QuantileRegressor para estimar a mediana, bem como um quantil baixo e um quantil alto fixados em 5% e 95%, respectivamente. Usaremos os quantis em 5% e 95% para encontrar os valores discrepantes na amostra de treinamento além do intervalo central de 90%.
from sklearn.linear_model import QuantileRegressor
## Esta linha visa evitar incompatibilidades se houver versões mais antigas do SciPy.
## Você deve usar `solver="highs"` com versões recentes do SciPy.
solver = "highs" if sp_version >= parse_version("1.6.0") else "interior-point"
quantiles = [0.05, 0.5, 0.95]
predictions = {}
out_bounds_predictions = np.zeros_like(y_true_mean, dtype=np.bool_)
for quantile in quantiles:
qr = QuantileRegressor(quantile=quantile, alpha=0, solver=solver)
y_pred = qr.fit(X, y_normal).predict(X)
predictions[quantile] = y_pred
if quantile == min(quantiles):
out_bounds_predictions = np.logical_or(
out_bounds_predictions, y_pred >= y_normal
)
elif quantile == max(quantiles):
out_bounds_predictions = np.logical_or(
out_bounds_predictions, y_pred <= y_normal
)
plt.plot(X, y_true_mean, color="black", linestyle="dashed", label="Média Verdadeira")
for quantile, y_pred in predictions.items():
plt.plot(X, y_pred, label=f"Quantil: {quantile}")
plt.scatter(
x[out_bounds_predictions],
y_normal[out_bounds_predictions],
color="black",
marker="+",
alpha=0.5,
label="Fora do intervalo",
)
plt.scatter(
x[~out_bounds_predictions],
y_normal[~out_bounds_predictions],
color="black",
alpha=0.5,
label="Dentro do intervalo",
)
plt.legend()
plt.xlabel("x")
plt.ylabel("y")
_ = plt.title("Quantil de alvo distribuído normalmente heterocedástico")
quantiles = [0.05, 0.5, 0.95]
predictions = {}
out_bounds_predictions = np.zeros_like(y_true_mean, dtype=np.bool_)
for quantile in quantiles:
qr = QuantileRegressor(quantile=quantile, alpha=0, solver=solver)
y_pred = qr.fit(X, y_pareto).predict(X)
predictions[quantile] = y_pred
if quantile == min(quantiles):
out_bounds_predictions = np.logical_or(
out_bounds_predictions, y_pred >= y_pareto
)
elif quantile == max(quantiles):
out_bounds_predictions = np.logical_or(
out_bounds_predictions, y_pred <= y_pareto
)
plt.plot(X, y_true_mean, color="black", linestyle="dashed", label="Média Verdadeira")
for quantile, y_pred in predictions.items():
plt.plot(X, y_pred, label=f"Quantil: {quantile}")
plt.scatter(
x[out_bounds_predictions],
y_pareto[out_bounds_predictions],
color="black",
marker="+",
alpha=0.5,
label="Fora do intervalo",
)
plt.scatter(
x[~out_bounds_predictions],
y_pareto[~out_bounds_predictions],
color="black",
alpha=0.5,
label="Dentro do intervalo",
)
plt.legend()
plt.xlabel("x")
plt.ylabel("y")
_ = plt.title("Quantil de alvo distribuído assimétricamente de Pareto")
Comparar QuantileRegressor e LinearRegression
Vamos comparar QuantileRegressor e LinearRegression e avaliar seu desempenho usando o erro absoluto médio (MAE) e o erro quadrático médio (MSE).
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import cross_validate
linear_regression = LinearRegression()
quantile_regression = QuantileRegressor(quantile=0.5, alpha=0, solver=solver)
y_pred_lr = linear_regression.fit(X, y_pareto).predict(X)
y_pred_qr = quantile_regression.fit(X, y_pareto).predict(X)
print(f"""Erro de treinamento (desempenho intra-amostra)
{linear_regression.__class__.__name__}:
MAE = {mean_absolute_error(y_pareto, y_pred_lr):.3f}
MSE = {mean_squared_error(y_pareto, y_pred_lr):.3f}
{quantile_regression.__class__.__name__}:
MAE = {mean_absolute_error(y_pareto, y_pred_qr):.3f}
MSE = {mean_squared_error(y_pareto, y_pred_qr):.3f}
""")
cv_results_lr = cross_validate(
linear_regression,
X,
y_pareto,
cv=3,
scoring=["neg_mean_absolute_error", "neg_mean_squared_error"],
)
cv_results_qr = cross_validate(
quantile_regression,
X,
y_pareto,
cv=3,
scoring=["neg_mean_absolute_error", "neg_mean_squared_error"],
)
print(f"""Erro de teste (desempenho validado cruzadamente)
{linear_regression.__class__.__name__}:
MAE = {-cv_results_lr["test_neg_mean_absolute_error"].mean():.3f}
MSE = {-cv_results_lr["test_neg_mean_squared_error"].mean():.3f}
{quantile_regression.__class__.__name__}:
MAE = {-cv_results_qr["test_neg_mean_absolute_error"].mean():.3f}
MSE = {-cv_results_qr["test_neg_mean_squared_error"].mean():.3f}
""")
Resumo
Neste tutorial, aprendemos como realizar regressão de quantis usando o scikit-learn. Geramos dois conjuntos de dados sintéticos para ilustrar como a regressão de quantis pode prever quantis condicionais não triviais. Usamos a classe QuantileRegressor para estimar a mediana, bem como um quantil baixo e um quantil alto fixados em 5% e 95%, respectivamente. Comparamos QuantileRegressor com LinearRegression e avaliamos seu desempenho usando o erro absoluto médio (MAE) e o erro quadrático médio (MSE).