class UnstandardizeAnalyticMultiOutputObjective(AnalyticMultiOutputObjective): r"""Objective that unstandardizes the posterior. TODO: remove this when MultiTask models support outcome transforms. Example: >>> unstd_objective = UnstandardizeAnalyticMultiOutputObjective(Y_mean, Y_std) >>> unstd_posterior = unstd_objective(posterior) """ def __init__(self, Y_mean: Tensor, Y_std: Tensor) -> None: r"""Initialize objective. Args: Y_mean: `m`-dim tensor of outcome means Y_std: `m`-dim tensor of outcome standard deviations """ if Y_mean.ndim > 1 or Y_std.ndim > 1: raise BotorchTensorDimensionError( "Y_mean and Y_std must both be 1-dimensional, but got " f"{Y_mean.ndim} and {Y_std.ndim}") super().__init__() self.outcome_transform = Standardize(m=Y_mean.shape[0]).to(Y_mean) Y_std_unsqueezed = Y_std.unsqueeze(0) self.outcome_transform.means = Y_mean.unsqueeze(0) self.outcome_transform.stdvs = Y_std_unsqueezed self.outcome_transform._stdvs_sq = Y_std_unsqueezed.pow(2) self.outcome_transform.eval() def forward(self, posterior: GPyTorchPosterior) -> Tensor: return self.outcome_transform.untransform_posterior(posterior)
def __init__(self, Y_mean: Tensor, Y_std: Tensor, outcomes: Optional[List[int]] = None) -> None: r"""Initialize objective. Args: Y_mean: `m`-dim tensor of outcome means Y_std: `m`-dim tensor of outcome standard deviations outcomes: A list of `m' <= m` indices that specifies which of the `m` model outputs should be considered as the outcomes for MOO. If omitted, use all model outcomes. Typically used for constrained optimization. """ if Y_mean.ndim > 1 or Y_std.ndim > 1: raise BotorchTensorDimensionError( "Y_mean and Y_std must both be 1-dimensional, but got " f"{Y_mean.ndim} and {Y_std.ndim}") if outcomes is not None: if len(outcomes) < 2: raise BotorchTensorDimensionError( "Must specify at least two outcomes for MOO.") elif len(outcomes) > Y_mean.shape[-1]: raise BotorchTensorDimensionError( f"Cannot specify more ({len(outcomes)}) outcomes that present in " f"the normalization inputs ({Y_mean.shape[-1]}).") super().__init__() self.outcome_transform = Standardize(m=Y_mean.shape[0], outputs=outcomes).to(Y_mean) Y_std_unsqueezed = Y_std.unsqueeze(0) self.outcome_transform.means = Y_mean.unsqueeze(0) self.outcome_transform.stdvs = Y_std_unsqueezed self.outcome_transform._stdvs_sq = Y_std_unsqueezed.pow(2) self.outcome_transform.eval()
def test_batched_to_model_list(self): for dtype in (torch.float, torch.double): # test SingleTaskGP train_X = torch.rand(10, 2, device=self.device, dtype=dtype) train_Y1 = train_X.sum(dim=-1) train_Y2 = train_X[:, 0] - train_X[:, 1] train_Y = torch.stack([train_Y1, train_Y2], dim=-1) batch_gp = SingleTaskGP(train_X, train_Y) list_gp = batched_to_model_list(batch_gp) self.assertIsInstance(list_gp, ModelListGP) # test FixedNoiseGP batch_gp = FixedNoiseGP(train_X, train_Y, torch.rand_like(train_Y)) list_gp = batched_to_model_list(batch_gp) self.assertIsInstance(list_gp, ModelListGP) # test SingleTaskMultiFidelityGP for lin_trunc in (False, True): batch_gp = SingleTaskMultiFidelityGP( train_X, train_Y, iteration_fidelity=1, linear_truncated=lin_trunc) list_gp = batched_to_model_list(batch_gp) self.assertIsInstance(list_gp, ModelListGP) # test HeteroskedasticSingleTaskGP batch_gp = HeteroskedasticSingleTaskGP(train_X, train_Y, torch.rand_like(train_Y)) with self.assertRaises(NotImplementedError): batched_to_model_list(batch_gp) # test with transforms input_tf = Normalize( d=2, bounds=torch.tensor([[0.0, 0.0], [1.0, 1.0]], device=self.device, dtype=dtype), ) octf = Standardize(m=2) batch_gp = SingleTaskGP(train_X, train_Y, outcome_transform=octf, input_transform=input_tf) list_gp = batched_to_model_list(batch_gp) for i, m in enumerate(list_gp.models): self.assertIsInstance(m.input_transform, Normalize) self.assertTrue( torch.equal(m.input_transform.bounds, input_tf.bounds)) self.assertIsInstance(m.outcome_transform, Standardize) self.assertEqual(m.outcome_transform._m, 1) expected_octf = octf.subset_output(idcs=[i]) for attr_name in ["means", "stdvs", "_stdvs_sq"]: self.assertTrue( torch.equal( m.outcome_transform.__getattr__(attr_name), expected_octf.__getattr__(attr_name), ))
def test_unstandardize_mo_objective(self): for objective_class in ( UnstandardizeMCMultiOutputObjective, UnstandardizeAnalyticMultiOutputObjective, ): Y_mean = torch.ones(2) Y_std = torch.ones(2) with self.assertRaises(BotorchTensorDimensionError): objective_class(Y_mean=Y_mean.unsqueeze(0), Y_std=Y_std) with self.assertRaises(BotorchTensorDimensionError): objective_class(Y_mean=Y_mean, Y_std=Y_std.unsqueeze(0)) with self.assertRaises(BotorchTensorDimensionError): objective_class(Y_mean=Y_mean, Y_std=Y_std, outcomes=[0]) with self.assertRaises(BotorchTensorDimensionError): objective_class(Y_mean=Y_mean, Y_std=Y_std, outcomes=[0, 1, 2]) objective = objective_class(Y_mean=Y_mean, Y_std=Y_std) for batch_shape, m, outcomes, dtype in itertools.product( ([], [3]), (2, 3), (None, [-2, -1]), (torch.float, torch.double)): Y_mean = torch.rand(m, dtype=dtype, device=self.device) Y_std = torch.rand(m, dtype=dtype, device=self.device).clamp_min(1e-3) objective = objective_class(Y_mean=Y_mean, Y_std=Y_std, outcomes=outcomes) if objective_class == UnstandardizeAnalyticMultiOutputObjective: if outcomes is None: # passing outcomes is not currently supported mean = torch.rand(2, m) variance = variance = torch.rand(2, m) mock_posterior = MockPosterior(mean=mean, variance=variance) tf_posterior = objective(mock_posterior) tf = Standardize(m=m) tf.means = Y_mean tf.stdvs = Y_std tf._stdvs_sq = Y_std.pow(2) tf.eval() expected_posterior = tf.untransform_posterior( mock_posterior) self.assertTrue( torch.equal(tf_posterior.mean, expected_posterior.mean)) self.assertTrue( torch.equal(tf_posterior.variance, expected_posterior.variance)) else: samples = torch.rand(*batch_shape, 2, m, dtype=dtype, device=self.device) obj_expected = samples * Y_std.to(dtype=dtype) + Y_mean.to( dtype=dtype) if outcomes is not None: obj_expected = obj_expected[..., outcomes] self.assertTrue( torch.equal(objective(samples), obj_expected))
def load_gp(log_dir): try: # The GP state, i.e., hyperparameters, normalization, etc. model_file = os.path.join(log_dir, "model_state.pth") with open(model_file, "rb") as f: state_dict = torch.load(f) # Get the evaluated data points eval_dict = load_eval(log_dir) train_X = eval_dict["train_inputs"] train_Y = eval_dict["train_targets"] # The bounds of the domain config_dict = load_config(log_dir) lb = torch.tensor(config_dict["lower_bound"]) ub = torch.tensor(config_dict["upper_bound"]) bounds = torch.stack((lb, ub)) # Create GP instance and load respective parameters gp = SingleTaskGP( train_X=train_X, train_Y=train_Y, outcome_transform=Standardize(m=1), input_transform=Normalize(d=1, bounds=bounds), ) gp.load_state_dict(state_dict=state_dict) except FileNotFoundError: print(f"The model file could not be found in: {log_dir}") exit(1) return gp
def _getBatchedModel( self, kind="SingleTaskGP", double=False, outcome_transform=False ): dtype = torch.double if double else torch.float train_x = torch.linspace(0, 1, 10, device=self.device, dtype=dtype).unsqueeze( -1 ) noise = torch.tensor(NOISE, device=self.device, dtype=dtype) train_y1 = torch.sin(train_x * (2 * math.pi)) + noise train_y2 = torch.sin(train_x * (2 * math.pi)) + noise train_y = torch.cat([train_y1, train_y2], dim=-1) kwargs = {} if outcome_transform: kwargs["outcome_transform"] = Standardize(m=2) if kind == "SingleTaskGP": model = SingleTaskGP(train_x, train_y, **kwargs) elif kind == "FixedNoiseGP": model = FixedNoiseGP( train_x, train_y, 0.1 * torch.ones_like(train_y), **kwargs ) elif kind == "HeteroskedasticSingleTaskGP": model = HeteroskedasticSingleTaskGP( train_x, train_y, 0.1 * torch.ones_like(train_y), **kwargs ) else: raise NotImplementedError mll = ExactMarginalLogLikelihood(model.likelihood, model) return mll.to(device=self.device, dtype=dtype)
def initialize_model(train_x, train_obj): # define models for objective and constraint model = SingleTaskGP(train_x, train_obj, outcome_transform=Standardize(m=train_obj.shape[-1])) mll = ExactMarginalLogLikelihood(model.likelihood, model) return mll, model
def test_transforms(self): train_x = torch.rand(10, 3, device=self.device) train_y = torch.randn(10, 4, 5, device=self.device) # test handling of Standardize with self.assertWarns(RuntimeWarning): model = HigherOrderGP(train_X=train_x, train_Y=train_y, outcome_transform=Standardize(m=5)) self.assertIsInstance(model.outcome_transform, FlattenedStandardize) self.assertEqual(model.outcome_transform.output_shape, train_y.shape[1:]) self.assertEqual(model.outcome_transform.batch_shape, torch.Size()) model = HigherOrderGP( train_X=train_x, train_Y=train_y, input_transform=Normalize(d=3), outcome_transform=FlattenedStandardize(train_y.shape[1:]), ) mll = ExactMarginalLogLikelihood(model.likelihood, model) fit_gpytorch_torch(mll, options={"maxiter": 1, "disp": False}) test_x = torch.rand(2, 5, 3, device=self.device) test_y = torch.randn(2, 5, 4, 5, device=self.device) posterior = model.posterior(test_x) self.assertIsInstance(posterior, TransformedPosterior) conditioned_model = model.condition_on_observations(test_x, test_y) self.assertIsInstance(conditioned_model, HigherOrderGP) self.check_transform_forward(model) self.check_transform_untransform(model)
class UnstandardizeAnalyticMultiOutputObjective(AnalyticMultiOutputObjective): r"""Objective that unstandardizes the posterior. TODO: remove this when MultiTask models support outcome transforms. Example: >>> unstd_objective = UnstandardizeAnalyticMultiOutputObjective(Y_mean, Y_std) >>> unstd_posterior = unstd_objective(posterior) """ def __init__(self, Y_mean: Tensor, Y_std: Tensor, outcomes: Optional[List[int]] = None) -> None: r"""Initialize objective. Args: Y_mean: `m`-dim tensor of outcome means Y_std: `m`-dim tensor of outcome standard deviations outcomes: A list of `m' <= m` indices that specifies which of the `m` model outputs should be considered as the outcomes for MOO. If omitted, use all model outcomes. Typically used for constrained optimization. """ if Y_mean.ndim > 1 or Y_std.ndim > 1: raise BotorchTensorDimensionError( "Y_mean and Y_std must both be 1-dimensional, but got " f"{Y_mean.ndim} and {Y_std.ndim}") if outcomes is not None: if len(outcomes) < 2: raise BotorchTensorDimensionError( "Must specify at least two outcomes for MOO.") elif len(outcomes) > Y_mean.shape[-1]: raise BotorchTensorDimensionError( f"Cannot specify more ({len(outcomes)}) outcomes that present in " f"the normalization inputs ({Y_mean.shape[-1]}).") super().__init__() self.outcome_transform = Standardize(m=Y_mean.shape[0], outputs=outcomes).to(Y_mean) Y_std_unsqueezed = Y_std.unsqueeze(0) self.outcome_transform.means = Y_mean.unsqueeze(0) self.outcome_transform.stdvs = Y_std_unsqueezed self.outcome_transform._stdvs_sq = Y_std_unsqueezed.pow(2) self.outcome_transform.eval() def forward(self, posterior: GPyTorchPosterior) -> Tensor: return self.outcome_transform.untransform_posterior(posterior)
def initialize_model(train_x, train_obj, train_con, state_dict=None): # define models for objective and constraint train_y = torch.cat([train_obj, train_con], dim=-1) model = SingleTaskGP(train_x, train_y, outcome_transform=Standardize(m=train_y.shape[-1])) mll = ExactMarginalLogLikelihood(model.likelihood, model) # load state dict if it is passed if state_dict is not None: model.load_state_dict(state_dict) return mll, model
def __init__(self, Y_mean: Tensor, Y_std: Tensor) -> None: r"""Initialize objective. Args: Y_mean: `m`-dim tensor of outcome means Y_std: `m`-dim tensor of outcome standard deviations """ if Y_mean.ndim > 1 or Y_std.ndim > 1: raise BotorchTensorDimensionError( "Y_mean and Y_std must both be 1-dimensional, but got " f"{Y_mean.ndim} and {Y_std.ndim}") super().__init__() self.outcome_transform = Standardize(m=Y_mean.shape[0]).to(Y_mean) Y_std_unsqueezed = Y_std.unsqueeze(0) self.outcome_transform.means = Y_mean.unsqueeze(0) self.outcome_transform.stdvs = Y_std_unsqueezed self.outcome_transform._stdvs_sq = Y_std_unsqueezed.pow(2) self.outcome_transform.eval()
def test_with_transforms(self): dim = 2 bounds = torch.stack([torch.zeros(dim), torch.ones(dim) * 3]) intf = Normalize(d=dim, bounds=bounds) octf = Standardize(m=1) # update octf state with dummy data octf(torch.rand(5, 1) * 7) octf.eval() model = DummyDeterministicModel(octf, intf) # check that the posterior output agrees with the manually transformed one test_X = torch.rand(3, dim) expected_Y, _ = octf.untransform(model.forward(intf(test_X))) with warnings.catch_warnings(record=True) as ws: posterior = model.posterior(test_X) msg = "does not have a `train_inputs` attribute" self.assertTrue(any(msg in str(w.message) for w in ws)) self.assertTrue(torch.allclose(expected_Y, posterior.mean)) # check that model.train/eval works and raises the warning model.train() with self.assertWarns(RuntimeWarning): model.eval()
def initialize_model(train_x, train_obj, train_con, state_dict=None): if problem.num_constraints == 1: # define models for objective and constraint # model_obj = SingleTaskGP(train_x, train_obj, outcome_transform=Standardize(m=train_obj.shape[-1])) # model_con = SingleTaskGP(train_x, train_con, outcome_transform=Standardize(m=train_con.shape[-1])) model_obj = SingleTaskGP(train_x, train_obj) model_con = SingleTaskGP(train_x, train_con) # combine into a multi-output GP model model = ModelListGP(model_obj, model_con) mll = SumMarginalLogLikelihood(model.likelihood, model) else: train_y = torch.cat([train_obj, train_con], dim=-1) model = SingleTaskGP( train_x, train_y, outcome_transform=Standardize(m=train_y.shape[-1])) mll = ExactMarginalLogLikelihood(model.likelihood, model) # load state dict if it is passed if state_dict is not None: model.load_state_dict(state_dict) return mll, model
def test_standardize(self): # test error on incompatible dim tf = Standardize(m=1) with self.assertRaises(RuntimeError): tf(torch.zeros(3, 2, device=self.device), None) # test error on incompatible batch shape with self.assertRaises(RuntimeError): tf(torch.zeros(2, 3, 1, device=self.device), None) ms = (1, 2) batch_shapes = (torch.Size(), torch.Size([2])) dtypes = (torch.float, torch.double) # test transform, untransform, untransform_posterior for m, batch_shape, dtype in itertools.product(ms, batch_shapes, dtypes): # test init tf = Standardize(m=m, batch_shape=batch_shape) self.assertTrue(tf.training) self.assertEqual(tf._m, m) self.assertIsNone(tf._outputs) self.assertEqual(tf._batch_shape, batch_shape) self.assertEqual(tf._min_stdv, 1e-8) # no observation noise Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Y_tf, Yvar_tf = tf(Y, None) self.assertTrue(tf.training) self.assertTrue(torch.all(Y_tf.mean(dim=-2).abs() < 1e-4)) self.assertIsNone(Yvar_tf) tf.eval() self.assertFalse(tf.training) Y_utf, Yvar_utf = tf.untransform(Y_tf, Yvar_tf) torch.allclose(Y_utf, Y) self.assertIsNone(Yvar_utf) # subset_output tf_subset = tf.subset_output(idcs=[0]) Y_tf_subset, Yvar_tf_subset = tf_subset(Y[..., [0]]) self.assertTrue(torch.equal(Y_tf[..., [0]], Y_tf_subset)) self.assertIsNone(Yvar_tf_subset) with self.assertRaises(RuntimeError): tf.subset_output(idcs=[0, 1, 2]) # with observation noise tf = Standardize(m=m, batch_shape=batch_shape) Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Yvar = 1e-8 + torch.rand( *batch_shape, 3, m, device=self.device, dtype=dtype ) Y_tf, Yvar_tf = tf(Y, Yvar) self.assertTrue(tf.training) self.assertTrue(torch.all(Y_tf.mean(dim=-2).abs() < 1e-4)) Yvar_tf_expected = Yvar / Y.std(dim=-2, keepdim=True) ** 2 self.assertTrue(torch.allclose(Yvar_tf, Yvar_tf_expected)) tf.eval() self.assertFalse(tf.training) Y_utf, Yvar_utf = tf.untransform(Y_tf, Yvar_tf) torch.allclose(Y_utf, Y) torch.allclose(Yvar_utf, Yvar) # untransform_posterior for interleaved, lazy in itertools.product((True, False), (True, False)): if m == 1 and interleaved: # interleave has no meaning for m=1 continue shape = batch_shape + torch.Size([3, m]) posterior = _get_test_posterior( shape, device=self.device, dtype=dtype, interleaved=interleaved, lazy=lazy, ) p_utf = tf.untransform_posterior(posterior) self.assertEqual(p_utf.device.type, self.device.type) self.assertTrue(p_utf.dtype == dtype) mean_expected = tf.means + tf.stdvs * posterior.mean variance_expected = tf.stdvs ** 2 * posterior.variance self.assertTrue(torch.allclose(p_utf.mean, mean_expected)) self.assertTrue(torch.allclose(p_utf.variance, variance_expected)) samples = p_utf.rsample() self.assertEqual(samples.shape, torch.Size([1]) + shape) samples = p_utf.rsample(sample_shape=torch.Size([4])) self.assertEqual(samples.shape, torch.Size([4]) + shape) samples2 = p_utf.rsample(sample_shape=torch.Size([4, 2])) self.assertEqual(samples2.shape, torch.Size([4, 2]) + shape) # TODO: Test expected covar (both interleaved and non-interleaved) # untransform_posterior for non-GPyTorch posterior posterior2 = TransformedPosterior( posterior=posterior, sample_transform=lambda s: s, mean_transform=lambda m, v: m, variance_transform=lambda m, v: v, ) p_utf2 = tf.untransform_posterior(posterior2) self.assertEqual(p_utf2.device.type, self.device.type) self.assertTrue(p_utf2.dtype == dtype) mean_expected = tf.means + tf.stdvs * posterior.mean variance_expected = tf.stdvs ** 2 * posterior.variance self.assertTrue(torch.allclose(p_utf2.mean, mean_expected)) self.assertTrue(torch.allclose(p_utf2.variance, variance_expected)) # TODO: Test expected covar (both interleaved and non-interleaved) samples = p_utf2.rsample() self.assertEqual(samples.shape, torch.Size([1]) + shape) samples = p_utf2.rsample(sample_shape=torch.Size([4])) self.assertEqual(samples.shape, torch.Size([4]) + shape) samples2 = p_utf2.rsample(sample_shape=torch.Size([4, 2])) self.assertEqual(samples2.shape, torch.Size([4, 2]) + shape) # test error on incompatible output dimension tf_big = Standardize(m=4).eval() with self.assertRaises(RuntimeError): tf_big.untransform_posterior(posterior2) # test transforming a subset of outcomes for batch_shape, dtype in itertools.product(batch_shapes, dtypes): m = 2 outputs = [-1] # test init tf = Standardize(m=m, outputs=outputs, batch_shape=batch_shape) self.assertTrue(tf.training) self.assertEqual(tf._m, m) self.assertEqual(tf._outputs, [1]) self.assertEqual(tf._batch_shape, batch_shape) self.assertEqual(tf._min_stdv, 1e-8) # no observation noise Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Y_tf, Yvar_tf = tf(Y, None) self.assertTrue(tf.training) Y_tf_mean = Y_tf.mean(dim=-2) self.assertTrue(torch.all(Y_tf_mean[..., 1].abs() < 1e-4)) self.assertTrue(torch.allclose(Y_tf_mean[..., 0], Y.mean(dim=-2)[..., 0])) self.assertIsNone(Yvar_tf) tf.eval() self.assertFalse(tf.training) Y_utf, Yvar_utf = tf.untransform(Y_tf, Yvar_tf) torch.allclose(Y_utf, Y) self.assertIsNone(Yvar_utf) # subset_output tf_subset = tf.subset_output(idcs=[0]) Y_tf_subset, Yvar_tf_subset = tf_subset(Y[..., [0]]) self.assertTrue(torch.equal(Y_tf[..., [0]], Y_tf_subset)) self.assertIsNone(Yvar_tf_subset) with self.assertRaises(RuntimeError): tf.subset_output(idcs=[0, 1, 2]) # with observation noise tf = Standardize(m=m, outputs=outputs, batch_shape=batch_shape) Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Yvar = 1e-8 + torch.rand( *batch_shape, 3, m, device=self.device, dtype=dtype ) Y_tf, Yvar_tf = tf(Y, Yvar) self.assertTrue(tf.training) Y_tf_mean = Y_tf.mean(dim=-2) self.assertTrue(torch.all(Y_tf_mean[..., 1].abs() < 1e-4)) self.assertTrue(torch.allclose(Y_tf_mean[..., 0], Y.mean(dim=-2)[..., 0])) Yvar_tf_expected = Yvar / Y.std(dim=-2, keepdim=True) ** 2 self.assertTrue(torch.allclose(Yvar_tf[..., 1], Yvar_tf_expected[..., 1])) self.assertTrue(torch.allclose(Yvar_tf[..., 0], Yvar[..., 0])) tf.eval() self.assertFalse(tf.training) Y_utf, Yvar_utf = tf.untransform(Y_tf, Yvar_tf) torch.allclose(Y_utf, Y) torch.allclose(Yvar_utf, Yvar) # error on untransform_posterior with self.assertRaises(NotImplementedError): tf.untransform_posterior(None)
def qei_candidates_func( train_x: "torch.Tensor", train_obj: "torch.Tensor", train_con: Optional["torch.Tensor"], bounds: "torch.Tensor", ) -> "torch.Tensor": """Quasi MC-based batch Expected Improvement (qEI). The default value of ``candidates_func`` in :class:`~optuna.integration.BoTorchSampler` with single-objective optimization. Args: train_x: Previous parameter configurations. A ``torch.Tensor`` of shape ``(n_trials, n_params)``. ``n_trials`` is the number of already observed trials and ``n_params`` is the number of parameters. ``n_params`` may be larger than the actual number of parameters if categorical parameters are included in the search space, since these parameters are one-hot encoded. Values are not normalized. train_obj: Previously observed objectives. A ``torch.Tensor`` of shape ``(n_trials, n_objectives)``. ``n_trials`` is identical to that of ``train_x``. ``n_objectives`` is the number of objectives. Observations are not normalized. train_con: Objective constraints. A ``torch.Tensor`` of shape ``(n_trials, n_constraints)``. ``n_trials`` is identical to that of ``train_x``. ``n_constraints`` is the number of constraints. A constraint is violated if strictly larger than 0. If no constraints are involved in the optimization, this argument will be :obj:`None`. bounds: Search space bounds. A ``torch.Tensor`` of shape ``(n_params, 2)``. ``n_params`` is identical to that of ``train_x``. The first and the second column correspond to the lower and upper bounds for each parameter respectively. Returns: Next set of candidates. Usually the return value of BoTorch's ``optimize_acqf``. """ if train_obj.size(-1) != 1: raise ValueError("Objective may only contain single values with qEI.") if train_con is not None: train_y = torch.cat([train_obj, train_con], dim=-1) is_feas = (train_con <= 0).all(dim=-1) train_obj_feas = train_obj[is_feas] if train_obj_feas.numel() == 0: # TODO(hvy): Do not use 0 as the best observation. _logger.warning( "No objective values are feasible. Using 0 as the best objective in qEI." ) best_f = torch.zeros(()) else: best_f = train_obj_feas.max() constraints = [] n_constraints = train_con.size(1) for i in range(n_constraints): constraints.append(lambda Z, i=i: Z[..., -n_constraints + i]) objective = ConstrainedMCObjective( objective=lambda Z: Z[..., 0], constraints=constraints, ) else: train_y = train_obj best_f = train_obj.max() objective = None # Using the default identity objective. train_x = normalize(train_x, bounds=bounds) model = SingleTaskGP(train_x, train_y, outcome_transform=Standardize(m=train_y.size(-1))) mll = ExactMarginalLogLikelihood(model.likelihood, model) fit_gpytorch_model(mll) acqf = qExpectedImprovement( model=model, best_f=best_f, sampler=SobolQMCNormalSampler(num_samples=256), objective=objective, ) standard_bounds = torch.zeros_like(bounds) standard_bounds[1] = 1 candidates, _ = optimize_acqf( acq_function=acqf, bounds=standard_bounds, q=1, num_restarts=10, raw_samples=512, options={ "batch_limit": 5, "maxiter": 200 }, sequential=True, ) candidates = unnormalize(candidates.detach(), bounds=bounds) return candidates
def qparego_candidates_func( train_x: "torch.Tensor", train_obj: "torch.Tensor", train_con: Optional["torch.Tensor"], bounds: "torch.Tensor", ) -> "torch.Tensor": """Quasi MC-based extended ParEGO (qParEGO) for constrained multi-objective optimization. The default value of ``candidates_func`` in :class:`~optuna.integration.BoTorchSampler` with multi-objective optimization when the number of objectives is larger than three. .. seealso:: :func:`~optuna.integration.botorch.qei_candidates_func` for argument and return value descriptions. """ n_objectives = train_obj.size(-1) weights = sample_simplex(n_objectives).squeeze() scalarization = get_chebyshev_scalarization(weights=weights, Y=train_obj) if train_con is not None: train_y = torch.cat([train_obj, train_con], dim=-1) constraints = [] n_constraints = train_con.size(1) for i in range(n_constraints): constraints.append(lambda Z, i=i: Z[..., -n_constraints + i]) objective = ConstrainedMCObjective( objective=lambda Z: scalarization(Z[..., :n_objectives]), constraints=constraints, ) else: train_y = train_obj objective = GenericMCObjective(scalarization) train_x = normalize(train_x, bounds=bounds) model = SingleTaskGP(train_x, train_y, outcome_transform=Standardize(m=train_y.size(-1))) mll = ExactMarginalLogLikelihood(model.likelihood, model) fit_gpytorch_model(mll) acqf = qExpectedImprovement( model=model, best_f=objective(train_y).max(), sampler=SobolQMCNormalSampler(num_samples=256), objective=objective, ) standard_bounds = torch.zeros_like(bounds) standard_bounds[1] = 1 candidates, _ = optimize_acqf( acq_function=acqf, bounds=standard_bounds, q=1, num_restarts=20, raw_samples=1024, options={ "batch_limit": 5, "maxiter": 200 }, sequential=True, ) candidates = unnormalize(candidates.detach(), bounds=bounds) return candidates
def qehvi_candidates_func( train_x: "torch.Tensor", train_obj: "torch.Tensor", train_con: Optional["torch.Tensor"], bounds: "torch.Tensor", ) -> "torch.Tensor": """Quasi MC-based batch Expected Hypervolume Improvement (qEHVI). The default value of ``candidates_func`` in :class:`~optuna.integration.BoTorchSampler` with multi-objective optimization when the number of objectives is three or less. .. seealso:: :func:`~optuna.integration.botorch.qei_candidates_func` for argument and return value descriptions. """ n_objectives = train_obj.size(-1) if train_con is not None: train_y = torch.cat([train_obj, train_con], dim=-1) is_feas = (train_con <= 0).all(dim=-1) train_obj_feas = train_obj[is_feas] constraints = [] n_constraints = train_con.size(1) for i in range(n_constraints): constraints.append(lambda Z, i=i: Z[..., -n_constraints + i]) additional_qehvi_kwargs = { "objective": IdentityMCMultiOutputObjective(outcomes=list(range(n_objectives))), "constraints": constraints, } else: train_y = train_obj train_obj_feas = train_obj additional_qehvi_kwargs = {} train_x = normalize(train_x, bounds=bounds) model = SingleTaskGP(train_x, train_y, outcome_transform=Standardize(m=train_y.shape[-1])) mll = ExactMarginalLogLikelihood(model.likelihood, model) fit_gpytorch_model(mll) # Approximate box decomposition similar to Ax when the number of objectives is large. # https://github.com/facebook/Ax/blob/master/ax/models/torch/botorch_moo_defaults if n_objectives > 2: alpha = 10**(-8 + n_objectives) else: alpha = 0.0 partitioning = NondominatedPartitioning(num_outcomes=n_objectives, Y=train_obj_feas, alpha=alpha) ref_point = train_obj.min(dim=0).values - 1e-8 ref_point_list = ref_point.tolist() acqf = qExpectedHypervolumeImprovement( model=model, ref_point=ref_point_list, partitioning=partitioning, sampler=SobolQMCNormalSampler(num_samples=256), **additional_qehvi_kwargs, ) standard_bounds = torch.zeros_like(bounds) standard_bounds[1] = 1 candidates, _ = optimize_acqf( acq_function=acqf, bounds=standard_bounds, q=1, num_restarts=20, raw_samples=1024, options={ "batch_limit": 5, "maxiter": 200, "nonnegative": True }, sequential=True, ) candidates = unnormalize(candidates.detach(), bounds=bounds) return candidates
def test_model_list_to_batched(self): for dtype in (torch.float, torch.double): # basic test train_X = torch.rand(10, 2, device=self.device, dtype=dtype) train_Y1 = train_X.sum(dim=-1, keepdim=True) train_Y2 = (train_X[:, 0] - train_X[:, 1]).unsqueeze(-1) gp1 = SingleTaskGP(train_X, train_Y1) gp2 = SingleTaskGP(train_X, train_Y2) list_gp = ModelListGP(gp1, gp2) batch_gp = model_list_to_batched(list_gp) self.assertIsInstance(batch_gp, SingleTaskGP) # test degenerate (single model) batch_gp = model_list_to_batched(ModelListGP(gp1)) self.assertEqual(batch_gp._num_outputs, 1) # test different model classes gp2 = FixedNoiseGP(train_X, train_Y1, torch.ones_like(train_Y1)) with self.assertRaises(UnsupportedError): model_list_to_batched(ModelListGP(gp1, gp2)) # test non-batched models gp1_ = SimpleGPyTorchModel(train_X, train_Y1) gp2_ = SimpleGPyTorchModel(train_X, train_Y2) with self.assertRaises(UnsupportedError): model_list_to_batched(ModelListGP(gp1_, gp2_)) # test list of multi-output models train_Y = torch.cat([train_Y1, train_Y2], dim=-1) gp2 = SingleTaskGP(train_X, train_Y) with self.assertRaises(UnsupportedError): model_list_to_batched(ModelListGP(gp1, gp2)) # test different training inputs gp2 = SingleTaskGP(2 * train_X, train_Y2) with self.assertRaises(UnsupportedError): model_list_to_batched(ModelListGP(gp1, gp2)) # check scalar agreement gp2 = SingleTaskGP(train_X, train_Y2) gp2.likelihood.noise_covar.noise_prior.rate.fill_(1.0) with self.assertRaises(UnsupportedError): model_list_to_batched(ModelListGP(gp1, gp2)) # check tensor shape agreement gp2 = SingleTaskGP(train_X, train_Y2) gp2.covar_module.raw_outputscale = torch.nn.Parameter( torch.tensor([0.0], device=self.device, dtype=dtype)) with self.assertRaises(UnsupportedError): model_list_to_batched(ModelListGP(gp1, gp2)) # test HeteroskedasticSingleTaskGP gp2 = HeteroskedasticSingleTaskGP(train_X, train_Y1, torch.ones_like(train_Y1)) with self.assertRaises(NotImplementedError): model_list_to_batched(ModelListGP(gp2)) # test custom likelihood gp2 = SingleTaskGP(train_X, train_Y2, likelihood=GaussianLikelihood()) with self.assertRaises(NotImplementedError): model_list_to_batched(ModelListGP(gp2)) # test FixedNoiseGP train_X = torch.rand(10, 2, device=self.device, dtype=dtype) train_Y1 = train_X.sum(dim=-1, keepdim=True) train_Y2 = (train_X[:, 0] - train_X[:, 1]).unsqueeze(-1) gp1_ = FixedNoiseGP(train_X, train_Y1, torch.rand_like(train_Y1)) gp2_ = FixedNoiseGP(train_X, train_Y2, torch.rand_like(train_Y2)) list_gp = ModelListGP(gp1_, gp2_) batch_gp = model_list_to_batched(list_gp) # test SingleTaskMultiFidelityGP gp1_ = SingleTaskMultiFidelityGP(train_X, train_Y1, iteration_fidelity=1) gp2_ = SingleTaskMultiFidelityGP(train_X, train_Y2, iteration_fidelity=1) list_gp = ModelListGP(gp1_, gp2_) batch_gp = model_list_to_batched(list_gp) gp2_ = SingleTaskMultiFidelityGP(train_X, train_Y2, iteration_fidelity=2) list_gp = ModelListGP(gp1_, gp2_) with self.assertRaises(UnsupportedError): model_list_to_batched(list_gp) # test input transform input_tf = Normalize( d=2, bounds=torch.tensor([[0.0, 0.0], [1.0, 1.0]], device=self.device, dtype=dtype), ) gp1_ = SingleTaskGP(train_X, train_Y1, input_transform=input_tf) gp2_ = SingleTaskGP(train_X, train_Y2, input_transform=input_tf) list_gp = ModelListGP(gp1_, gp2_) batch_gp = model_list_to_batched(list_gp) self.assertIsInstance(batch_gp.input_transform, Normalize) self.assertTrue( torch.equal(batch_gp.input_transform.bounds, input_tf.bounds)) # test different input transforms input_tf2 = Normalize( d=2, bounds=torch.tensor([[-1.0, -1.0], [1.0, 1.0]], device=self.device, dtype=dtype), ) gp1_ = SingleTaskGP(train_X, train_Y1, input_transform=input_tf) gp2_ = SingleTaskGP(train_X, train_Y2, input_transform=input_tf2) list_gp = ModelListGP(gp1_, gp2_) with self.assertRaises(UnsupportedError): model_list_to_batched(list_gp) # test batched input transform input_tf2 = Normalize( d=2, bounds=torch.tensor([[-1.0, -1.0], [1.0, 1.0]], device=self.device, dtype=dtype), batch_shape=torch.Size([3]), ) gp1_ = SingleTaskGP(train_X, train_Y1, input_transform=input_tf2) gp2_ = SingleTaskGP(train_X, train_Y2, input_transform=input_tf2) list_gp = ModelListGP(gp1_, gp2_) with self.assertRaises(UnsupportedError): model_list_to_batched(list_gp) # test outcome transform octf = Standardize(m=1) gp1_ = SingleTaskGP(train_X, train_Y1, outcome_transform=octf) gp2_ = SingleTaskGP(train_X, train_Y2, outcome_transform=octf) list_gp = ModelListGP(gp1_, gp2_) with self.assertRaises(UnsupportedError): model_list_to_batched(list_gp)
def test_batched_multi_output_to_single_output(self): for dtype in (torch.float, torch.double): # basic test train_X = torch.rand(10, 2, device=self.device, dtype=dtype) train_Y = torch.stack( [ train_X.sum(dim=-1), (train_X[:, 0] - train_X[:, 1]), ], dim=1, ) batched_mo_model = SingleTaskGP(train_X, train_Y) batched_so_model = batched_multi_output_to_single_output( batched_mo_model) self.assertIsInstance(batched_so_model, SingleTaskGP) self.assertEqual(batched_so_model.num_outputs, 1) # test non-batched models non_batch_model = SimpleGPyTorchModel(train_X, train_Y[:, :1]) with self.assertRaises(UnsupportedError): batched_multi_output_to_single_output(non_batch_model) gp2 = HeteroskedasticSingleTaskGP(train_X, train_Y, torch.ones_like(train_Y)) with self.assertRaises(NotImplementedError): batched_multi_output_to_single_output(gp2) # test custom likelihood gp2 = SingleTaskGP(train_X, train_Y, likelihood=GaussianLikelihood()) with self.assertRaises(NotImplementedError): batched_multi_output_to_single_output(gp2) # test FixedNoiseGP train_X = torch.rand(10, 2, device=self.device, dtype=dtype) batched_mo_model = FixedNoiseGP(train_X, train_Y, torch.rand_like(train_Y)) batched_so_model = batched_multi_output_to_single_output( batched_mo_model) self.assertIsInstance(batched_so_model, FixedNoiseGP) self.assertEqual(batched_so_model.num_outputs, 1) # test SingleTaskMultiFidelityGP batched_mo_model = SingleTaskMultiFidelityGP(train_X, train_Y, iteration_fidelity=1) batched_so_model = batched_multi_output_to_single_output( batched_mo_model) self.assertIsInstance(batched_so_model, SingleTaskMultiFidelityGP) self.assertEqual(batched_so_model.num_outputs, 1) # test input transform input_tf = Normalize( d=2, bounds=torch.tensor([[0.0, 0.0], [1.0, 1.0]], device=self.device, dtype=dtype), ) batched_mo_model = SingleTaskGP(train_X, train_Y, input_transform=input_tf) batch_so_model = batched_multi_output_to_single_output( batched_mo_model) self.assertIsInstance(batch_so_model.input_transform, Normalize) self.assertTrue( torch.equal(batch_so_model.input_transform.bounds, input_tf.bounds)) # test batched input transform input_tf2 = Normalize( d=2, bounds=torch.tensor([[-1.0, -1.0], [1.0, 1.0]], device=self.device, dtype=dtype), batch_shape=torch.Size([2]), ) batched_mo_model = SingleTaskGP(train_X, train_Y, input_transform=input_tf2) batched_so_model = batched_multi_output_to_single_output( batched_mo_model) self.assertIsInstance(batch_so_model.input_transform, Normalize) self.assertTrue( torch.equal(batch_so_model.input_transform.bounds, input_tf.bounds)) # test outcome transform batched_mo_model = SingleTaskGP(train_X, train_Y, outcome_transform=Standardize(m=2)) with self.assertRaises(NotImplementedError): batched_multi_output_to_single_output(batched_mo_model)
def test_chained_outcome_transform(self): ms = (1, 2) batch_shapes = (torch.Size(), torch.Size([2])) dtypes = (torch.float, torch.double) # test transform and untransform for m, batch_shape, dtype in itertools.product(ms, batch_shapes, dtypes): # test init tf1 = Log() tf2 = Standardize(m=m, batch_shape=batch_shape) tf = ChainedOutcomeTransform(b=tf1, a=tf2) self.assertTrue(tf.training) self.assertEqual(list(tf.keys()), ["b", "a"]) self.assertEqual(tf["b"], tf1) self.assertEqual(tf["a"], tf2) # make copies for validation below tf1_, tf2_ = deepcopy(tf1), deepcopy(tf2) # no observation noise Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Y_tf, Yvar_tf = tf(Y, None) Y_tf_, Yvar_tf_ = tf2_(*tf1_(Y, None)) self.assertTrue(tf.training) self.assertIsNone(Yvar_tf_) self.assertTrue(torch.allclose(Y_tf, Y_tf_)) tf.eval() self.assertFalse(tf.training) Y_utf, Yvar_utf = tf.untransform(Y_tf, Yvar_tf) torch.allclose(Y_utf, Y) self.assertIsNone(Yvar_utf) # subset_output tf_subset = tf.subset_output(idcs=[0]) Y_tf_subset, Yvar_tf_subset = tf_subset(Y[..., [0]]) self.assertTrue(torch.equal(Y_tf[..., [0]], Y_tf_subset)) self.assertIsNone(Yvar_tf_subset) with self.assertRaises(RuntimeError): tf.subset_output(idcs=[0, 1, 2]) # test error if observation noise present Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Yvar = 1e-8 + torch.rand( *batch_shape, 3, m, device=self.device, dtype=dtype ) with self.assertRaises(NotImplementedError): tf(Y, Yvar) # untransform_posterior tf1 = Log() tf2 = Standardize(m=m, batch_shape=batch_shape) tf = ChainedOutcomeTransform(log=tf1, standardize=tf2) Y_tf, Yvar_tf = tf(Y, None) tf.eval() shape = batch_shape + torch.Size([3, m]) posterior = _get_test_posterior(shape, device=self.device, dtype=dtype) p_utf = tf.untransform_posterior(posterior) self.assertIsInstance(p_utf, TransformedPosterior) self.assertEqual(p_utf.device.type, self.device.type) self.assertTrue(p_utf.dtype == dtype) samples = p_utf.rsample() self.assertEqual(samples.shape, torch.Size([1]) + shape) samples = p_utf.rsample(sample_shape=torch.Size([4])) self.assertEqual(samples.shape, torch.Size([4]) + shape) samples2 = p_utf.rsample(sample_shape=torch.Size([4, 2])) self.assertEqual(samples2.shape, torch.Size([4, 2]) + shape) # test transforming a subset of outcomes for batch_shape, dtype in itertools.product(batch_shapes, dtypes): m = 2 outputs = [-1] # test init tf1 = Log(outputs=outputs) tf2 = Standardize(m=m, outputs=outputs, batch_shape=batch_shape) tf = ChainedOutcomeTransform(log=tf1, standardize=tf2) self.assertTrue(tf.training) self.assertEqual(sorted(tf.keys()), ["log", "standardize"]) self.assertEqual(tf["log"], tf1) self.assertEqual(tf["standardize"], tf2) self.assertEqual(tf["log"]._outputs, [-1]) # don't know dimension yet self.assertEqual(tf["standardize"]._outputs, [1]) # make copies for validation below tf1_, tf2_ = deepcopy(tf1), deepcopy(tf2) # no observation noise Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Y_tf, Yvar_tf = tf(Y, None) Y_tf_, Yvar_tf_ = tf2_(*tf1_(Y, None)) self.assertTrue(tf.training) self.assertIsNone(Yvar_tf_) self.assertTrue(torch.allclose(Y_tf, Y_tf_)) tf.eval() self.assertFalse(tf.training) Y_utf, Yvar_utf = tf.untransform(Y_tf, Yvar_tf) torch.allclose(Y_utf, Y) self.assertIsNone(Yvar_utf) # with observation noise Y = torch.rand(*batch_shape, 3, m, device=self.device, dtype=dtype) Yvar = 1e-8 + torch.rand( *batch_shape, 3, m, device=self.device, dtype=dtype ) with self.assertRaises(NotImplementedError): tf(Y, Yvar) # error on untransform_posterior with self.assertRaises(NotImplementedError): tf.untransform_posterior(None)
def test_KroneckerMultiTaskGP_default(self): bounds = torch.tensor([[-1.0, 0.0], [1.0, 1.0]]) for batch_shape, dtype, use_intf, use_octf in itertools.product( (torch.Size(), ), # torch.Size([3])), TODO: Fix and test batch mode (torch.float, torch.double), (False, True), (False, True), ): tkwargs = {"device": self.device, "dtype": dtype} octf = Standardize(m=2) if use_octf else None intf = (Normalize( d=2, bounds=bounds.to( **tkwargs), transform_on_train=True) if use_intf else None) # initialization with default settings model, train_X, _ = _get_kronecker_model_and_training_data( model_kwargs={ "outcome_transform": octf, "input_transform": intf }, batch_shape=batch_shape, **tkwargs, ) self.assertIsInstance(model, KroneckerMultiTaskGP) self.assertEqual(model.num_outputs, 2) self.assertIsInstance(model.likelihood, MultitaskGaussianLikelihood) self.assertEqual(model.likelihood.rank, 0) self.assertIsInstance(model.mean_module, MultitaskMean) self.assertIsInstance(model.covar_module, MultitaskKernel) base_kernel = model.covar_module self.assertIsInstance(base_kernel.data_covar_module, MaternKernel) self.assertIsInstance(base_kernel.task_covar_module, IndexKernel) task_covar_prior = base_kernel.task_covar_module.IndexKernelPrior self.assertIsInstance(task_covar_prior, LKJCovariancePrior) self.assertEqual(task_covar_prior.correlation_prior.eta, 1.5) self.assertIsInstance(task_covar_prior.sd_prior, SmoothedBoxPrior) lengthscale_prior = base_kernel.data_covar_module.lengthscale_prior self.assertIsInstance(lengthscale_prior, GammaPrior) self.assertEqual(lengthscale_prior.concentration, 3.0) self.assertEqual(lengthscale_prior.rate, 6.0) self.assertEqual( base_kernel.task_covar_module.covar_factor.shape[-1], 2) # test model fitting mll = ExactMarginalLogLikelihood(model.likelihood, model) with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=OptimizationWarning) mll = fit_gpytorch_model(mll, options={"maxiter": 1}, max_retries=1) # test posterior test_x = torch.rand(2, 2, **tkwargs) posterior_f = model.posterior(test_x) if not use_octf: self.assertIsInstance(posterior_f, GPyTorchPosterior) self.assertIsInstance(posterior_f.mvn, MultitaskMultivariateNormal) else: self.assertIsInstance(posterior_f, TransformedPosterior) self.assertIsInstance(posterior_f._posterior.mvn, MultitaskMultivariateNormal) self.assertEqual(posterior_f.mean.shape, torch.Size([2, 2])) self.assertEqual(posterior_f.variance.shape, torch.Size([2, 2])) if use_octf: # ensure un-transformation is applied tmp_tf = model.outcome_transform del model.outcome_transform p_tf = model.posterior(test_x) model.outcome_transform = tmp_tf expected_var = tmp_tf.untransform_posterior(p_tf).variance self.assertTrue( torch.allclose(posterior_f.variance, expected_var)) else: # test observation noise # TODO: outcome transform + likelihood noise? posterior_noisy = model.posterior(test_x, observation_noise=True) self.assertTrue( torch.allclose( posterior_noisy.variance, model.likelihood(posterior_f.mvn).variance, )) # test posterior (batch eval) test_x = torch.rand(3, 2, 2, **tkwargs) posterior_f = model.posterior(test_x) if not use_octf: self.assertIsInstance(posterior_f, GPyTorchPosterior) self.assertIsInstance(posterior_f.mvn, MultitaskMultivariateNormal) else: self.assertIsInstance(posterior_f, TransformedPosterior) self.assertIsInstance(posterior_f._posterior.mvn, MultitaskMultivariateNormal) self.assertEqual(posterior_f.mean.shape, torch.Size([3, 2, 2])) self.assertEqual(posterior_f.variance.shape, torch.Size([3, 2, 2]))
def test_MultiTaskGP(self): bounds = torch.tensor([[-1.0, 0.0], [1.0, 1.0]]) for dtype, use_intf, use_octf in itertools.product( (torch.float, torch.double), (False, True), (False, True)): tkwargs = {"device": self.device, "dtype": dtype} octf = Standardize(m=1) if use_octf else None intf = (Normalize( d=2, bounds=bounds.to( **tkwargs), transform_on_train=True) if use_intf else None) model, train_X, _ = _get_model_and_training_data( input_transform=intf, outcome_transform=octf, **tkwargs) self.assertIsInstance(model, MultiTaskGP) self.assertEqual(model.num_outputs, 2) self.assertIsInstance(model.likelihood, GaussianLikelihood) self.assertIsInstance(model.mean_module, ConstantMean) self.assertIsInstance(model.covar_module, ScaleKernel) matern_kernel = model.covar_module.base_kernel self.assertIsInstance(matern_kernel, MaternKernel) self.assertIsInstance(matern_kernel.lengthscale_prior, GammaPrior) self.assertIsInstance(model.task_covar_module, IndexKernel) self.assertEqual(model._rank, 2) self.assertEqual(model.task_covar_module.covar_factor.shape[-1], model._rank) if use_intf: self.assertIsInstance(model.input_transform, Normalize) # test model fitting mll = ExactMarginalLogLikelihood(model.likelihood, model) with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=OptimizationWarning) mll = fit_gpytorch_model(mll, options={"maxiter": 1}, max_retries=1) # test posterior test_x = torch.rand(2, 1, **tkwargs) posterior_f = model.posterior(test_x) self.assertIsInstance(posterior_f, GPyTorchPosterior) self.assertIsInstance(posterior_f.mvn, MultitaskMultivariateNormal) self.assertEqual(posterior_f.mean.shape, torch.Size([2, 2])) self.assertEqual(posterior_f.variance.shape, torch.Size([2, 2])) # check that training data has input transform applied # check that the train inputs have been transformed and set on the model if use_intf: self.assertTrue( torch.equal(model.train_inputs[0], model.input_transform(train_X))) # test that posterior w/ observation noise raises appropriate error with self.assertRaises(NotImplementedError): model.posterior(test_x, observation_noise=True) with self.assertRaises(NotImplementedError): model.posterior(test_x, observation_noise=torch.rand(2, **tkwargs)) # test posterior w/ single output index posterior_f = model.posterior(test_x, output_indices=[0]) self.assertIsInstance(posterior_f, GPyTorchPosterior) self.assertIsInstance(posterior_f.mvn, MultivariateNormal) self.assertEqual(posterior_f.mean.shape, torch.Size([2, 1])) self.assertEqual(posterior_f.variance.shape, torch.Size([2, 1])) # test posterior w/ bad output index with self.assertRaises(ValueError): model.posterior(test_x, output_indices=[2]) # test posterior (batch eval) test_x = torch.rand(3, 2, 1, **tkwargs) posterior_f = model.posterior(test_x) self.assertIsInstance(posterior_f, GPyTorchPosterior) self.assertIsInstance(posterior_f.mvn, MultitaskMultivariateNormal) # test that unsupported batch shape MTGPs throw correct error with self.assertRaises(ValueError): MultiTaskGP(torch.rand(2, 2, 2), torch.rand(2, 2, 1), 0) # test that bad feature index throws correct error train_X, train_Y = _get_random_mt_data(**tkwargs) with self.assertRaises(ValueError): MultiTaskGP(train_X, train_Y, 2) # test that bad output task throws correct error with self.assertRaises(RuntimeError): MultiTaskGP(train_X, train_Y, 0, output_tasks=[2]) # test outcome transform if use_octf: # ensure un-transformation is applied tmp_tf = model.outcome_transform del model.outcome_transform p_utf = model.posterior(test_x) model.outcome_transform = tmp_tf expected_var = tmp_tf.untransform_posterior(p_utf).variance self.assertTrue( torch.allclose(posterior_f.variance, expected_var))