Custom GP modeling for BO¶
In [1]:
Copied!
# Ignore all warnings
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
import pandas as pd
import torch
from xopt.vocs import VOCS
my_vocs = VOCS(
variables = {"x":[0,1]},
objectives = {"y":"MAXIMIZE"},
constraints = {"c": ["LESS_THAN", 0]}
)
# Ignore all warnings
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
import pandas as pd
import torch
from xopt.vocs import VOCS
my_vocs = VOCS(
variables = {"x":[0,1]},
objectives = {"y":"MAXIMIZE"},
constraints = {"c": ["LESS_THAN", 0]}
)
In [2]:
Copied!
# define test functions
def y(x):
return torch.sin(2*3.14*x)
def c(x):
return 5.0*torch.cos(2*3.14*x + 0.25)
test_x = torch.linspace(*torch.tensor(my_vocs.bounds.flatten()), 100)
# define training data to pass to the generator
train_x = torch.tensor((0.2,0.5, 0.6))
train_y = y(train_x)
train_c = c(train_x)
training_data = pd.DataFrame(
{"x": train_x.numpy(), "y": train_y.numpy(), "c": train_c}
)
def plot_ground_truth():
fig,ax = plt.subplots()
ax.plot(test_x, y(test_x),'--C0')
ax.plot(test_x, c(test_x),'--C1')
ax.plot(train_x, train_y,'oC0')
ax.plot(train_x, train_c,'oC1')
return ax
plot_ground_truth()
# define test functions
def y(x):
return torch.sin(2*3.14*x)
def c(x):
return 5.0*torch.cos(2*3.14*x + 0.25)
test_x = torch.linspace(*torch.tensor(my_vocs.bounds.flatten()), 100)
# define training data to pass to the generator
train_x = torch.tensor((0.2,0.5, 0.6))
train_y = y(train_x)
train_c = c(train_x)
training_data = pd.DataFrame(
{"x": train_x.numpy(), "y": train_y.numpy(), "c": train_c}
)
def plot_ground_truth():
fig,ax = plt.subplots()
ax.plot(test_x, y(test_x),'--C0')
ax.plot(test_x, c(test_x),'--C1')
ax.plot(train_x, train_y,'oC0')
ax.plot(train_x, train_c,'oC1')
return ax
plot_ground_truth()
Out[2]:
<Axes: >
Custom kernel definition¶
In this example we know that the target optimization function is periodic, so it makes sense to use a periodic kernel for the GP model with no noise. Here we define a function to create that model.
In [3]:
Copied!
from xopt.generators.bayesian.expected_improvement import ExpectedImprovementGenerator
from xopt.generators.bayesian.models.standard import StandardModelConstructor
from gpytorch.kernels import PeriodicKernel, ScaleKernel
# note the creation of options beforehand
# specify a periodic kernel for each output (objectives and constraints)
covar_module = {"y": ScaleKernel(PeriodicKernel())}
gp_constructor = StandardModelConstructor(
covar_modules=covar_module
)
generator = ExpectedImprovementGenerator(
vocs=my_vocs, gp_constructor=gp_constructor
)
generator
from xopt.generators.bayesian.expected_improvement import ExpectedImprovementGenerator
from xopt.generators.bayesian.models.standard import StandardModelConstructor
from gpytorch.kernels import PeriodicKernel, ScaleKernel
# note the creation of options beforehand
# specify a periodic kernel for each output (objectives and constraints)
covar_module = {"y": ScaleKernel(PeriodicKernel())}
gp_constructor = StandardModelConstructor(
covar_modules=covar_module
)
generator = ExpectedImprovementGenerator(
vocs=my_vocs, gp_constructor=gp_constructor
)
generator
Out[3]:
ExpectedImprovementGenerator(supports_batch_generation=True, supports_multi_objective=False, vocs=VOCS(variables={'x': [0.0, 1.0]}, constraints={'c': ['LESS_THAN', 0.0]}, objectives={'y': 'MAXIMIZE'}, constants={}, observables=[]), data=None, model=None, n_monte_carlo_samples=128, turbo_controller=None, use_cuda=False, gp_constructor=StandardModelConstructor(name='standard', use_low_noise_prior=True, covar_modules={'y': ScaleKernel( (base_kernel): PeriodicKernel( (raw_lengthscale_constraint): Positive() (raw_period_length_constraint): Positive() ) (raw_outputscale_constraint): Positive() )}, mean_modules={}, trainable_mean_keys=[], transform_inputs=True, custom_noise_prior=None), numerical_optimizer=LBFGSOptimizer(name='LBFGS', n_restarts=20, max_iter=2000, max_time=None), max_travel_distances=None, fixed_features=None, computation_time=None, log_transform_acquisition_function=False, n_interpolate_points=None, n_candidates=1)
In [4]:
Copied!
# view custom model from data
generator.add_data(training_data)
model = generator.train_model()
fig, ax = generator.visualize_model(n_grid=len(test_x))
# plot ground truth
ax[0].plot(test_x, y(test_x), "C0-.")
ax[1].plot(test_x, c(test_x), "C2-.");
# view custom model from data
generator.add_data(training_data)
model = generator.train_model()
fig, ax = generator.visualize_model(n_grid=len(test_x))
# plot ground truth
ax[0].plot(test_x, y(test_x), "C0-.")
ax[1].plot(test_x, c(test_x), "C2-.");
In [5]:
Copied!
model
model
Out[5]:
ModelListGP( (models): ModuleList( (0): SingleTaskGP( (likelihood): GaussianLikelihood( (noise_covar): HomoskedasticNoise( (noise_prior): GammaPrior() (raw_noise_constraint): GreaterThan(1.000E-04) ) ) (mean_module): ConstantMean() (covar_module): ScaleKernel( (base_kernel): PeriodicKernel( (raw_lengthscale_constraint): Positive() (raw_period_length_constraint): Positive() ) (raw_outputscale_constraint): Positive() ) (outcome_transform): Standardize() (input_transform): Normalize() ) (1): SingleTaskGP( (likelihood): GaussianLikelihood( (noise_covar): HomoskedasticNoise( (noise_prior): GammaPrior() (raw_noise_constraint): GreaterThan(1.000E-04) ) ) (mean_module): ConstantMean() (covar_module): ScaleKernel( (base_kernel): MaternKernel( (lengthscale_prior): GammaPrior() (raw_lengthscale_constraint): Positive() ) (outputscale_prior): GammaPrior() (raw_outputscale_constraint): Positive() ) (outcome_transform): Standardize() (input_transform): Normalize() ) ) (likelihood): LikelihoodList( (likelihoods): ModuleList( (0-1): 2 x GaussianLikelihood( (noise_covar): HomoskedasticNoise( (noise_prior): GammaPrior() (raw_noise_constraint): GreaterThan(1.000E-04) ) ) ) ) )
In [6]:
Copied!
# get the next point from the generator
generator.generate(1)
# get the next point from the generator
generator.generate(1)
Out[6]:
[{'x': 0.0}]
Custom prior mean function¶
Here we assume we have some knowledge of the ground truth function, which we can take advantage of to speed up optimization. This "prior mean" function is specified by a pytorch module.
In [7]:
Copied!
class ConstraintPrior(torch.nn.Module):
def forward(self, X):
return c(X).squeeze(dim=-1)
gp_constructor = StandardModelConstructor(
mean_modules={"c": ConstraintPrior()}
)
generator = ExpectedImprovementGenerator(
vocs=my_vocs, gp_constructor=gp_constructor,
)
class ConstraintPrior(torch.nn.Module):
def forward(self, X):
return c(X).squeeze(dim=-1)
gp_constructor = StandardModelConstructor(
mean_modules={"c": ConstraintPrior()}
)
generator = ExpectedImprovementGenerator(
vocs=my_vocs, gp_constructor=gp_constructor,
)
In [8]:
Copied!
# view custom model from data
generator.add_data(training_data)
model = generator.train_model()
fig, ax = generator.visualize_model(n_grid=len(test_x))
# plot ground truth
ax[0].plot(test_x, y(test_x), "C0-.")
ax[1].plot(test_x, c(test_x), "C2-.");
# view custom model from data
generator.add_data(training_data)
model = generator.train_model()
fig, ax = generator.visualize_model(n_grid=len(test_x))
# plot ground truth
ax[0].plot(test_x, y(test_x), "C0-.")
ax[1].plot(test_x, c(test_x), "C2-.");
In [9]:
Copied!
model
model
Out[9]:
ModelListGP( (models): ModuleList( (0): SingleTaskGP( (likelihood): GaussianLikelihood( (noise_covar): HomoskedasticNoise( (noise_prior): GammaPrior() (raw_noise_constraint): GreaterThan(1.000E-04) ) ) (mean_module): ConstantMean() (covar_module): ScaleKernel( (base_kernel): MaternKernel( (lengthscale_prior): GammaPrior() (raw_lengthscale_constraint): Positive() ) (outputscale_prior): GammaPrior() (raw_outputscale_constraint): Positive() ) (outcome_transform): Standardize() (input_transform): Normalize() ) (1): SingleTaskGP( (likelihood): GaussianLikelihood( (noise_covar): HomoskedasticNoise( (noise_prior): GammaPrior() (raw_noise_constraint): GreaterThan(1.000E-04) ) ) (mean_module): CustomMean( (_model): ConstraintPrior() (input_transformer): Normalize() (outcome_transformer): Standardize() ) (covar_module): ScaleKernel( (base_kernel): MaternKernel( (lengthscale_prior): GammaPrior() (raw_lengthscale_constraint): Positive() ) (outputscale_prior): GammaPrior() (raw_outputscale_constraint): Positive() ) (outcome_transform): Standardize() (input_transform): Normalize() ) ) (likelihood): LikelihoodList( (likelihoods): ModuleList( (0-1): 2 x GaussianLikelihood( (noise_covar): HomoskedasticNoise( (noise_prior): GammaPrior() (raw_noise_constraint): GreaterThan(1.000E-04) ) ) ) ) )
In [10]:
Copied!
list(model.named_parameters())
list(model.named_parameters())
Out[10]:
[('models.0.likelihood.noise_covar.raw_noise', Parameter containing: tensor([-23.1607], dtype=torch.float64, requires_grad=True)), ('models.0.mean_module.raw_constant', Parameter containing: tensor(-0.2223, dtype=torch.float64, requires_grad=True)), ('models.0.covar_module.raw_outputscale', Parameter containing: tensor(3.0724, dtype=torch.float64, requires_grad=True)), ('models.0.covar_module.base_kernel.raw_lengthscale', Parameter containing: tensor([[-0.7308]], dtype=torch.float64, requires_grad=True)), ('models.1.likelihood.noise_covar.raw_noise', Parameter containing: tensor([-23.2184], dtype=torch.float64, requires_grad=True)), ('models.1.covar_module.raw_outputscale', Parameter containing: tensor(-6.7635, dtype=torch.float64, requires_grad=True)), ('models.1.covar_module.base_kernel.raw_lengthscale', Parameter containing: tensor([[-0.5597]], dtype=torch.float64, requires_grad=True))]
In [ ]:
Copied!