Center-gap GPQR (independent)#

In this example, the data generating process is $$Y = \cos(2 \pi (X+0.1)) + \epsilon, \quad \epsilon \sim \mathcal{N}(0, X+0.1).$$

Central quantile $Q_{\tau_0}(x)$ and gaps $\Delta Q_{\tau_i}(x)$ of $Y$ are modeled by latent GP $g_i(x)$: $$Q_{\tau_0}(x) = g_0(x;\theta), \quad \Delta Q_{\tau_i}(x) = \log (1 + \exp g_i(x)),$$ where $$g_0(x;\theta) \sim \mathcal{N}(\cos(2 \pi x + \theta) + c_0, k(x, x’)), \quad g_i(x) \sim \mathcal{N}(c_i, k(x, x’)).$$

Parameters $\theta$ and $c_i$ are learned by maximizing the marginal likelihood.

import os

import torch
from torch.distributions import Normal
from gpytorch.variational import CholeskyVariationalDistribution
from gpytorch.variational import VariationalStrategy
from gpytorch.variational import IndependentMultitaskVariationalStrategy
from gpytorch.means import ConstantMean, Mean
from gpytorch.kernels import RBFKernel, ScaleKernel
from gpytorch.mlls import VariationalELBO
import matplotlib.pyplot as plt

from gpytorch_qr.means import CenterGapMean
from gpytorch_qr.models import CenterGapQuantileGP
from gpytorch_qr.likelihoods import CenterGapQuantileLikelihood

try:
    import sys

    sys.path.insert(0, os.path.abspath("../.."))

    import config_notebook
except ImportError:
    print("Output will not be deterministic SVG.")

torch.manual_seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

n_epochs = int(os.getenv("GPYTORCHQR_N_EPOCHS", 10000))

Data preparation#

def f(x):
    return torch.cos((x + 0.1) * 2 * 3.14)


def std(x):
    return x + 0.1


x_range = torch.linspace(0, 1, 100).reshape(-1, 1).to(device)
x = x_range.repeat(5, 1)
y = (f(x) + torch.randn(x.shape, device=device).mul(std(x))).squeeze()

q = torch.tensor([0.1, 0.25, 0.5, 0.75, 0.9]).to(device)
true_quantiles = f(x_range) + std(x_range) * Normal(0, 1).icdf(q)

x_pred = torch.linspace(0, 1.5, 100).reshape(-1, 1).to(device)
plt.scatter(x.cpu(), y.cpu(), c="k", marker=".")
plt.plot(x_range.cpu(), true_quantiles.cpu(), "--", c="gray")
plt.show()
../../../_images/37f9c150aebe18256c77f78e98fa80d336205295c22d4d6dd20dcf78d5eb3698.svg

Prior mean#

class PriorMean(Mean):
    def __init__(self, batch_shape=torch.Size([])):
        super().__init__()
        self.batch_shape = batch_shape
        self.theta = torch.nn.Parameter(torch.tensor(0.0))
        if len(batch_shape) == 0:
            self.register_parameter("offset", torch.nn.Parameter(torch.tensor(0.0)))
        else:
            self.register_parameter(
                "offset", torch.nn.Parameter(torch.zeros(*batch_shape))
            )

    def forward(self, x):
        # x: (N, D)
        m = torch.cos(2 * 3.14 * x + self.theta)  # (N, D)
        ret = m + self.offset.reshape(*self.offset.shape, 1, 1)  # (B, N, D)
        return ret.squeeze(-1)  # (B, N)


prior_mean = PriorMean().to(device)
plt.scatter(x.cpu(), y.cpu(), c="k", marker=".")
plt.plot(x_pred.cpu(), prior_mean(x_pred).detach().cpu())
plt.show()
../../../_images/e718ef8a0bdf9af98bf81782caef02a991ae60475ddad02de408faa71535bb4a.svg

Define models and likelihoods#

class MyGP(CenterGapQuantileGP):
    def __init__(
        self,
        inducing_points,
        num_quantiles,
        num_lower_quantiles,
    ):
        N, D = inducing_points.size()
        variational_distribution = CholeskyVariationalDistribution(
            N,
            batch_shape=torch.Size([num_quantiles]),
        )
        variational_strategy = IndependentMultitaskVariationalStrategy(
            VariationalStrategy(
                self,
                inducing_points,
                variational_distribution,
                learn_inducing_locations=True,
            ),
            num_tasks=num_quantiles,
        )

        mean = CenterGapMean(
            PriorMean(batch_shape=torch.Size([1])),
            ConstantMean(batch_shape=torch.Size([num_quantiles - 1])),
        )
        covar = ScaleKernel(
            RBFKernel(ard_num_dims=D, batch_shape=torch.Size([num_quantiles])),
            batch_shape=torch.Size([num_quantiles]),
        )
        super().__init__(
            variational_strategy, mean, covar, [num_quantiles], [num_lower_quantiles]
        )


inducing_points = torch.linspace(0, 1, 10).reshape(-1, 1).to(device)
central_q_index = (q - 0.5).abs().argmin().item()
gp = MyGP(inducing_points, len(q), central_q_index).to(device)
likelihood = CenterGapQuantileLikelihood(q, central_q_index).to(device)

Train#

gp.train()
likelihood.train()
mll = VariationalELBO(likelihood, gp, num_data=y.numel())
optimizer = torch.optim.Adam(
    list(gp.parameters()) + list(likelihood.parameters()),
    lr=0.001,
)

for _ in range(n_epochs):
    output = gp(x)
    loss = -mll(output, y)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

Evaluate#

gp.eval()
with torch.no_grad():
    mean_q = gp.mean_quantiles_mc(x_pred)
    lower_q, upper_q = gp.quantile_quantiles_mc(
        x_pred, torch.tensor([0.025, 0.975]).to(device)
    )

Plot result#

colors = plt.cm.tab10.colors

plt.scatter(x.cpu(), y.cpu(), c="gray", marker=".", alpha=0.1)
plt.plot(x_range.cpu(), true_quantiles.cpu(), "--", c="k")

for i in range(len(q)):
    plt.plot(x_pred.cpu(), mean_q[:, i].cpu(), color=colors[i])
    plt.fill_between(
        x_pred.cpu().squeeze(),
        lower_q[:, i].cpu(),
        upper_q[:, i].cpu(),
        color=colors[i],
        alpha=0.3,
    )
plt.show()
../../../_images/26bd5faa5c6a9d0f72efae4eb1bdb62ff52cf43dd20e0c8deea3aad5fe8489c7.svg