def test_fixed_features(self, cuda=False): device = torch.device("cuda" if cuda else "cpu") train_X = torch.rand(5, 3, device=device) train_Y = train_X.norm(dim=-1) model = SingleTaskGP(train_X, train_Y).to(device=device).eval() qEI = qExpectedImprovement(model, best_f=0.0) # test single point test_X = torch.rand(1, 3, device=device) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=test_X[..., -1:]) qei = qEI(test_X) qei_ff = qEI_ff(test_X[..., :-1]) self.assertTrue(torch.allclose(qei, qei_ff)) # test list input qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=[0.5]) qei_ff = qEI_ff(test_X[..., :-1]) # test q-batch test_X = torch.rand(2, 3, device=device) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[1], values=test_X[..., [1]]) qei = qEI(test_X) qei_ff = qEI_ff(test_X[..., [0, 2]]) self.assertTrue(torch.allclose(qei, qei_ff)) # test t-batch with broadcasting test_X = torch.rand(2, 3, device=device).expand(4, 2, 3) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=test_X[0, :, -1:]) qei = qEI(test_X) qei_ff = qEI_ff(test_X[..., :-1]) self.assertTrue(torch.allclose(qei, qei_ff)) # test gradient test_X = torch.rand(1, 3, device=device, requires_grad=True) test_X_ff = test_X[..., :-1].detach().clone().requires_grad_(True) qei = qEI(test_X) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=test_X[..., [2]].detach()) qei_ff = qEI_ff(test_X_ff) self.assertTrue(torch.allclose(qei, qei_ff)) qei.backward() qei_ff.backward() self.assertTrue(torch.allclose(test_X.grad[..., :-1], test_X_ff.grad)) # test error b/c of incompatible input shapes with self.assertRaises(ValueError): qEI_ff(test_X)
def _get_best_point_acqf( self, X_observed: Tensor, objective_weights: Tensor, mc_samples: int = 512, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, seed_inner: Optional[int] = None, qmc: bool = True, **kwargs: Any, ) -> Tuple[AcquisitionFunction, Optional[List[int]]]: model = self.model # subset model only to the outcomes we need for the optimization if kwargs.get("subset_model", True): model, objective_weights, outcome_constraints = subset_model( model=model, # pyre-ignore: [6] objective_weights=objective_weights, outcome_constraints=outcome_constraints, ) fixed_features = fixed_features or {} target_fidelities = target_fidelities or {} objective = ScalarizedObjective(weights=objective_weights) acq_function = PosteriorMean( model=model, objective=objective # pyre-ignore: [6] ) if self.fidelity_features: # we need to optimize at the target fidelities if any(f in self.fidelity_features for f in fixed_features): raise RuntimeError("Fixed features cannot also be fidelity features") elif not set(self.fidelity_features) == set(target_fidelities): raise RuntimeError( "Must provide a target fidelity for every fidelity feature" ) # make sure to not modify fixed_features in-place fixed_features = {**fixed_features, **target_fidelities} elif target_fidelities: raise RuntimeError( "Must specify fidelity_features in fit() when using target fidelities" ) if fixed_features: acq_function = FixedFeatureAcquisitionFunction( acq_function=acq_function, d=X_observed.size(-1), columns=list(fixed_features.keys()), values=list(fixed_features.values()), ) non_fixed_idcs = [ i for i in range(self.Xs[0].size(-1)) if i not in fixed_features ] else: non_fixed_idcs = None return acq_function, non_fixed_idcs
def _get_best_point_acqf( self, X_observed: Tensor, objective_weights: Tensor, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, ) -> Tuple[AcquisitionFunction, Optional[List[int]]]: fixed_features = fixed_features or {} target_fidelities = target_fidelities or {} objective = ScalarizedObjective(weights=objective_weights) acq_function = PosteriorMean( model=self.model, objective=objective # pyre-ignore: [6] ) if self.fidelity_features: # we need to optimize at the target fidelities if any(f in self.fidelity_features for f in fixed_features): raise RuntimeError("Fixed features cannot also be fidelity features") elif not set(self.fidelity_features) == set(target_fidelities): raise RuntimeError( "Must provide a target fidelity for every fidelity feature" ) # make sure to not modify fixed_features in-place fixed_features = {**fixed_features, **target_fidelities} elif target_fidelities: raise RuntimeError( "Must specify fidelity_features in fit() when using target fidelities" ) if fixed_features: acq_function = FixedFeatureAcquisitionFunction( acq_function=acq_function, d=X_observed.size(-1), columns=list(fixed_features.keys()), values=list(fixed_features.values()), ) non_fixed_idcs = [ i for i in range(self.Xs[0].size(-1)) if i not in fixed_features ] else: non_fixed_idcs = None return acq_function, non_fixed_idcs
def get_out_of_sample_best_point_acqf( model: Model, Xs: List[Tensor], X_observed: Tensor, objective_weights: Tensor, mc_samples: int = 512, fixed_features: Optional[Dict[int, float]] = None, fidelity_features: Optional[List[int]] = None, target_fidelities: Optional[Dict[int, float]] = None, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, seed_inner: Optional[int] = None, qmc: bool = True, **kwargs: Any, ) -> Tuple[AcquisitionFunction, Optional[List[int]]]: """Picks an appropriate acquisition function to find the best out-of-sample (predicted by the given surrogate model) point and instantiates it. NOTE: Typically the appropriate function is the posterior mean, but can differ to account for fidelities etc. """ model = model # subset model only to the outcomes we need for the optimization if kwargs.get(Keys.SUBSET_MODEL, True): subset_model_results = subset_model( model=model, objective_weights=objective_weights, outcome_constraints=outcome_constraints, ) model = subset_model_results.model objective_weights = subset_model_results.objective_weights outcome_constraints = subset_model_results.outcome_constraints fixed_features = fixed_features or {} target_fidelities = target_fidelities or {} if fidelity_features: # we need to optimize at the target fidelities if any(f in fidelity_features for f in fixed_features): raise RuntimeError( "Fixed features cannot also be fidelity features.") elif set(fidelity_features) != set(target_fidelities): raise RuntimeError( "Must provide a target fidelity for every fidelity feature.") # make sure to not modify fixed_features in-place fixed_features = {**fixed_features, **target_fidelities} elif target_fidelities: raise RuntimeError( "Must specify fidelity_features in fit() when using target fidelities." ) acqf_class, acqf_options = pick_best_out_of_sample_point_acqf_class( outcome_constraints=outcome_constraints, mc_samples=mc_samples, qmc=qmc, seed_inner=seed_inner, ) objective, posterior_transform = get_botorch_objective_and_transform( model=model, objective_weights=objective_weights, outcome_constraints=outcome_constraints, X_observed=X_observed, ) if objective is not None: if not isinstance(objective, MCAcquisitionObjective): raise UnsupportedError( f"Unknown objective type: {objective.__class__}" # pragma: nocover ) acqf_options = {"objective": objective, **acqf_options} if posterior_transform is not None: acqf_options = { "posterior_transform": posterior_transform, **acqf_options } acqf = acqf_class(model=model, **acqf_options) # pyre-ignore [45] if fixed_features: acqf = FixedFeatureAcquisitionFunction( acq_function=acqf, d=X_observed.size(-1), columns=list(fixed_features.keys()), values=list(fixed_features.values()), ) non_fixed_idcs = [ i for i in range(Xs[0].size(-1)) if i not in fixed_features ] else: non_fixed_idcs = None return acqf, non_fixed_idcs
def _get_best_point_acqf( self, X_observed: Tensor, objective_weights: Tensor, mc_samples: int = 512, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, seed_inner: Optional[int] = None, qmc: bool = True, ) -> Tuple[AcquisitionFunction, Optional[List[int]]]: model = self.model fixed_features = fixed_features or {} target_fidelities = target_fidelities or {} objective = _get_objective( model=model, # pyre-ignore [6] objective_weights=objective_weights, outcome_constraints=outcome_constraints, X_observed=X_observed, ) if isinstance(objective, ScalarizedObjective): acq_function = PosteriorMean( model=model, objective=objective # pyre-ignore: [6] ) elif isinstance(objective, MCAcquisitionObjective): if qmc: sampler = SobolQMCNormalSampler(num_samples=mc_samples, seed=seed_inner) else: sampler = IIDNormalSampler(num_samples=mc_samples, seed=seed_inner) acq_function = qSimpleRegret( model=model, sampler=sampler, objective=objective # pyre-ignore [6] ) else: raise UnsupportedError( f"Unknown objective type: {objective.__class__}" # pragma: nocover ) if self.fidelity_features: # we need to optimize at the target fidelities if any(f in self.fidelity_features for f in fixed_features): raise RuntimeError( "Fixed features cannot also be fidelity features") elif not set(self.fidelity_features) == set(target_fidelities): raise RuntimeError( "Must provide a target fidelity for every fidelity feature" ) # make sure to not modify fixed_features in-place fixed_features = {**fixed_features, **target_fidelities} elif target_fidelities: raise RuntimeError( "Must specify fidelity_features in fit() when using target fidelities" ) if fixed_features: acq_function = FixedFeatureAcquisitionFunction( acq_function=acq_function, d=X_observed.size(-1), columns=list(fixed_features.keys()), values=list(fixed_features.values()), ) non_fixed_idcs = [ i for i in range(self.Xs[0].size(-1)) if i not in fixed_features ] else: non_fixed_idcs = None return acq_function, non_fixed_idcs
def optimize_objective( model: Model, objective: Union[ScalarizedObjective, MCAcquisitionObjective], bounds: Tensor, q: int, linear_constraints: Optional[Tuple[Tensor, Tensor]] = None, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, qmc: bool = True, mc_samples: int = 512, seed_inner: Optional[int] = None, optimizer_options: Dict[str, Any] = None, post_processing_func: Optional[Callable[[Tensor], Tensor]] = None, batch_initial_conditions: Optional[Tensor] = None, sequential: bool = False, **ignore, ) -> Tuple[Tensor, Tensor]: r"""Optimize an objective under the given model. Args: model: The model to be used in the objective. objective: The objective to optimize. bounds: A `2 x d` tensor of lower and upper bounds for each column of `X`. q: The cardinality of input sets on which the objective is to be evaluated. linear_constraints: A tuple of (A, b). Given `k` linear constraints on a `d`-dimensional space, `A` is `k x d` and `b` is `k x 1` such that `A x <= b`. (Not used by single task models). fixed_features: A dictionary of feature assignments `{feature_index: value}` to hold fixed during generation. target_fidelities: A dictionary mapping input feature indices to fidelity values. Defaults to `{-1: 1.0}`. qmc: Toggle for enabling (qmc=1) or disabling (qmc=0) use of Quasi Monte Carlo. mc_samples: Integer number of samples used to estimate Monte Carlo objectives. seed_inner: Integer seed used to initialize the sampler passed to MCObjective. optimizer_options: Table used to lookup keyword arguments for the optimizer. post_processing_func: A function that post-processes an optimization result appropriately (i.e. according to `round-trip` transformations). batch_initial_conditions: A Tensor of initial values for the optimizer. sequential: If False, uses joint optimization, otherwise uses sequential optimization. Returns: A tuple of <torch.Tensor> containing the best input locations and corresponding objective values. """ if optimizer_options is None: optimizer_options = {} if isinstance(objective, MCAcquisitionObjective): sampler_cls = SobolQMCNormalSampler if qmc else IIDNormalSampler acqf_cls = qSimpleRegret acqf_opt = {"sampler": sampler_cls(num_samples=mc_samples, seed=seed_inner)} else: acqf_cls = PosteriorMean acqf_opt = {} acq_function = acqf_cls(model=model, objective=objective, **acqf_opt) if fixed_features: acq_function = FixedFeatureAcquisitionFunction( acq_function=acq_function, d=bounds.shape[-1], columns=list(fixed_features.keys()), values=list(fixed_features.values()), ) free_feature_dims = list(range(len(bounds)) - fixed_features.keys()) free_feature_bounds = bounds[:, free_feature_dims] # (2, d' <= d) else: free_feature_bounds = bounds if linear_constraints is None: inequality_constraints = None else: A, b = linear_constraints inequality_constraints = [] k, d = A.shape for i in range(k): indicies = A[i, :].nonzero(as_tuple=False).squeeze() coefficients = -A[i, indicies] rhs = -b[i, 0] inequality_constraints.append((indicies, coefficients, rhs)) return optimize_acqf( acq_function=acq_function, bounds=free_feature_bounds, q=q, num_restarts=optimizer_options.get("num_restarts", 60), raw_samples=optimizer_options.get("raw_samples", 1024), options={ "batch_limit": optimizer_options.get("batch_limit", 8), "maxiter": optimizer_options.get("maxiter", 200), "nonnegative": optimizer_options.get("nonnegative", False), "method": optimizer_options.get("method", "L-BFGS-B"), }, inequality_constraints=inequality_constraints, fixed_features=None, # handled inside the acquisition function post_processing_func=post_processing_func, batch_initial_conditions=batch_initial_conditions, return_best_only=True, sequential=sequential, )
def get_out_of_sample_best_point_acqf( model: Model, Xs: List[Tensor], X_observed: Tensor, objective_weights: Tensor, mc_samples: int = 512, fixed_features: Optional[Dict[int, float]] = None, fidelity_features: Optional[List[int]] = None, target_fidelities: Optional[Dict[int, float]] = None, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, seed_inner: Optional[int] = None, qmc: bool = True, **kwargs: Any, ) -> Tuple[AcquisitionFunction, Optional[List[int]]]: """Picks an appropriate acquisition function to find the best out-of-sample (not observed) point using the given surrogate model. NOTE: Typically the appropriate function is the posterior mean, but can differ to account for fidelities etc. """ model = model # subset model only to the outcomes we need for the optimization if kwargs.get("subset_model", True): model, objective_weights, outcome_constraints = subset_model( model=model, objective_weights=objective_weights, outcome_constraints=outcome_constraints, ) fixed_features = fixed_features or {} target_fidelities = target_fidelities or {} objective = get_botorch_objective( model=model, objective_weights=objective_weights, outcome_constraints=outcome_constraints, X_observed=X_observed, ) if isinstance(objective, ScalarizedObjective): acq_function = PosteriorMean(model=model, objective=objective) elif isinstance(objective, MCAcquisitionObjective): if qmc: sampler = SobolQMCNormalSampler(num_samples=mc_samples, seed=seed_inner) else: sampler = IIDNormalSampler(num_samples=mc_samples, seed=seed_inner) acq_function = qSimpleRegret(model=model, sampler=sampler, objective=objective) else: raise UnsupportedError( f"Unknown objective type: {objective.__class__}" # pragma: nocover ) if fidelity_features: # we need to optimize at the target fidelities if any(f in fidelity_features for f in fixed_features): raise RuntimeError( "Fixed features cannot also be fidelity features") elif set(fidelity_features) != set(target_fidelities): raise RuntimeError( "Must provide a target fidelity for every fidelity feature") # make sure to not modify fixed_features in-place fixed_features = {**fixed_features, **target_fidelities} elif target_fidelities: raise RuntimeError( "Must specify fidelity_features in fit() when using target fidelities" ) if fixed_features: acq_function = FixedFeatureAcquisitionFunction( acq_function=acq_function, d=X_observed.size(-1), columns=list(fixed_features.keys()), values=list(fixed_features.values()), ) non_fixed_idcs = [ i for i in range(Xs[0].size(-1)) if i not in fixed_features ] else: non_fixed_idcs = None return acq_function, non_fixed_idcs
def test_sample_points_around_best(self): tkwargs = {"device": self.device} _bounds = torch.ones(2, 2) _bounds[1] = 2 for dtype in (torch.float, torch.double): tkwargs["dtype"] = dtype bounds = _bounds.to(**tkwargs) X_train = 1 + torch.rand(20, 2, **tkwargs) model = MockModel( MockPosterior(mean=(2 * X_train + 1).sum(dim=-1, keepdim=True)) ) # test NEI with X_baseline acqf = qNoisyExpectedImprovement(model, X_baseline=X_train) with mock.patch( "botorch.optim.initializers.sample_perturbed_subset_dims" ) as mock_subset_dims: X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) mock_subset_dims.assert_not_called() self.assertTrue(X_rnd.shape, torch.Size([4, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test model that returns a batched mean model = MockModel( MockPosterior( mean=(2 * X_train + 1).sum(dim=-1, keepdim=True).unsqueeze(0) ) ) acqf = qNoisyExpectedImprovement(model, X_baseline=X_train) X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertTrue(X_rnd.shape, torch.Size([4, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test EI without X_baseline acqf = qExpectedImprovement(model, best_f=0.0) with warnings.catch_warnings(record=True) as w, settings.debug(True): X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) # set train inputs model.train_inputs = (X_train,) X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertTrue(X_rnd.shape, torch.Size([4, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test an acquisition function that has objective=None # and maximize=False pm = PosteriorMean(model, maximize=False) self.assertIsNone(pm.objective) self.assertFalse(pm.maximize) X_rnd = sample_points_around_best( acq_function=pm, n_discrete_points=4, sigma=0, bounds=bounds, best_pct=1e-8, # ensures that we only use best value ) idx = (-model.posterior(X_train).mean).argmax() self.assertTrue((X_rnd == X_train[idx : idx + 1]).all(dim=-1).all()) # test acquisition function that has no model ff = FixedFeatureAcquisitionFunction(pm, d=2, columns=[0], values=[0]) # set X_baseline for testing purposes ff.X_baseline = X_train with warnings.catch_warnings(record=True) as w, settings.debug(True): X_rnd = sample_points_around_best( acq_function=ff, n_discrete_points=4, sigma=1e-3, bounds=bounds, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) # test constraints with NEHVI constraints = [lambda Y: Y[..., 0]] ref_point = torch.zeros(2, **tkwargs) # test cases when there are and are not any feasible points for any_feas in (True, False): Y_train = torch.stack( [ torch.linspace(-0.5, 0.5, X_train.shape[0], **tkwargs) if any_feas else torch.ones(X_train.shape[0], **tkwargs), X_train.sum(dim=-1), ], dim=-1, ) moo_model = MockModel(MockPosterior(mean=Y_train, samples=Y_train)) acqf = qNoisyExpectedHypervolumeImprovement( moo_model, ref_point=ref_point, X_baseline=X_train, constraints=constraints, cache_root=False, ) X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=4, sigma=0.0, bounds=bounds, ) self.assertTrue(X_rnd.shape, torch.Size([4, 2])) # this should be true since sigma=0 # and we should only be returning feasible points violation = constraints[0](Y_train) neg_violation = -violation.clamp_min(0.0) feas = neg_violation == 0 eq_mask = (X_train.unsqueeze(1) == X_rnd.unsqueeze(0)).all(dim=-1) if feas.any(): # determine # create n_train x n_rnd tensor of booleans eq_mask = (X_train.unsqueeze(1) == X_rnd.unsqueeze(0)).all(dim=-1) # check that all X_rnd correspond to feasible points self.assertEqual(eq_mask[feas].sum(), 4) else: idcs = torch.topk(neg_violation, k=2).indices self.assertEqual(eq_mask[idcs].sum(), 4) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) # test that subset_dims is called if d>=21 X_train = 1 + torch.rand(20, 21, **tkwargs) model = MockModel( MockPosterior(mean=(2 * X_train + 1).sum(dim=-1, keepdim=True)) ) bounds = torch.ones(2, 21, **tkwargs) bounds[1] = 2 # test NEI with X_baseline acqf = qNoisyExpectedImprovement(model, X_baseline=X_train) with mock.patch( "botorch.optim.initializers.sample_perturbed_subset_dims", wraps=sample_perturbed_subset_dims, ) as mock_subset_dims: X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=5, sigma=1e-3, bounds=bounds ) self.assertTrue(X_rnd.shape, torch.Size([5, 2])) self.assertTrue((X_rnd >= 1).all()) self.assertTrue((X_rnd <= 2).all()) mock_subset_dims.assert_called_once() # test tiny prob_perturb to make sure we perturb at least one dimension X_rnd = sample_points_around_best( acq_function=acqf, n_discrete_points=5, sigma=1e-3, bounds=bounds, prob_perturb=1e-8, ) self.assertTrue( ((X_rnd.unsqueeze(0) == X_train.unsqueeze(1)).all(dim=-1)).sum() == 0 )
def test_get_X_baseline(self): tkwargs = {"device": self.device} for dtype in (torch.float, torch.double): tkwargs["dtype"] = dtype X_train = torch.rand(20, 2, **tkwargs) model = MockModel( MockPosterior(mean=(2 * X_train + 1).sum(dim=-1, keepdim=True))) # test NEI with X_baseline acqf = qNoisyExpectedImprovement(model, X_baseline=X_train[:2]) X = get_X_baseline(acq_function=acqf) self.assertTrue(torch.equal(X, acqf.X_baseline)) # test EI without X_baseline acqf = qExpectedImprovement(model, best_f=0.0) with warnings.catch_warnings( record=True) as w, settings.debug(True): X_rnd = get_X_baseline(acq_function=acqf, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) # set train inputs model.train_inputs = (X_train, ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test that we fail back to train_inputs if X_baseline is an empty tensor acqf.register_buffer("X_baseline", X_train[:0]) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test acquisitipon function without X_baseline or model acqf = FixedFeatureAcquisitionFunction(acqf, d=2, columns=[0], values=[0]) with warnings.catch_warnings( record=True) as w, settings.debug(True): X_rnd = get_X_baseline(acq_function=acqf, ) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, BotorchWarning)) self.assertIsNone(X_rnd) Y_train = 2 * X_train[:2] + 1 moo_model = MockModel(MockPosterior(mean=Y_train, samples=Y_train)) ref_point = torch.zeros(2, **tkwargs) # test NEHVI with X_baseline acqf = qNoisyExpectedHypervolumeImprovement( moo_model, ref_point=ref_point, X_baseline=X_train[:2], cache_root=False, ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, acqf.X_baseline)) # test qEHVI without train_inputs acqf = qExpectedHypervolumeImprovement( moo_model, ref_point=ref_point, partitioning=FastNondominatedPartitioning( ref_point=ref_point, Y=Y_train, ), ) # test extracting train_inputs from model list GP model_list = ModelListGP( SingleTaskGP(X_train, Y_train[:, :1]), SingleTaskGP(X_train, Y_train[:, 1:]), ) acqf = qExpectedHypervolumeImprovement( model_list, ref_point=ref_point, partitioning=FastNondominatedPartitioning( ref_point=ref_point, Y=Y_train, ), ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test MESMO for which we need to use # `acqf.mo_model` batched_mo_model = SingleTaskGP(X_train, Y_train) acqf = qMultiObjectiveMaxValueEntropy( batched_mo_model, sample_pareto_frontiers=lambda model: torch.rand( 10, 2, **tkwargs), ) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train)) # test that if there is an input transform that is applied # to the train_inputs when the model is in eval mode, we # extract the untransformed train_inputs model = SingleTaskGP(X_train, Y_train[:, :1], input_transform=Warp(indices=[0, 1])) model.eval() self.assertFalse(torch.equal(model.train_inputs[0], X_train)) acqf = qExpectedImprovement(model, best_f=0.0) X = get_X_baseline(acq_function=acqf, ) self.assertTrue(torch.equal(X, X_train))
def test_fixed_features(self): train_X = torch.rand(5, 3, device=self.device) train_Y = train_X.norm(dim=-1, keepdim=True) model = SingleTaskGP(train_X, train_Y).to(device=self.device).eval() qEI = qExpectedImprovement(model, best_f=0.0) for q in [1, 2]: # test single point test_X = torch.rand(q, 3, device=self.device) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=test_X[..., -1:]) qei = qEI(test_X) qei_ff = qEI_ff(test_X[..., :-1]) self.assertTrue(torch.allclose(qei, qei_ff)) # test list input with float qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=[0.5]) qei_ff = qEI_ff(test_X[..., :-1]) test_X_clone = test_X.clone() test_X_clone[..., 2] = 0.5 qei = qEI(test_X_clone) self.assertTrue(torch.allclose(qei, qei_ff)) # test list input with Tensor and float qEI_ff = FixedFeatureAcquisitionFunction( qEI, d=3, columns=[0, 2], values=[test_X[..., [0]], 0.5]) qei_ff = qEI_ff(test_X[..., [1]]) self.assertTrue(torch.allclose(qei, qei_ff)) # test t-batch with broadcasting and list of floats test_X = torch.rand(q, 3, device=self.device).expand(4, q, 3) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=test_X[0, :, -1:]) qei = qEI(test_X) qei_ff = qEI_ff(test_X[..., :-1]) self.assertTrue(torch.allclose(qei, qei_ff)) # test t-batch with broadcasting and list of floats and Tensor qEI_ff = FixedFeatureAcquisitionFunction( qEI, d=3, columns=[0, 2], values=[test_X[0, :, [0]], 0.5]) test_X_clone = test_X.clone() test_X_clone[..., 2] = 0.5 qei = qEI(test_X_clone) qei_ff = qEI_ff(test_X[..., [1]]) self.assertTrue(torch.allclose(qei, qei_ff)) # test gradient test_X = torch.rand(1, 3, device=self.device, requires_grad=True) test_X_ff = test_X[..., :-1].detach().clone().requires_grad_(True) qei = qEI(test_X) qEI_ff = FixedFeatureAcquisitionFunction(qEI, d=3, columns=[2], values=test_X[..., [2]].detach()) qei_ff = qEI_ff(test_X_ff) self.assertTrue(torch.allclose(qei, qei_ff)) qei.backward() qei_ff.backward() self.assertTrue(torch.allclose(test_X.grad[..., :-1], test_X_ff.grad)) test_X = test_X.detach().clone() test_X_ff = test_X[..., [1]].detach().clone().requires_grad_(True) test_X[..., 2] = 0.5 test_X.requires_grad_(True) qei = qEI(test_X) qEI_ff = FixedFeatureAcquisitionFunction( qEI, d=3, columns=[0, 2], values=[test_X[..., [0]].detach(), 0.5]) qei_ff = qEI_ff(test_X_ff) qei.backward() qei_ff.backward() self.assertTrue(torch.allclose(test_X.grad[..., [1]], test_X_ff.grad)) # test error b/c of incompatible input shapes with self.assertRaises(ValueError): qEI_ff(test_X)