def evaluate(self, X_actual: Tensor, bounds: Tensor, **kwargs: Any) -> Tensor: r"""Evaluate qKnowledgeGradient on the candidate set `X_actual` by solving the inner optimization problem. Args: X_actual: A `b x q x d` Tensor with `b` t-batches of `q` design points each. Unlike `forward()`, this does not include solutions of the inner optimization problem. bounds: A `2 x d` tensor of lower and upper bounds for each column of the solutions to the inner problem. kwargs: Additional keyword arguments. This includes the options for optimization of the inner problem, i.e. `num_restarts`, `raw_samples`, an `options` dictionary to be passed on to the optimization helpers, and a `scipy_options` dictionary to be passed to `scipy.minimize`. Returns: A Tensor of shape `b`. For t-batch b, the q-KG value of the design `X_actual[b]` is averaged across the fantasy models. NOTE: If `current_value` is not provided, then this is not the true KG value of `X_actual[b]`. """ # construct the fantasy model of shape `num_fantasies x b` fantasy_model = self.model.fantasize( X=X_actual, sampler=self.sampler, observation_noise=True ) # get the value function value_function = _get_value_function( model=fantasy_model, objective=self.objective, sampler=self.inner_sampler ) # optimize the inner problem from botorch.optim.initializers import gen_value_function_initial_conditions from botorch.generation.gen import gen_candidates_scipy initial_conditions = gen_value_function_initial_conditions( acq_function=value_function, bounds=bounds, num_restarts=kwargs.get("num_restarts", 20), raw_samples=kwargs.get("raw_samples", 1024), current_model=self.model, options={**kwargs.get("options", {}), **kwargs.get("scipy_options", {})}, ) _, values = gen_candidates_scipy( initial_conditions=initial_conditions, acquisition_function=value_function, lower_bounds=bounds[0], upper_bounds=bounds[1], options=kwargs.get("scipy_options"), ) # get the maximizer for each batch values, _ = torch.max(values, dim=0) if self.current_value is not None: values = values - self.current_value # return average over the fantasy samples return values.mean(dim=0)
def test_gen_value_function_initial_conditions(self): num_fantasies = 2 num_solutions = 3 num_restarts = 4 raw_samples = 5 n_train = 6 dim = 2 dtype = torch.float # run a thorough test with dtype float train_X = torch.rand(n_train, dim, device=self.device, dtype=dtype) train_Y = torch.rand(n_train, 1, device=self.device, dtype=dtype) model = SingleTaskGP(train_X, train_Y) fant_X = torch.rand(num_solutions, 1, dim, device=self.device, dtype=dtype) fantasy_model = model.fantasize(fant_X, IIDNormalSampler(num_fantasies)) bounds = torch.tensor([[0, 0], [1, 1]], device=self.device, dtype=dtype) value_function = PosteriorMean(fantasy_model) # test option error with self.assertRaises(ValueError): gen_value_function_initial_conditions( acq_function=value_function, bounds=bounds, num_restarts=num_restarts, raw_samples=raw_samples, current_model=model, options={"frac_random": 2.0}, ) # test output shape ics = gen_value_function_initial_conditions( acq_function=value_function, bounds=bounds, num_restarts=num_restarts, raw_samples=raw_samples, current_model=model, ) self.assertEqual( ics.shape, torch.Size([num_restarts, num_fantasies, num_solutions, 1, dim])) # test bounds self.assertTrue(torch.all(ics >= bounds[0])) self.assertTrue(torch.all(ics <= bounds[1])) # test dtype self.assertEqual(dtype, ics.dtype) # minimal test cases for when all raw samples are random, with dtype double dtype = torch.double n_train = 2 dim = 1 num_solutions = 1 train_X = torch.rand(n_train, dim, device=self.device, dtype=dtype) train_Y = torch.rand(n_train, 1, device=self.device, dtype=dtype) model = SingleTaskGP(train_X, train_Y) fant_X = torch.rand(1, 1, dim, device=self.device, dtype=dtype) fantasy_model = model.fantasize(fant_X, IIDNormalSampler(num_fantasies)) bounds = torch.tensor([[0], [1]], device=self.device, dtype=dtype) value_function = PosteriorMean(fantasy_model) ics = gen_value_function_initial_conditions( acq_function=value_function, bounds=bounds, num_restarts=1, raw_samples=1, current_model=model, options={"frac_random": 0.99}, ) self.assertEqual(ics.shape, torch.Size([1, num_fantasies, num_solutions, 1, dim])) # test bounds self.assertTrue(torch.all(ics >= bounds[0])) self.assertTrue(torch.all(ics <= bounds[1])) # test dtype self.assertEqual(dtype, ics.dtype)