def optimize_qparego_and_get_observation(model, train_obj, sampler): """Samples a set of random weights for each candidate in the batch, performs sequential greedy optimization of the qParEGO acquisition function, and returns a new candidate and observation.""" acq_func_list = [] for _ in range(BATCH_SIZE): weights = sample_simplex(problem.num_objectives, **tkwargs).squeeze() objective = GenericMCObjective( get_chebyshev_scalarization(weights=weights, Y=train_obj)) acq_func = qExpectedImprovement( # pyre-ignore: [28] model=model, objective=objective, best_f=objective(train_obj).max(), sampler=sampler, ) acq_func_list.append(acq_func) # optimize candidates, _ = optimize_acqf_list( acq_function_list=acq_func_list, bounds=standard_bounds, num_restarts=NUM_RESTARTS, raw_samples=RAW_SAMPLES, # used for intialization heuristic options={ "batch_limit": 5, "maxiter": 200 }, ) # observe new values new_x = unnormalize(candidates.detach(), bounds=problem.bounds) new_obj = problem(new_x) return new_x, new_obj
def randomize_objective_weights(objective_weights: Tensor, **acquisition_function_kwargs: Any) -> Tensor: """Generate a random weighting based on acquisition function settings. Args: objective_weights: Base weights to multiply by random values.. **acquisition_function_kwargs: Kwargs containing weight generation algorithm options. Returns: A normalized list of indices such that each index is between `0` and `d-1`. """ # Set distribution and sample weights. distribution = acquisition_function_kwargs.get( "random_scalarization_distribution", SIMPLEX) dtype = objective_weights.dtype device = objective_weights.device if distribution == SIMPLEX: random_weights = sample_simplex(len(objective_weights), dtype=dtype, device=device).squeeze() elif distribution == HYPERSPHERE: random_weights = torch.abs( sample_hypersphere(len(objective_weights), dtype=dtype, device=device).squeeze()) # pyre-fixme[61]: `random_weights` may not be initialized here. objective_weights = torch.mul(objective_weights, random_weights) return objective_weights
def test_sample_simplex(self): for d, n, qmc, seed, dtype in itertools.product( (1, 2, 3), (2, 5), (False, True), (None, 1234), (torch.float, torch.double) ): samples = sample_simplex( d=d, n=n, qmc=qmc, seed=seed, device=self.device, dtype=dtype ) self.assertEqual(samples.shape, torch.Size([n, d])) self.assertTrue(torch.all(samples >= 0)) self.assertTrue(torch.all(samples <= 1)) self.assertTrue(torch.max((samples.sum(dim=-1) - 1).abs()) < 1e-5) self.assertEqual(samples.device.type, self.device.type) self.assertEqual(samples.dtype, dtype)
def gen_pareto_front(self, n: int) -> Tensor: r"""Generate `n` pareto optimal points. The pareto points randomly sampled from the hyperplane sum_i f(x_i) = 0.5. """ f_X = 0.5 * sample_simplex( n=n, d=self.num_objectives, qmc=True, dtype=self.ref_point.dtype, device=self.ref_point.device, ) if self.negate: f_X *= -1 return f_X
def optimize_qparego_and_get_observation(model, train_obj, train_con, sampler, obj_func, time_list, global_start_time): """Samples a set of random weights for each candidate in the batch, performs sequential greedy optimization of the qParEGO acquisition function, and returns a new candidate and observation.""" acq_func_list = [] for _ in range(1): # sample random weights weights = sample_simplex(problem.num_objs, **tkwargs).squeeze() # construct augmented Chebyshev scalarization scalarization = get_chebyshev_scalarization(weights=weights, Y=train_obj) # initialize ConstrainedMCObjective constrained_objective = get_constrained_mc_objective(train_obj=train_obj, train_con=train_con, scalarization=scalarization) train_y = torch.cat([train_obj, train_con], dim=-1) acq_func = qExpectedImprovement( # pyre-ignore: [28] model=model, objective=constrained_objective, best_f=constrained_objective(train_y).max(), sampler=sampler, ) acq_func_list.append(acq_func) # optimize candidates, _ = optimize_acqf_list( acq_function_list=acq_func_list, bounds=standard_bounds, num_restarts=20, raw_samples=1024, # used for intialization heuristic options={"batch_limit": 5, "maxiter": 200}, ) # observe new values new_x = candidates.detach() new_obj = [] new_con = [] for x in new_x: res = obj_func(x) y = res['objs'] c = res['constraints'] new_obj.append(y) new_con.append(c) global_time = time.time() - global_start_time time_list.append(global_time) new_obj = torch.tensor(new_obj, **tkwargs).reshape(new_x.shape[0], -1) new_con = torch.tensor(new_con, **tkwargs).reshape(new_x.shape[0], -1) print(f'evaluate {new_x.shape[0]} configs on real objective') return new_x, new_obj, new_con
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 sample_simplex(dim: int) -> Tensor: """Sample uniformly from a dim-simplex.""" return botorch_sampling.sample_simplex(dim, dtype=torch.double).squeeze()