Note
Go to the end to download the full example code. or to run this example in your browser via Binder
ParEGO¶
An example of how to use multi-objective optimization with ParEGO. Both accuracy and run-time are going to be optimized on the digits dataset using an MLP, and the configurations are shown in a plot, highlighting the best ones in a Pareto front. The red cross indicates the best configuration selected by SMAC.
In the optimization, SMAC evaluates the configurations on two different seeds. Therefore, the plot shows the mean accuracy and run-time of each configuration.

[WARNING][target_function_runner.py:74] The argument budget is not set by SMAC: Consider removing it from the target function.
[INFO][abstract_initial_design.py:147] Using 5 initial design configurations and 0 additional configurations.
[INFO][abstract_intensifier.py:516] Added config 71813f as new incumbent because there are no incumbents yet.
[INFO][abstract_intensifier.py:603] Config 854c79 is a new incumbent. Total number of incumbents: 2.
[INFO][abstract_intensifier.py:603] Config 5704a1 is a new incumbent. Total number of incumbents: 3.
[INFO][abstract_intensifier.py:595] Added config 038a40 and rejected config 5704a1 as incumbent because it is not better than the incumbents on 2 instances:
[INFO][abstract_intensifier.py:595] Added config c28013 and rejected config 038a40 as incumbent because it is not better than the incumbents on 2 instances:
[INFO][abstract_intensifier.py:603] Config 370059 is a new incumbent. Total number of incumbents: 4.
[INFO][abstract_intensifier.py:603] Config c4388b is a new incumbent. Total number of incumbents: 5.
[INFO][abstract_intensifier.py:603] Config 1da4cc is a new incumbent. Total number of incumbents: 6.
[INFO][abstract_intensifier.py:603] Config 8c80dc is a new incumbent. Total number of incumbents: 7.
[INFO][abstract_intensifier.py:595] Added config 0c6f4f and rejected config 8c80dc as incumbent because it is not better than the incumbents on 2 instances:
[INFO][abstract_intensifier.py:603] Config b67313 is a new incumbent. Total number of incumbents: 7.
[INFO][abstract_intensifier.py:595] Added config a9fb9c and rejected config c4388b as incumbent because it is not better than the incumbents on 2 instances:
[INFO][abstract_intensifier.py:603] Config de04e5 is a new incumbent. Total number of incumbents: 8.
[INFO][abstract_intensifier.py:595] Added config 6b18bb and rejected config 1da4cc as incumbent because it is not better than the incumbents on 2 instances:
[INFO][smbo.py:328] Configuration budget is exhausted:
[INFO][smbo.py:329] --- Remaining wallclock time: -1.1430540084838867
[INFO][smbo.py:330] --- Remaining cpu time: inf
[INFO][smbo.py:331] --- Remaining trials: 157
Validated costs from default config:
--- [0.60155834 0.16424942]
Validated costs from the Pareto front (incumbents):
--- [0.72761606 0.202667 ]
--- [0.05092309 0.74047458]
--- [0.07790932 0.54386055]
--- [0.0901563 0.41713023]
--- [0.09015166 0.45533073]
--- [0.09154751 0.41889274]
--- [0.66499923 0.39320683]
--- [0.69558651 0.32330561]
from __future__ import annotations
import time
import warnings
import matplotlib.pyplot as plt
import numpy as np
from ConfigSpace import (
Categorical,
Configuration,
ConfigurationSpace,
EqualsCondition,
Float,
InCondition,
Integer,
)
from sklearn.datasets import load_digits
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.neural_network import MLPClassifier
from smac import HyperparameterOptimizationFacade as HPOFacade
from smac import Scenario
from smac.facade.abstract_facade import AbstractFacade
from smac.multi_objective.parego import ParEGO
__copyright__ = "Copyright 2021, AutoML.org Freiburg-Hannover"
__license__ = "3-clause BSD"
digits = load_digits()
class MLP:
@property
def configspace(self) -> ConfigurationSpace:
cs = ConfigurationSpace()
n_layer = Integer("n_layer", (1, 5), default=1)
n_neurons = Integer("n_neurons", (8, 256), log=True, default=10)
activation = Categorical("activation", ["logistic", "tanh", "relu"], default="tanh")
solver = Categorical("solver", ["lbfgs", "sgd", "adam"], default="adam")
batch_size = Integer("batch_size", (30, 300), default=200)
learning_rate = Categorical("learning_rate", ["constant", "invscaling", "adaptive"], default="constant")
learning_rate_init = Float("learning_rate_init", (0.0001, 1.0), default=0.001, log=True)
cs.add([n_layer, n_neurons, activation, solver, batch_size, learning_rate, learning_rate_init])
use_lr = EqualsCondition(child=learning_rate, parent=solver, value="sgd")
use_lr_init = InCondition(child=learning_rate_init, parent=solver, values=["sgd", "adam"])
use_batch_size = InCondition(child=batch_size, parent=solver, values=["sgd", "adam"])
# We can also add multiple conditions on hyperparameters at once:
cs.add([use_lr, use_batch_size, use_lr_init])
return cs
def train(self, config: Configuration, seed: int = 0, budget: int = 10) -> dict[str, float]:
lr = config.get("learning_rate", "constant")
lr_init = config.get("learning_rate_init", 0.001)
batch_size = config.get("batch_size", 200)
start_time = time.time()
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
classifier = MLPClassifier(
hidden_layer_sizes=[config["n_neurons"]] * config["n_layer"],
solver=config["solver"],
batch_size=batch_size,
activation=config["activation"],
learning_rate=lr,
learning_rate_init=lr_init,
max_iter=int(np.ceil(budget)),
random_state=seed,
)
# Returns the 5-fold cross validation accuracy
cv = StratifiedKFold(n_splits=5, random_state=seed, shuffle=True) # to make CV splits consistent
score = cross_val_score(classifier, digits.data, digits.target, cv=cv, error_score="raise")
return {
"1 - accuracy": 1 - np.mean(score),
"time": time.time() - start_time,
}
def plot_pareto(smac: AbstractFacade, incumbents: list[Configuration]) -> None:
"""Plots configurations from SMAC and highlights the best configurations in a Pareto front."""
average_costs = []
average_pareto_costs = []
for config in smac.runhistory.get_configs():
# Since we use multiple seeds, we have to average them to get only one cost value pair for each configuration
average_cost = smac.runhistory.average_cost(config)
if config in incumbents:
average_pareto_costs += [average_cost]
else:
average_costs += [average_cost]
# Let's work with a numpy array
costs = np.vstack(average_costs)
pareto_costs = np.vstack(average_pareto_costs)
pareto_costs = pareto_costs[pareto_costs[:, 0].argsort()] # Sort them
costs_x, costs_y = costs[:, 0], costs[:, 1]
pareto_costs_x, pareto_costs_y = pareto_costs[:, 0], pareto_costs[:, 1]
plt.scatter(costs_x, costs_y, marker="x", label="Configuration")
plt.scatter(pareto_costs_x, pareto_costs_y, marker="x", c="r", label="Incumbent")
plt.step(
[pareto_costs_x[0]] + pareto_costs_x.tolist() + [np.max(costs_x)], # We add bounds
[np.max(costs_y)] + pareto_costs_y.tolist() + [np.min(pareto_costs_y)], # We add bounds
where="post",
linestyle=":",
)
plt.title("Pareto-Front")
plt.xlabel(smac.scenario.objectives[0])
plt.ylabel(smac.scenario.objectives[1])
plt.legend()
plt.show()
if __name__ == "__main__":
mlp = MLP()
objectives = ["1 - accuracy", "time"]
# Define our environment variables
scenario = Scenario(
mlp.configspace,
objectives=objectives,
walltime_limit=30, # After 30 seconds, we stop the hyperparameter optimization
n_trials=200, # Evaluate max 200 different trials
n_workers=1,
)
# We want to run five random configurations before starting the optimization.
initial_design = HPOFacade.get_initial_design(scenario, n_configs=5)
multi_objective_algorithm = ParEGO(scenario)
intensifier = HPOFacade.get_intensifier(scenario, max_config_calls=2)
# Create our SMAC object and pass the scenario and the train method
smac = HPOFacade(
scenario,
mlp.train,
initial_design=initial_design,
multi_objective_algorithm=multi_objective_algorithm,
intensifier=intensifier,
overwrite=True,
)
# Let's optimize
incumbents = smac.optimize()
# Get cost of default configuration
default_cost = smac.validate(mlp.configspace.get_default_configuration())
print(f"Validated costs from default config: \n--- {default_cost}\n")
print("Validated costs from the Pareto front (incumbents):")
for incumbent in incumbents:
cost = smac.validate(incumbent)
print("---", cost)
# Let's plot a pareto front
plot_pareto(smac, incumbents)
Total running time of the script: (0 minutes 38.654 seconds)