1. Ax - Multi-Objective Optimization for Marketing Campaigns
Adaptive Experimentation (Ax) is an open-source tool created and maintained by The Meta Adaptive Experimentation Team initially for internal use and open to the public.
Ax is a platform to optimize almost any type of experiment. It’s well suited when the experiment complies with a few characteristics:
- The benefit or outcome can be measured or quantified.
- The benefit or outcome cannot be calculated from the inputs. There is no formula that can be used to calculate the benefit or outcome from the inputs. The only way to know the result is to run an experiment.
- The cost of running an experiment is elevated and the number of experiments has to be reduced to a minimum.
1.1. Bayesian Optimization¶
Ax optimization algorithms are based on Bayesian Optimization. Internal implementation is based on BoTorch a library for Bayesian Optimization built on top of PyTorch.
1.2. Adaptive Experimentation¶
Adaptive Experimentation is a technique to optimize experiments based on the results of the previous experiments. It is a way to find the best configuration of the experiment parameters to maximize the benefit or outcome while minimizing the cost of running the experiment.
The basic adaptive experimentation flow works as follows (2):
- Configure your optimization experiment, defining the space of values to search over, objective(s), constraints, etc.
- Suggest new trials, to be evaluated one at a time or in a parallel (a “batch”)
- Evaluate the suggested trials by executing the black box function and reporting the results back to the optimization algorithm
- Repeat steps 2 and 3 until a stopping condition is met or the evaluation budget is exhausted
Bayesian optimization, one of the most effective forms of adaptive experimentation, intelligently balances tradeoffs between exploration (learning how new parameterizations perform) and exploitation (refining parameterizations previously observed to be good).
2. Ax API¶
2.1. Overview¶
The following diagram shows the main components of the Ax API and the steps to run an Adaptive Experiment:

The library is organized around the following main components:
- Client: The main module that will create an Experiment, return the Trials to be evaluated and store the results.
- Experiment: The entity that contains the configuration and all the information about the current experiment.
- OptimizationConfig: Defines the optimization problem, the inputs, outputs, and constraints.
- Trial: A new trial is generated on each iteration. It contains the inputs to be evaluated (Also called Arm)
- GenerationStrategy: The entity that generates the new inputs to be evaluated. It leverages different strategies on different stages (Sobol, Transfer-Learning BayesOpt, BoTorch)

For more information, see the Ax Glossary
2.2. API Reference¶
Only the most important modules and methods are documented here. For more detailed information, see the Ax API Reference
Clientconfigure_experiment: Receives an ExperimentConfig. Creates the Experiment Object. No value is returned, the Client is stateful.configure_optimization: Receives theobjectiveexpression and outcome constraints. Outcome constraints are optional and define when the optimization should stop.get_next_trials: Receives the number of trials to generate in parallel. Returns a list of Trials. Each Trial will contain a suggested input to be evaluated.complete_trial: Receives the result of the evaluation of the Trial. Ax will update the Experiment with the result. Metadata can be attached for future reference.get_best_parameterization: Returns the Trial or input values that maximized the objective function.compute_analyses: Renders multiple visualizations of the experiment results. The visualization methods to render can be passed as a parameter.get_pareto_frontier: If the Experiment has been defined with two or more objectives, this method returns the list of tuples which are part of the Pareto frontier.
IRunner: Abstract class that can be implemented to define a experiment that’s delegated to an external service to be completed asynchronously. Defines the methodsrun_trialandpoll_trial.IMetric: Abstract class that works together with theIRunnerobject to retrieve the results from an external service.
3. Hartmann Function Optimization¶
To demonstrate the usage of the Ax API, we will use the Hartmann Function in 6 dimensions. This function is a common benchmark for optimization problems and it’s demonstrated in the Ax documentation.
The Hartmann Function is defined as:
It’s definition makes this function to have multiple local optima and one global optima.
The following is a visualization of a Hartmann Function in 2 dimensions:

3.1. Install Dependencies¶
# %load_ext autoreload
# %autoreload 2
%matplotlib inline
# Install AX with the extension for Jupyter notebooks
! pip3 install "ax-platform[notebook]" joblib --break-system-packages
# Import the necessary packages
from Ax_utils import hartmann6
import numpy as np
from ax.api.client import Client
from ax.api.configs import RangeParameterConfig
import matplotlib.pyplot as plt3.2. Initialize the Client and Setup the Experiment¶
# Create the Ax Client
client = Client()
# The Hartmann function has 6 variables that go between 0 and 1.
parameters = [
RangeParameterConfig(name="x1", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x2", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x3", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x4", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x5", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x6", parameter_type="float", bounds=(0, 1)),
]
client.configure_experiment(parameters=parameters)3.3. Configure the Optimization Problem¶
In this case we want to minimize the result of the Hartmann function.
# The "-" sign transforms this into a minimization problem.
client.configure_optimization(objective="-hartmann")Call the Hartmann function in the Utils file to verify it works.
hartmann6(0.1, 0.45, 0.8, 0.25, 0.552, 1.0)np.float64(-0.4878737485613134)3.4. Define the Optimization Loop¶
N number of trials can be run in parallel. This may accelerate the optimization process but will require more experiments to be conducted in total.
for _ in range(10):
# Request a series of inputs to Ax client
trials = client.get_next_trials(max_trials=5)
# Conduct the experiments (5 times in this case)
for trial_index, parameters in trials.items():
x1 = parameters["x1"]
x2 = parameters["x2"]
x3 = parameters["x3"]
x4 = parameters["x4"]
x5 = parameters["x5"]
x6 = parameters["x6"]
result = hartmann6(x1, x2, x3, x4, x5, x6)
# Send the results back to Ax with the trial index for reference
client.complete_trial(
trial_index=trial_index, raw_data={"hartmann": result}
)[INFO 02-27 07:31:26] ax.api.client: Generated new trial 46 with parameters {'x1': 0.186632, 'x2': 0.150243, 'x3': 0.468214, 'x4': 0.278402, 'x5': 0.306174, 'x6': 0.605136} using GenerationNode MBM.
[INFO 02-27 07:31:26] ax.api.client: Generated new trial 47 with parameters {'x1': 0.203971, 'x2': 0.178306, 'x3': 0.431212, 'x4': 0.230722, 'x5': 0.324976, 'x6': 0.645022} using GenerationNode MBM.
[INFO 02-27 07:31:26] ax.api.client: Generated new trial 48 with parameters {'x1': 0.140154, 'x2': 0.141705, 'x3': 0.503319, 'x4': 0.308021, 'x5': 0.297584, 'x6': 0.557114} using GenerationNode MBM.
[INFO 02-27 07:31:26] ax.api.client: Generated new trial 49 with parameters {'x1': 0.192156, 'x2': 0.244518, 'x3': 0.459616, 'x4': 0.294103, 'x5': 0.304565, 'x6': 0.611126} using GenerationNode MBM.
[INFO 02-27 07:31:26] ax.api.client: Generated new trial 50 with parameters {'x1': 0.164623, 'x2': 0.043706, 'x3': 0.456912, 'x4': 0.245221, 'x5': 0.300956, 'x6': 0.591028} using GenerationNode MBM.
[INFO 02-27 07:31:26] ax.api.client: Trial 46 marked COMPLETED.
[INFO 02-27 07:31:26] ax.api.client: Trial 47 marked COMPLETED.
[INFO 02-27 07:31:26] ax.api.client: Trial 48 marked COMPLETED.
[INFO 02-27 07:31:26] ax.api.client: Trial 49 marked COMPLETED.
[INFO 02-27 07:31:26] ax.api.client: Trial 50 marked COMPLETED.
[INFO 02-27 07:31:30] ax.api.client: Generated new trial 51 with parameters {'x1': 0.210465, 'x2': 0.166986, 'x3': 0.462475, 'x4': 0.26857, 'x5': 0.30135, 'x6': 0.656841} using GenerationNode MBM.
[INFO 02-27 07:31:30] ax.api.client: Generated new trial 52 with parameters {'x1': 0.148034, 'x2': 0.234646, 'x3': 0.319922, 'x4': 0.038129, 'x5': 0.217503, 'x6': 0.511898} using GenerationNode MBM.
[INFO 02-27 07:31:30] ax.api.client: Generated new trial 53 with parameters {'x1': 0.0, 'x2': 0.76486, 'x3': 0.0, 'x4': 0.127335, 'x5': 0.316295, 'x6': 0.46588} using GenerationNode MBM.
[INFO 02-27 07:31:30] ax.api.client: Generated new trial 54 with parameters {'x1': 0.178078, 'x2': 0.189188, 'x3': 0.470265, 'x4': 0.22941, 'x5': 0.294723, 'x6': 0.635056} using GenerationNode MBM.
[INFO 02-27 07:31:30] ax.api.client: Generated new trial 55 with parameters {'x1': 0.205908, 'x2': 0.162695, 'x3': 0.457684, 'x4': 0.269822, 'x5': 0.3304, 'x6': 0.642929} using GenerationNode MBM.
[INFO 02-27 07:31:30] ax.api.client: Trial 51 marked COMPLETED.
[INFO 02-27 07:31:30] ax.api.client: Trial 52 marked COMPLETED.
[INFO 02-27 07:31:30] ax.api.client: Trial 53 marked COMPLETED.
[INFO 02-27 07:31:30] ax.api.client: Trial 54 marked COMPLETED.
[INFO 02-27 07:31:30] ax.api.client: Trial 55 marked COMPLETED.
[INFO 02-27 07:31:35] ax.api.client: Generated new trial 56 with parameters {'x1': 0.534117, 'x2': 0.031068, 'x3': 0.123908, 'x4': 0.327765, 'x5': 0.313894, 'x6': 0.623378} using GenerationNode MBM.
[INFO 02-27 07:31:35] ax.api.client: Generated new trial 57 with parameters {'x1': 0.206283, 'x2': 0.145615, 'x3': 0.432052, 'x4': 0.289022, 'x5': 0.307448, 'x6': 0.666056} using GenerationNode MBM.
[INFO 02-27 07:31:35] ax.api.client: Generated new trial 58 with parameters {'x1': 0.804135, 'x2': 0.939087, 'x3': 0.0, 'x4': 1.0, 'x5': 0.147346, 'x6': 1.0} using GenerationNode MBM.
[INFO 02-27 07:31:35] ax.api.client: Generated new trial 59 with parameters {'x1': 0.269543, 'x2': 0.142673, 'x3': 0.44047, 'x4': 0.287589, 'x5': 0.310735, 'x6': 0.662913} using GenerationNode MBM.
[INFO 02-27 07:31:35] ax.api.client: Generated new trial 60 with parameters {'x1': 0.145299, 'x2': 0.147479, 'x3': 0.402755, 'x4': 0.285435, 'x5': 0.305871, 'x6': 0.664984} using GenerationNode MBM.
[INFO 02-27 07:31:35] ax.api.client: Trial 56 marked COMPLETED.
[INFO 02-27 07:31:35] ax.api.client: Trial 57 marked COMPLETED.
[INFO 02-27 07:31:35] ax.api.client: Trial 58 marked COMPLETED.
[INFO 02-27 07:31:35] ax.api.client: Trial 59 marked COMPLETED.
[INFO 02-27 07:31:35] ax.api.client: Trial 60 marked COMPLETED.
[INFO 02-27 07:31:40] ax.api.client: Generated new trial 61 with parameters {'x1': 0.049131, 'x2': 1.0, 'x3': 1.0, 'x4': 1.0, 'x5': 0.853768, 'x6': 0.084845} using GenerationNode MBM.
[INFO 02-27 07:31:40] ax.api.client: Generated new trial 62 with parameters {'x1': 0.207462, 'x2': 0.15505, 'x3': 0.460493, 'x4': 0.277081, 'x5': 0.315444, 'x6': 0.661131} using GenerationNode MBM.
[INFO 02-27 07:31:40] ax.api.client: Generated new trial 63 with parameters {'x1': 0.788352, 'x2': 1.0, 'x3': 1.0, 'x4': 1.0, 'x5': 0.9559, 'x6': 0.345795} using GenerationNode MBM.
[INFO 02-27 07:31:40] ax.api.client: Generated new trial 64 with parameters {'x1': 0.217061, 'x2': 0.17933, 'x3': 0.453875, 'x4': 0.284015, 'x5': 0.31268, 'x6': 0.662374} using GenerationNode MBM.
[INFO 02-27 07:31:40] ax.api.client: Generated new trial 65 with parameters {'x1': 0.010546, 'x2': 1.0, 'x3': 1.0, 'x4': 1.0, 'x5': 0.426259, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:40] ax.api.client: Trial 61 marked COMPLETED.
[INFO 02-27 07:31:40] ax.api.client: Trial 62 marked COMPLETED.
[INFO 02-27 07:31:40] ax.api.client: Trial 63 marked COMPLETED.
[INFO 02-27 07:31:40] ax.api.client: Trial 64 marked COMPLETED.
[INFO 02-27 07:31:40] ax.api.client: Trial 65 marked COMPLETED.
[INFO 02-27 07:31:45] ax.api.client: Generated new trial 66 with parameters {'x1': 0.26359, 'x2': 0.241551, 'x3': 0.411434, 'x4': 0.288206, 'x5': 0.347718, 'x6': 0.695804} using GenerationNode MBM.
[INFO 02-27 07:31:45] ax.api.client: Generated new trial 67 with parameters {'x1': 0.884885, 'x2': 0.0, 'x3': 0.0, 'x4': 0.92145, 'x5': 0.296767, 'x6': 0.613516} using GenerationNode MBM.
[INFO 02-27 07:31:45] ax.api.client: Generated new trial 68 with parameters {'x1': 1.0, 'x2': 0.0, 'x3': 0.0, 'x4': 1.0, 'x5': 0.342599, 'x6': 0.298232} using GenerationNode MBM.
[INFO 02-27 07:31:45] ax.api.client: Generated new trial 69 with parameters {'x1': 0.20937, 'x2': 0.146297, 'x3': 0.470245, 'x4': 0.283189, 'x5': 0.303573, 'x6': 0.661102} using GenerationNode MBM.
[INFO 02-27 07:31:45] ax.api.client: Generated new trial 70 with parameters {'x1': 1.0, 'x2': 0.0, 'x3': 0.0, 'x4': 1.0, 'x5': 0.500082, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:45] ax.api.client: Trial 66 marked COMPLETED.
[INFO 02-27 07:31:45] ax.api.client: Trial 67 marked COMPLETED.
[INFO 02-27 07:31:45] ax.api.client: Trial 68 marked COMPLETED.
[INFO 02-27 07:31:45] ax.api.client: Trial 69 marked COMPLETED.
[INFO 02-27 07:31:45] ax.api.client: Trial 70 marked COMPLETED.
[INFO 02-27 07:31:51] ax.api.client: Generated new trial 71 with parameters {'x1': 0.195043, 'x2': 0.804224, 'x3': 0.0, 'x4': 0.647166, 'x5': 0.169022, 'x6': 0.021623} using GenerationNode MBM.
[INFO 02-27 07:31:51] ax.api.client: Generated new trial 72 with parameters {'x1': 0.880267, 'x2': 1.0, 'x3': 0.53356, 'x4': 0.738062, 'x5': 0.0, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:51] ax.api.client: Generated new trial 73 with parameters {'x1': 0.365602, 'x2': 1.0, 'x3': 0.0, 'x4': 0.880123, 'x5': 0.196678, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:51] ax.api.client: Generated new trial 74 with parameters {'x1': 1.0, 'x2': 1.0, 'x3': 0.947206, 'x4': 0.789086, 'x5': 0.0, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:51] ax.api.client: Generated new trial 75 with parameters {'x1': 1.0, 'x2': 0.818775, 'x3': 1.0, 'x4': 1.0, 'x5': 0.019057, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:51] ax.api.client: Trial 71 marked COMPLETED.
[INFO 02-27 07:31:51] ax.api.client: Trial 72 marked COMPLETED.
[INFO 02-27 07:31:51] ax.api.client: Trial 73 marked COMPLETED.
[INFO 02-27 07:31:51] ax.api.client: Trial 74 marked COMPLETED.
[INFO 02-27 07:31:52] ax.api.client: Trial 75 marked COMPLETED.
[INFO 02-27 07:31:58] ax.api.client: Generated new trial 76 with parameters {'x1': 0.0, 'x2': 1.0, 'x3': 0.0, 'x4': 0.61617, 'x5': 0.554803, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:58] ax.api.client: Generated new trial 77 with parameters {'x1': 0.0, 'x2': 0.428395, 'x3': 0.0, 'x4': 1.0, 'x5': 0.0, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:58] ax.api.client: Generated new trial 78 with parameters {'x1': 0.432176, 'x2': 1.0, 'x3': 0.0, 'x4': 0.468875, 'x5': 0.45354, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:58] ax.api.client: Generated new trial 79 with parameters {'x1': 0.0, 'x2': 0.951904, 'x3': 0.0, 'x4': 0.904031, 'x5': 0.0, 'x6': 0.3175} using GenerationNode MBM.
[INFO 02-27 07:31:58] ax.api.client: Generated new trial 80 with parameters {'x1': 0.13241, 'x2': 1.0, 'x3': 0.0, 'x4': 0.25102, 'x5': 0.668914, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:31:58] ax.api.client: Trial 76 marked COMPLETED.
[INFO 02-27 07:31:58] ax.api.client: Trial 77 marked COMPLETED.
[INFO 02-27 07:31:58] ax.api.client: Trial 78 marked COMPLETED.
[INFO 02-27 07:31:58] ax.api.client: Trial 79 marked COMPLETED.
[INFO 02-27 07:31:58] ax.api.client: Trial 80 marked COMPLETED.
[INFO 02-27 07:32:04] ax.api.client: Generated new trial 81 with parameters {'x1': 0.752389, 'x2': 1.0, 'x3': 0.0, 'x4': 0.366935, 'x5': 0.308275, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:04] ax.api.client: Generated new trial 82 with parameters {'x1': 0.794345, 'x2': 0.757362, 'x3': 0.0, 'x4': 0.492922, 'x5': 0.377844, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:04] ax.api.client: Generated new trial 83 with parameters {'x1': 0.69286, 'x2': 1.0, 'x3': 0.0, 'x4': 0.464021, 'x5': 0.333256, 'x6': 0.196805} using GenerationNode MBM.
[INFO 02-27 07:32:04] ax.api.client: Generated new trial 84 with parameters {'x1': 0.868709, 'x2': 1.0, 'x3': 0.0, 'x4': 0.623402, 'x5': 0.526065, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:04] ax.api.client: Generated new trial 85 with parameters {'x1': 0.543306, 'x2': 1.0, 'x3': 0.20177, 'x4': 0.271768, 'x5': 0.31237, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:04] ax.api.client: Trial 81 marked COMPLETED.
[INFO 02-27 07:32:04] ax.api.client: Trial 82 marked COMPLETED.
[INFO 02-27 07:32:04] ax.api.client: Trial 83 marked COMPLETED.
[INFO 02-27 07:32:04] ax.api.client: Trial 84 marked COMPLETED.
[INFO 02-27 07:32:04] ax.api.client: Trial 85 marked COMPLETED.
[INFO 02-27 07:32:10] ax.api.client: Generated new trial 86 with parameters {'x1': 0.139164, 'x2': 0.017546, 'x3': 0.667712, 'x4': 0.211682, 'x5': 0.724673, 'x6': 0.357136} using GenerationNode MBM.
[INFO 02-27 07:32:10] ax.api.client: Generated new trial 87 with parameters {'x1': 0.07606, 'x2': 0.126785, 'x3': 0.41994, 'x4': 0.078155, 'x5': 0.768246, 'x6': 0.512793} using GenerationNode MBM.
[INFO 02-27 07:32:10] ax.api.client: Generated new trial 88 with parameters {'x1': 0.0, 'x2': 0.0, 'x3': 0.67635, 'x4': 0.0, 'x5': 0.92501, 'x6': 0.398591} using GenerationNode MBM.
[INFO 02-27 07:32:10] ax.api.client: Generated new trial 89 with parameters {'x1': 0.321293, 'x2': 0.406475, 'x3': 0.0, 'x4': 0.554326, 'x5': 0.502267, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:10] ax.api.client: Generated new trial 90 with parameters {'x1': 0.069969, 'x2': 0.0, 'x3': 0.884353, 'x4': 0.106374, 'x5': 0.896224, 'x6': 0.252817} using GenerationNode MBM.
[INFO 02-27 07:32:10] ax.api.client: Trial 86 marked COMPLETED.
[INFO 02-27 07:32:10] ax.api.client: Trial 87 marked COMPLETED.
[INFO 02-27 07:32:10] ax.api.client: Trial 88 marked COMPLETED.
[INFO 02-27 07:32:10] ax.api.client: Trial 89 marked COMPLETED.
[INFO 02-27 07:32:10] ax.api.client: Trial 90 marked COMPLETED.
[INFO 02-27 07:32:17] ax.api.client: Generated new trial 91 with parameters {'x1': 0.127952, 'x2': 1.0, 'x3': 0.0, 'x4': 0.301801, 'x5': 0.303488, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:17] ax.api.client: Generated new trial 92 with parameters {'x1': 1.0, 'x2': 0.02647, 'x3': 0.896574, 'x4': 1.0, 'x5': 0.0, 'x6': 1.0} using GenerationNode MBM.
[INFO 02-27 07:32:17] ax.api.client: Generated new trial 93 with parameters {'x1': 0.205164, 'x2': 0.135734, 'x3': 0.468957, 'x4': 0.281967, 'x5': 0.316727, 'x6': 0.662729} using GenerationNode MBM.
[INFO 02-27 07:32:17] ax.api.client: Generated new trial 94 with parameters {'x1': 0.35328, 'x2': 1.0, 'x3': 0.375841, 'x4': 0.630584, 'x5': 0.407537, 'x6': 0.0} using GenerationNode MBM.
[INFO 02-27 07:32:17] ax.api.client: Generated new trial 95 with parameters {'x1': 1.0, 'x2': 0.417271, 'x3': 1.0, 'x4': 1.0, 'x5': 0.0, 'x6': 1.0} using GenerationNode MBM.
[INFO 02-27 07:32:17] ax.api.client: Trial 91 marked COMPLETED.
[INFO 02-27 07:32:17] ax.api.client: Trial 92 marked COMPLETED.
[INFO 02-27 07:32:17] ax.api.client: Trial 93 marked COMPLETED.
[INFO 02-27 07:32:17] ax.api.client: Trial 94 marked COMPLETED.
[INFO 02-27 07:32:17] ax.api.client: Trial 95 marked COMPLETED.
The logs show how Ax tells us to try different values combinating Exploration and Exploitation. This means, finding a balance between trying the best values known at the moment and exploring new ones.
best_parameters, prediction, index, name = client.get_best_parameterization()
print("Best Parameters:", best_parameters)
print("Prediction (mean, variance):", prediction)Best Parameters: {'x1': 0.20936983498640072, 'x2': 0.14629699805872345, 'x3': 0.47024518896967993, 'x4': 0.2831887014641851, 'x5': 0.303573068387771, 'x6': 0.6611021805326271}
Prediction (mean, variance): {'hartmann': (np.float64(-3.3158909507406618), np.float64(6.469546100443751e-05))}
The experiment, after 95 trials defined the minimum value found for the Hartmann function is -3.31.
We known the global minimum is -3.32, so the experiment found a good solution in a limited number of trials, taking into consideration it had to navigate 6 dimensions in a problem with multiple local optima.
3.5. Get Visualizations¶
Ax default visualizations show many aspects of the optimization process. Among them, the evolution of the output metric over time.
cards = client.compute_analyses(display=True)4. Multi-Objective Optimization¶
Now we will define a Multi-Objective Optimization problem. We will still use the Hartmann function but we will define two objectives:
- Minimize the Hartmann function
- Minimize the distance to the point (0.1, 0.1, 0.1, 0.1, 0.1, 0.1)
The last objective is something we just made up to test the multi-objective capabilities of Ax.
client = Client()
parameters = [
RangeParameterConfig(name="x1", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x2", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x3", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x4", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x5", parameter_type="float", bounds=(0, 1)),
RangeParameterConfig(name="x6", parameter_type="float", bounds=(0, 1)),
]
client.configure_experiment(parameters=parameters)
# Minimize the Hartmann function and the distance
# Multi-Objective Optimization allows us to define constraints in the objective function. In this case we are not making use of it
client.configure_optimization(
objective="-hartmann, -distance", outcome_constraints=[]
)
# In this case we may need more trials for the multi-objective optimization to converge
for _ in range(20):
# Request a series of inputs to Ax client
trials = client.get_next_trials(max_trials=5)
# Conduct the experiments (5 times in this case)
for trial_index, parameters in trials.items():
x1 = parameters["x1"]
x2 = parameters["x2"]
x3 = parameters["x3"]
x4 = parameters["x4"]
x5 = parameters["x5"]
x6 = parameters["x6"]
result = hartmann6(x1, x2, x3, x4, x5, x6)
distance = np.linalg.norm(
np.array([x1, x2, x3, x4, x5, x6])
- np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
)
# Send the results back to Ax with the trial index for reference
client.complete_trial(
trial_index=trial_index,
raw_data={"hartmann": result, "distance": distance},
)[INFO 02-27 07:33:20] ax.api.client: GenerationStrategy(name='Center+Sobol+MBM:fast', nodes=[CenterGenerationNode(next_node_name='Sobol'), GenerationNode(node_name='Sobol', generator_specs=[GeneratorSpec(generator_enum=Sobol, model_key_override=None)], transition_criteria=[MinTrials(transition_to='MBM'), MinTrials(transition_to='MBM')]), GenerationNode(node_name='MBM', generator_specs=[GeneratorSpec(generator_enum=BoTorch, model_key_override=None)], transition_criteria=[])]) chosen based on user input and problem structure.
frontier = client.get_pareto_frontier()
for parameters, metrics, trial_index, arm_name in frontier:
print(
f"Trial {trial_index} - {arm_name} - Metrics: {metrics} - Parameters: {parameters}"
)/opt/homebrew/Caskroom/miniconda/base/envs/msml610-project/lib/python3.13/site-packages/ax/adapter/transforms/winsorize.py:131: AxOptimizationWarning:
Encountered a `MultiObjective` without objective thresholds. We will winsorize each objective separately. We strongly recommend specifying the objective thresholds when using multi-objective optimization.
[INFO 02-28 11:16:30] ax.service.utils.best_point: Using inferred objective thresholds: [ObjectiveThreshold(distance <= 0.8003070411695381), ObjectiveThreshold(hartmann <= 0.21312487574709826)], as objective thresholds were not specified as part of the optimization configuration on the experiment.
Trial 85 - 85_0 - Metrics: {'distance': (np.float64(0.47443615065859773), 7.070527175113317e-06), 'hartmann': (np.float64(-2.2178207886891697), 2.922747426339589e-05)} - Parameters: {'x1': 0.16954483269972762, 'x2': 0.15766973422598893, 'x3': 0.21470985166699, 'x4': 0.24557888932509284, 'x5': 0.2636284070662792, 'x6': 0.4930549765504886}
Trial 63 - 63_0 - Metrics: {'distance': (np.float64(0.45487235356976846), 9.949837056861813e-06), 'hartmann': (np.float64(-2.0693194476101113), 5.1106136241268004e-05)} - Parameters: {'x1': 0.17814584591077073, 'x2': 0.15943571742989351, 'x3': 0.23195841148798244, 'x4': 0.2385526851382132, 'x5': 0.269343584979879, 'x6': 0.4630224553010107}
Trial 72 - 72_0 - Metrics: {'distance': (np.float64(0.49658122120096154), 7.70361314132717e-06), 'hartmann': (np.float64(-2.36274315117152), 3.3110716244741346e-05)} - Parameters: {'x1': 0.17765882952369147, 'x2': 0.1540498861128613, 'x3': 0.2288968802982959, 'x4': 0.2601974263518138, 'x5': 0.27497512828340154, 'x6': 0.5037220516380456}
Trial 77 - 77_0 - Metrics: {'distance': (np.float64(0.4334390010433775), 1.1049953135778932e-05), 'hartmann': (np.float64(-1.9028144321448033), 5.6185715083452634e-05)} - Parameters: {'x1': 0.1331440001202445, 'x2': 0.14088845163511088, 'x3': 0.20009335289474714, 'x4': 0.24936823694556517, 'x5': 0.27527117930253087, 'x6': 0.4468954461096993}
Trial 95 - 95_0 - Metrics: {'distance': (np.float64(0.5165761863918608), 1.1576439045167156e-05), 'hartmann': (np.float64(-2.498073881277361), 5.605033404653109e-05)} - Parameters: {'x1': 0.15791408103182447, 'x2': 0.16684865475239163, 'x3': 0.25401998392332786, 'x4': 0.25710267066178333, 'x5': 0.2676010299364228, 'x6': 0.5259943961015281}
Trial 91 - 91_0 - Metrics: {'distance': (np.float64(0.4040594873930997), 1.1373681481220897e-05), 'hartmann': (np.float64(-1.7161862141847386), 4.647633590096082e-05)} - Parameters: {'x1': 0.14270471626299064, 'x2': 0.1520533088514125, 'x3': 0.20005500864250264, 'x4': 0.2301007948296687, 'x5': 0.24074073633027734, 'x6': 0.4358572226203956}
Trial 49 - 49_0 - Metrics: {'distance': (np.float64(0.4199032500548166), 1.4490752953188848e-05), 'hartmann': (np.float64(-1.7314690748945558), 6.589590090356616e-05)} - Parameters: {'x1': 0.1521582808143382, 'x2': 0.11395799394495276, 'x3': 0.1350864447224617, 'x4': 0.19257390254244006, 'x5': 0.28348547790200235, 'x6': 0.4540021145306308}
Trial 66 - 66_0 - Metrics: {'distance': (np.float64(0.5395196251099111), 9.976284467988066e-06), 'hartmann': (np.float64(-2.620861652884117), 3.245541701619061e-05)} - Parameters: {'x1': 0.20354150885921177, 'x2': 0.17461503412176405, 'x3': 0.2670467458111102, 'x4': 0.26566596685502064, 'x5': 0.2809752686116866, 'x6': 0.5309978500136058}
Trial 69 - 69_0 - Metrics: {'distance': (np.float64(0.38239744722415003), 9.165379545537563e-06), 'hartmann': (np.float64(-1.5472577406912638), 4.524057953872223e-05)} - Parameters: {'x1': 0.1582301887161509, 'x2': 0.11170362025271663, 'x3': 0.19692495288287698, 'x4': 0.21582192298426192, 'x5': 0.25598290850703315, 'x6': 0.4066905338944305}
Trial 75 - 75_0 - Metrics: {'distance': (np.float64(0.5710062743339589), 9.75490873456022e-06), 'hartmann': (np.float64(-2.79678150769025), 3.8315258736344126e-05)} - Parameters: {'x1': 0.20135933114963717, 'x2': 0.1772707635240661, 'x3': 0.27964954328354785, 'x4': 0.2799033202776836, 'x5': 0.28418626867257984, 'x6': 0.5590063885613694}
Trial 94 - 94_0 - Metrics: {'distance': (np.float64(0.3325505654137838), 1.3094020173394444e-05), 'hartmann': (np.float64(-1.2385073672120213), 4.811231287177148e-05)} - Parameters: {'x1': 0.13614100588626615, 'x2': 0.14243052828375843, 'x3': 0.17681362594592015, 'x4': 0.21218570412959115, 'x5': 0.22348871849185223, 'x6': 0.3783813142547195}
Trial 46 - 46_0 - Metrics: {'distance': (np.float64(0.35879757719284705), 1.2942242753342888e-05), 'hartmann': (np.float64(-1.3103105141412759), 5.029155449115827e-05)} - Parameters: {'x1': 0.13645910489779695, 'x2': 0.09671489962085611, 'x3': 0.13991147419902364, 'x4': 0.17037079186530144, 'x5': 0.26698257503146317, 'x6': 0.39836320458185054}
Trial 80 - 80_0 - Metrics: {'distance': (np.float64(0.3503749214271976), 2.2027607619084653e-05), 'hartmann': (np.float64(-1.2539212347285937), 9.16173541321063e-05)} - Parameters: {'x1': 0.08850778592884079, 'x2': 0.10508640968108521, 'x3': 0.17121418054741, 'x4': 0.2355340339927731, 'x5': 0.26820707976810004, 'x6': 0.3654717049405758}
Trial 68 - 68_0 - Metrics: {'distance': (np.float64(0.31971618931539), 1.1620024063745143e-05), 'hartmann': (np.float64(-1.0949399911507367), 6.453013925058416e-05)} - Parameters: {'x1': 0.14002986321973243, 'x2': 0.0772578337322347, 'x3': 0.18140334064968344, 'x4': 0.19326299691280213, 'x5': 0.24094609412491289, 'x6': 0.35210803907769306}
Trial 62 - 62_0 - Metrics: {'distance': (np.float64(0.6062191140466917), 1.4279246938972302e-05), 'hartmann': (np.float64(-2.9341715962361223), 6.210193711791646e-05)} - Parameters: {'x1': 0.21678828026381997, 'x2': 0.20736026972058014, 'x3': 0.33109904970196874, 'x4': 0.27259295436252096, 'x5': 0.29236941362300506, 'x6': 0.5715395420611195}
Trial 84 - 84_0 - Metrics: {'distance': (np.float64(0.27630321100334854), 1.4684496460958558e-05), 'hartmann': (np.float64(-0.9157025485858821), 6.559846628250137e-05)} - Parameters: {'x1': 0.1534928555274513, 'x2': 0.12572047627111974, 'x3': 0.139437010887472, 'x4': 0.19385844050505144, 'x5': 0.22720703625404476, 'x6': 0.32710243777219006}
Trial 93 - 93_0 - Metrics: {'distance': (np.float64(0.24446813386730804), 1.4008329867568896e-05), 'hartmann': (np.float64(-0.7430852691870745), 5.8464455200813974e-05)} - Parameters: {'x1': 0.12946589524953492, 'x2': 0.1284217052005888, 'x3': 0.1495013623078229, 'x4': 0.18614329759075096, 'x5': 0.20303433741108873, 'x6': 0.307018176097996}
Trial 70 - 70_0 - Metrics: {'distance': (np.float64(0.6501275004274671), 1.5277347722226646e-05), 'hartmann': (np.float64(-3.0919603645386773), 4.2325963503761956e-05)} - Parameters: {'x1': 0.2298853471724412, 'x2': 0.198794471221808, 'x3': 0.35547642799478685, 'x4': 0.29580938388182354, 'x5': 0.2881140310308913, 'x6': 0.6065638035120949}
Trial 82 - 82_0 - Metrics: {'distance': (np.float64(0.2050842188402302), 1.3216917072108369e-05), 'hartmann': (np.float64(-0.5577834814714754), 4.0172126463062665e-05)} - Parameters: {'x1': 0.13455759919564034, 'x2': 0.10533676213586561, 'x3': 0.12635608562776063, 'x4': 0.16375598202191882, 'x5': 0.20791925735876066, 'x6': 0.2676271244221982}
Trial 90 - 90_0 - Metrics: {'distance': (np.float64(0.6816860285499684), 6.706565480399275e-05), 'hartmann': (np.float64(-3.1100078462502783), 0.00012177103973581973)} - Parameters: {'x1': 0.10425814658973603, 'x2': 0.18271000861680095, 'x3': 0.4222983037478045, 'x4': 0.2770002161872171, 'x5': 0.2808563348824362, 'x6': 0.640805382908224}
Trial 83 - 83_0 - Metrics: {'distance': (np.float64(0.15259693330890656), 1.3785211492457854e-05), 'hartmann': (np.float64(-0.3355703270825052), 5.133662737279571e-05)} - Parameters: {'x1': 0.11665544547579507, 'x2': 0.09418006812399181, 'x3': 0.11671857443355926, 'x4': 0.13506822291459625, 'x5': 0.18517257199033654, 'x6': 0.219407990672493}
Trial 79 - 79_0 - Metrics: {'distance': (np.float64(0.7037064466692435), 3.2376820728899864e-05), 'hartmann': (np.float64(-3.187823102874785), 0.00010044224538609893)} - Parameters: {'x1': 0.23319659562802894, 'x2': 0.19832738945307316, 'x3': 0.4050130733901992, 'x4': 0.30845950401643923, 'x5': 0.28713126936940014, 'x6': 0.6432840581424498}
Trial 92 - 92_0 - Metrics: {'distance': (np.float64(0.12062579922847771), 2.0091201467248237e-05), 'hartmann': (np.float64(-0.22321837459599614), 7.902817104040931e-05)} - Parameters: {'x1': 0.11293916950274466, 'x2': 0.10591672295646172, 'x3': 0.09783457677263732, 'x4': 0.12917082870072813, 'x5': 0.15813782110091182, 'x6': 0.18572439669390486}
Trial 65 - 65_0 - Metrics: {'distance': (np.float64(0.7363988717157577), 2.022366748060947e-05), 'hartmann': (np.float64(-3.1944121790440647), 8.050274355556564e-05)} - Parameters: {'x1': 0.23504299422727912, 'x2': 0.22346808393886347, 'x3': 0.4923250171814252, 'x4': 0.2889973137863814, 'x5': 0.2996603026693693, 'x6': 0.629462697392165}
Trial 81 - 81_0 - Metrics: {'distance': (np.float64(0.09731717717797161), 2.0273760550894247e-05), 'hartmann': (np.float64(-0.09665122014300742), 9.621497872312788e-05)} - Parameters: {'x1': 0.071792947178012, 'x2': 0.09142582375586354, 'x3': 0.0992525656647285, 'x4': 0.07741764872538856, 'x5': 0.12327791047094322, 'x6': 0.13382648000134797}
distances = []
hartmanns = []
for parameters, metrics, trial_index, arm_name in frontier:
distances.append(metrics["distance"])
hartmanns.append(metrics["hartmann"])
plt.figure(figsize=(8, 6))
plt.scatter(distances, hartmanns, c="blue", edgecolor="k", alpha=0.7)
plt.xlabel("distance")
plt.ylabel("hartmann")
plt.title("Pareto Frontier: Distance vs Hartmann")
plt.grid(True)
plt.show()
The previous plot shows the Pareto Frontier and how we can find a better minimum for the Hartmann function while a larger distance is allowed. This will tell us how much we can keep optimizing as we keep moving from the epicenter.
In this case much more experiments have to be conducted and they are not enough to find the global minimum.