def test_affine_acquisition_objective(self, cuda=False): device = torch.device("cuda") if cuda else torch.device("cpu") for dtype in (torch.float, torch.double): offset = torch.rand(1).item() for batch_shape in ([], [3]): for o in (1, 2): weights = torch.randn(o, device=device, dtype=dtype) obj = ScalarizedObjective(weights=weights, offset=offset) posterior = _get_test_posterior(batch_shape, device, dtype, o=o) mean, covar = posterior.mvn.mean, posterior.mvn.covariance_matrix new_posterior = obj(posterior) exp_size = torch.Size(batch_shape + [1, 1]) self.assertEqual(new_posterior.mean.shape, exp_size) new_mean_exp = offset + mean @ weights self.assertTrue( torch.allclose(new_posterior.mean[..., -1], new_mean_exp)) self.assertEqual(new_posterior.variance.shape, exp_size) new_covar_exp = ((covar @ weights) @ weights).unsqueeze(-1) self.assertTrue( torch.allclose(new_posterior.variance[..., -1], new_covar_exp)) # test error with self.assertRaises(ValueError): ScalarizedObjective(weights=torch.rand(2, o))
def test_affine_acquisition_objective(self): for batch_shape, m, dtype in itertools.product( ([], [3]), (1, 2), (torch.float, torch.double) ): offset = torch.rand(1).item() weights = torch.randn(m, device=self.device, dtype=dtype) obj = ScalarizedObjective(weights=weights, offset=offset) posterior = _get_test_posterior( batch_shape, m=m, device=self.device, dtype=dtype ) mean, covar = posterior.mvn.mean, posterior.mvn.covariance_matrix new_posterior = obj(posterior) exp_size = torch.Size(batch_shape + [1, 1]) self.assertEqual(new_posterior.mean.shape, exp_size) new_mean_exp = offset + mean @ weights self.assertTrue(torch.allclose(new_posterior.mean[..., -1], new_mean_exp)) self.assertEqual(new_posterior.variance.shape, exp_size) new_covar_exp = ((covar @ weights) @ weights).unsqueeze(-1) self.assertTrue( torch.allclose(new_posterior.variance[..., -1], new_covar_exp) ) # test error with self.assertRaises(ValueError): ScalarizedObjective(weights=torch.rand(2, m)) # test evaluate Y = torch.rand(2, m, device=self.device, dtype=dtype) val = obj.evaluate(Y) val_expected = offset + Y @ weights self.assertTrue(torch.equal(val, val_expected))
def test_expected_improvement_batch(self, cuda=False): device = torch.device("cuda") if cuda else torch.device("cpu") for dtype in (torch.float, torch.double): mean = torch.tensor([-0.5, 0.0, 0.5], device=device, dtype=dtype).view( 3, 1, 1 ) variance = torch.ones(3, 1, 1, device=device, dtype=dtype) mm = MockModel(MockPosterior(mean=mean, variance=variance)) module = ExpectedImprovement(model=mm, best_f=0.0) X = torch.empty(3, 1, 1, device=device, dtype=dtype) # dummy ei = module(X) ei_expected = torch.tensor( [0.19780, 0.39894, 0.69780], device=device, dtype=dtype ) self.assertTrue(torch.allclose(ei, ei_expected, atol=1e-4)) # check for proper error if multi-output model mean2 = torch.rand(3, 1, 2, device=device, dtype=dtype) variance2 = torch.rand(3, 1, 2, device=device, dtype=dtype) mm2 = MockModel(MockPosterior(mean=mean2, variance=variance2)) module2 = ExpectedImprovement(model=mm2, best_f=0.0) with self.assertRaises(UnsupportedError): module2(X) # test objective (single-output) mean = torch.tensor([[[0.5]], [[0.25]]], device=device, dtype=dtype) covar = torch.tensor([[[[0.16]]], [[[0.125]]]], device=device, dtype=dtype) mvn = MultivariateNormal(mean, covar) p = GPyTorchPosterior(mvn) mm = MockModel(p) weights = torch.tensor([0.5], device=device, dtype=dtype) obj = ScalarizedObjective(weights) ei = ExpectedImprovement(model=mm, best_f=0.0, objective=obj) X = torch.rand(2, 1, 2, device=device, dtype=dtype) ei_expected = torch.tensor([[0.2601], [0.1500]], device=device, dtype=dtype) torch.allclose(ei(X), ei_expected, atol=1e-4) # test objective (multi-output) mean = torch.tensor( [[[-0.25, 0.5]], [[0.2, -0.1]]], device=device, dtype=dtype ) covar = torch.tensor( [[[0.5, 0.125], [0.125, 0.5]], [[0.25, -0.1], [-0.1, 0.25]]], device=device, dtype=dtype, ) mvn = MultitaskMultivariateNormal(mean, covar) p = GPyTorchPosterior(mvn) mm = MockModel(p) weights = torch.tensor([2.0, 1.0], device=device, dtype=dtype) obj = ScalarizedObjective(weights) ei = ExpectedImprovement(model=mm, best_f=0.0, objective=obj) X = torch.rand(2, 1, 2, device=device, dtype=dtype) ei_expected = torch.tensor([0.6910, 0.5371], device=device, dtype=dtype) torch.allclose(ei(X), ei_expected, atol=1e-4) # test bad objective class with self.assertRaises(UnsupportedError): ExpectedImprovement(model=mm, best_f=0.0, objective=IdentityMCObjective())
def test_expected_improvement(self): for dtype in (torch.float, torch.double): mean = torch.tensor([[-0.5]], device=self.device, dtype=dtype) variance = torch.ones(1, 1, device=self.device, dtype=dtype) mm = MockModel(MockPosterior(mean=mean, variance=variance)) # basic test module = ExpectedImprovement(model=mm, best_f=0.0) X = torch.empty(1, 1, device=self.device, dtype=dtype) # dummy ei = module(X) ei_expected = torch.tensor(0.19780, device=self.device, dtype=dtype) self.assertTrue(torch.allclose(ei, ei_expected, atol=1e-4)) # test maximize module = ExpectedImprovement(model=mm, best_f=0.0, maximize=False) X = torch.empty(1, 1, device=self.device, dtype=dtype) # dummy ei = module(X) ei_expected = torch.tensor(0.6978, device=self.device, dtype=dtype) self.assertTrue(torch.allclose(ei, ei_expected, atol=1e-4)) with self.assertRaises(UnsupportedError): module.set_X_pending(None) # test objective (single-output) mean = torch.tensor([0.5], device=self.device, dtype=dtype) covar = torch.tensor([[0.16]], device=self.device, dtype=dtype) mvn = MultivariateNormal(mean, covar) p = GPyTorchPosterior(mvn) mm = MockModel(p) weights = torch.tensor([0.5], device=self.device, dtype=dtype) obj = ScalarizedObjective(weights) ei = ExpectedImprovement(model=mm, best_f=0.0, objective=obj) X = torch.rand(1, 2, device=self.device, dtype=dtype) ei_expected = torch.tensor(0.2601, device=self.device, dtype=dtype) torch.allclose(ei(X), ei_expected, atol=1e-4) # test objective (multi-output) mean = torch.tensor([[-0.25, 0.5]], device=self.device, dtype=dtype) covar = torch.tensor([[[0.5, 0.125], [0.125, 0.5]]], device=self.device, dtype=dtype) mvn = MultitaskMultivariateNormal(mean, covar) p = GPyTorchPosterior(mvn) mm = MockModel(p) weights = torch.tensor([2.0, 1.0], device=self.device, dtype=dtype) obj = ScalarizedObjective(weights) ei = ExpectedImprovement(model=mm, best_f=0.0, objective=obj) X = torch.rand(1, 2, device=self.device, dtype=dtype) ei_expected = torch.tensor(0.6910, device=self.device, dtype=dtype) torch.allclose(ei(X), ei_expected, atol=1e-4)
def test_get_best_f_analytic(self): with self.assertRaises(NotImplementedError): get_best_f_analytic(training_data=self.nbd_td) best_f = get_best_f_analytic(training_data=self.bd_td) best_f_expected = self.bd_td.Y.squeeze().max() self.assertEqual(best_f, best_f_expected) with self.assertRaises(NotImplementedError): get_best_f_analytic(training_data=self.bd_td_mo) obj = ScalarizedObjective(weights=torch.rand(2)) best_f = get_best_f_analytic(training_data=self.bd_td_mo, objective=obj) best_f_expected = obj.evaluate(self.bd_td_mo.Y).max() self.assertEqual(best_f, best_f_expected)
def get_botorch_objective( model: Model, objective_weights: Tensor, use_scalarized_objective: bool = True, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, objective_thresholds: Optional[Tensor] = None, X_observed: Optional[Tensor] = None, ) -> AcquisitionObjective: """Constructs a BoTorch `AcquisitionObjective` object. Args: model: A BoTorch Model objective_weights: The objective is to maximize a weighted sum of the columns of f(x). These are the weights. use_scalarized_objective: A boolean parameter that defaults to True, specifying whether ScalarizedObjective should be used. NOTE: when using outcome_constraints, use_scalarized_objective will be ignored. outcome_constraints: A tuple of (A, b). For k outcome constraints and m outputs at f(x), A is (k x m) and b is (k x 1) such that A f(x) <= b. (Not used by single task models) objective_thresholds: A tensor containing thresholds forming a reference point from which to calculate pareto frontier hypervolume. Points that do not dominate the objective_thresholds contribute nothing to hypervolume. X_observed: Observed points that are feasible and appear in the objective or the constraints. None if there are no such points. Returns: A BoTorch `AcquisitionObjective` object. It will be one of: `ScalarizedObjective`, `LinearMCOObjective`, `ConstrainedMCObjective`. """ if objective_thresholds is not None: nonzero_idcs = torch.nonzero(objective_weights).view(-1) objective_weights = objective_weights[nonzero_idcs] return WeightedMCMultiOutputObjective(weights=objective_weights, outcomes=nonzero_idcs.tolist()) if X_observed is None: raise UnsupportedError( "X_observed is required to construct a BoTorch Objective.") if outcome_constraints: if use_scalarized_objective: logger.warning( "Currently cannot use ScalarizedObjective when there are outcome " "constraints. Ignoring (default) kwarg `use_scalarized_objective`" "= True. Creating ConstrainedMCObjective.") obj_tf = get_objective_weights_transform(objective_weights) def objective(samples: Tensor, X: Optional[Tensor] = None) -> Tensor: return obj_tf(samples) con_tfs = get_outcome_constraint_transforms(outcome_constraints) inf_cost = get_infeasible_cost(X=X_observed, model=model, objective=obj_tf) return ConstrainedMCObjective(objective=objective, constraints=con_tfs or [], infeasible_cost=inf_cost) elif use_scalarized_objective: return ScalarizedObjective(weights=objective_weights) return LinearMCObjective(weights=objective_weights)
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 test_deprecate_acqf_objective(self): mean = torch.zeros(1, 2) variance = torch.ones(1, 2) mm = MockModel(MockPosterior(mean=mean, variance=variance)) obj = ScalarizedObjective(weights=torch.ones(2)) # check for deprecation warning with self.assertWarns(DeprecationWarning): acqf = DummyAnalyticAcquisitionFunction(model=mm, objective=obj) # check that posterior transform was created and assigned self.assertIsInstance(acqf.posterior_transform, ScalarizedPosteriorTransform) self.assertFalse(hasattr(acqf, "objective"))
def __init__( self, model: Model, beta: Tensor, weights: Tensor, maximize: bool = True, ) -> None: objective = ScalarizedObjective(weights=torch.tensor([0.1, 0.5]),offset=0.0) super().__init__(model=model,objective=objective) self.maximize = maximize self.register_buffer("beta", torch.as_tensor(beta)) self.register_buffer("weights", torch.as_tensor(weights))
def test_deprecate_objective_arg(self): objective = ScalarizedObjective(weights=torch.ones(1)) post_tf = ScalarizedPosteriorTransform(weights=torch.zeros(1)) with self.assertRaises(RuntimeError): _deprecate_objective_arg(posterior_transform=post_tf, objective=objective) with self.assertWarns(DeprecationWarning): new_tf = _deprecate_objective_arg(objective=objective) self.assertTrue(torch.equal(new_tf.weights, objective.weights)) self.assertIsInstance(new_tf, ScalarizedPosteriorTransform) new_tf = _deprecate_objective_arg(posterior_transform=post_tf) self.assertEqual(id(new_tf), id(post_tf)) self.assertIsNone(_deprecate_objective_arg()) with self.assertRaises(UnsupportedError): _deprecate_objective_arg(objective=DummyObjective())
def get_botorch_objective( model: Model, objective_weights: Tensor, use_scalarized_objective: bool = True, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, X_observed: Optional[Tensor] = None, ) -> AcquisitionObjective: """Constructs a BoTorch `AcquisitionObjective` object. Args: model: A BoTorch Model objective_weights: The objective is to maximize a weighted sum of the columns of f(x). These are the weights. use_scalarized_objective: A boolean parameter that defaults to True, specifying whether ScalarizedObjective should be used. NOTE: when using outcome_constraints, use_scalarized_objective will be ignored. outcome_constraints: A tuple of (A, b). For k outcome constraints and m outputs at f(x), A is (k x m) and b is (k x 1) such that A f(x) <= b. (Not used by single task models) X_observed: Observed points that are feasible and appear in the objective or the constraints. None if there are no such points. Returns: A BoTorch `AcquisitionObjective` object. It will be one of: `ScalarizedObjective`, `LinearMCOObjective`, `ConstrainedMCObjective`. """ if X_observed is None: raise UnsupportedError( "X_observed is required to construct a BoTorch Objective.") if outcome_constraints: if use_scalarized_objective: logger.warning( "Currently cannot use ScalarizedObjective when there are outcome " "constraints. Ignoring (default) kwarg `use_scalarized_objective`" "= True. Creating ConstrainedMCObjective.") obj_tf = get_objective_weights_transform(objective_weights) con_tfs = get_outcome_constraint_transforms(outcome_constraints) inf_cost = get_infeasible_cost(X=X_observed, model=model, objective=obj_tf) return ConstrainedMCObjective(objective=obj_tf, constraints=con_tfs or [], infeasible_cost=inf_cost) if use_scalarized_objective: return ScalarizedObjective(weights=objective_weights) return LinearMCObjective(weights=objective_weights)
def test_optimize_objective(self, mock_optimize_acqf): from botorch.acquisition.input_constructors import optimize_objective mock_model = MockModel( posterior=MockPosterior(mean=None, variance=None)) bounds = torch.rand(2, len(self.bounds)) A = torch.rand(1, bounds.shape[-1]) b = torch.zeros([1, 1]) idx = A[0].nonzero(as_tuple=False).squeeze() inequality_constraints = ((idx, -A[0, idx], -b[0, 0]), ) with self.subTest("scalarObjective_linearConstraints"): _ = optimize_objective( model=mock_model, bounds=bounds, q=1, objective=ScalarizedObjective( weights=torch.rand(bounds.shape[-1])), linear_constraints=(A, b), fixed_features=None, ) kwargs = mock_optimize_acqf.call_args[1] self.assertIsInstance(kwargs["acq_function"], PosteriorMean) self.assertTrue(torch.equal(kwargs["bounds"], bounds)) self.assertEqual(len(kwargs["inequality_constraints"]), 1) for a, b in zip(kwargs["inequality_constraints"][0], inequality_constraints[0]): self.assertTrue(torch.equal(a, b)) with self.subTest("mcObjective_fixedFeatures"): _ = optimize_objective( model=mock_model, bounds=bounds, q=1, objective=LinearMCObjective( weights=torch.rand(bounds.shape[-1])), fixed_features={0: 0.5}, ) kwargs = mock_optimize_acqf.call_args[1] self.assertIsInstance(kwargs["acq_function"], FixedFeatureAcquisitionFunction) self.assertIsInstance(kwargs["acq_function"].acq_func, qSimpleRegret) self.assertTrue(torch.equal(kwargs["bounds"], bounds[:, 1:]))
def test_get_best_f_analytic(self): with self.assertRaises(NotImplementedError): get_best_f_analytic(training_data=self.nbd_td) best_f = get_best_f_analytic(training_data=self.bd_td) best_f_expected = self.bd_td.Y.squeeze().max() self.assertEqual(best_f, best_f_expected) with self.assertRaises(NotImplementedError): get_best_f_analytic(training_data=self.bd_td_mo) weights = torch.rand(2) obj = ScalarizedObjective(weights=weights) best_f_obj = get_best_f_analytic(training_data=self.bd_td_mo, objective=obj) post_tf = ScalarizedPosteriorTransform(weights=weights) best_f_tf = get_best_f_analytic(training_data=self.bd_td_mo, posterior_transform=post_tf) best_f_expected = post_tf.evaluate(self.bd_td_mo.Y).max() self.assertEqual(best_f_obj, best_f_expected) self.assertEqual(best_f_tf, best_f_expected)
def __init__(self, model: Model, options: dict) -> None: # MCAcquisitionFunction.__init__(self, model=model, sampler=sampler, objective=IdentityMCObjective()) AnalyticAcquisitionFunction.__init__( self, model=model, objective=ScalarizedObjective(weights=torch.Tensor([1.0]))) AcquisitionBaseTools.__init__(self, model=model, iden="Xsearch", Nrestarts_eta=options["Nrestarts_eta"]) self.u_vec = None self.Nsamples_fmin = options["Nsamples_fmin"] self.Nrestarts = options["Nrestarts_safe"] self.debug = False self.method = options["method_safe"] self.disp_info_scipy_opti = options["disp_info_scipy_opti"] self.dim = self.model.dim
def _get_objective( model: Model, objective_weights: Tensor, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, X_observed: Optional[Tensor] = None, ) -> AcquisitionObjective: if outcome_constraints is None: objective = ScalarizedObjective(weights=objective_weights) else: X_observed = torch.as_tensor(X_observed) obj_tf = get_objective_weights_transform(objective_weights) con_tfs = get_outcome_constraint_transforms(outcome_constraints) inf_cost = get_infeasible_cost(X=X_observed, model=model, objective=obj_tf) objective = ConstrainedMCObjective(objective=obj_tf, constraints=con_tfs or [], infeasible_cost=inf_cost) return objective
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 __init__( self, model: Model, beta: Tensor, weights: Tensor, # objective: ScalarizedObjective, X_pending: Optional[Tensor] = None, # objective_weights: Tensor, # outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, X_observed: Optional[Tensor] = None, # X_pending: Optional[Tensor] = None, maximize: bool = True, ) -> None: objective = ScalarizedObjective(weights=weights, offset=0.0) super().__init__(model=model, objective=objective) self.maximize = maximize self.X_observed = X_observed # self.X_pending = X_pending self.register_buffer("beta", torch.as_tensor(beta)) self.register_buffer("weights", torch.as_tensor(weights))
def get_botorch_objective( model: Model, objective_weights: Tensor, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, X_observed: Optional[Tensor] = None, ) -> AcquisitionObjective: """Constructs a BoTorch `Objective`.""" if X_observed is None: raise UnsupportedError( "X_observed is required to construct a BoTorch Objective.") if outcome_constraints is None: objective = ScalarizedObjective(weights=objective_weights) else: obj_tf = get_objective_weights_transform(objective_weights) con_tfs = get_outcome_constraint_transforms(outcome_constraints) inf_cost = get_infeasible_cost(X=X_observed, model=model, objective=obj_tf) objective = ConstrainedMCObjective(objective=obj_tf, constraints=con_tfs or [], infeasible_cost=inf_cost) return objective
def test_initialize_q_knowledge_gradient(self): for dtype in (torch.float, torch.double): mean = torch.zeros(1, 1, device=self.device, dtype=dtype) mm = MockModel(MockPosterior(mean=mean)) # test error when neither specifying neither sampler nor num_fantasies with self.assertRaises(ValueError): qKnowledgeGradient(model=mm, num_fantasies=None) # test error when sampler and num_fantasies arg are inconsistent sampler = IIDNormalSampler(num_samples=16) with self.assertRaises(ValueError): qKnowledgeGradient(model=mm, num_fantasies=32, sampler=sampler) # test default construction qKG = qKnowledgeGradient(model=mm, num_fantasies=32) self.assertEqual(qKG.num_fantasies, 32) self.assertIsInstance(qKG.sampler, SobolQMCNormalSampler) self.assertEqual(qKG.sampler.sample_shape, torch.Size([32])) self.assertIsNone(qKG.objective) self.assertIsNone(qKG.inner_sampler) self.assertIsNone(qKG.X_pending) self.assertIsNone(qKG.current_value) self.assertEqual(qKG.get_augmented_q_batch_size(q=3), 32 + 3) # test custom construction obj = GenericMCObjective(lambda Y, X: Y.mean(dim=-1)) sampler = IIDNormalSampler(num_samples=16) X_pending = torch.zeros(2, 2, device=self.device, dtype=dtype) qKG = qKnowledgeGradient( model=mm, num_fantasies=16, sampler=sampler, objective=obj, X_pending=X_pending, ) self.assertEqual(qKG.num_fantasies, 16) self.assertEqual(qKG.sampler, sampler) self.assertEqual(qKG.sampler.sample_shape, torch.Size([16])) self.assertEqual(qKG.objective, obj) self.assertIsInstance(qKG.inner_sampler, SobolQMCNormalSampler) self.assertEqual(qKG.inner_sampler.sample_shape, torch.Size([128])) self.assertTrue(torch.equal(qKG.X_pending, X_pending)) self.assertIsNone(qKG.current_value) self.assertEqual(qKG.get_augmented_q_batch_size(q=3), 16 + 3) # test assignment of num_fantasies from sampler if not provided qKG = qKnowledgeGradient(model=mm, num_fantasies=None, sampler=sampler) self.assertEqual(qKG.sampler.sample_shape, torch.Size([16])) # test custom construction with inner sampler and current value inner_sampler = SobolQMCNormalSampler(num_samples=256) current_value = torch.zeros(1, device=self.device, dtype=dtype) qKG = qKnowledgeGradient( model=mm, num_fantasies=8, objective=obj, inner_sampler=inner_sampler, current_value=current_value, ) self.assertEqual(qKG.num_fantasies, 8) self.assertEqual(qKG.sampler.sample_shape, torch.Size([8])) self.assertEqual(qKG.objective, obj) self.assertIsInstance(qKG.inner_sampler, SobolQMCNormalSampler) self.assertEqual(qKG.inner_sampler, inner_sampler) self.assertIsNone(qKG.X_pending) self.assertTrue(torch.equal(qKG.current_value, current_value)) self.assertEqual(qKG.get_augmented_q_batch_size(q=3), 8 + 3) # test construction with non-MC objective (ScalarizedObjective) qKG_s = qKnowledgeGradient( model=mm, num_fantasies=16, sampler=sampler, objective=ScalarizedObjective(weights=torch.rand(2)), ) self.assertIsNone(qKG_s.inner_sampler) self.assertIsInstance(qKG_s.objective, ScalarizedObjective) # test error if no objective and multi-output model mean2 = torch.zeros(1, 2, device=self.device, dtype=dtype) mm2 = MockModel(MockPosterior(mean=mean2)) with self.assertRaises(UnsupportedError): qKnowledgeGradient(model=mm2)
def __init__(self, model: Model) -> None: super().__init__(model=model, objective=ScalarizedObjective(weights=Tensor([1.0])))
def test_evaluate_q_knowledge_gradient(self): for dtype in (torch.float, torch.double): # basic test n_f = 4 mean = torch.rand(n_f, 1, 1, device=self.device, dtype=dtype) variance = torch.rand(n_f, 1, 1, device=self.device, dtype=dtype) mfm = MockModel(MockPosterior(mean=mean, variance=variance)) with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f: with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 1 mm = MockModel(None) qKG = qKnowledgeGradient(model=mm, num_fantasies=n_f) X = torch.rand(n_f + 1, 1, device=self.device, dtype=dtype) val = qKG(X) patch_f.assert_called_once() cargs, ckwargs = patch_f.call_args self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1])) self.assertTrue(torch.allclose(val, mean.mean(), atol=1e-4)) self.assertTrue(torch.equal(qKG.extract_candidates(X), X[..., :-n_f, :])) # batched evaluation b = 2 mean = torch.rand(n_f, b, 1, device=self.device, dtype=dtype) variance = torch.rand(n_f, b, 1, device=self.device, dtype=dtype) mfm = MockModel(MockPosterior(mean=mean, variance=variance)) X = torch.rand(b, n_f + 1, 1, device=self.device, dtype=dtype) with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f: with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 1 mm = MockModel(None) qKG = qKnowledgeGradient(model=mm, num_fantasies=n_f) val = qKG(X) patch_f.assert_called_once() cargs, ckwargs = patch_f.call_args self.assertEqual(ckwargs["X"].shape, torch.Size([b, 1, 1])) self.assertTrue( torch.allclose(val, mean.mean(dim=0).squeeze(-1), atol=1e-4) ) self.assertTrue(torch.equal(qKG.extract_candidates(X), X[..., :-n_f, :])) # pending points and current value X_pending = torch.rand(2, 1, device=self.device, dtype=dtype) mean = torch.rand(n_f, 1, 1, device=self.device, dtype=dtype) variance = torch.rand(n_f, 1, 1, device=self.device, dtype=dtype) mfm = MockModel(MockPosterior(mean=mean, variance=variance)) current_value = torch.rand(1, device=self.device, dtype=dtype) X = torch.rand(n_f + 1, 1, device=self.device, dtype=dtype) with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f: with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 1 mm = MockModel(None) qKG = qKnowledgeGradient( model=mm, num_fantasies=n_f, X_pending=X_pending, current_value=current_value, ) val = qKG(X) patch_f.assert_called_once() cargs, ckwargs = patch_f.call_args self.assertEqual(ckwargs["X"].shape, torch.Size([1, 3, 1])) self.assertTrue(torch.allclose(val, mean.mean() - current_value, atol=1e-4)) self.assertTrue(torch.equal(qKG.extract_candidates(X), X[..., :-n_f, :])) # test objective (inner MC sampling) objective = GenericMCObjective(objective=lambda Y, X: Y.norm(dim=-1)) samples = torch.randn(3, 1, 1, device=self.device, dtype=dtype) mfm = MockModel(MockPosterior(samples=samples)) X = torch.rand(n_f + 1, 1, device=self.device, dtype=dtype) with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f: with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 1 mm = MockModel(None) qKG = qKnowledgeGradient( model=mm, num_fantasies=n_f, objective=objective ) val = qKG(X) patch_f.assert_called_once() cargs, ckwargs = patch_f.call_args self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1])) self.assertTrue(torch.allclose(val, objective(samples).mean(), atol=1e-4)) self.assertTrue(torch.equal(qKG.extract_candidates(X), X[..., :-n_f, :])) # test non-MC objective (ScalarizedObjective) weights = torch.rand(2, device=self.device, dtype=dtype) objective = ScalarizedObjective(weights=weights) mean = torch.tensor([1.0, 0.5], device=self.device, dtype=dtype).expand( n_f, 1, 2 ) cov = torch.tensor( [[1.0, 0.1], [0.1, 0.5]], device=self.device, dtype=dtype ).expand(n_f, 2, 2) posterior = GPyTorchPosterior(MultitaskMultivariateNormal(mean, cov)) mfm = MockModel(posterior) with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f: with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 2 mm = MockModel(None) qKG = qKnowledgeGradient( model=mm, num_fantasies=n_f, objective=objective ) val = qKG(X) patch_f.assert_called_once() cargs, ckwargs = patch_f.call_args self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1])) val_expected = (mean * weights).sum(-1).mean(0) self.assertTrue(torch.allclose(val, val_expected))
def obj_grad_noisy(x): return obj_grad(x) + 0.1 * torch.randn(x.size()) num_seed_points = 1 train_x = 2 * torch.rand(num_seed_points, 1) - 1 # train_x = 0.0 * torch.rand(10, 1) train_y = torch.cat([obj_noisy(train_x), obj_grad_noisy(train_x)], dim=-1) model = GPWithDerivatives(train_X=train_x, train_Y=train_y) # model.likelihood.noise_covar.register_constraint("raw_noise", GreaterThan(1e1)) for it in range(25): # Acquisition function... objective = ScalarizedObjective(torch.tensor([1.0, 0.0])) candidate_x, acq_value = optimize_acqf( UpperConfidenceBound(model, beta=0.1, objective=objective), # ExpectedImprovement(model, best_f=torch.max(train_y)) bounds=torch.Tensor([[-1], [1]]), q=1, num_restarts=5, raw_samples=20, ) candidate_y = torch.cat( [obj_noisy(candidate_x), obj_grad_noisy(candidate_x)], dim=-1) train_x = torch.cat([train_x, candidate_x]) train_y = torch.cat([train_y, candidate_y]) # This is currently an error "Cannot yet add fantasy observations to multitask GPs, but this is coming soon!"
def test_max_posterior_sampling(self): batch_shapes = (torch.Size(), torch.Size([3]), torch.Size([3, 2])) dtypes = (torch.float, torch.double) for batch_shape, dtype, N, num_samples, d in itertools.product( batch_shapes, dtypes, (5, 6), (1, 2), (1, 2) ): tkwargs = {"device": self.device, "dtype": dtype} # X is `batch_shape x N x d` = batch_shape x N x 1. X = torch.randn(*batch_shape, N, d, **tkwargs) # the event shape is `num_samples x batch_shape x N x m` psamples = torch.zeros(num_samples, *batch_shape, N, 1, **tkwargs) psamples[..., 0, :] = 1.0 # IdentityMCObjective, with replacement with mock.patch.object(MockPosterior, "rsample", return_value=psamples): mp = MockPosterior(None) with mock.patch.object(MockModel, "posterior", return_value=mp): mm = MockModel(None) MPS = MaxPosteriorSampling(mm) s = MPS(X, num_samples=num_samples) self.assertTrue(torch.equal(s, X[..., [0] * num_samples, :])) # ScalarizedMCObjective, with replacement with mock.patch.object(MockPosterior, "rsample", return_value=psamples): mp = MockPosterior(None) with mock.patch.object(MockModel, "posterior", return_value=mp): mm = MockModel(None) with mock.patch.object( ScalarizedObjective, "forward", return_value=mp ): obj = ScalarizedObjective(torch.rand(2, **tkwargs)) MPS = MaxPosteriorSampling(mm, objective=obj) s = MPS(X, num_samples=num_samples) self.assertTrue(torch.equal(s, X[..., [0] * num_samples, :])) # without replacement psamples[..., 1, 0] = 1e-6 with mock.patch.object(MockPosterior, "rsample", return_value=psamples): mp = MockPosterior(None) with mock.patch.object(MockModel, "posterior", return_value=mp): mm = MockModel(None) MPS = MaxPosteriorSampling(mm, replacement=False) if len(batch_shape) > 1: with self.assertRaises(NotImplementedError): MPS(X, num_samples=num_samples) else: s = MPS(X, num_samples=num_samples) # order is not guaranteed, need to sort self.assertTrue( torch.equal( torch.sort(s, dim=-2).values, torch.sort(X[..., :num_samples, :], dim=-2).values, ) ) # ScalarizedMCObjective, without replacement with mock.patch.object(MockPosterior, "rsample", return_value=psamples): mp = MockPosterior(None) with mock.patch.object(MockModel, "posterior", return_value=mp): mm = MockModel(None) with mock.patch.object( ScalarizedObjective, "forward", return_value=mp ): obj = ScalarizedObjective(torch.rand(2, **tkwargs)) MPS = MaxPosteriorSampling(mm, objective=obj, replacement=False) if len(batch_shape) > 1: with self.assertRaises(NotImplementedError): MPS(X, num_samples=num_samples) else: s = MPS(X, num_samples=num_samples) # order is not guaranteed, need to sort self.assertTrue( torch.equal( torch.sort(s, dim=-2).values, torch.sort(X[..., :num_samples, :], dim=-2).values, ) )
def test_q_neg_int_post_variance(self): no = "botorch.utils.testing.MockModel.num_outputs" for dtype in (torch.float, torch.double): # basic test mean = torch.zeros(4, 1, device=self.device, dtype=dtype) variance = torch.rand(4, 1, device=self.device, dtype=dtype) mc_points = torch.rand(10, 1, device=self.device, dtype=dtype) mfm = MockModel(MockPosterior(mean=mean, variance=variance)) with mock.patch.object(MockModel, "fantasize", return_value=mfm): with mock.patch( no, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 1 # TODO: Make this work with arbitrary models mm = MockModel(None) qNIPV = qNegIntegratedPosteriorVariance( model=mm, mc_points=mc_points) X = torch.empty(1, 1, device=self.device, dtype=dtype) # dummy val = qNIPV(X) self.assertTrue( torch.allclose(val, -(variance.mean()), atol=1e-4)) # batched model mean = torch.zeros(2, 4, 1, device=self.device, dtype=dtype) variance = torch.rand(2, 4, 1, device=self.device, dtype=dtype) mc_points = torch.rand(2, 10, 1, device=self.device, dtype=dtype) mfm = MockModel(MockPosterior(mean=mean, variance=variance)) with mock.patch.object(MockModel, "fantasize", return_value=mfm): with mock.patch( no, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 1 # TODO: Make this work with arbitrary models mm = MockModel(None) qNIPV = qNegIntegratedPosteriorVariance( model=mm, mc_points=mc_points) # TODO: Allow broadcasting for batch evaluation X = torch.empty(2, 1, 1, device=self.device, dtype=dtype) # dummy val = qNIPV(X) val_exp = -variance.mean(dim=-2).squeeze(-1) self.assertTrue(torch.allclose(val, val_exp, atol=1e-4)) # multi-output model mean = torch.zeros(4, 2, device=self.device, dtype=dtype) variance = torch.rand(4, 2, device=self.device, dtype=dtype) cov = torch.diag_embed(variance.view(-1)) f_posterior = GPyTorchPosterior( MultitaskMultivariateNormal(mean, cov)) mc_points = torch.rand(10, 1, device=self.device, dtype=dtype) mfm = MockModel(f_posterior) with mock.patch.object(MockModel, "fantasize", return_value=mfm): with mock.patch( no, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 2 mm = MockModel(None) # check error if objective is not ScalarizedObjective with self.assertRaises(UnsupportedError): qNegIntegratedPosteriorVariance( model=mm, mc_points=mc_points, objective=IdentityMCObjective(), ) weights = torch.tensor([0.5, 0.5], device=self.device, dtype=dtype) qNIPV = qNegIntegratedPosteriorVariance( model=mm, mc_points=mc_points, objective=ScalarizedObjective(weights=weights), ) X = torch.empty(1, 1, device=self.device, dtype=dtype) # dummy val = qNIPV(X) self.assertTrue( torch.allclose(val, -0.5 * variance.mean(), atol=1e-4)) # batched multi-output model mean = torch.zeros(4, 3, 1, 2, device=self.device, dtype=dtype) variance = torch.rand(4, 3, 1, 2, device=self.device, dtype=dtype) cov = torch.diag_embed(variance.view(4, 3, -1)) f_posterior = GPyTorchPosterior( MultitaskMultivariateNormal(mean, cov)) mc_points = torch.rand(4, 1, device=self.device, dtype=dtype) mfm = MockModel(f_posterior) with mock.patch.object(MockModel, "fantasize", return_value=mfm): with mock.patch( no, new_callable=mock.PropertyMock) as mock_num_outputs: mock_num_outputs.return_value = 2 mm = MockModel(None) weights = torch.tensor([0.5, 0.5], device=self.device, dtype=dtype) qNIPV = qNegIntegratedPosteriorVariance( model=mm, mc_points=mc_points, objective=ScalarizedObjective(weights=weights), ) X = torch.empty(3, 1, 1, device=self.device, dtype=dtype) # dummy val = qNIPV(X) val_exp = -0.5 * variance.mean(dim=0).view(3, -1).mean(dim=-1) self.assertTrue(torch.allclose(val, val_exp, atol=1e-4))
def test_q_expected_improvement(self): for dtype in (torch.float, torch.double): # the event shape is `b x q x t` = 1 x 1 x 1 samples = torch.zeros(1, 1, 1, device=self.device, dtype=dtype) mm = MockModel(MockPosterior(samples=samples)) # X is `q x d` = 1 x 1. X is a dummy and unused b/c of mocking X = torch.zeros(1, 1, device=self.device, dtype=dtype) # basic test sampler = IIDNormalSampler(num_samples=2) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) # test shifting best_f value acqf = qExpectedImprovement(model=mm, best_f=-1, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 1.0) # test size verification of best_f with self.assertRaises(ValueError): qExpectedImprovement( model=mm, best_f=torch.zeros(2, device=self.device, dtype=dtype) ) # basic test, no resample sampler = IIDNormalSampler(num_samples=2, seed=12345) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) bs = acqf.sampler.base_samples.clone() res = acqf(X) self.assertTrue(torch.equal(acqf.sampler.base_samples, bs)) # basic test, qmc, no resample sampler = SobolQMCNormalSampler(num_samples=2) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertTrue(torch.equal(acqf.sampler.base_samples, bs)) # basic test, qmc, resample sampler = SobolQMCNormalSampler(num_samples=2, resample=True) acqf = qExpectedImprovement(model=mm, best_f=0, sampler=sampler) res = acqf(X) self.assertEqual(res.item(), 0.0) self.assertEqual(acqf.sampler.base_samples.shape, torch.Size([2, 1, 1, 1])) bs = acqf.sampler.base_samples.clone() acqf(X) self.assertFalse(torch.equal(acqf.sampler.base_samples, bs)) # basic test for X_pending and warning acqf.set_X_pending() self.assertIsNone(acqf.X_pending) acqf.set_X_pending(None) self.assertIsNone(acqf.X_pending) acqf.set_X_pending(X) self.assertEqual(acqf.X_pending, X) res = acqf(X) X2 = torch.zeros( 1, 1, 1, device=self.device, dtype=dtype, requires_grad=True ) with warnings.catch_warnings(record=True) as ws, settings.debug(True): acqf.set_X_pending(X2) self.assertEqual(acqf.X_pending, X2) self.assertEqual(len(ws), 1) self.assertTrue(issubclass(ws[-1].category, BotorchWarning)) # test bad objective type obj = ScalarizedObjective( weights=torch.rand(2, device=self.device, dtype=dtype) ) with self.assertRaises(UnsupportedError): qExpectedImprovement(model=mm, best_f=0, sampler=sampler, objective=obj)
def test_KnowledgeGradient_helpers(self): model = KnowledgeGradient() model.fit( Xs=self.Xs, Ys=self.Ys, Yvars=self.Yvars, search_space_digest=SearchSpaceDigest( feature_names=self.feature_names, bounds=self.bounds, ), metric_names=self.metric_names, ) # test _instantiate_KG objective = ScalarizedObjective(weights=self.objective_weights) # test acquisition setting acq_function = _instantiate_KG(model=model.model, objective=objective, n_fantasies=10, qmc=True) self.assertIsInstance(acq_function.sampler, SobolQMCNormalSampler) self.assertIsInstance(acq_function.objective, ScalarizedObjective) self.assertEqual(acq_function.num_fantasies, 10) acq_function = _instantiate_KG(model=model.model, objective=objective, n_fantasies=10, qmc=False) self.assertIsInstance(acq_function.sampler, IIDNormalSampler) acq_function = _instantiate_KG(model=model.model, objective=objective, qmc=False) self.assertIsNone(acq_function.inner_sampler) acq_function = _instantiate_KG(model=model.model, objective=objective, qmc=True, X_pending=self.X_dummy) self.assertIsNone(acq_function.inner_sampler) self.assertTrue(torch.equal(acq_function.X_pending, self.X_dummy)) # test _get_best_point_acqf acq_function, non_fixed_idcs = model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, ) self.assertIsInstance(acq_function, qSimpleRegret) self.assertIsInstance(acq_function.sampler, SobolQMCNormalSampler) self.assertIsNone(non_fixed_idcs) acq_function, non_fixed_idcs = model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, qmc=False, ) self.assertIsInstance(acq_function.sampler, IIDNormalSampler) self.assertIsNone(non_fixed_idcs) with self.assertRaises(RuntimeError): model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, target_fidelities={1: 1.0}, ) # multi-fidelity tests model = KnowledgeGradient() model.fit( Xs=self.Xs, Ys=self.Ys, Yvars=self.Yvars, search_space_digest=SearchSpaceDigest( feature_names=self.feature_names, bounds=self.bounds, fidelity_features=[-1], ), metric_names=self.metric_names, ) acq_function = _instantiate_KG( model=model.model, objective=objective, target_fidelities={2: 1.0}, current_value=0, ) self.assertIsInstance(acq_function, qMultiFidelityKnowledgeGradient) acq_function = _instantiate_KG( model=model.model, objective=LinearMCObjective(weights=self.objective_weights), ) self.assertIsInstance(acq_function.inner_sampler, SobolQMCNormalSampler) # test error that target fidelity and fidelity weight indices must match with self.assertRaises(RuntimeError): _instantiate_KG( model=model.model, objective=objective, target_fidelities={1: 1.0}, fidelity_weights={2: 1.0}, current_value=0, ) # test _get_best_point_acqf acq_function, non_fixed_idcs = model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, target_fidelities={2: 1.0}, ) self.assertIsInstance(acq_function, FixedFeatureAcquisitionFunction) self.assertIsInstance(acq_function.acq_func.sampler, SobolQMCNormalSampler) self.assertEqual(non_fixed_idcs, [0, 1]) acq_function, non_fixed_idcs = model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, target_fidelities={2: 1.0}, qmc=False, ) self.assertIsInstance(acq_function, FixedFeatureAcquisitionFunction) self.assertIsInstance(acq_function.acq_func.sampler, IIDNormalSampler) self.assertEqual(non_fixed_idcs, [0, 1]) # test error that fixed features are provided with self.assertRaises(RuntimeError): model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, qmc=False, ) # test error if fixed features are also fidelity features with self.assertRaises(RuntimeError): model._get_best_point_acqf( objective_weights=self.objective_weights, outcome_constraints=self.outcome_constraints, X_observed=self.X_dummy, fixed_features={2: 2.0}, target_fidelities={2: 1.0}, qmc=False, )
def test_qMS_init(self): d = 2 q = 1 num_data = 3 q_batch_sizes = [1, 1, 1] num_fantasies = [2, 2, 1] t_batch_size = [2] for dtype in (torch.float, torch.double): bounds = torch.tensor([[0], [1]], device=self.device, dtype=dtype) bounds = bounds.repeat(1, d) train_X = torch.rand(num_data, d, device=self.device, dtype=dtype) train_Y = torch.rand(num_data, 1, device=self.device, dtype=dtype) model = SingleTaskGP(train_X, train_Y) # exactly one of samplers or num_fantasies with self.assertRaises(UnsupportedError): qMultiStepLookahead( model=model, batch_sizes=q_batch_sizes, valfunc_cls=[qExpectedImprovement] * 4, valfunc_argfacs=[make_best_f] * 4, inner_mc_samples=[2] * 4, ) # cannot use qMS as its own valfunc_cls with self.assertRaises(UnsupportedError): qMultiStepLookahead( model=model, batch_sizes=q_batch_sizes, valfunc_cls=[qMultiStepLookahead] * 4, valfunc_argfacs=[make_best_f] * 4, num_fantasies=num_fantasies, inner_mc_samples=[2] * 4, ) # construct using samplers samplers = [ SobolQMCNormalSampler(num_samples=nf, resample=False, collapse_batch_dims=True) for nf in num_fantasies ] qMS = qMultiStepLookahead( model=model, batch_sizes=q_batch_sizes, valfunc_cls=[qExpectedImprovement] * 4, valfunc_argfacs=[make_best_f] * 4, inner_mc_samples=[2] * 4, samplers=samplers, ) self.assertEqual(qMS.num_fantasies, num_fantasies) # use default valfunc_cls, valfun_argfacs, inner_mc_samples qMS = qMultiStepLookahead( model=model, batch_sizes=q_batch_sizes, samplers=samplers, ) self.assertEqual(len(qMS._valfunc_cls), 4) self.assertEqual(len(qMS.inner_samplers), 4) self.assertEqual(len(qMS._valfunc_argfacs), 4) # _construct_inner_samplers error catching tests below # AnalyticAcquisitionFunction with MCAcquisitionObjective with self.assertRaises(UnsupportedError): qMultiStepLookahead( model=model, objective=IdentityMCObjective(), batch_sizes=q_batch_sizes, valfunc_cls=[ExpectedImprovement] * 4, valfunc_argfacs=[make_best_f] * 4, num_fantasies=num_fantasies, ) # AnalyticAcquisitionFunction and q > 1 with self.assertRaises(UnsupportedError): qMultiStepLookahead( model=model, batch_sizes=[2, 2, 2], valfunc_cls=[ExpectedImprovement] * 4, valfunc_argfacs=[make_best_f] * 4, num_fantasies=num_fantasies, inner_mc_samples=[2] * 4, ) # AnalyticAcquisitionFunction and inner_mc_samples with self.assertWarns(Warning): qMultiStepLookahead( model=model, batch_sizes=q_batch_sizes, valfunc_cls=[ExpectedImprovement] * 4, valfunc_argfacs=[make_best_f] * 4, num_fantasies=num_fantasies, inner_mc_samples=[2] * 4, ) # MCAcquisitionFunction and non MCAcquisitionObjective with self.assertRaises(UnsupportedError): qMultiStepLookahead( model=model, objective=ScalarizedObjective(weights=torch.tensor([1.0])), batch_sizes=[2, 2, 2], valfunc_cls=[qExpectedImprovement] * 4, valfunc_argfacs=[make_best_f] * 4, num_fantasies=num_fantasies, inner_mc_samples=[2] * 4, ) # test warmstarting qMS = qMultiStepLookahead( model=model, batch_sizes=q_batch_sizes, samplers=samplers, ) q_prime = qMS.get_augmented_q_batch_size(q) eval_X = torch.rand(t_batch_size + [q_prime, d]) warmstarted_X = warmstart_multistep( acq_function=qMS, bounds=bounds, num_restarts=5, raw_samples=10, full_optimizer=eval_X, ) self.assertEqual(warmstarted_X.shape, torch.Size([5, q_prime, d]))
torch_to_jax = lambda x: x.numpy() num_seed_points = 7 train_x = jax_to_torch([ flatten(init_random_params(rp.poop(), (-1, 28 * 28))[1]) @ basis for _ in range(num_seed_points) ]) train_y = jax_to_torch([loss_and_grad(torch_to_jax(train_x[i, :])) for i in range(num_seed_points)]) model = GPWithDerivatives(train_X=train_x, train_Y=train_y) # model.likelihood.noise_covar.register_constraint("raw_noise", GreaterThan(1e1)) for it in range(25): t0 = time.time() # Negate the first element of the objective, since botorch maximizes. objective = ScalarizedObjective(torch.tensor([-1.0] + [0.0] * dim_basis)) # q: number of candidates. q = 10 # candidate_x has shape (q, dim_basis) t1 = time.time() candidate_x, acq_value = optimize_acqf( # UpperConfidenceBound(model, beta=0.1, objective=objective), qUpperConfidenceBound(model, beta=0.1, objective=objective), bounds=torch.Tensor([[-1] * dim_basis, [1] * dim_basis]), q=q, # Not sure why these are necessary: num_restarts=1, raw_samples=1, )
def test_KnowledgeGradient(self): model = KnowledgeGradient() model.fit( Xs=self.Xs, Ys=self.Ys, Yvars=self.Yvars, search_space_digest=SearchSpaceDigest( feature_names=self.feature_names, bounds=self.bounds, ), metric_names=self.metric_names, ) n = 2 X_dummy = torch.rand(1, n, 4, dtype=self.dtype, device=self.device) acq_dummy = torch.tensor(0.0, dtype=self.dtype, device=self.device) with mock.patch(self.optimize_acqf) as mock_optimize_acqf: mock_optimize_acqf.side_effect = [(X_dummy, acq_dummy)] Xgen, wgen, _, __ = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=None, model_gen_options={ "acquisition_function_kwargs": self.acq_options, "optimizer_kwargs": self.optimizer_options, }, ) self.assertTrue(torch.equal(Xgen, X_dummy.cpu())) self.assertTrue(torch.equal(wgen, torch.ones(n, dtype=self.dtype))) # called once, the best point call is not caught by mock mock_optimize_acqf.assert_called_once() ini_dummy = torch.rand(10, 32, 3, dtype=self.dtype, device=self.device) optimizer_options2 = { "num_restarts": 1, "raw_samples": 1, "maxiter": 5, "batch_limit": 1, "partial_restarts": 2, } with mock.patch( "ax.models.torch.botorch_kg.gen_one_shot_kg_initial_conditions", return_value=ini_dummy, ) as mock_warmstart_initialization: Xgen, wgen, _, __ = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=None, model_gen_options={ "acquisition_function_kwargs": self.acq_options, "optimizer_kwargs": optimizer_options2, }, ) mock_warmstart_initialization.assert_called_once() obj = ScalarizedObjective(weights=self.objective_weights) dummy_acq = PosteriorMean(model=model.model, objective=obj) with mock.patch("ax.models.torch.utils.PosteriorMean", return_value=dummy_acq) as mock_posterior_mean: Xgen, wgen, _, __ = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=None, model_gen_options={ "acquisition_function_kwargs": self.acq_options, "optimizer_kwargs": optimizer_options2, }, ) self.assertEqual(mock_posterior_mean.call_count, 2) # Check best point selection within bounds (some numerical tolerance) xbest = model.best_point(bounds=self.bounds, objective_weights=self.objective_weights) lb = torch.tensor([b[0] for b in self.bounds]) - 1e-5 ub = torch.tensor([b[1] for b in self.bounds]) + 1e-5 self.assertTrue(torch.all(xbest <= ub)) self.assertTrue(torch.all(xbest >= lb)) # test error message linear_constraints = ( torch.tensor([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), torch.tensor([[0.5], [1.0]]), ) with self.assertRaises(UnsupportedError): Xgen, wgen = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=linear_constraints, ) # test input warping self.assertFalse(model.use_input_warping) model = KnowledgeGradient(use_input_warping=True) model.fit( Xs=self.Xs, Ys=self.Ys, Yvars=self.Yvars, search_space_digest=SearchSpaceDigest( feature_names=self.feature_names, bounds=self.bounds, ), metric_names=self.metric_names, ) self.assertTrue(model.use_input_warping) self.assertTrue(hasattr(model.model, "input_transform")) self.assertIsInstance(model.model.input_transform, Warp) # test loocv pseudo likelihood self.assertFalse(model.use_loocv_pseudo_likelihood) model = KnowledgeGradient(use_loocv_pseudo_likelihood=True) model.fit( Xs=self.Xs, Ys=self.Ys, Yvars=self.Yvars, search_space_digest=SearchSpaceDigest( feature_names=self.feature_names, bounds=self.bounds, ), metric_names=self.metric_names, ) self.assertTrue(model.use_loocv_pseudo_likelihood)
def test_KnowledgeGradient(self): model = KnowledgeGradient() model.fit( Xs=self.Xs, Ys=self.Ys, Yvars=self.Yvars, bounds=self.bounds, feature_names=self.feature_names, metric_names=self.metric_names, task_features=[], fidelity_features=[], ) n = 2 best_point_dummy = torch.rand(1, 3, dtype=self.dtype, device=self.device) X_dummy = torch.rand(1, n, 4, dtype=self.dtype, device=self.device) acq_dummy = torch.tensor(0.0, dtype=self.dtype, device=self.device) with mock.patch(self.optimize_acqf) as mock_optimize_acqf: mock_optimize_acqf.side_effect = [ (best_point_dummy, None), (X_dummy, acq_dummy), ] Xgen, wgen, _ = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=None, model_gen_options={ "acquisition_function_kwargs": self.acq_options, "optimizer_kwargs": self.optimizer_options, }, ) self.assertTrue(torch.equal(Xgen, X_dummy.cpu())) self.assertTrue(torch.equal(wgen, torch.ones(n, dtype=self.dtype))) mock_optimize_acqf.assert_called( ) # called twice, once for best_point ini_dummy = torch.rand(10, 32, 3, dtype=self.dtype, device=self.device) optimizer_options2 = { "num_restarts": 1, "raw_samples": 1, "maxiter": 5, "batch_limit": 1, "partial_restarts": 2, } with mock.patch( "ax.models.torch.botorch_kg.gen_one_shot_kg_initial_conditions", return_value=ini_dummy, ) as mock_warmstart_initialization: Xgen, wgen, _ = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=None, model_gen_options={ "acquisition_function_kwargs": self.acq_options, "optimizer_kwargs": optimizer_options2, }, ) mock_warmstart_initialization.assert_called_once() obj = ScalarizedObjective(weights=self.objective_weights) dummy_acq = PosteriorMean(model=model.model, objective=obj) with mock.patch("ax.models.torch.botorch_kg.PosteriorMean", return_value=dummy_acq) as mock_posterior_mean: Xgen, wgen, _ = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=None, model_gen_options={ "acquisition_function_kwargs": self.acq_options, "optimizer_kwargs": optimizer_options2, }, ) self.assertEqual(mock_posterior_mean.call_count, 2) # Check best point selection X_dummy = torch.rand(3) acq_dummy = torch.tensor(0.0) with mock.patch(self.optimize_acqf, return_value=(X_dummy, acq_dummy)) as mock_optimize_acqf: xbest = model.best_point(bounds=self.bounds, objective_weights=self.objective_weights) self.assertTrue(torch.equal(xbest, X_dummy)) mock_optimize_acqf.assert_called_once() # test error message linear_constraints = ( torch.tensor([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), torch.tensor([[0.5], [1.0]]), ) with self.assertRaises(UnsupportedError): Xgen, wgen = model.gen( n=n, bounds=self.bounds, objective_weights=self.objective_weights, outcome_constraints=None, linear_constraints=linear_constraints, )