Esempio n. 1
0
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
Esempio n. 2
0
def get_PosteriorMean(
    model: Model,
    objective_weights: Tensor,
    outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None,
    X_observed: Optional[Tensor] = None,
    X_pending: Optional[Tensor] = None,
    **kwargs: Any,
) -> AcquisitionFunction:
    r"""Instantiates a PosteriorMean acquisition function.

    Note: If no OutcomeConstraints given, return an analytic acquisition
    function. This requires {optimizer_kwargs: {joint_optimization: True}} or an
    optimizer that does not assume pending point support.

    Args:
        objective_weights: The objective is to maximize a weighted sum of
            the columns of f(x). These are the weights.
        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: A tensor containing points observed for all objective
            outcomes and outcomes that appear in the outcome constraints (if
            there are any).
        X_pending: A tensor containing points whose evaluation is pending (i.e.
            that have been submitted for evaluation) present for all objective
            outcomes and outcomes that appear in the outcome constraints (if
            there are any).

    Returns:
        PosteriorMean: The instantiated acquisition function.
    """
    if X_observed is None:
        raise ValueError("There are no feasible observed points.")
    # construct Objective module
    if kwargs.get("chebyshev_scalarization", False):
        obj_tf = get_chebyshev_scalarization(
            weights=objective_weights,
            Y=torch.stack(kwargs.get("Ys")).transpose(0, 1).squeeze(-1),
        )
    else:
        obj_tf = get_objective_weights_transform(objective_weights)

    def obj_fn(samples: Tensor, X: Optional[Tensor] = None) -> Tensor:
        return obj_tf(samples)

    if outcome_constraints is None:
        objective = GenericMCObjective(objective=obj_fn)
    else:
        con_tfs = get_outcome_constraint_transforms(outcome_constraints)
        inf_cost = get_infeasible_cost(X=X_observed,
                                       model=model,
                                       objective=obj_fn)
        objective = ConstrainedMCObjective(objective=obj_fn,
                                           constraints=con_tfs or [],
                                           infeasible_cost=inf_cost)
    # Use qSimpleRegret, not analytic posterior, to handle arbitrary objective fns.
    acq_func = qSimpleRegret(model, objective=objective)
    return acq_func
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))
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
Esempio n. 5
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. 6
0
def get_NEI(
    model: Model,
    objective_weights: Tensor,
    outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None,
    X_observed: Optional[Tensor] = None,
    X_pending: Optional[Tensor] = None,
    **kwargs: Any,
) -> AcquisitionFunction:
    r"""Instantiates a qNoisyExpectedImprovement acquisition function.

    Args:
        model: The underlying model which the acqusition function uses
            to estimate acquisition values of candidates.
        objective_weights: The objective is to maximize a weighted sum of
            the columns of f(x). These are the weights.
        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: A tensor containing points observed for all objective
            outcomes and outcomes that appear in the outcome constraints (if
            there are any).
        X_pending: A tensor containing points whose evaluation is pending (i.e.
            that have been submitted for evaluation) present for all objective
            outcomes and outcomes that appear in the outcome constraints (if
            there are any).
        mc_samples: The number of MC samples to use (default: 512).
        qmc: If True, use qMC instead of MC (default: True).
        prune_baseline: If True, prune the baseline points for NEI (default: True).
        chebyshev_scalarization: Use augmented Chebyshev scalarization.

    Returns:
        qNoisyExpectedImprovement: The instantiated acquisition function.
    """
    if X_observed is None:
        raise ValueError("There are no feasible observed points.")
    # construct Objective module
    if kwargs.get("chebyshev_scalarization", False):
        if "Ys" not in kwargs:
            raise ValueError("Chebyshev Scalarization requires Ys argument")
        Y_tensor = torch.cat(kwargs.get("Ys"), dim=-1)
        obj_tf = get_chebyshev_scalarization(weights=objective_weights,
                                             Y=Y_tensor)
    else:
        obj_tf = get_objective_weights_transform(objective_weights)
    if outcome_constraints is None:
        objective = GenericMCObjective(objective=obj_tf)
    else:
        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 get_acquisition_function(
        acquisition_function_name="qNEI",
        model=model,
        objective=objective,
        X_observed=X_observed,
        X_pending=X_pending,
        prune_baseline=kwargs.get("prune_baseline", True),
        mc_samples=kwargs.get("mc_samples", 512),
        qmc=kwargs.get("qmc", True),
        # pyre-fixme[6]: Expected `Optional[int]` for 9th param but got
        #  `Union[float, int]`.
        seed=torch.randint(1, 10000, (1, )).item(),
    )
Esempio n. 7
0
def _get_acquisition_func(
    model: Model,
    acquisition_function_name: str,
    objective_weights: Tensor,
    outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None,
    X_observed: Optional[Tensor] = None,
    X_pending: Optional[Tensor] = None,
    mc_objective: Type[GenericMCObjective] = GenericMCObjective,
    constrained_mc_objective: Optional[
        Type[ConstrainedMCObjective]
    ] = ConstrainedMCObjective,
    mc_objective_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> AcquisitionFunction:
    r"""Instantiates a acquisition function.

    Args:
        model: The underlying model which the acqusition function uses
            to estimate acquisition values of candidates.
        acquisition_function_name: Name of the acquisition function.
        objective_weights: The objective is to maximize a weighted sum of
            the columns of f(x). These are the weights.
        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: A tensor containing points observed for all objective
            outcomes and outcomes that appear in the outcome constraints (if
            there are any).
        X_pending: A tensor containing points whose evaluation is pending (i.e.
            that have been submitted for evaluation) present for all objective
            outcomes and outcomes that appear in the outcome constraints (if
            there are any).
        mc_objective: GenericMCObjective class, used for constructing a
            MC-objective. If constructing a penalized MC-objective, pass in
            PenalizedMCObjective together with mc_objective_kwargs .
        constrained_mc_objective: ConstrainedMCObjective class, used when
            applying constraints on the outcomes.
        mc_objective_kwargs: kwargs for constructing MC-objective.
            For GenericMCObjective, leave it as None. For PenalizedMCObjective,
            it needs to be specified in the format of kwargs.
        mc_samples: The number of MC samples to use (default: 512).
        qmc: If True, use qMC instead of MC (default: True).
        prune_baseline: If True, prune the baseline points for NEI (default: True).
        chebyshev_scalarization: Use augmented Chebyshev scalarization.

    Returns:
        The instantiated acquisition function.
    """
    if X_observed is None:
        raise ValueError("There are no feasible observed points.")
    # construct Objective module
    if kwargs.get("chebyshev_scalarization", False):
        with torch.no_grad():
            Y = model.posterior(X_observed).mean
        obj_tf = get_chebyshev_scalarization(weights=objective_weights, Y=Y)
    else:
        obj_tf = get_objective_weights_transform(objective_weights)

    def objective(samples: Tensor, X: Optional[Tensor] = None) -> Tensor:
        return obj_tf(samples)

    if outcome_constraints is None:
        mc_objective_kwargs = {} if mc_objective_kwargs is None else mc_objective_kwargs
        objective = mc_objective(objective=objective, **mc_objective_kwargs)
    else:
        if constrained_mc_objective is None:
            raise ValueError(
                "constrained_mc_objective cannot be set to None "
                "when applying outcome constraints."
            )
        if issubclass(mc_objective, PenalizedMCObjective):
            raise RuntimeError(
                "Outcome constraints are not supported for PenalizedMCObjective."
            )
        con_tfs = get_outcome_constraint_transforms(outcome_constraints)
        inf_cost = get_infeasible_cost(X=X_observed, model=model, objective=objective)
        objective = constrained_mc_objective(
            objective=objective, constraints=con_tfs or [], infeasible_cost=inf_cost
        )
    return get_acquisition_function(
        acquisition_function_name=acquisition_function_name,
        model=model,
        objective=objective,
        X_observed=X_observed,
        X_pending=X_pending,
        prune_baseline=kwargs.get("prune_baseline", True),
        mc_samples=kwargs.get("mc_samples", 512),
        qmc=kwargs.get("qmc", True),
        # pyre-fixme[6]: Expected `Optional[int]` for 9th param but got
        #  `Union[float, int]`.
        seed=torch.randint(1, 10000, (1,)).item(),
        marginalize_dim=kwargs.get("marginalize_dim"),
    )
Esempio n. 8
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))