Skip to content
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

Use Standardize by default for SingleTaskGP #2458

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions botorch/acquisition/analytic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1091,15 +1091,17 @@ def _get_noiseless_fantasy_model(
# are used across all batches (by default, a GP with batched training data
# uses independent hyperparameters for each batch).

# Don't apply `outcome_transform` and `input_transform` here,
# since the data being passed has already been transformed.
# So we will instead set them afterwards.
# We don't want to use the true `outcome_transform` and `input_transform` here
# since the data being passed has already been transformed. We thus pass `None`
# and will instead set them afterwards.
fantasy_model = SingleTaskGP(
train_X=model.train_inputs[0],
train_Y=model.train_targets.unsqueeze(-1),
train_Yvar=model.likelihood.noise_covar.noise.unsqueeze(-1),
covar_module=deepcopy(model.covar_module),
mean_module=deepcopy(model.mean_module),
outcome_transform=None,
input_transform=None,
)

Yvar = torch.full_like(Y_fantasized, 1e-7)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class qMultiObjectiveMaxValueEntropy(
_default_sample_shape: The `sample_shape` for the default sampler.
Example:
>>> model = SingleTaskGP(train_X, train_Y)
>>> model = SingleTaskGP(train_X, train_Y, outcome_transform=None)
>>> MESMO = qMultiObjectiveMaxValueEntropy(model, sample_pfs)
>>> mesmo = MESMO(test_X)
"""
Expand Down
7 changes: 6 additions & 1 deletion botorch/models/contextual.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ def __init__(
dimension is set to 1 for each categorical variable.
context_weight_dict: Known population weights of each context.
"""
super().__init__(train_X=train_X, train_Y=train_Y, train_Yvar=train_Yvar)
super().__init__(
train_X=train_X,
train_Y=train_Y,
train_Yvar=train_Yvar,
outcome_transform=None,
)
self.covar_module = LCEAKernel(
decomposition=decomposition,
batch_shape=self._aug_batch_shape,
Expand Down
15 changes: 14 additions & 1 deletion botorch/models/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import torch
from botorch.exceptions import UnsupportedError
from botorch.exceptions.warnings import BotorchWarning
from botorch.models import SingleTaskGP
from botorch.models.gp_regression import HeteroskedasticSingleTaskGP
from botorch.models.gp_regression_fidelity import SingleTaskMultiFidelityGP
from botorch.models.gp_regression_mixed import MixedSingleTaskGP
Expand Down Expand Up @@ -99,7 +100,8 @@ def _check_compatibility(models: ModuleList) -> None:
# TODO: Add support for outcome transforms.
if any(getattr(m, "outcome_transform", None) is not None for m in models):
raise UnsupportedError(
"Conversion of models with outcome transforms is currently unsupported."
"Conversion of models with outcome transforms is unsupported. "
"To fix this error, explicitly pass `outcome_transform=None`.",
)

# check that each model is single-output
Expand Down Expand Up @@ -179,6 +181,11 @@ def model_list_to_batched(model_list: ModelListGP) -> BatchedMultiOutputGPyTorch
batch_length = len(models)
covar_module = _batched_kernel(models[0].covar_module, batch_length)
kwargs["covar_module"] = covar_module
# SingleTaskGP uses a default outcome transforms while this converter doesn't
# support outcome transforms. We need to explicitly pass down `None` to make
# sure no outcome transform is being used.
if isinstance(models[0], SingleTaskGP):
kwargs["outcome_transform"] = None

# construct the batched GP model
input_transform = getattr(models[0], "input_transform", None)
Expand Down Expand Up @@ -418,6 +425,12 @@ def batched_multi_output_to_single_output(
kwargs["train_Yvar"] = noise_covar.noise.clone().unsqueeze(-1)
if isinstance(batch_mo_model, SingleTaskMultiFidelityGP):
kwargs.update(batch_mo_model._init_args)
# SingleTaskGP uses a default outcome transforms while this converter doesn't
# support outcome transforms. We need to explicitly pass down `None` to make
# sure no outcome transform is being used.
if isinstance(batch_mo_model, SingleTaskGP):
kwargs["outcome_transform"] = None

single_outcome_model = batch_mo_model.__class__(
input_transform=input_transform, **kwargs
)
Expand Down
16 changes: 13 additions & 3 deletions botorch/models/gp_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from botorch.models.gpytorch import BatchedMultiOutputGPyTorchModel
from botorch.models.model import FantasizeMixin
from botorch.models.transforms.input import InputTransform
from botorch.models.transforms.outcome import Log, OutcomeTransform
from botorch.models.transforms.outcome import Log, OutcomeTransform, Standardize
from botorch.models.utils import validate_input_scaling
from botorch.models.utils.gpytorch_modules import (
get_covar_module_with_dim_scaled_prior,
Expand All @@ -46,6 +46,7 @@
)
from botorch.utils.containers import BotorchContainer
from botorch.utils.datasets import SupervisedDataset
from botorch.utils.types import _DefaultType, DEFAULT
from gpytorch.constraints.constraints import GreaterThan
from gpytorch.distributions.multivariate_normal import MultivariateNormal
from gpytorch.likelihoods.gaussian_likelihood import (
Expand Down Expand Up @@ -134,7 +135,7 @@ def __init__(
likelihood: Optional[Likelihood] = None,
covar_module: Optional[Module] = None,
mean_module: Optional[Mean] = None,
outcome_transform: Optional[OutcomeTransform] = None,
outcome_transform: Optional[Union[OutcomeTransform, _DefaultType]] = DEFAULT,
input_transform: Optional[InputTransform] = None,
) -> None:
r"""
Expand All @@ -154,16 +155,24 @@ def __init__(
outcome_transform: An outcome transform that is applied to the
training data during instantiation and to the posterior during
inference (that is, the `Posterior` obtained by calling
`.posterior` on the model will be on the original scale).
`.posterior` on the model will be on the original scale). We use a
`Standardize` transform if no `outcome_transform` is specified.
Pass down `None` to use no outcome transform.
input_transform: An input transform that is applied in the model's
forward pass.
"""
self._validate_tensor_args(X=train_X, Y=train_Y, Yvar=train_Yvar)
if outcome_transform == DEFAULT:
outcome_transform = Standardize(
m=train_Y.shape[-1], batch_shape=train_X.shape[:-2]
)
with torch.no_grad():
transformed_X = self.transform_inputs(
X=train_X, input_transform=input_transform
)
if outcome_transform is not None:
train_Y, train_Yvar = outcome_transform(train_Y, train_Yvar)
# Validate again after applying the transforms
self._validate_tensor_args(X=transformed_X, Y=train_Y, Yvar=train_Yvar)
ignore_X_dims = getattr(self, "_ignore_X_dims_scaling_check", None)
validate_input_scaling(
Expand Down Expand Up @@ -352,6 +361,7 @@ def __init__(
train_X=train_X,
train_Y=train_Y,
likelihood=likelihood,
outcome_transform=None,
input_transform=input_transform,
)
self.register_added_loss_term("noise_added_loss")
Expand Down
35 changes: 28 additions & 7 deletions test/acquisition/multi_objective/test_max_value_entropy_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
qMultiObjectiveMaxValueEntropy,
)
from botorch.acquisition.multi_objective.utils import compute_sample_box_decomposition
from botorch.exceptions.errors import UnsupportedError
from botorch.models.gp_regression import SingleTaskGP
from botorch.models.model_list_gp_regression import ModelListGP
from botorch.models.transforms.outcome import Standardize
Expand Down Expand Up @@ -71,15 +72,30 @@ def test_multi_objective_max_value_entropy(self):
# test batched model
train_X = torch.rand(1, 1, 2, dtype=dtype, device=self.device)
train_Y = torch.rand(1, 1, m, dtype=dtype, device=self.device)
model = SingleTaskGP(train_X, train_Y)
model = SingleTaskGP(train_X, train_Y, outcome_transform=None)
with self.assertRaises(NotImplementedError):
qMultiObjectiveMaxValueEntropy(model, dummy_sample_pareto_frontiers)
qMultiObjectiveMaxValueEntropy(
model=model, sample_pareto_frontiers=dummy_sample_pareto_frontiers
)
# test initialization
train_X = torch.rand(4, 2, dtype=dtype, device=self.device)
train_Y = torch.rand(4, m, dtype=dtype, device=self.device)
# test batched MO model
# Models with outcome transforms aren't supported.
model = SingleTaskGP(train_X, train_Y)
mesmo = qMultiObjectiveMaxValueEntropy(model, dummy_sample_pareto_frontiers)
with self.assertRaisesRegex(
UnsupportedError,
"Conversion of models with outcome transforms is unsupported. "
"To fix this error, explicitly pass `outcome_transform=None`.",
):
qMultiObjectiveMaxValueEntropy(
model=ModelListGP(model, model),
sample_pareto_frontiers=dummy_sample_pareto_frontiers,
)
# test batched MO model
model = SingleTaskGP(train_X, train_Y, outcome_transform=None)
mesmo = qMultiObjectiveMaxValueEntropy(
model=model, sample_pareto_frontiers=dummy_sample_pareto_frontiers
)
self.assertEqual(mesmo.num_fantasies, 16)
# Initialize the sampler.
dummy_post = model.posterior(train_X[:1])
Expand All @@ -98,11 +114,16 @@ def test_multi_objective_max_value_entropy(self):
)
# test ModelListGP
model = ModelListGP(
*[SingleTaskGP(train_X, train_Y[:, i : i + 1]) for i in range(m)]
*[
SingleTaskGP(train_X, train_Y[:, i : i + 1], outcome_transform=None)
for i in range(m)
]
)
mock_sample_pfs = mock.Mock()
mock_sample_pfs.return_value = dummy_sample_pareto_frontiers(model=model)
mesmo = qMultiObjectiveMaxValueEntropy(model, mock_sample_pfs)
mesmo = qMultiObjectiveMaxValueEntropy(
model=model, sample_pareto_frontiers=mock_sample_pfs
)
self.assertEqual(mesmo.num_fantasies, 16)
# Initialize the sampler.
dummy_post = model.posterior(train_X[:1])
Expand Down Expand Up @@ -156,7 +177,7 @@ def test_multi_objective_max_value_entropy(self):
],
dim=1,
)
fantasy_model = SingleTaskGP(fant_X, fant_Y)
fantasy_model = SingleTaskGP(fant_X, fant_Y, outcome_transform=None)

# test with X_pending is not None
with mock.patch.object(
Expand Down
Loading
Loading