Objective

Optimization problems involve either a single target quantity of interest or several (potentially conflicting) targets that need to be considered simultaneously. BayBE uses the concept of an Objective to allow the user to control how these different types of scenarios are handled.

SingleTargetObjective

The need to optimize a single Target is the most basic type of situation one can encounter in experimental design. In this scenario, the fact that only one target shall be considered in the design is communicated to BayBE by wrapping the target into a SingleTargetObjective:

from baybe.targets import NumericalTarget
from baybe.objectives import SingleTargetObjective

target = NumericalTarget(name="Yield")
objective = SingleTargetObjective(target)

In fact, the role of the SingleTargetObjective is to merely signal the absence of other Targets in the optimization problem. Because this fairly trivial conversion step requires no additional user configuration, we provide a convenience constructor for it:

Convenience Construction and Implicit Conversion

  • The conversion from a single Target to a SingleTargetObjective describes a one-to-one relationship and can be triggered directly from the corresponding target object:

    objective = target.to_objective()
    
  • Also, other class constructors that expect an Objective object (such as Campaigns) will happily accept individual Targets instead and apply the necessary conversion behind the scenes.

DesirabilityObjective

The DesirabilityObjective enables the combination of multiple targets via scalarization into a single numerical value (commonly referred to as the overall desirability), a method also utilized in classical DOE.

Target Normalization

Since desirability computation relies on scalarization, and because targets can vary arbitrarily in scale, it is (by default) required that all targets are properly normalized before entering the computation to enable meaningful combination into desirability values. This can be achieved by applying appropriate normalizing target transformations.

Alternatively, if you know what you are doing, you can also disable this requirement via the require_normalization flag.

Besides the list of Targets to be scalarized, this objective type takes additional optional arguments that let us control its behavior:

  • weights: Specifies the relative importance of the targets in the form of a sequence of positive numbers, one for each target considered. Note that BayBE automatically normalizes the weights, so only their relative scales matter.

  • as_pre_transformation: By default, the desirability is computed via posterior transformations, enabling access to information even for the original targets (e.g. in SHAPInsight or posterior_stats). However, this requires one model per target. With as_pre_transformation=True you can change this behavior, e.g. due to computational limitations. The objective will then apply the transformation directly and fits a single model on the resulting “Desirability”.

  • require_normalization: A Boolean flag controlling the target normalization requirement.

  • scalarizer: Specifies the scalarization function to be used for combining the normalized target values.

The definitions of the scalarizers are as follows, where \(\{t_i\}\) refer the transformed target measurements of a single experiment and \(\{w_i\}\) are the corresponding target weights:

\[\begin{split}\begin{align*} \text{MEAN} &= \frac{\sum_{i} w_i \cdot t_i}{\sum_{i} w_i} \\ \text{GEOM_MEAN} &= \left( \prod_i t_i^{w_i} \right)^{1/\sum_{i} w_i} \end{align*}\end{split}\]

Example 1 – Normalized Targets

Here, we consider four normalized targets, each with a distict optimization goal chosen arbitrarily for demonstration purposes. The first target is given twice as much importance as each of the other three by assigning it a higher weight:

from baybe.targets import NumericalTarget
from baybe.objectives import DesirabilityObjective

t1 = NumericalTarget.normalized_ramp(name="t1", cutoffs=(0, 100), descending=True)
t2 = NumericalTarget.normalized_sigmoid(name="t2", anchors=[(0, 0.1), (100, 0.9)])
t3 = NumericalTarget.match_bell(name="t3", match_value=50, sigma=10)
t4 = NumericalTarget(name="t4").exp().clamp(max=10).normalize()
objective = DesirabilityObjective(
    targets=[t1, t2, t3, t4],
    weights=[2.0, 1.0, 1.0, 1.0],  # optional (by default, all weights are equal)
    scalarizer="GEOM_MEAN",  # optional
)

Example 2 – Non-Normalized Targets

Sometimes, we explicitly want to bypass the normalization requirement, for example, when the target ranges are unknown. In this case, using the unweighted arithmetic mean is a reasonable choice:

from baybe.targets import NumericalTarget
from baybe.objectives import DesirabilityObjective

t1 = NumericalTarget(name="t_max")
t2 = NumericalTarget.match_absolute(name="t_match", match_value=0)
objective = DesirabilityObjective(
    targets=[t1, t2],
    scalarizer="MEAN",
    require_normalization=False,  # disable normalization requirement
)

ParetoObjective

The ParetoObjective can be used when the goal is to find a set of solutions that represent optimal trade-offs among multiple conflicting targets. Unlike the DesirabilityObjective, this approach does not aggregate the targets into a single scalar value but instead seeks to identify the Pareto front – the set of non-dominated target configurations.

Non-Dominated Configurations

A target configuration is considered non-dominated (or Pareto-optimal) if no other configuration is better in all targets.

Identifying the Pareto front requires maintaining explicit models for each of the targets involved. Accordingly, it requires to use acquisition functions capable of processing vector-valued input, such as qLogNoisyExpectedHypervolumeImprovement. This differs from the DesirabilityObjective, which relies on a single predictive model to describe the associated desirability values. However, the drawback of the latter is that the exact trade-off between the targets must be specified in advance, through explicit target weights. By contrast, the Pareto approach allows to specify this trade-off after the experiments have been carried out, giving the user the flexibility to adjust their preferences post-hoc – knowing that each of the obtained points is optimal with respect to a particular preference model.

To set up a ParetoObjective, simply specify the corresponding target objects:

from baybe.targets import NumericalTarget
from baybe.objectives import ParetoObjective

target_1 = NumericalTarget(name="t_1")
target_2 = NumericalTarget(name="t_2", minimize=True)
target_3 = NumericalTarget.match_absolute(name="t_3", match_value=0)
objective = ParetoObjective(targets=[target_1, target_2, target_3])

Convenience Multi-Output Casting

ParetoObjective requires a multi-output surrogate model. If you attempt to use a single-output model, BayBE will automatically turn it into a CompositeSurrogate using independent replicates.