<jupyter_start><jupyter_text>GPyTorch regression with derivative information IntroductionIn this notebook, we show how to train a GP regression model in GPyTorch of an unknown function given function value and derivative observations. We consider modeling the function:\begin{align}              y &= \sin(2x) + cos(x) + \epsilon \\  \frac{dy}{dx} &= 2\cos(2x) - \sin(x) + \epsilon \\         \epsilon &\sim \mathcal{N}(0, 0.5)\end{align}using 50 value and derivative observations.<jupyter_code>import torch
import gpytorch
import math
from matplotlib import pyplot as plt
import numpy as np

%matplotlib inline
%load_ext autoreload
%autoreload 2<jupyter_output><empty_output><jupyter_text>Setting up the training dataWe use 50 uniformly distributed points in the interval $[0, 5 \pi]$<jupyter_code>lb, ub = 0.0, 5*math.pi
n = 50

train_x = torch.linspace(lb, ub, n).unsqueeze(-1)
train_y = torch.stack([
    torch.sin(2*train_x) + torch.cos(train_x), 
    -torch.sin(train_x) + 2*torch.cos(2*train_x)
], -1).squeeze(1)

train_y += 0.05 * torch.randn(n, 2)<jupyter_output><empty_output><jupyter_text>Setting up the modelA GP prior on the function values implies a multi-output GP prior on the function values and the partial derivatives, see 9.4 in http://www.gaussianprocess.org/gpml/chapters/RW9.pdf for more details. This allows using a `MultitaskMultivariateNormal` and `MultitaskGaussianLikelihood` to train a GP model from both function values and gradients. The resulting RBF kernel that models the covariance between the values and partial derivatives has been implemented in `RBFKernelGrad` and the extension of a constant mean is implemented in `ConstantMeanGrad`.The `RBFKernelGrad` is generally worse conditioned than the `RBFKernel`, so we place a lower bound on the noise parameter to keep the smallest eigenvalues of the kernel matrix away from zero.<jupyter_code>class GPModelWithDerivatives(gpytorch.models.ExactGP):
    def __init__(self, train_x, train_y, likelihood):
        super(GPModelWithDerivatives, self).__init__(train_x, train_y, likelihood)
        self.mean_module = gpytorch.means.ConstantMeanGrad()
        self.base_kernel = gpytorch.kernels.RBFKernelGrad()
        self.covar_module = gpytorch.kernels.ScaleKernel(self.base_kernel)

    def forward(self, x):
        mean_x = self.mean_module(x)
        covar_x = self.covar_module(x)
        return gpytorch.distributions.MultitaskMultivariateNormal(mean_x, covar_x)

likelihood = gpytorch.likelihoods.MultitaskGaussianLikelihood(num_tasks=2)  # Value + Derivative
model = GPModelWithDerivatives(train_x, train_y, likelihood)<jupyter_output><empty_output><jupyter_text>The model training is similar to training a standard GP regression model<jupyter_code># this is for running the notebook in our testing framework
import os
smoke_test = ('CI' in os.environ)
training_iter = 2 if smoke_test else 50


# Find optimal model hyperparameters
model.train()
likelihood.train()

# Use the adam optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)  # Includes GaussianLikelihood parameters

# "Loss" for GPs - the marginal log likelihood
mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model)

for i in range(training_iter):
    optimizer.zero_grad()
    output = model(train_x)
    loss = -mll(output, train_y)
    loss.backward()
    print('Iter %d/%d - Loss: %.3f   lengthscale: %.3f   noise: %.3f' % (
        i + 1, training_iter, loss.item(),
        model.covar_module.base_kernel.lengthscale.item(),
        model.likelihood.noise.item()
    ))        
    optimizer.step()<jupyter_output>Iter 1/50 - Loss: 71.141   lengthscale: 0.693   noise: 0.693
Iter 2/50 - Loss: 69.100   lengthscale: 0.744   noise: 0.644
Iter 3/50 - Loss: 66.347   lengthscale: 0.797   noise: 0.598
Iter 4/50 - Loss: 64.771   lengthscale: 0.845   noise: 0.554
Iter 5/50 - Loss: 63.744   lengthscale: 0.886   noise: 0.513
Iter 6/50 - Loss: 61.682   lengthscale: 0.928   noise: 0.474
Iter 7/50 - Loss: 59.820   lengthscale: 0.961   noise: 0.437
Iter 8/50 - Loss: 57.801   lengthscale: 0.987   noise: 0.402
Iter 9/50 - Loss: 56.894   lengthscale: 1.004   noise: 0.370
Iter 10/50 - Loss: 54.522   lengthscale: 1.010   noise: 0.340
Iter 11/50 - Loss: 53.263   lengthscale: 1.006   noise: 0.311
Iter 12/50 - Loss: 50.900   lengthscale: 0.998   noise: 0.285
Iter 13/50 - Loss: 49.472   lengthscale: 0.986   noise: 0.260
Iter 14/50 - Loss: 47.405   lengthscale: 0.980   noise: 0.238
Iter 15/50 - Loss: 46.851   lengthscale: 0.982   noise: 0.217
Iter 16/50 - Loss: 43.638   lengthscale: 0.991   noise: 0.198
Iter 17/50 - Loss[...]<jupyter_text>Model predictions are also similar to GP regression with only function values, butwe need more CG iterations to get accurate estimates of the predictive variance<jupyter_code># Set into eval mode
model.train()
model.eval()
likelihood.eval()

# Initialize plots
f, (y1_ax, y2_ax) = plt.subplots(1, 2, figsize=(12, 6))

# Make predictions
with torch.no_grad(), gpytorch.settings.max_cg_iterations(50):
    test_x = torch.linspace(lb, ub, 500)
    predictions = likelihood(model(test_x))
    mean = predictions.mean
    lower, upper = predictions.confidence_region()
    
# Plot training data as black stars
y1_ax.plot(train_x.detach().numpy(), train_y[:, 0].detach().numpy(), 'k*')
# Predictive mean as blue line
y1_ax.plot(test_x.numpy(), mean[:, 0].numpy(), 'b')
# Shade in confidence 
y1_ax.fill_between(test_x.numpy(), lower[:, 0].numpy(), upper[:, 0].numpy(), alpha=0.5)
y1_ax.legend(['Observed Values', 'Mean', 'Confidence'])
y1_ax.set_title('Function values')

# Plot training data as black stars
y2_ax.plot(train_x.detach().numpy(), train_y[:, 1].detach().numpy(), 'k*')
# Predictive mean as blue line
y2_ax.plot(test_x.numpy(), mean[:, 1].numpy(), 'b')
# Shade in confidence 
y2_ax.fill_between(test_x.numpy(), lower[:, 1].numpy(), upper[:, 1].numpy(), alpha=0.5)
y2_ax.legend(['Observed Derivatives', 'Mean', 'Confidence'])
y2_ax.set_title('Derivatives')

None<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11816519860395545, 'fraction_numerical': 0.05683895629051022, 'mean_word_length': 4.2743207712532865, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '97549', 'license': 'MIT', 'n_tokens_mistral': 2292, 'n_tokens_neox': 2038, 'n_words': 587, 'path': 'examples/08_Advanced_Usage/Simple_GP_Regression_Derivative_Information_1d.ipynb', 'repo_name': 'Mehdishishehbor/gpytorch', 'size': '6017'}