Source code for ax.models.torch.botorch_modular.acquisition

#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from __future__ import annotations

from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union

from ax.core.types import TConfig
from ax.models.torch.botorch_modular.list_surrogate import ListSurrogate
from ax.models.torch.botorch_modular.surrogate import Surrogate
from ax.models.torch.utils import (
    _get_X_pending_and_observed,
    get_botorch_objective,
    subset_model,
)
from ax.utils.common.base import Base
from ax.utils.common.constants import Keys
from ax.utils.common.docutils import copy_doc
from ax.utils.common.typeutils import checked_cast, not_none
from botorch.acquisition.acquisition import AcquisitionFunction
from botorch.acquisition.analytic import AnalyticAcquisitionFunction
from botorch.acquisition.objective import AcquisitionObjective
from botorch.models.model import Model
from botorch.optim.optimize import optimize_acqf
from botorch.utils.containers import TrainingData
from torch import Tensor


[docs]class Optimizer: # NOTE: Stub for future BoTorch optimizer class. pass
[docs]class Acquisition(Base): """ **All classes in 'botorch_modular' directory are under construction, incomplete, and should be treated as alpha versions only.** Ax wrapper for BoTorch `AcquisitionFunction`, subcomponent of `BoTorchModel` and is not meant to be used outside of it. Args: surrogate: Surrogate model, with which this acquisition function will be used. bounds: A list of (lower, upper) tuples for each column of X in the training data of the surrogate model. objective_weights: The objective is to maximize a weighted sum of the columns of f(x). These are the weights. botorch_acqf_class: Type of BoTorch `AcquistitionFunction` that should be used. Subclasses of `Acquisition` often specify these via `default_botorch_acqf_class` attribute, in which case specifying one here is not required. options: Optional mapping of kwargs to the underlying `Acquisition Function` in BoTorch. pending_observations: A list of tensors, each of which contains points whose evaluation is pending (i.e. that have been submitted for evaluation) for a given outcome. A list of m (k_i x d) feature tensors X for m outcomes and k_i, pending observations for outcome i. 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) linear_constraints: A tuple of (A, b). For k linear constraints on d-dimensional x, A is (k x d) and b is (k x 1) such that A x <= b. (Not used by single task models) fixed_features: A map {feature_index: value} for features that should be fixed to a particular value during generation. target_fidelities: Optional mapping from parameter name to its target fidelity, applicable to fidelity parameters only. """ surrogate: Surrogate acqf: AcquisitionFunction # BoTorch `AcquisitionFunction` class associated with this `Acquisition` # class by default. `None` for the base `Acquisition` class, but can be # specified in subclasses. default_botorch_acqf_class: Optional[Type[AcquisitionFunction]] = None # BoTorch `AcquisitionFunction` class associated with this `Acquisition` # instance. Determined during `__init__`, do not set manually. _botorch_acqf_class: Type[AcquisitionFunction] def __init__( self, surrogate: Surrogate, bounds: List[Tuple[float, float]], objective_weights: Tensor, botorch_acqf_class: Optional[Type[AcquisitionFunction]] = None, options: Optional[Dict[str, Any]] = None, pending_observations: Optional[List[Tensor]] = None, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, linear_constraints: Optional[Tuple[Tensor, Tensor]] = None, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, ) -> None: if not botorch_acqf_class and not self.default_botorch_acqf_class: raise ValueError( f"Acquisition class {self.__class__} does not specify a default " "BoTorch `AcquisitionFunction`, so `botorch_acqf_class` " "argument must be specified." ) self._botorch_acqf_class = not_none( botorch_acqf_class or self.default_botorch_acqf_class ) self.surrogate = surrogate self.options = options or {} trd = self._extract_training_data(surrogate=surrogate) Xs = ( # Assumes 1-D objective_weights, which should be safe. [trd.X for o in range(objective_weights.shape[0])] if isinstance(trd, TrainingData) else [i.X for i in trd.values()] ) X_pending, X_observed = _get_X_pending_and_observed( Xs=Xs, pending_observations=pending_observations, objective_weights=objective_weights, outcome_constraints=outcome_constraints, bounds=bounds, linear_constraints=linear_constraints, fixed_features=fixed_features, ) # Subset model only to the outcomes we need for the optimization. if self.options.get(Keys.SUBSET_MODEL, True): model, objective_weights, outcome_constraints, _ = subset_model( self.surrogate.model, objective_weights=objective_weights, outcome_constraints=outcome_constraints, ) else: model = self.surrogate.model objective = self._get_botorch_objective( model=model, objective_weights=objective_weights, outcome_constraints=outcome_constraints, X_observed=X_observed, ) model_deps = self.compute_model_dependencies( surrogate=surrogate, bounds=bounds, objective_weights=objective_weights, pending_observations=pending_observations, outcome_constraints=outcome_constraints, linear_constraints=linear_constraints, fixed_features=fixed_features, target_fidelities=target_fidelities, options=self.options, ) X_baseline = X_observed overriden_X_baseline = model_deps.get(Keys.X_BASELINE) if overriden_X_baseline is not None: X_baseline = overriden_X_baseline model_deps.pop(Keys.X_BASELINE) self._instantiate_acqf( model=model, objective=objective, model_dependent_kwargs=model_deps, X_pending=X_pending, X_baseline=X_baseline, )
[docs] def optimize( self, bounds: Tensor, n: int, optimizer_class: Optional[Optimizer] = None, inequality_constraints: Optional[List[Tuple[Tensor, Tensor, float]]] = None, fixed_features: Optional[Dict[int, float]] = None, rounding_func: Optional[Callable[[Tensor], Tensor]] = None, optimizer_options: Optional[Dict[str, Any]] = None, ) -> Tuple[Tensor, Tensor]: """Generate a set of candidates via multi-start optimization. Obtains candidates and their associated acquisition function values. """ optimizer_options = optimizer_options or {} # NOTE: Could make use of `optimizer_class` when its added to BoTorch. return optimize_acqf( self.acqf, bounds=bounds, q=n, inequality_constraints=inequality_constraints, fixed_features=fixed_features, post_processing_func=rounding_func, **optimizer_options, )
[docs] def evaluate(self, X: Tensor) -> Tensor: """Evaluate the acquisition function on the candidate set `X`. Args: X: A `batch_shape x q x d`-dim Tensor of t-batches with `q` `d`-dim design points each. Returns: A `batch_shape'`-dim Tensor of acquisition values at the given design points `X`, where `batch_shape'` is the broadcasted batch shape of model and input `X`. """ # NOTE: `AcquisitionFunction.__call__` calls `forward`, # so below is equivalent to `self.acqf.forward(X=X)`. return self.acqf(X=X)
[docs] @copy_doc(Surrogate.best_in_sample_point) def best_point( self, bounds: List[Tuple[float, float]], objective_weights: Tensor, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, linear_constraints: Optional[Tuple[Tensor, Tensor]] = None, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, options: Optional[TConfig] = None, ) -> Tuple[Tensor, float]: return self.surrogate.best_in_sample_point( bounds=bounds, objective_weights=objective_weights, outcome_constraints=outcome_constraints, linear_constraints=linear_constraints, fixed_features=fixed_features, options=options, )
[docs] def compute_model_dependencies( self, surrogate: Surrogate, bounds: List[Tuple[float, float]], objective_weights: Tensor, pending_observations: Optional[List[Tensor]] = None, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, linear_constraints: Optional[Tuple[Tensor, Tensor]] = None, fixed_features: Optional[Dict[int, float]] = None, target_fidelities: Optional[Dict[int, float]] = None, options: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Computes inputs to acquisition function class based on the given surrogate model. NOTE: When subclassing `Acquisition` from a superclass where this method returns a non-empty dictionary of kwargs to `AcquisitionFunction`, call `super().compute_model_dependencies` and then update that dictionary of options with the options for the subclass you are creating (unless the superclass' model dependencies should not be propagated to the subclass). See `MultiFidelityKnowledgeGradient.compute_model_dependencies` for an example. Args: surrogate: The surrogate object containing the BoTorch `Model`, with which this `Acquisition` is to be used. bounds: A list of (lower, upper) tuples for each column of X in the training data of the surrogate model. objective_weights: The objective is to maximize a weighted sum of the columns of f(x). These are the weights. pending_observations: A list of tensors, each of which contains points whose evaluation is pending (i.e. that have been submitted for evaluation) for a given outcome. A list of m (k_i x d) feature tensors X for m outcomes and k_i, pending observations for outcome i. 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) linear_constraints: A tuple of (A, b). For k linear constraints on d-dimensional x, A is (k x d) and b is (k x 1) such that A x <= b. (Not used by single task models) fixed_features: A map {feature_index: value} for features that should be fixed to a particular value during generation. target_fidelities: Optional mapping from parameter name to its target fidelity, applicable to fidelity parameters only. options: The `options` kwarg dict, passed on initialization of the `Acquisition` object. Returns: A dictionary of surrogate model-dependent options, to be passed as kwargs to BoTorch`AcquisitionFunction` constructor. """ return {}
def _get_botorch_objective( self, model: Model, objective_weights: Tensor, outcome_constraints: Optional[Tuple[Tensor, Tensor]] = None, X_observed: Optional[Tensor] = None, ) -> AcquisitionObjective: return get_botorch_objective( model=model, objective_weights=objective_weights, use_scalarized_objective=issubclass( self._botorch_acqf_class, AnalyticAcquisitionFunction ), outcome_constraints=outcome_constraints, X_observed=X_observed, ) def _instantiate_acqf( self, model: Model, objective: AcquisitionObjective, model_dependent_kwargs: Dict[str, Any], X_pending: Optional[Tensor] = None, X_baseline: Optional[Tensor] = None, ) -> None: self.acqf = self._botorch_acqf_class( # pyre-ignore[28]: Some kwargs are # not expected in base `AcquisitionFunction` but are expected in # its subclasses. model=model, objective=objective, X_pending=X_pending, X_baseline=X_baseline, **self.options, **model_dependent_kwargs, ) @classmethod def _extract_training_data( cls, surrogate: Surrogate ) -> Union[TrainingData, Dict[str, TrainingData]]: if isinstance(surrogate, ListSurrogate): return checked_cast(dict, surrogate.training_data_per_outcome) else: return checked_cast(TrainingData, surrogate.training_data)