gp2Scale#

gp2Scale is a special setting in fvgp that combines non-stationary, compactly-supported kernels, HPC distributed computing, and sparse linear algebra to allow scale-up of exact GPs to millions of data points. gp2Scale holds the world record in this category! Here we run a moderately-sized GP, just because we assume you might run this locally.

I hope it is clear how cool it is what is happening here. If you have a dask client that points to a remote cluster with 500 GPUs, you will distribute the covariance matrix computation across those. The full matrix is sparse and will be fast to work with in downstream operations. The algorithm only makes use of naturally-occuring sparsity, so the result is exact in contrast to Vecchia or inducing-point methods.

##first install the newest version of fvgp
#!pip install fvgp~=4.8.7
#!pip install imate

Setup#

import numpy as np
import matplotlib.pyplot as plt
from fvgp import GP
from dask.distributed import Client
import sys
%load_ext autoreload
%autoreload 2

#further control plotting 
from loguru import logger
logger.disable("fvgp")


client = Client() ##this is the client you can make locally like this or 
#your HPC team can provide a script to get it. We included an example to get gp2Scale going
#on Perlmutter


#It's good practice to make sure to wait for all the workers to be ready
client.wait_for_workers(4)

Preparing the data and some other inputs#

def f1(x):
    return ((np.sin(5. * x) + np.cos(10. * x) + (2.* (x-0.4)**2) * np.cos(100. * x)))

input_dim = 1
N = 2000
x_data = np.random.rand(N,input_dim)
y_data = f1(x_data).reshape(len(x_data))
hps_n = 2

hps_bounds = np.array([[0.1,1.],      ##signal var of Wendland kernel
                       [0.001,0.04]])  ##length scale for Wendland kernel

Standard MCMC training#

from fvgp.kernels import wendland_anisotropic_gp2Scale_cpu
def kernel(x1,x2,hps):
    return wendland_anisotropic_gp2Scale_cpu(x1,x2,hps)


init_hps = np.array([0.2, 0.02])
my_gp2S = GP(x_data,y_data, kernel_function=kernel, 
             init_hyperparameters = init_hps, #compute_device = 'gpu', #you can use gpus here
             gp2Scale = True, gp2Scale_batch_size= 1000, gp2Scale_distribution="blockwise" ,
             dask_client = client, linalg_mode = "Chol",
            )

#optional data update
x_update = np.random.rand(5,input_dim)
y_update = f1(x_update).reshape(len(x_update))
my_gp2S.update_gp_data(x_update, y_update, append = True, rank_n_update = True)

print("Bayesian Optimization")
my_gp2S.train(hyperparameter_bounds = hps_bounds, max_iter = 20, info=True, method = "bo")
print("Trained likelihood BO: ", my_gp2S.log_likelihood())
print("MCMC")
my_gp2S.train(hyperparameter_bounds = hps_bounds, max_iter = 20, info=True, method = "mcmc")
print("Trained likelihood MCMC: ", my_gp2S.log_likelihood())
/home/marcus/Coding/fvGP/fvgp/gp.py:536: UserWarning: No noise function or measurement noise provided. Noise variances will be set to (0.01 * mean(|y_data|))^2.
  self.likelihood = GPlikelihood(self.data,
Bayesian Optimization
fvGP bo: evaluating a space-filling design of 6 points in 2 hyperparameters (budget 20)
fvGP bo: design complete, best f(x)= -6016.822833873692
fvGP bo evaluation 7 of at most 20: f(x)= -6717.094181691152, best= -6717.094181691152, EI was 387.2484767340749
fvGP bo evaluation 8 of at most 20: f(x)= -6763.67124717356, best= -6763.67124717356, EI was 84.1596001727191
fvGP bo evaluation 9 of at most 20: f(x)= -6601.007082053854, best= -6763.67124717356, EI was 43.02015324233612
fvGP bo evaluation 10 of at most 20: f(x)= -6711.642931488907, best= -6763.67124717356, EI was 5.792428528176185
fvGP bo evaluation 11 of at most 20: f(x)= -6757.190165253495, best= -6763.67124717356, EI was 154.13420839781406
fvGP bo evaluation 12 of at most 20: f(x)= -6774.327434440128, best= -6774.327434440128, EI was 39.907822179018225
fvGP bo evaluation 13 of at most 20: f(x)= -6694.413723378116, best= -6774.327434440128, EI was 6.253562880815704
fvGP bo evaluation 14 of at most 20: f(x)= -6775.950510497239, best= -6775.950510497239, EI was 3.1057630314289018
fvGP bo evaluation 15 of at most 20: f(x)= -6523.688758252579, best= -6775.950510497239, EI was 0.03072534111569434
fvGP bo evaluation 16 of at most 20: f(x)= -6776.380315275647, best= -6776.380315275647, EI was 0.771727171854196
fvGP bo evaluation 17 of at most 20: f(x)= -6493.963915644109, best= -6776.380315275647, EI was 2.7736089738593374e-07
fvGP bo evaluation 18 of at most 20: f(x)= -6759.821460196373, best= -6776.380315275647, EI was 1.0619979690262273e-10
fvGP bo evaluation 19 of at most 20: f(x)= -6660.103737659295, best= -6776.380315275647, EI was 2.575620447929114e-10
fvGP bo evaluation 20 of at most 20: f(x)= -6766.527384299113, best= -6776.380315275647, EI was 0.014621153610502514
fvGP bo finished after 20 evaluations (budget): f(x)= -6776.380315275647 at [0.18506157 0.04      ]
Trained likelihood BO:  6776.380315275647
MCMC
Starting likelihood. f(x)=  5311.48816038792
Finished  10  out of  20  iterations. f(x)=  5697.8003718924565
Trained likelihood MCMC:  5783.537711105288

A custom MCMC#

from fvgp import ProposalDistribution
init_s = (np.diag(hps_bounds[:,1]-hps_bounds[:,0])/100.)**2


def obj_func(hps,args):
    return my_gp2S.log_likelihood(hyperparameters=hps[0:2])

from fvgp import gpMCMC
def proposal_distribution(x0, hps, obj):
    cov = obj.prop_args["prop_Sigma"]
    proposal_hps = np.zeros((len(x0)))
    proposal_hps = np.random.multivariate_normal(
        mean = x0, cov = cov, size = 1).reshape(len(x0))
    return proposal_hps

def in_bounds(v,bounds):
    if any(v<bounds[:,0]) or any(v>bounds[:,1]): return False
    return True

def prior_function(theta,bounds,args):
    if in_bounds(theta, bounds): 
        return 0.
    else: 
        return -np.inf
        
pd = ProposalDistribution([0,1] ,proposal_dist=proposal_distribution,
                        init_prop_Sigma = init_s, adapt_callable="normal")

my_mcmc = gpMCMC(obj_func, bounds=hps_bounds, prior_function=prior_function, proposal_distributions=[pd],)
logger.disable("fvgp")
hps = np.random.uniform(
                        low = hps_bounds[:,0], 
                        high = hps_bounds[:,1], 
                        size = len(hps_bounds))
mcmc_result = my_mcmc.run_mcmc(x0=hps, n_updates=110, break_condition="default", info = True)

my_gp2S.set_hyperparameters(mcmc_result["x"][-1])
Starting likelihood. f(x)=  2993.796299916403
Finished  10  out of  110  iterations. f(x)=  3297.493614936596
Finished  20  out of  110  iterations. f(x)=  3565.042450551092
Finished  30  out of  110  iterations. f(x)=  3754.3358447164146
Finished  40  out of  110  iterations. f(x)=  4228.883327513869
Finished  50  out of  110  iterations. f(x)=  4582.15306558611
Finished  60  out of  110  iterations. f(x)=  4948.885408432042
Finished  70  out of  110  iterations. f(x)=  5125.138404968175
Finished  80  out of  110  iterations. f(x)=  5278.500405120966
Finished  90  out of  110  iterations. f(x)=  5522.750494256003
Finished  100  out of  110  iterations. f(x)=  5762.610795630837

Posterior evaluation#

x_pred = np.linspace(0,1,1000) ##for big GPs, this is usually not a good idea, but in 1d, we can still do it
                               ##It's better to do predictions only for a handful of points at a time.

mean1 = my_gp2S.posterior_mean(x_pred.reshape(1000,1))["m(x)"]
var1 =  my_gp2S.posterior_covariance(x_pred.reshape(1000,1), variance_only=False)["v(x)"]


plt.figure(figsize = (16,10))
plt.plot(x_pred,mean1, label = "posterior mean", linewidth = 4)
plt.plot(x_pred,f1(x_pred), label = "latent function", linewidth = 4)
plt.fill_between(x_pred, mean1 - 3. * np.sqrt(var1), mean1 + 3. * np.sqrt(var1), alpha = 0.5, color = "grey", label = "var")
plt.scatter(x_data,y_data, color = 'black')
<matplotlib.collections.PathCollection at 0x7fcb4021f150>
../_images/ca582b488f3b0260857a924e4c40a4dc0019d191dce96e97846cffe22ce3002a.png