Source code for botorch.acquisition.preference
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
r"""
Preference acquisition functions. This includes:
Analytical EUBO acquisition function as introduced in [Lin2022preference]_.
.. [Lin2022preference]
Lin, Z.J., Astudillo, R., Frazier, P.I. and Bakshy, E. Preference Exploration
for Efficient Bayesian Optimization with Multiple Outcomes. International
Conference on Artificial Intelligence and Statistics (AISTATS), 2022.
"""
from __future__ import annotations
from typing import Optional
import torch
from botorch.acquisition import AnalyticAcquisitionFunction
from botorch.exceptions.errors import UnsupportedError
from botorch.models.deterministic import DeterministicModel
from botorch.models.model import Model
from botorch.utils.transforms import match_batch_shape, t_batch_mode_transform
from torch import Tensor
SIGMA_JITTER = 1e-8
[docs]class AnalyticExpectedUtilityOfBestOption(AnalyticAcquisitionFunction):
r"""Analytic Prefential Expected Utility of Best Options, i.e., Analytical EUBO"""
def __init__(
self,
pref_model: Model,
outcome_model: Optional[DeterministicModel] = None,
previous_winner: Optional[Tensor] = None,
) -> None:
r"""Analytic implementation of Expected Utility of the Best Option under the
Laplace model (assumes a PairwiseGP is used as the preference model) as
proposed in [Lin2022preference]_.
Args:
pref_model: The preference model that maps the outcomes (i.e., Y) to
scalar-valued utility.
model: A deterministic model that maps parameters (i.e., X) to outcomes
(i.e., Y). The outcome model f defines the search space of Y = f(X).
If model is None, we are directly calculating EUBO on the parameter
space. When used with `OneSamplePosteriorDrawModel`, we are obtaining
EUBO-zeta as described in [Lin2022preference].
previous_winner: Tensor representing the previous winner in the Y space.
"""
pref_model.eval()
super().__init__(model=pref_model)
# ensure the model is in eval mode
self.add_module("outcome_model", outcome_model)
self.register_buffer("previous_winner", previous_winner)
tkwargs = {
"dtype": pref_model.datapoints.dtype,
"device": pref_model.datapoints.device,
}
std_norm = torch.distributions.normal.Normal(
torch.zeros(1, **tkwargs),
torch.ones(1, **tkwargs),
)
self.std_norm = std_norm
[docs] @t_batch_mode_transform()
def forward(self, X: Tensor) -> Tensor:
r"""Evaluate analytical EUBO on the candidate set X.
Args:
X: A `batch_shape x q x d`-dim Tensor, where `q = 2` if `previous_winner`
is not `None`, and `q = 1` otherwise.
Returns:
The acquisition value for each batch as a tensor of shape `batch_shape`.
"""
if not (
(X.shape[-2] == 2)
or ((X.shape[-2] == 1) and (self.previous_winner is not None))
):
raise UnsupportedError(
f"{self.__class__.__name__} only support q=2 or q=1"
"with a previous winner specified"
)
Y = X if self.outcome_model is None else self.outcome_model(X)
if self.previous_winner is not None:
Y = torch.cat([Y, match_batch_shape(self.previous_winner, Y)], dim=-2)
# Calling forward directly instead of posterior here to
# obtain the full covariance matrix
pref_posterior = self.model(Y)
pref_mean = pref_posterior.mean
pref_cov = pref_posterior.covariance_matrix
delta = pref_mean[..., 0] - pref_mean[..., 1]
sigma = torch.sqrt(
pref_cov[..., 0, 0]
+ pref_cov[..., 1, 1]
- pref_cov[..., 0, 1]
- pref_cov[..., 1, 0]
+ SIGMA_JITTER
)
u = delta / sigma
ucdf = self.std_norm.cdf(u)
updf = torch.exp(self.std_norm.log_prob(u))
acqf_val = sigma * (updf + u * ucdf)
if self.previous_winner is None:
acqf_val = acqf_val + pref_mean[..., 1]
return acqf_val