-
Notifications
You must be signed in to change notification settings - Fork 404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Question] GP classification / discrete outputs #640
Comments
Good question. The SingleTaskGP is what you fit on your (continuous) objective? I know @mshvartsman has used a probit setup like this before, though I believe not in this constrained setting. Sticking your probit model into a
where Let me know how this works out, this seems like an interesting use case, happy to help out with making this work. |
Thanks! Yes, the continous objectives are modeled by a SingleTaskGP. For the constrained ParEGO acquisition I should be able to simply use
For the combining the 2 models I tried
The constructor works when I add the Bernoulli likelihood to model for the constraint.
If I need to implement a custom class based off |
So the issue is the In the meantime, one way to circumvent this would be to avoid using the outcome transform and just pass in standardized data - that way you wouldn't hit that issue. The downside is that now your objective is on a standardized scale so probability-weighted objective doesn't really make a lot of sense (btw the same issue occurs when your objective takes on negative values). To fix that you could keep the mean/sem from your manual standardization, and then add them into the objective to do the rescaling there:
where
|
Sorry, I haven't used probit GPs for constraints, so haven't seen this issue. As a minor point, unless I'm missing something, your literal snippet won't work (there's no |
@Balandat Thanks, I'll try that. @mshvartsman Thanks. Indeed the optimizer.step() got lost while pasting here. I updated the initial post to avoid confusion. Using L-BFGS instead of Adam would be very nice indeed. However, |
You should be able to set that - Looking through some of Michael's code, I've seen he has successfully done that on a GP model subclassing
|
@mshvartsman Sorry, I had not seen #641 . import botorch
import gpytorch
import matplotlib.pyplot as plt
import torch
from botorch.acquisition.monte_carlo import qExpectedImprovement
from botorch.fit import fit_gpytorch_model
from botorch.models import ModelListGP, SingleTaskGP
from botorch.models.gpytorch import GPyTorchModel
from functools import partial
from gpytorch.mlls.exact_marginal_log_likelihood import ExactMarginalLogLikelihood
from gpytorch.models import ApproximateGP
from gpytorch.variational import CholeskyVariationalDistribution, VariationalStrategy
N = 10 # initial observations
problem = botorch.test_functions.multi_objective.BraninCurrin(noise_std=0)
def feasibility_constraint(X):
return ((X ** 2).sum(axis=-1) < 0.5).to(dtype=torch.float32)
train_x = botorch.utils.sampling.draw_sobol_samples(
problem.bounds, n=N, q=1, seed=42
).squeeze()
train_y = problem(train_x)
train_y_mean = train_y.mean(dim=0)
train_y_std = train_y.std(dim=0)
train_yn = (train_y - train_y_mean) / train_y_std
train_f = feasibility_constraint(train_x)
class GPClassificationModel(ApproximateGP, GPyTorchModel):
def __init__(self, train_x, train_y):
self.train_inputs = (train_x,)
self.train_targets = train_y
variational_distribution = CholeskyVariationalDistribution(train_x.size(0))
variational_strategy = VariationalStrategy(
self, train_x, variational_distribution
)
super(GPClassificationModel, self).__init__(variational_strategy)
self.mean_module = gpytorch.means.ConstantMean()
self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel())
self.likelihood = gpytorch.likelihoods.BernoulliLikelihood()
def forward(self, x):
mean_x = self.mean_module(x)
covar_x = self.covar_module(x)
return gpytorch.distributions.MultivariateNormal(mean_x, covar_x)
obj1_model = SingleTaskGP(train_x, train_yn[:, [0]])
obj1_mll = ExactMarginalLogLikelihood(obj1_model.likelihood, obj1_model)
fit_gpytorch_model(obj1_mll)
obj2_model = SingleTaskGP(train_x, train_yn[:, [1]])
obj2_mll = ExactMarginalLogLikelihood(obj2_model.likelihood, obj2_model)
fit_gpytorch_model(obj2_mll)
constr_model = GPClassificationModel(train_x, train_f)
constr_mll = gpytorch.mlls.VariationalELBO(constr_model.likelihood, constr_model, N)
fit_gpytorch_model(constr_mll)
models = ModelListGP(obj1_model, obj2_model, constr_model)
# propose next point
train_yn_min = train_yn.min(dim=0).values
train_yn_max = train_yn.max(dim=0).values
train_data = torch.cat([train_yn, (train_f.view(-1, 1) * 200 - 100)], dim=1)
def objective_func(samples, weights):
Yn, F = samples[..., :-1], samples[..., -1]
# place outcomes on a scale from 0 (worst) to 1 (best)
Yu = 1 - (Yn - train_yn_min) / (train_yn_max - train_yn_min)
scalarization = (weights * Yu).min(dim=-1).values
p = constr_model.likelihood(F).probs
return p * scalarization
objective = botorch.acquisition.GenericMCObjective(
partial(objective_func, weights=torch.tensor([1, 1]))
)
acquisition = qExpectedImprovement(
model=models, objective=objective, best_f=objective(train_data).max()
)
candidate, acq_value = botorch.optim.optimize_acqf(
acquisition, bounds=problem.bounds, q=1, num_restarts=5, raw_samples=256
)
print(candidate, acq_value)
# plot
n_grid = 51
dx = torch.linspace(0, 1, n_grid)
X1_grid, X2_grid = torch.meshgrid(dx, dx)
X = torch.stack([X1_grid.reshape(-1), X2_grid.reshape(-1)], dim=1)
with torch.no_grad():
posterior_mean = models.posterior(X).mean.reshape(n_grid, n_grid, 3)
weights = torch.tensor([1, 1])
fig, (ax1, ax2, ax3, ax4) = plt.subplots(
ncols=4, figsize=(15, 4), sharex=True, sharey=True
)
ax1.contourf(X1_grid, X2_grid, posterior_mean[..., 0], levels=16)
ax2.contourf(X1_grid, X2_grid, posterior_mean[..., 1], levels=16)
ax3.contourf(X1_grid, X2_grid, constr_model.likelihood(posterior_mean[..., 2]).probs)
ax4.contourf(X1_grid, X2_grid, objective_func(posterior_mean, weights))
ax1.scatter(*train_x.T, c="r")
ax2.scatter(*train_x.T, c="r")
ax3.scatter(*train_x.T, c="r")
ax1.set(xlabel="x1", title="output 1 (minimize)", ylabel="x2")
ax2.set(xlabel="x1", title="output 2 (minimize)")
ax3.set(xlabel="x1", title="feasibility constraint (maximize)")
ax4.set(xlabel="x1", title=f"objective, w={weights.numpy()} (maximize)")
fig.suptitle("Model posterior")
fig.tight_layout()
plt.show() |
Hi David,
Great to see that we could work out a solution here. This seems like it
could be helpful to a lot of other people out there. Would you be
interested in contributing a brief tutorial on this?
…On Sat, Dec 26, 2020 at 2:21 PM David Walz ***@***.***> wrote:
Closed #640 <#640>.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#640 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAW34MTG25LWALNHOO4G5DSWZAURANCNFSM4VF5N6KA>
.
|
Glad to see you got it to work.
I'm actually somewhat unsure about this statement now - the way the outcome transforms are being handled inside the |
Actually, I think it's because |
I'm implementing a BO loop with feasibility constraints along the lines of https://botorch.org/tutorials/constrained_multi_objective_bo
However, in my case evaluations of the feasibility constraint are discrete (0, 1) for which a GP model with a binomial likelihood seems to a suitable approach.
Now I'm wondering how to feed this model together with a
SingleTaskGP
to the acquisition function.Do I have to base my GPClassificationModel on
ApproximateGP
or can I simply combine it inModelListGPyTorchModel
?The text was updated successfully, but these errors were encountered: