Objectives
In BoTorch, an objective is a module that allows for convenient transformation of model outputs into a scalar function to be optimized. Typical use cases for this are the scalarization of outputs for a multi-output model (see e.g. [1]), or optimization subject to outcome constraints, which can be achieved by weighting the objective by the probability of feasibility [2].
When using classical analytic formulations of acquisition functions, one needs to be careful that the transformation results in a posterior distribution of the transformed outputs that still satisfies the assumptions of the analytic formulation. For instance, to use standard Expected Improvement on a transformed output of a model, the transformation needs to be affine (because Gaussians are closed under affine transformations). When using MC-based acquisition functions, however, fewer assumptions are required, and one can apply general transformations to the model outputs with relative impunity so long gradients can be back-propagated through the transformation.
All BoTorch objectives are derived from
MCAcquisitionObjective
.
BoTorch implements several MC-based objectives, including
LinearMCObjective
for linear
combinations of model outputs, and
ConstrainedMCObjective
for
constrained objectives (using a sigmoid approximation for the constraints).
Using custom objectives
Utilizing GenericMCObjective
The GenericMCObjective
allows
simply using a generic callable to implement an ad-hoc objective. The callable
is expected to map a sample_shape x batch_shape x q x o
-dimensional tensor of
posterior samples and an (optional) batch_shape x q x d
-dimensional tensor of
inputs to a sample_shape x batch_shape x q
-dimensional tensor of sampled
objective values.
For instance, say you have a multi-output model with $o=2$ outputs, and you want to optimize a $obj(y) = 1 - \|y - y_0\|_2$, where $y_0 \in \mathbb{R}^2$. For this you would use the following custom objective (here we can ignore the inputs $X$ as the objective does not depend on it):
obj = lambda xi, X: 1 - torch.linalg.norm(xi - y_0, dim=-1)
mc_objective = GenericMCObjective(obj)
Implementing a custom objective module
Instead of using GenericMCObjective
, you can also implement your own
MCAcquisitionObjective
modules to make them easier to re-use, or support
more complex logic. The only thing required to implement
is a forward
method that takes in a
sample_shape x batch_shape x q x o
-dimensional tensor of
posterior samples and maps it to a
sample_shape x batch_shape x q
-dimensional tensor of sampled objective values.
A custom objective module of the above example would be
class MyCustomObjective(MCAcquisitionObjective):
def forward(self, samples, X=None):
return 1 - torch.linalg.norm(samples - y_0, dim=-1)