Esempio n. 1
0
 def test_normalize_unnormalize(self):
     for dtype in (torch.float, torch.double):
         X = torch.tensor([0.0, 0.25, 0.5], device=self.device, dtype=dtype).view(
             -1, 1
         )
         expected_X_normalized = torch.tensor(
             [0.0, 0.5, 1.0], device=self.device, dtype=dtype
         ).view(-1, 1)
         bounds = torch.tensor([0.0, 0.5], device=self.device, dtype=dtype).view(
             -1, 1
         )
         X_normalized = normalize(X, bounds=bounds)
         self.assertTrue(torch.equal(expected_X_normalized, X_normalized))
         self.assertTrue(torch.equal(X, unnormalize(X_normalized, bounds=bounds)))
         X2 = torch.tensor(
             [[0.25, 0.125, 0.0], [0.25, 0.0, 0.5]], device=self.device, dtype=dtype
         ).transpose(1, 0)
         expected_X2_normalized = torch.tensor(
             [[1.0, 0.5, 0.0], [0.5, 0.0, 1.0]], device=self.device, dtype=dtype
         ).transpose(1, 0)
         bounds2 = torch.tensor(
             [[0.0, 0.0], [0.25, 0.5]], device=self.device, dtype=dtype
         )
         X2_normalized = normalize(X2, bounds=bounds2)
         self.assertTrue(torch.equal(X2_normalized, expected_X2_normalized))
         self.assertTrue(torch.equal(X2, unnormalize(X2_normalized, bounds=bounds2)))
Esempio n. 2
0
 def obj(Y: Tensor, X: Optional[Tensor] = None) -> Tensor:
     # scale to [0,1]
     Y_normalized = normalize(Y, bounds=Y_bounds)
     # If minimizing an objective, convert Y_normalized values to [-1,0],
     # such that min(w*y) makes sense, we want all w*y's to be positive
     Y_normalized[..., minimize] = Y_normalized[..., minimize] - 1
     return chebyshev_obj(Y=Y_normalized)
Esempio n. 3
0
 def test_get_chebyshev_scalarization(self):
     tkwargs = {"device": self.device}
     Y_train = torch.rand(4, 2, **tkwargs)
     Y_bounds = torch.stack(
         [
             Y_train.min(dim=-2, keepdim=True).values,
             Y_train.max(dim=-2, keepdim=True).values,
         ],
         dim=0,
     )
     for dtype in (torch.float, torch.double):
         for batch_shape in (torch.Size([]), torch.Size([3])):
             tkwargs["dtype"] = dtype
             Y_test = torch.rand(batch_shape + torch.Size([5, 2]),
                                 **tkwargs)
             Y_train = Y_train.to(**tkwargs)
             Y_bounds = Y_bounds.to(**tkwargs)
             normalized_Y_test = normalize(Y_test, Y_bounds)
             # test wrong shape
             with self.assertRaises(BotorchTensorDimensionError):
                 get_chebyshev_scalarization(weights=torch.zeros(
                     3, **tkwargs),
                                             Y=Y_train)
             weights = torch.ones(2, **tkwargs)
             # test batch Y
             with self.assertRaises(NotImplementedError):
                 get_chebyshev_scalarization(weights=weights,
                                             Y=Y_train.unsqueeze(0))
             # basic test
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train)
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = normalized_Y_test.min(
                 dim=-1).values + 0.05 * normalized_Y_test.sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # test different alpha
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train, alpha=1.0)
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = normalized_Y_test.min(
                 dim=-1).values + normalized_Y_test.sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # Test different weights
             weights = torch.tensor([0.3, 0.7], **tkwargs)
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train)
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = (weights * normalized_Y_test).min(
                 dim=-1).values + 0.05 * (weights *
                                          normalized_Y_test).sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
Esempio n. 4
0
 def test_normalize_unnormalize(self, cuda=False):
     tkwargs = {"device": torch.device("cuda" if cuda else "cpu")}
     for dtype in (torch.float, torch.double):
         tkwargs["dtype"] = dtype
         X = torch.tensor([0.0, 0.25, 0.5], **tkwargs).view(-1, 1)
         expected_X_normalized = torch.tensor([0.0, 0.5, 1.0], **tkwargs).view(-1, 1)
         bounds = torch.tensor([0.0, 0.5], **tkwargs).view(-1, 1)
         X_normalized = normalize(X, bounds=bounds)
         self.assertTrue(torch.equal(expected_X_normalized, X_normalized))
         self.assertTrue(torch.equal(X, unnormalize(X_normalized, bounds=bounds)))
         X2 = torch.tensor(
             [[0.25, 0.125, 0.0], [0.25, 0.0, 0.5]], **tkwargs
         ).transpose(1, 0)
         expected_X2_normalized = torch.tensor(
             [[1.0, 0.5, 0.0], [0.5, 0.0, 1.0]], **tkwargs
         ).transpose(1, 0)
         bounds2 = torch.tensor([[0.0, 0.0], [0.25, 0.5]], **tkwargs)
         X2_normalized = normalize(X2, bounds=bounds2)
         self.assertTrue(torch.equal(X2_normalized, expected_X2_normalized))
         self.assertTrue(torch.equal(X2, unnormalize(X2_normalized, bounds=bounds2)))
Esempio n. 5
0
 def test_normalize_unnormalize(self, cuda=False):
     tkwargs = {"device": torch.device("cuda" if cuda else "cpu")}
     for dtype in (torch.float, torch.double):
         tkwargs["dtype"] = dtype
         X = torch.tensor([0.0, 0.25, 0.5], **tkwargs).view(-1, 1)
         expected_X_normalized = torch.tensor([0.0, 0.5, 1.0],
                                              **tkwargs).view(-1, 1)
         bounds = torch.tensor([0.0, 0.5], **tkwargs).view(-1, 1)
         X_normalized = normalize(X, bounds=bounds)
         self.assertTrue(torch.equal(expected_X_normalized, X_normalized))
         self.assertTrue(
             torch.equal(X, unnormalize(X_normalized, bounds=bounds)))
         X2 = torch.tensor([[0.25, 0.125, 0.0], [0.25, 0.0, 0.5]],
                           **tkwargs).transpose(1, 0)
         expected_X2_normalized = torch.tensor(
             [[1.0, 0.5, 0.0], [0.5, 0.0, 1.0]], **tkwargs).transpose(1, 0)
         bounds2 = torch.tensor([[0.0, 0.0], [0.25, 0.5]], **tkwargs)
         X2_normalized = normalize(X2, bounds=bounds2)
         self.assertTrue(torch.equal(X2_normalized, expected_X2_normalized))
         self.assertTrue(
             torch.equal(X2, unnormalize(X2_normalized, bounds=bounds2)))
Esempio n. 6
0
def sample_truncated_normal_perturbations(
    X: Tensor,
    n_discrete_points: int,
    sigma: float,
    bounds: Tensor,
    qmc: bool = True,
) -> Tensor:
    r"""Sample points around `X`.

    Sample perturbed points around `X` such that the added perturbations
    are sampled from N(0, sigma^2 I) and truncated to be within [0,1]^d.

    Args:
        X: A `n x d`-dim tensor starting points.
        n_discrete_points: The number of points to sample.
        sigma: The standard deviation of the additive gaussian noise for
            perturbing the points.
        bounds: A `2 x d`-dim tensor containing the bounds.
        qmc: A boolean indicating whether to use qmc.

    Returns:
        A `n_discrete_points x d`-dim tensor containing the sampled points.
    """
    X = normalize(X, bounds=bounds)
    d = X.shape[1]
    # sample points from N(X_center, sigma^2 I), truncated to be within
    # [0, 1]^d.
    if X.shape[0] > 1:
        rand_indices = torch.randint(X.shape[0], (n_discrete_points, ),
                                     device=X.device)
        X = X[rand_indices]
    if qmc:
        std_bounds = torch.zeros(2, d, dtype=X.dtype, device=X.device)
        std_bounds[1] = 1
        u = draw_sobol_samples(bounds=std_bounds, n=n_discrete_points,
                               q=1).squeeze(1)
    else:
        u = torch.rand((n_discrete_points, d), dtype=X.dtype, device=X.device)
    # compute bounds to sample from
    a = -X
    b = 1 - X
    # compute z-score of bounds
    alpha = a / sigma
    beta = b / sigma
    normal = Normal(0, 1)
    cdf_alpha = normal.cdf(alpha)
    # use inverse transform
    perturbation = normal.icdf(cdf_alpha + u *
                               (normal.cdf(beta) - cdf_alpha)) * sigma
    # add perturbation and clip points that are still outside
    perturbed_X = (X + perturbation).clamp(0.0, 1.0)
    return unnormalize(perturbed_X, bounds=bounds)
def generate_train_dataset(objective, bounds, task_list, training_size=20):
    # Sample data for each base task
    data_by_task = {}
    for task in task_list:
        # draw points from a sobol sequence
        raw_x = draw_sobol_samples(bounds=bounds,
                                   n=training_size,
                                   q=1,
                                   seed=task + 5397923).squeeze(1)
        # get observed values
        f_x = objective(raw_x, task)
        train_y = f_x + noise_std * torch.randn_like(f_x)
        train_yvar = torch.full_like(train_y, noise_std**2)
        # store training data
        data_by_task[task] = {
            # scale x to [0, 1]
            'train_x': normalize(raw_x, bounds=bounds),
            'train_y': train_y,
            'train_yvar': train_yvar,
        }
    return data_by_task
Esempio n. 8
0
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
Esempio n. 9
0
def qehvi_candidates_func(
    train_x: "torch.Tensor",
    train_obj: "torch.Tensor",
    train_con: Optional["torch.Tensor"],
    bounds: "torch.Tensor",
) -> "torch.Tensor":
    """Quasi MC-based batch Expected Hypervolume Improvement (qEHVI).

    The default value of ``candidates_func`` in :class:`~optuna.integration.BoTorchSampler`
    with multi-objective optimization when the number of objectives is three or less.

    .. seealso::
        :func:`~optuna.integration.botorch.qei_candidates_func` for argument and return value
        descriptions.
    """

    n_objectives = train_obj.size(-1)

    if train_con is not None:
        train_y = torch.cat([train_obj, train_con], dim=-1)

        is_feas = (train_con <= 0).all(dim=-1)
        train_obj_feas = train_obj[is_feas]

        constraints = []
        n_constraints = train_con.size(1)

        for i in range(n_constraints):
            constraints.append(lambda Z, i=i: Z[..., -n_constraints + i])
        additional_qehvi_kwargs = {
            "objective":
            IdentityMCMultiOutputObjective(outcomes=list(range(n_objectives))),
            "constraints":
            constraints,
        }
    else:
        train_y = train_obj

        train_obj_feas = train_obj

        additional_qehvi_kwargs = {}

    train_x = normalize(train_x, bounds=bounds)

    model = SingleTaskGP(train_x,
                         train_y,
                         outcome_transform=Standardize(m=train_y.shape[-1]))
    mll = ExactMarginalLogLikelihood(model.likelihood, model)
    fit_gpytorch_model(mll)

    # Approximate box decomposition similar to Ax when the number of objectives is large.
    # https://github.com/facebook/Ax/blob/master/ax/models/torch/botorch_moo_defaults
    if n_objectives > 2:
        alpha = 10**(-8 + n_objectives)
    else:
        alpha = 0.0
    partitioning = NondominatedPartitioning(num_outcomes=n_objectives,
                                            Y=train_obj_feas,
                                            alpha=alpha)

    ref_point = train_obj.min(dim=0).values - 1e-8
    ref_point_list = ref_point.tolist()

    acqf = qExpectedHypervolumeImprovement(
        model=model,
        ref_point=ref_point_list,
        partitioning=partitioning,
        sampler=SobolQMCNormalSampler(num_samples=256),
        **additional_qehvi_kwargs,
    )

    standard_bounds = torch.zeros_like(bounds)
    standard_bounds[1] = 1

    candidates, _ = optimize_acqf(
        acq_function=acqf,
        bounds=standard_bounds,
        q=1,
        num_restarts=20,
        raw_samples=1024,
        options={
            "batch_limit": 5,
            "maxiter": 200,
            "nonnegative": True
        },
        sequential=True,
    )

    candidates = unnormalize(candidates.detach(), bounds=bounds)

    return candidates
Esempio n. 10
0
 def obj(Y: Tensor, X: Optional[Tensor] = None) -> Tensor:
     # scale to [0,1]
     Y_normalized = normalize(Y, bounds=Y_bounds)
     product = weights * Y_normalized
     return product.min(dim=-1).values + alpha * product.sum(dim=-1)
Esempio n. 11
0
 def obj(Y: Tensor, X: Optional[Tensor] = None) -> Tensor:
     # scale to [0,1]
     Y_normalized = normalize(Y, bounds=Y_bounds)
     return chebyshev_obj(Y=Y_normalized)
Esempio n. 12
0
def run_RGPE(test_task: int, objective, bounds, base_model_list):
    input_dim = bounds.shape[1]
    best_rgpe_all = []
    best_argmax_rgpe_all = []
    # Average over multiple trials
    for trial in range(N_TRIALS):
        print(f"Trial {trial + 1} of {N_TRIALS}")
        best_BMs = []
        best_rgpe = []
        # Initial random observations
        raw_x = draw_sobol_samples(bounds=bounds,
                                   n=RANDOM_INITIALIZATION_SIZE,
                                   q=1,
                                   seed=trial).squeeze(1)
        train_x = normalize(raw_x, bounds=bounds)
        train_y_noiseless = objective(raw_x, shift=test_task)
        train_y = train_y_noiseless + noise_std * torch.randn_like(
            train_y_noiseless)
        train_yvar = torch.full_like(train_y, noise_std**2)
        # keep track of the best observed point at each iteration
        best_value = train_y.max().item()
        best_rgpe.append(best_value)

        # Run N_BATCH rounds of BayesOpt after the initial random batch
        for iteration in range(N_BATCH):
            target_model = get_fitted_model(train_x, train_y, train_yvar)
            model_list = base_model_list + [target_model]
            rank_weights = compute_rank_weights(
                train_x,
                train_y,
                base_model_list,
                target_model,
                NUM_POSTERIOR_SAMPLES,
            )

            # create model and acquisition function
            rgpe_model = RGPE(model_list, rank_weights)
            sampler_qnei = SobolQMCNormalSampler(num_samples=MC_SAMPLES)
            qNEI = qNoisyExpectedImprovement(
                model=rgpe_model,
                X_baseline=train_x,
                sampler=sampler_qnei,
            )

            # optimize
            candidate, _ = optimize_acqf(
                acq_function=qNEI,
                bounds=bounds,
                q=Q_BATCH_SIZE,
                num_restarts=N_RESTARTS,
                raw_samples=N_RESTART_CANDIDATES,
            )

            # fetch the new values
            new_x = candidate.detach()
            new_y_noiseless = objective(unnormalize(new_x, bounds=bounds),
                                        shift=test_task)
            new_y = new_y_noiseless + noise_std * torch.randn_like(
                new_y_noiseless)
            new_yvar = torch.full_like(new_y, noise_std**2)

            # update training points
            train_x = torch.cat((train_x, new_x))
            train_y = torch.cat((train_y, new_y))
            train_yvar = torch.cat((train_yvar, new_yvar))

            # get the new best observed value
            best_value = train_y.max().item()
            best_idx = torch.argmax(train_y).item()
            best_candidate = train_x[best_idx].view(1, -1)
            _, best_BM = objective(unnormalize(best_candidate, bounds=bounds),
                                   shift=test_task,
                                   include_BMs=True)
            best_rgpe.append(best_value)
            best_BMs.append(best_BM)

        best_rgpe_all.append(best_rgpe)
        best_argmax_rgpe_all.append(best_BMs)
    BM_winner_idx = np.argmax(np.array(best_rgpe_all)[:, -1], axis=0)
    BM_winner = np.reshape(np.array(best_argmax_rgpe_all[BM_winner_idx][-1]),
                           (2, input_dim))
    return BM_winner
Esempio n. 13
0
                gp_recon_model_name_str = ''.join(gp_recon_model_name)
                print('gp error model name', gp_recon_model_name_str)
                save_folder = os.path.join(data_folder, gp_recon_model_name_str)

                if not os.path.exists(os.path.join(data_folder, gp_recon_model_name_str)):
                    os.mkdir(os.path.join(data_folder, gp_recon_model_name_str))

                r_train_target = r_train.exp() if transfo == "exp" else r_train
                if transfo == 'normalize':
                    rbounds = torch.zeros(2, r_train.shape[1], **tkwargs)
                    rbounds[0] = torch.quantile(r_train, .0005, dim=0)
                    rbounds[1] = torch.quantile(r_train, .9995, dim=0)
                    rdelta = .05 * (rbounds[1] - rbounds[0])
                    rbounds[0] -= rdelta
                    rbounds[1] += rdelta
                    r_train_target = normalize(r_train, rbounds)

                train_kw_error = dict(
                    train_x=x_train,
                    train_y=r_train_target,
                    n_inducing_points=500,
                    tkwargs=tkwargs,
                    init=True,
                    scale=True,
                    covar_name=ker_name,
                    gp_file='',
                    save_file=os.path.join(data_folder, f"{gp_recon_model_name_str}/gp.npz"),
                    input_wp=inp_w,
                    outcome_transform=Standardize(m=1) if transfo == 'standardize' else None,
                    options={'lr': 1e-2, 'maxiter': 500}
                )
    def observe(self, X, y):
        """Send an observation of a suggestion back to the optimizer.
        Parameters
        ----------
        X : list of dict-like
            Places where the objective function has already been evaluated.
            Each suggestion is a dictionary where each key corresponds to a
            parameter being optimized.
        y : array-like, shape (n,)
            Corresponding values where objective has been evaluated
        """
        try:
            assert len(X) == len(y)
            c = 0

            for x_, y_ in zip(X, y):
                # Archive stores all the solutions
                self.archive.append(x_)
                self.arc_fitness.append(
                    -y_)  # As BoTorch solves a maximization problem

                if self.iter == 1:
                    self.population.append(x_)
                    self.fitness.append(y_)
                else:
                    if y_ <= self.fitness[c]:
                        self.population[c] = x_
                        self.fitness[c] = y_

                    c += 1

                # Just ignore, any inf observations we got, unclear if right thing
                if np.isfinite(y_):
                    self._observe(x_, y_)

            # Transform the data (seen till now) into tensors and train the model
            train_x = normalize(torch.from_numpy(
                self.search_space.warp(self.archive)),
                                bounds=self.torch_bounds)
            train_y = standardize(
                torch.from_numpy(
                    np.array(self.arc_fitness).reshape(len(self.arc_fitness),
                                                       1)))
            # Fit the GP based on the actual observed values
            if self.iter == 1:
                self.model, mll = self.make_model(train_x, train_y)
            else:
                self.model, mll = self.make_model(train_x, train_y,
                                                  self.model.state_dict())

            # mll.train()
            fit_gpytorch_model(mll)

            # define the sampler
            sampler = SobolQMCNormalSampler(num_samples=512)

            # define the acquisition function
            self.acquisition = qExpectedImprovement(model=self.model,
                                                    best_f=train_y.max(),
                                                    sampler=sampler)

        except Exception as e:
            print('Error: {} in observe()'.format(e))
Esempio n. 15
0
def qei_candidates_func(
    train_x: "torch.Tensor",
    train_obj: "torch.Tensor",
    train_con: Optional["torch.Tensor"],
    bounds: "torch.Tensor",
) -> "torch.Tensor":
    """Quasi MC-based batch Expected Improvement (qEI).

    The default value of ``candidates_func`` in :class:`~optuna.integration.BoTorchSampler`
    with single-objective optimization.

    Args:
        train_x:
            Previous parameter configurations. A ``torch.Tensor`` of shape
            ``(n_trials, n_params)``. ``n_trials`` is the number of already observed trials
            and ``n_params`` is the number of parameters. ``n_params`` may be larger than the
            actual number of parameters if categorical parameters are included in the search
            space, since these parameters are one-hot encoded.
            Values are not normalized.
        train_obj:
            Previously observed objectives. A ``torch.Tensor`` of shape
            ``(n_trials, n_objectives)``. ``n_trials`` is identical to that of ``train_x``.
            ``n_objectives`` is the number of objectives. Observations are not normalized.
        train_con:
            Objective constraints. A ``torch.Tensor`` of shape ``(n_trials, n_constraints)``.
            ``n_trials`` is identical to that of ``train_x``. ``n_constraints`` is the number of
            constraints. A constraint is violated if strictly larger than 0. If no constraints are
            involved in the optimization, this argument will be :obj:`None`.
        bounds:
            Search space bounds. A ``torch.Tensor`` of shape ``(n_params, 2)``. ``n_params`` is
            identical to that of ``train_x``. The first and the second column correspond to the
            lower and upper bounds for each parameter respectively.

    Returns:
        Next set of candidates. Usually the return value of BoTorch's ``optimize_acqf``.

    """

    if train_obj.size(-1) != 1:
        raise ValueError("Objective may only contain single values with qEI.")
    if train_con is not None:
        train_y = torch.cat([train_obj, train_con], dim=-1)

        is_feas = (train_con <= 0).all(dim=-1)
        train_obj_feas = train_obj[is_feas]

        if train_obj_feas.numel() == 0:
            # TODO(hvy): Do not use 0 as the best observation.
            _logger.warning(
                "No objective values are feasible. Using 0 as the best objective in qEI."
            )
            best_f = torch.zeros(())
        else:
            best_f = train_obj_feas.max()

        constraints = []
        n_constraints = train_con.size(1)
        for i in range(n_constraints):
            constraints.append(lambda Z, i=i: Z[..., -n_constraints + i])
        objective = ConstrainedMCObjective(
            objective=lambda Z: Z[..., 0],
            constraints=constraints,
        )
    else:
        train_y = train_obj

        best_f = train_obj.max()

        objective = None  # Using the default identity objective.

    train_x = normalize(train_x, bounds=bounds)

    model = SingleTaskGP(train_x,
                         train_y,
                         outcome_transform=Standardize(m=train_y.size(-1)))
    mll = ExactMarginalLogLikelihood(model.likelihood, model)
    fit_gpytorch_model(mll)

    acqf = qExpectedImprovement(
        model=model,
        best_f=best_f,
        sampler=SobolQMCNormalSampler(num_samples=256),
        objective=objective,
    )

    standard_bounds = torch.zeros_like(bounds)
    standard_bounds[1] = 1

    candidates, _ = optimize_acqf(
        acq_function=acqf,
        bounds=standard_bounds,
        q=1,
        num_restarts=10,
        raw_samples=512,
        options={
            "batch_limit": 5,
            "maxiter": 200
        },
        sequential=True,
    )

    candidates = unnormalize(candidates.detach(), bounds=bounds)

    return candidates
Esempio n. 16
0
    def train_loop(self):
        from botorch.models import SingleTaskGP
        from botorch.fit import fit_gpytorch_model
        from gpytorch.mlls import ExactMarginalLogLikelihood
        from botorch.optim import optimize_acqf
        from botorch.acquisition.monte_carlo import qExpectedImprovement
        from botorch.sampling.samplers import SobolQMCNormalSampler

        seed = 1
        torch.manual_seed(seed)
        dt, d = torch.float32, 3
        lb, ub = [1e-4, 0.1, 0.1], [3e-3, 1 - 1e-3, 1 - 1e-3]
        bounds = torch.tensor([lb, ub], dtype=dt)

        def gen_initial_data():
            # auto
            # x = unnormalize(torch.rand(1, 3, dtype=dt), bounds=bounds)
            # manual
            x = torch.tensor([[1e-3, 0.9, 0.999]])
            print('BO Initialization: \n')
            print('Initial Hyper-parameter: ' + str(x))
            obj = self.train(x.view(-1))
            print('Initial Error: ' + str(obj))
            return x, obj.unsqueeze(1)

        def get_fitted_model(x, obj, state_dict=None):
            # initialize and fit model
            fitted_model = SingleTaskGP(train_X=x, train_Y=obj)
            if state_dict is not None:
                fitted_model.load_state_dict(state_dict)
            mll = ExactMarginalLogLikelihood(fitted_model.likelihood,
                                             fitted_model)
            mll.to(x)
            fit_gpytorch_model(mll)
            return fitted_model

        def optimize_acqf_and_get_observation(acq_func):
            """Optimizes the acquisition function,
            and returns a new candidate and a noisy observation"""
            candidates, _ = optimize_acqf(
                acq_function=acq_func,
                bounds=torch.stack([
                    torch.zeros(d, dtype=dt),
                    torch.ones(d, dtype=dt),
                ]),
                q=1,
                num_restarts=10,
                raw_samples=200,
            )

            x = unnormalize(candidates.detach(), bounds=bounds)
            print('Hyper-parameter: ' + str(x))
            obj = self.train(x.view(-1)).unsqueeze(-1)
            print(print('Error: ' + str(obj)))
            return x, obj

        N_BATCH = 500
        MC_SAMPLES = 2000
        best_observed = []
        train_x, train_obj = gen_initial_data()  # (1,3), (1,1)
        best_observed.append(train_obj.view(-1))

        print(f"\nRunning BO......\n ", end='')
        state_dict = None
        for iteration in range(N_BATCH):
            # fit the model
            model = get_fitted_model(
                normalize(train_x, bounds=bounds),
                standardize(train_obj),
                state_dict=state_dict,
            )

            # define the qNEI acquisition module using a QMC sampler
            qmc_sampler = SobolQMCNormalSampler(num_samples=MC_SAMPLES,
                                                seed=seed)
            qEI = qExpectedImprovement(model=model,
                                       sampler=qmc_sampler,
                                       best_f=standardize(train_obj).max())

            # optimize and get new observation
            new_x, new_obj = optimize_acqf_and_get_observation(qEI)

            # update training points
            train_x = torch.cat((train_x, new_x))
            train_obj = torch.cat((train_obj, new_obj))

            # update progress
            best_value = train_obj.max().item()
            best_observed.append(best_value)

            state_dict = model.state_dict()
            print(".", end='')

        print(best_observed)
Esempio n. 17
0
 def test_get_chebyshev_scalarization(self):
     tkwargs = {"device": self.device}
     Y_train = torch.rand(4, 2, **tkwargs)
     Y_bounds = torch.stack(
         [
             Y_train.min(dim=-2, keepdim=True).values,
             Y_train.max(dim=-2, keepdim=True).values,
         ],
         dim=0,
     )
     for dtype in (torch.float, torch.double):
         for batch_shape in (torch.Size([]), torch.Size([3])):
             tkwargs["dtype"] = dtype
             Y_test = torch.rand(batch_shape + torch.Size([5, 2]),
                                 **tkwargs)
             Y_train = Y_train.to(**tkwargs)
             Y_bounds = Y_bounds.to(**tkwargs)
             normalized_Y_test = normalize(Y_test, Y_bounds)
             # test wrong shape
             with self.assertRaises(BotorchTensorDimensionError):
                 get_chebyshev_scalarization(weights=torch.zeros(
                     3, **tkwargs),
                                             Y=Y_train)
             weights = torch.ones(2, **tkwargs)
             # test batch Y
             with self.assertRaises(NotImplementedError):
                 get_chebyshev_scalarization(weights=weights,
                                             Y=Y_train.unsqueeze(0))
             # basic test
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train)
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = normalized_Y_test.min(
                 dim=-1).values + 0.05 * normalized_Y_test.sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # test different alpha
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train, alpha=1.0)
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = normalized_Y_test.min(
                 dim=-1).values + normalized_Y_test.sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # Test different weights
             weights = torch.tensor([0.3, 0.7], **tkwargs)
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train)
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = (weights * normalized_Y_test).min(
                 dim=-1).values + 0.05 * (weights *
                                          normalized_Y_test).sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # test that when minimizing an objective (i.e. with a negative weight),
             # normalized Y values are shifted from [0,1] to [-1,0]
             weights = torch.tensor([0.3, -0.7], **tkwargs)
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train)
             Y_transformed = objective_transform(Y_test)
             normalized_Y_test[..., -1] = normalized_Y_test[..., -1] - 1
             expected_Y_transformed = (weights * normalized_Y_test).min(
                 dim=-1).values + 0.05 * (weights *
                                          normalized_Y_test).sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # test that with no observations there is no normalization
             weights = torch.tensor([0.3, 0.7], **tkwargs)
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=Y_train[:0])
             Y_transformed = objective_transform(Y_test)
             expected_Y_transformed = (weights * Y_test).min(
                 dim=-1).values + 0.05 * (weights * Y_test).sum(dim=-1)
             self.assertTrue(
                 torch.equal(Y_transformed, expected_Y_transformed))
             # test that with one observation, we normalize by subtracting Y_train
             single_Y_train = Y_train[:1]
             objective_transform = get_chebyshev_scalarization(
                 weights=weights, Y=single_Y_train)
             Y_transformed = objective_transform(Y_test)
             normalized_Y_test = Y_test - single_Y_train
             expected_Y_transformed = (weights * normalized_Y_test).min(
                 dim=-1).values + 0.05 * (weights *
                                          normalized_Y_test).sum(dim=-1)
             self.assertTrue(
                 torch.allclose(Y_transformed, expected_Y_transformed))