This tutorial illustrates the core visualization utilities available in Ax.
import numpy as np
from ax.service.ax_client import AxClient, ObjectiveProperties
from ax.modelbridge.cross_validation import cross_validate
from ax.plot.contour import interact_contour
from ax.plot.diagnostic import interact_cross_validation
from ax.plot.scatter import(
interact_fitted,
plot_objective_vs_constraints,
tile_fitted,
)
from ax.plot.slice import plot_slice
from ax.utils.measurement.synthetic_functions import hartmann6
from ax.utils.notebook.plotting import render, init_notebook_plotting
init_notebook_plotting()
[INFO 12-29 21:28:38] ax.utils.notebook.plotting: Injecting Plotly library into cell. Do not overwrite or delete cell.
The vizualizations require an experiment object and a model fit on the evaluated data. The routine below is a copy of the Service API tutorial, so the explanation here is omitted. Retrieving the experiment and model objects for each API paradigm is shown in the respective tutorials
noise_sd = 0.1
param_names = [f"x{i+1}" for i in range(6)] # x1, x2, ..., x6
def noisy_hartmann_evaluation_function(parameterization):
x = np.array([parameterization.get(p_name) for p_name in param_names])
noise1, noise2 = np.random.normal(0, noise_sd, 2)
return {
"hartmann6": (hartmann6(x) + noise1, noise_sd),
"l2norm": (np.sqrt((x ** 2).sum()) + noise2, noise_sd)
}
ax_client = AxClient()
ax_client.create_experiment(
name="test_visualizations",
parameters=[
{
"name": p_name,
"type": "range",
"bounds": [0.0, 1.0],
}
for p_name in param_names
],
objectives={"hartmann6": ObjectiveProperties(minimize=True)},
outcome_constraints=["l2norm <= 1.25"]
)
[INFO 12-29 21:28:38] ax.service.ax_client: Starting optimization with verbose logging. To disable logging, set the `verbose_logging` argument to `False`. Note that float values in the logs are rounded to 6 decimal points. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x1. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x2. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x3. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x4. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x5. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x6. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 12-29 21:28:38] ax.service.utils.instantiation: Created search space: SearchSpace(parameters=[RangeParameter(name='x1', parameter_type=FLOAT, range=[0.0, 1.0]), RangeParameter(name='x2', parameter_type=FLOAT, range=[0.0, 1.0]), RangeParameter(name='x3', parameter_type=FLOAT, range=[0.0, 1.0]), RangeParameter(name='x4', parameter_type=FLOAT, range=[0.0, 1.0]), RangeParameter(name='x5', parameter_type=FLOAT, range=[0.0, 1.0]), RangeParameter(name='x6', parameter_type=FLOAT, range=[0.0, 1.0])], parameter_constraints=[]). [INFO 12-29 21:28:38] ax.modelbridge.dispatch_utils: Using Bayesian optimization since there are more ordered parameters than there are categories for the unordered categorical parameters. [INFO 12-29 21:28:38] ax.modelbridge.dispatch_utils: Calculating the number of remaining initialization trials based on num_initialization_trials=None max_initialization_trials=None num_tunable_parameters=6 num_trials=None use_batch_trials=False [INFO 12-29 21:28:38] ax.modelbridge.dispatch_utils: calculated num_initialization_trials=12 [INFO 12-29 21:28:38] ax.modelbridge.dispatch_utils: num_completed_initialization_trials=0 num_remaining_initialization_trials=12 [INFO 12-29 21:28:38] ax.modelbridge.dispatch_utils: Using Bayesian Optimization generation strategy: GenerationStrategy(name='Sobol+GPEI', steps=[Sobol for 12 trials, GPEI for subsequent trials]). Iterations after 12 will take longer to generate due to model-fitting.
for i in range(20):
parameters, trial_index = ax_client.get_next_trial()
# Local evaluation here can be replaced with deployment to external system.
ax_client.complete_trial(trial_index=trial_index, raw_data=noisy_hartmann_evaluation_function(parameters))
/home/runner/work/Ax/Ax/ax/core/observation.py:274: FutureWarning: In a future version of pandas, a length 1 tuple will be returned when iterating over a groupby with a grouper equal to a list of length 1. Don't supply a list with a single grouper to avoid this warning. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 0 with parameters {'x1': 0.509467, 'x2': 0.028962, 'x3': 0.919525, 'x4': 0.718578, 'x5': 0.205345, 'x6': 0.74537}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 0 with data: {'hartmann6': (-0.294883, 0.1), 'l2norm': (1.540891, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 1 with parameters {'x1': 0.030807, 'x2': 0.733315, 'x3': 0.805419, 'x4': 0.732529, 'x5': 0.226611, 'x6': 0.99587}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 1 with data: {'hartmann6': (-0.335388, 0.1), 'l2norm': (1.784087, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 2 with parameters {'x1': 0.544919, 'x2': 0.9505, 'x3': 0.93452, 'x4': 0.003054, 'x5': 0.693434, 'x6': 0.049953}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 2 with data: {'hartmann6': (0.070062, 0.1), 'l2norm': (1.534787, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 3 with parameters {'x1': 0.458143, 'x2': 0.862874, 'x3': 0.585976, 'x4': 0.042028, 'x5': 0.928453, 'x6': 0.167484}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 3 with data: {'hartmann6': (-0.204773, 0.1), 'l2norm': (1.505643, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 4 with parameters {'x1': 0.854988, 'x2': 0.117923, 'x3': 0.417829, 'x4': 0.588809, 'x5': 0.235495, 'x6': 0.00535}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 4 with data: {'hartmann6': (-0.101418, 0.1), 'l2norm': (1.122423, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 5 with parameters {'x1': 0.060659, 'x2': 0.36236, 'x3': 0.698587, 'x4': 0.533907, 'x5': 0.162588, 'x6': 0.22702}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 5 with data: {'hartmann6': (-0.089756, 0.1), 'l2norm': (1.01303, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 6 with parameters {'x1': 0.681922, 'x2': 0.045512, 'x3': 0.276457, 'x4': 0.682553, 'x5': 0.888026, 'x6': 0.926312}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 6 with data: {'hartmann6': (-0.014906, 0.1), 'l2norm': (1.499251, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 7 with parameters {'x1': 0.643002, 'x2': 0.806809, 'x3': 0.602528, 'x4': 0.542176, 'x5': 0.873224, 'x6': 0.263903}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 7 with data: {'hartmann6': (-0.550204, 0.1), 'l2norm': (1.614762, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 8 with parameters {'x1': 0.438348, 'x2': 0.01965, 'x3': 0.225021, 'x4': 0.70105, 'x5': 0.820094, 'x6': 0.747691}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 8 with data: {'hartmann6': (0.022785, 0.1), 'l2norm': (1.46755, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 9 with parameters {'x1': 0.334092, 'x2': 0.049544, 'x3': 0.31188, 'x4': 0.561487, 'x5': 0.623716, 'x6': 0.951681}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 9 with data: {'hartmann6': (-0.065793, 0.1), 'l2norm': (1.22676, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 10 with parameters {'x1': 0.013929, 'x2': 0.867573, 'x3': 0.524857, 'x4': 0.61982, 'x5': 0.352199, 'x6': 0.357934}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 10 with data: {'hartmann6': (-0.226878, 0.1), 'l2norm': (1.236159, 0.1)}. [INFO 12-29 21:28:38] ax.service.ax_client: Generated new trial 11 with parameters {'x1': 0.10638, 'x2': 0.925673, 'x3': 0.347194, 'x4': 0.73536, 'x5': 0.144307, 'x6': 0.885829}. [INFO 12-29 21:28:38] ax.service.ax_client: Completed trial 11 with data: {'hartmann6': (-0.025526, 0.1), 'l2norm': (1.622494, 0.1)}. [INFO 12-29 21:28:51] ax.service.ax_client: Generated new trial 12 with parameters {'x1': 0.289555, 'x2': 0.68663, 'x3': 0.616159, 'x4': 0.595965, 'x5': 0.395963, 'x6': 0.235655}. [INFO 12-29 21:28:51] ax.service.ax_client: Completed trial 12 with data: {'hartmann6': (-1.094233, 0.1), 'l2norm': (1.210456, 0.1)}. [INFO 12-29 21:29:13] ax.service.ax_client: Generated new trial 13 with parameters {'x1': 0.2114, 'x2': 0.659825, 'x3': 0.629484, 'x4': 0.576865, 'x5': 0.256249, 'x6': 0.226572}. [INFO 12-29 21:29:13] ax.service.ax_client: Completed trial 13 with data: {'hartmann6': (-0.771727, 0.1), 'l2norm': (1.092837, 0.1)}. [INFO 12-29 21:29:40] ax.service.ax_client: Generated new trial 14 with parameters {'x1': 0.383577, 'x2': 0.690274, 'x3': 0.614337, 'x4': 0.612205, 'x5': 0.333837, 'x6': 0.169309}. [INFO 12-29 21:29:40] ax.service.ax_client: Completed trial 14 with data: {'hartmann6': (-1.958704, 0.1), 'l2norm': (1.137047, 0.1)}. [INFO 12-29 21:29:54] ax.service.ax_client: Generated new trial 15 with parameters {'x1': 0.411374, 'x2': 0.657816, 'x3': 0.611819, 'x4': 0.603055, 'x5': 0.245093, 'x6': 0.163786}. [INFO 12-29 21:29:54] ax.service.ax_client: Completed trial 15 with data: {'hartmann6': (-1.631368, 0.1), 'l2norm': (1.141971, 0.1)}. [INFO 12-29 21:30:03] ax.service.ax_client: Generated new trial 16 with parameters {'x1': 0.401157, 'x2': 0.728809, 'x3': 0.619218, 'x4': 0.636153, 'x5': 0.338858, 'x6': 0.118562}. [INFO 12-29 21:30:03] ax.service.ax_client: Completed trial 16 with data: {'hartmann6': (-2.245452, 0.1), 'l2norm': (1.219328, 0.1)}. [INFO 12-29 21:30:16] ax.service.ax_client: Generated new trial 17 with parameters {'x1': 0.433516, 'x2': 0.73424, 'x3': 0.550635, 'x4': 0.632669, 'x5': 0.363638, 'x6': 0.11215}. [INFO 12-29 21:30:16] ax.service.ax_client: Completed trial 17 with data: {'hartmann6': (-2.313931, 0.1), 'l2norm': (1.463567, 0.1)}. [INFO 12-29 21:30:34] ax.service.ax_client: Generated new trial 18 with parameters {'x1': 0.422282, 'x2': 0.697169, 'x3': 0.68871, 'x4': 0.623526, 'x5': 0.330549, 'x6': 0.108302}. [INFO 12-29 21:30:34] ax.service.ax_client: Completed trial 18 with data: {'hartmann6': (-2.171868, 0.1), 'l2norm': (1.329198, 0.1)}. [INFO 12-29 21:30:50] ax.service.ax_client: Generated new trial 19 with parameters {'x1': 0.405729, 'x2': 0.686861, 'x3': 0.567166, 'x4': 0.646465, 'x5': 0.268599, 'x6': 0.113926}. [INFO 12-29 21:30:50] ax.service.ax_client: Completed trial 19 with data: {'hartmann6': (-2.101002, 0.1), 'l2norm': (1.257834, 0.1)}.
The plot below shows the response surface for hartmann6
metric as a function of the x1
, x2
parameters.
The other parameters are fixed in the middle of their respective ranges, which in this example is 0.5 for all of them.
# this could alternately be done with `ax.plot.contour.plot_contour`
render(ax_client.get_contour_plot(param_x="x1", param_y="x2", metric_name='hartmann6'))
[INFO 12-29 21:30:50] ax.service.ax_client: Retrieving contour plot with parameter 'x1' on X-axis and 'x2' on Y-axis, for metric 'hartmann6'. Remaining parameters are affixed to the middle of their range.
The plot below allows toggling between different pairs of parameters to view the contours.
model = ax_client.generation_strategy.model
render(interact_contour(model=model, metric_name='hartmann6'))
This plot illustrates the tradeoffs achievable for 2 different metrics. The plot takes the x-axis metric as input (usually the objective) and allows toggling among all other metrics for the y-axis.
This is useful to get a sense of the pareto frontier (i.e. what is the best objective value achievable for different bounds on the constraint)
render(plot_objective_vs_constraints(model, 'hartmann6', rel=False))
CV plots are useful to check how well the model predictions calibrate against the actual measurements. If all points are close to the dashed line, then the model is a good predictor of the real data.
cv_results = cross_validate(model)
render(interact_cross_validation(cv_results))
Slice plots show the metric outcome as a function of one parameter while fixing the others. They serve a similar function as contour plots.
render(plot_slice(model, "x2", "hartmann6"))
Tile plots are useful for viewing the effect of each arm.
render(interact_fitted(model, rel=False))
Total runtime of script: 2 minutes, 31.61 seconds.