Cross validation#
Model selection by 5-fold cross validation.
import os
import torch
import matplotlib.pyplot as plt
torch.manual_seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
n_epochs = int(os.getenv("GPYTORCHQR_N_EPOCHS", 5000))
Input data#
def mean(x):
# x: (N, 1)
return torch.cos(x.squeeze(-1) * 2 * 3.14)
def std(x):
# x: (N, 1)
return x.squeeze(-1) + 0.1
x_range = torch.linspace(0, 1, 100, device=device).reshape(-1, 1)
x = x_range.repeat(2, 1)
y = mean(x) + torch.randn(len(x), device=device).mul(std(x))
q = torch.tensor([0.1, 0.5, 0.9], device=device)
standard_normal = torch.distributions.Normal(
torch.zeros((), device=device), torch.ones((), device=device)
)
icdf = standard_normal.icdf(q)
true_quantiles = mean(x_range).unsqueeze(1) + std(x_range).unsqueeze(1) * icdf
x_pred = torch.linspace(0, 1.5, 100, device=device).reshape(-1, 1)
plt.scatter(x.cpu(), y.cpu(), c="k", marker=".")
plt.plot(x_range.cpu(), true_quantiles.cpu(), "--", c="gray")
plt.show()
from sklearn.model_selection import KFold
K = 5
kf = KFold(n_splits=K, shuffle=True, random_state=42)
x_train_list, y_train_list, x_test_list, y_test_list = [], [], [], []
for train_idx, test_idx in kf.split(x.cpu()):
x_train_list.append(x[train_idx])
y_train_list.append(y[train_idx])
x_test_list.append(x[test_idx])
y_test_list.append(y[test_idx])
x_train_cv = torch.stack(x_train_list).to(device)
y_train_cv = torch.stack(y_train_list).to(device)
x_test_cv = torch.stack(x_test_list).to(device)
y_test_cv = torch.stack(y_test_list).to(device)
Model#
from gpytorch.means import ConstantMean
from gpytorch.kernels import ScaleKernel, RBFKernel
from gpytorch.variational import (
CholeskyVariationalDistribution,
VariationalStrategy,
IndependentMultitaskVariationalStrategy,
)
from gpytorch_qr.models import CenterGapQuantileGP
from gpytorch_qr.likelihoods import CenterGapQuantilesLikelihood
class QuantileGPModel(CenterGapQuantileGP):
def __init__(
self,
inducing_points,
num_quantiles,
num_lower_quantiles,
num_folds,
):
N, D = inducing_points.size()
batch_shape = torch.Size([num_folds, num_quantiles])
variational_distribution = CholeskyVariationalDistribution(
N,
batch_shape=batch_shape,
)
variational_strategy = IndependentMultitaskVariationalStrategy(
VariationalStrategy(
self,
inducing_points,
variational_distribution,
learn_inducing_locations=True,
),
num_tasks=num_quantiles,
)
mean = ConstantMean(batch_shape=batch_shape)
covar = ScaleKernel(
RBFKernel(ard_num_dims=D, batch_shape=batch_shape),
batch_shape=batch_shape,
)
super().__init__(
variational_strategy, mean, covar, [num_quantiles], [num_lower_quantiles]
)
inducing_points = torch.linspace(0, 1, 10, device=device).reshape(-1, 1)
central_q_index = 1
likelihood = CenterGapQuantilesLikelihood(
q,
central_q_index,
batch_shape=torch.Size([K]),
).to(device)
model = QuantileGPModel(
inducing_points,
len(q),
central_q_index,
num_folds=K,
).to(device)
from gpytorch.mlls import VariationalELBO
optimizer = torch.optim.Adam(
list(model.parameters()) + list(likelihood.parameters()),
lr=0.001,
)
mll = VariationalELBO(likelihood, model, num_data=y.numel())
train_losses, test_losses = [], []
for _ in range(n_epochs):
model.train()
likelihood.train()
output = model(x_train_cv)
train_loss = -mll(output, y_train_cv)
train_loss.sum().backward()
optimizer.step()
optimizer.zero_grad()
model.eval()
likelihood.eval()
with torch.no_grad():
output = model(x_test_cv)
test_loss = -mll(output, y_test_cv)
train_losses.append(train_loss.mean().item() / len(q))
test_losses.append(test_loss.mean().item() / len(q))
model.eval()
likelihood.eval()
with torch.no_grad():
mean_q = model.mean_quantiles_mc(x_pred)
lower_q, upper_q = model.quantile_quantiles_mc(
x_pred, torch.tensor([0.025, 0.975], device=device)
)
pred_mean_q = mean_q.detach().cpu()
pred_lower_q = lower_q.detach().cpu()
pred_upper_q = upper_q.detach().cpu()
fig, axes = plt.subplots(1, K, figsize=(10, 5), sharex=True, sharey=True)
for i in range(K):
axes[i].scatter(x.cpu(), y.cpu(), c="k", marker=".")
for j in range(len(q)):
axes[i].plot(
x_pred.cpu(),
pred_mean_q[i, :, j],
label=f"q={q[j].item():.2f}",
)
axes[i].fill_between(
x_pred.cpu().squeeze(-1),
pred_lower_q[i, :, j],
pred_upper_q[i, :, j],
alpha=0.2,
)
fig.show()
Plot loss by epoch#
plt.plot(train_losses, label="Train Loss")
plt.plot(test_losses, label="Test Loss")
plt.legend()
plt.show()