import numpy as np
import bofire.strategies.api as strategies
import bofire.surrogates.api as surrogates
from bofire.benchmarks.api import Himmelblau
from bofire.data_models.features.api import CategoricalInput, ContinuousOutput
from bofire.data_models.objectives.api import (
MaximizeObjective,
MaximizeSigmoidObjective,
)
from bofire.data_models.strategies.api import MultiplicativeSoboStrategy
from bofire.data_models.surrogates.api import (
BotorchSurrogates,
CategoricalDeterministicSurrogate,
LinearDeterministicSurrogate,
)Input Features as Output Objectives
This notebook demonstrates how to put objectives on input features or a combination of input features. Possible usecases are favoring lower or higher amounts of an ingredient or to take into account a known (linear) cost function. In case of categorical inputs it can be used to penalize the optimizer for choosing specific categories.
Imports
Setup an Example
We use Himmelblau as example with an additional objective on x_2 which pushes it to be larger 3 during the optimization. In addition, we introduce a categorical feature called x_cat which is mapped by an CategoricalDeterministicSurrogate to a continuous output called y_cat.
bench = Himmelblau()
experiments = bench.f(bench.domain.inputs.sample(10), return_complete=True)
domain = bench.domain
# setup extra feature `y_x2` that is the same as `x_2` and is taken into account in the optimization by a sigmoid objective
domain.outputs.features.append(
ContinuousOutput(key="y_x2", objective=MaximizeSigmoidObjective(tp=3, steepness=10))
)
experiments["y_x2"] = experiments.x_2
# add extra categorical input feature and corresponding output feature
domain.inputs.features.append(CategoricalInput(key="x_cat", categories=["a", "b", "c"]))
domain.outputs.features.append(
ContinuousOutput(key="y_cat", objective=MaximizeObjective())
)
# generate random values for the new categorical feature
experiments["x_cat"] = np.random.choice(["a", "b", "c"], size=experiments.shape[0])The LinearDeterministicSurrogate can be used to model that y_x2 = x_2.
surrogate_data = LinearDeterministicSurrogate(
inputs=domain.inputs.get_by_keys(["x_2"]),
outputs=domain.outputs.get_by_keys(["y_x2"]),
coefficients={"x_2": 1},
intercept=0,
)
surrogate = surrogates.map(surrogate_data)
surrogate.predict(experiments[domain.inputs.get_keys()].copy())| y_x2_pred | y_x2_sd | |
|---|---|---|
| 0 | 1.997736 | 0.0 |
| 1 | -2.719967 | 0.0 |
| 2 | 2.290222 | 0.0 |
| 3 | 2.292196 | 0.0 |
| 4 | -3.815097 | 0.0 |
| 5 | 0.449665 | 0.0 |
| 6 | 2.882852 | 0.0 |
| 7 | -0.247336 | 0.0 |
| 8 | 3.497335 | 0.0 |
| 9 | -2.830277 | 0.0 |
The CategoricalDeterministicSurrogate can be used to map categories to specific continuous values.
categorical_surrogate_data = CategoricalDeterministicSurrogate(
inputs=domain.inputs.get_by_keys(["x_cat"]),
outputs=domain.outputs.get_by_keys(["y_cat"]),
mapping={"a": 1, "b": 0.2, "c": 0.3},
)
surrogate = surrogates.map(categorical_surrogate_data)
surrogate.predict(experiments[domain.inputs.get_keys()].copy())
experiments["y_cat"] = surrogate.predict(experiments[domain.inputs.get_keys()].copy())[
"y_cat_pred"
]
experiments| x_1 | x_2 | y | valid_y | y_x2 | x_cat | y_cat | |
|---|---|---|---|---|---|---|---|
| 0 | -5.522035 | 1.997736 | 534.625622 | 1 | 1.997736 | a | 1.0 |
| 1 | 3.467733 | -2.719967 | 17.817910 | 1 | -2.719967 | a | 1.0 |
| 2 | -0.206985 | 2.290222 | 78.964711 | 1 | 2.290222 | b | 0.2 |
| 3 | 1.382206 | 2.292196 | 46.335673 | 1 | 2.292196 | c | 0.3 |
| 4 | -0.664918 | -3.815097 | 254.055367 | 1 | -3.815097 | c | 0.3 |
| 5 | 4.832463 | 0.449665 | 167.763130 | 1 | 0.449665 | a | 1.0 |
| 6 | 4.749012 | 2.882852 | 245.118911 | 1 | 2.882852 | c | 0.3 |
| 7 | 3.140534 | -0.247336 | 16.343525 | 1 | -0.247336 | a | 1.0 |
| 8 | -3.616145 | 3.497335 | 33.676573 | 1 | 3.497335 | a | 1.0 |
| 9 | 2.746860 | -2.830277 | 53.619167 | 1 | -2.830277 | c | 0.3 |
Next we setup a SoboStrategy using the custom surrogates for outputs y_x2 and y_cat and ask for a candidate. Note that the surrogate specs for output y is automatically generated and defaulted to be a SingleTaskGPSurrogate.
strategy_data = MultiplicativeSoboStrategy(
domain=domain,
surrogate_specs=BotorchSurrogates(
surrogates=[surrogate_data, categorical_surrogate_data]
),
)
strategy = strategies.map(strategy_data)
strategy.tell(experiments)
strategy.ask(4)| x_1 | x_2 | x_cat | y_pred | y_cat_pred | y_x2_pred | y_sd | y_cat_sd | y_x2_sd | y_des | y_x2_des | y_cat_des | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -2.432430 | 5.999801 | a | -84.299131 | 1.0 | 5.999801 | 98.300606 | 0.0 | 0.0 | 84.299131 | 1.000000 | 1.0 |
| 1 | -2.471754 | 3.449480 | a | -71.909130 | 1.0 | 3.449480 | 70.431686 | 0.0 | 0.0 | 71.909130 | 0.988956 | 1.0 |
| 2 | -3.065456 | 6.000000 | a | -59.528692 | 1.0 | 6.000000 | 79.227715 | 0.0 | 0.0 | 59.528692 | 1.000000 | 1.0 |
| 3 | 1.499031 | 6.000000 | a | 39.695362 | 1.0 | 6.000000 | 84.050011 | 0.0 | 0.0 | -39.695362 | 1.000000 | 1.0 |