Basics of Trust Region Controllers in Xopt¶
Trust Region Bayesian Optimization (TuRBO) is an advanced optimization algorithm designed for solving high-dimensional black-box optimization problems. It combines the strengths of Bayesian Optimization (BO) with trust region methods to improve scalability and efficiency.
Key Features:¶
Trust Regions:
- TuRBO uses local trust regions to focus the search in promising areas of the parameter space.
- Each trust region is a bounded subspace where the optimization is performed, and its size is dynamically adjusted based on the success of the optimization.
Bayesian Surrogate Model:
- A Gaussian Process (GP) or other surrogate models are used to approximate the objective function.
- This surrogate model is used to predict the objective function and guide the search as well as define the size of the trust region.
Adaptivity:
- The algorithm adapts the size of the trust region based on the success or failure of the optimization steps. If the optimization within a trust region is successful, the region expands; otherwise, it shrinks.
Advantages:¶
- Scales better to high-dimensional problems compared to standard Bayesian Optimization.
- Efficiently balances exploration and exploitation within trust regions.
Disadvantages:¶
- Severely restricts exploration of the parameter space potentially leading to convergence to local minima, thus making it sensitive to initial sampling points.
- Introduces additional algorithm hyperparameters which can cause issues.
- May struggle with noisy objective functions or discontinuous landscapes.
Defining a TuRBO Controller¶
Currently, Xopt supports 3 different TuRBO controller types, the most basic of which is the OptimizeTurboController. To create this controller we need to define our optimization problem and some data.
import numpy as np
from xopt import VOCS
import pandas as pd
# create evaluation function
def sphere_function(inputs):
"""
2D Sphere objective function.
Compatible with Xopt.
"""
x, y = inputs["x"], inputs["y"]
return {"f": np.sum(np.square(np.stack([x, y], axis=-1)), axis=-1)}
# create a VOCS object
vocs = VOCS(
variables={"x": {-5, 5}, "y": {-5, 5}},
objectives={"f": "MINIMIZE"},
)
# random sample 10 points
x0 = vocs.random_inputs(10)
# evaluate the function at the random points
f = []
for i in range(len(x0)):
f += [sphere_function(x0[i]) | x0[i]]
# print the results
data = pd.DataFrame(f)
data
--------------------------------------------------------------------------- ValidationError Traceback (most recent call last) Cell In[1], line 17 13 return {"f": np.sum(np.square(np.stack([x, y], axis=-1)), axis=-1)} 16 # create a VOCS object ---> 17 vocs = VOCS( 18 variables={"x": {-5, 5}, "y": {-5, 5}}, 19 objectives={"f": "MINIMIZE"}, 20 ) 22 # random sample 10 points 23 x0 = vocs.random_inputs(10) File ~/miniconda3/envs/xopt-dev/lib/python3.13/site-packages/pydantic/main.py:250, in BaseModel.__init__(self, **data) 248 # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks 249 __tracebackhide__ = True --> 250 validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) 251 if self is not validated_self: 252 warnings.warn( 253 'A custom validator is returning a value other than `self`.\n' 254 "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n" 255 'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.', 256 stacklevel=2, 257 ) ValidationError: 1 validation error for VOCS variables Value error, could not convert {-5, 5} to tuple of floats for key 'x' [type=value_error, input_value={'x': (-5, 5), 'y': {-5, 5}}, input_type=dict] For further information visit https://errors.pydantic.dev/2.12/v/value_error
Create the ExpectedImprovementGenerator and train the GP model¶
Here we create the ExpectedImprovementGenerator, add data to the generator, and train the model from the data.
from xopt.generators.bayesian import ExpectedImprovementGenerator
generator = ExpectedImprovementGenerator(vocs=vocs) # create the generator
generator.gp_constructor.use_low_noise_prior = True
generator.add_data(data) # add the data to the generator
generator.train_model() # train the model
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[2], line 3 1 from xopt.generators.bayesian import ExpectedImprovementGenerator ----> 3 generator = ExpectedImprovementGenerator(vocs=vocs) # create the generator 4 generator.gp_constructor.use_low_noise_prior = True 5 generator.add_data(data) # add the data to the generator NameError: name 'vocs' is not defined
Create the Optimize Turbo Controller¶
Here we create the controller and view the different parameters with their descriptions.
from xopt.generators.bayesian.turbo import OptimizeTurboController
turbo_controller = OptimizeTurboController(vocs=vocs)
print(turbo_controller.__doc__)
print("-" * 20)
# examine the attributes of the controller
for field_name, field in turbo_controller.model_fields.items():
print(f"Field: {field_name}")
print(f" Description: {field.description}")
print(f" Type: {field.annotation}")
print(f" Default: {field.default}")
print(f" Value: {getattr(turbo_controller, field_name)}")
print("-" * 20)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[3], line 3 1 from xopt.generators.bayesian.turbo import OptimizeTurboController ----> 3 turbo_controller = OptimizeTurboController(vocs=vocs) 5 print(turbo_controller.__doc__) 6 print("-" * 20) NameError: name 'vocs' is not defined
Getting the Trust Region¶
Here we get the current trust region
trust_region = turbo_controller.get_trust_region(
generator=generator
) # get the trust region of the model
print(f"Trust Region: {trust_region}")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[4], line 1 ----> 1 trust_region = turbo_controller.get_trust_region( 2 generator=generator 3 ) # get the trust region of the model 4 print(f"Trust Region: {trust_region}") NameError: name 'turbo_controller' is not defined
Update the trust region¶
Add another data point to the generator (as if we performed one optimization step) and update the turbo controller. We will add a point that improves over the best function value measured so far so this measurement will count as a success.
# add a new point to the generator
new_point = pd.DataFrame({"x": [0.0], "y": [0.0], "f": [0.0]})
generator.add_data(new_point) # add the new point to the generator
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[5], line 3 1 # add a new point to the generator 2 new_point = pd.DataFrame({"x": [0.0], "y": [0.0], "f": [0.0]}) ----> 3 generator.add_data(new_point) # add the new point to the generator NameError: name 'generator' is not defined
generator.train_model() # train the model again
# update the TuRBO controller
turbo_controller.update_state(generator)
# get the new trust region
trust_region = turbo_controller.get_trust_region(
generator=generator
) # get the trust region of the model
print(f"New Trust Region: {trust_region}")
# get the number of successes and failures
print(f"Number of successes: {turbo_controller.success_counter}")
print(f"Number of failures: {turbo_controller.failure_counter}")
# get the base length scale of the trust region
print(f"Base length scale: {turbo_controller.length}")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[6], line 1 ----> 1 generator.train_model() # train the model again 3 # update the TuRBO controller 4 turbo_controller.update_state(generator) NameError: name 'generator' is not defined