Skip to content

Commit

Permalink
Merge pull request #58 from luinardi/bopro_improvements
Browse files Browse the repository at this point in the history
BOPrO prior optimization improvements
  • Loading branch information
arturluis authored Sep 15, 2021
2 parents 515c0f9 + a257947 commit 730d3aa
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 59 deletions.
14 changes: 13 additions & 1 deletion hypermapper/bo.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ def main(config, black_box_function=None, profiling=None):
model_type = config["models"]["model"]
optimization_method = config["optimization_method"]
time_budget = config["time_budget"]
acquisition_function_optimizer = config["acquisition_function_optimizer"]
if (
acquisition_function_optimizer == "cma_es"
and not param_space.is_space_continuous()
):
print(
"Warning: CMA_ES can only be used with continuous search spaces (i.e. all parameters must be of type 'real')"
)
print("Switching acquisition function optimizer to local search")
acquisition_function_optimizer = "local_search"

input_params = param_space.get_input_parameters()
number_of_objectives = len(optimization_metrics)
objective_limits = {}
Expand Down Expand Up @@ -445,6 +456,7 @@ def main(config, black_box_function=None, profiling=None):
objective_limits,
classification_model,
profiling,
acquisition_function_optimizer,
)

else:
Expand All @@ -459,7 +471,7 @@ def main(config, black_box_function=None, profiling=None):
)
best_configuration = (
param_space.random_sample_configurations_without_repetitions(
tmp_fast_addressing_of_data_array, 1
tmp_fast_addressing_of_data_array, 1, use_priors=False
)[0]
)
local_search_t1 = datetime.datetime.now()
Expand Down
170 changes: 170 additions & 0 deletions hypermapper/cma_es.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import sys
import os
import numpy as np
import datetime
import cma
import contextlib
import copy
import random

# ensure backward compatibility
try:
from hypermapper.local_search import get_min_configurations
from hypermapper import space
from hypermapper.utility_functions import *
except ImportError:
if os.getenv("HYPERMAPPER_HOME"): # noqa
warnings.warn(
"Found environment variable 'HYPERMAPPER_HOME', used to update the system path. Support might be discontinued in the future. Please make sure your installation is working without this environment variable, e.g., by installing with 'pip install hypermapper'.",
DeprecationWarning,
2,
) # noqa
sys.path.append(os.environ["HYPERMAPPER_HOME"]) # noqa
ppath = os.getenv("PYTHONPATH")
if ppath:
path_items = ppath.split(":")

scripts_path = ["hypermapper/scripts", "hypermapper_dev/scripts"]

if os.getenv("HYPERMAPPER_HOME"):
scripts_path.append(os.path.join(os.getenv("HYPERMAPPER_HOME"), "scripts"))

truncated_items = [
p for p in sys.path if len([q for q in scripts_path if q in p]) == 0
]
if len(truncated_items) < len(sys.path):
warnings.warn(
"Found hypermapper in PYTHONPATH. Usage is deprecated and might break things. "
"Please remove all hypermapper references from PYTHONPATH. Trying to import"
"without hypermapper in PYTHONPATH..."
)
sys.path = truncated_items

sys.path.append(".") # noqa
sys.path = list(OrderedDict.fromkeys(sys.path))

from hypermapper.local_search import get_min_configurations
from hypermapper.utility_functions import *
from hypermapper import space


def cma_es(
param_space,
data_array,
fast_addressing_of_data_array,
optimization_objective,
logfile,
optimization_function,
optimization_function_parameters,
cma_es_random_points=10000,
cma_es_starting_points=10,
sigma=0.001,
):
"""
Optimize the acquisition function using a mix of random and local search.
This algorithm random samples N points and then does a local search on the
best points from the random search and the best points from previous iterations (if any).
:param local_search_starting_points: an integer for the number of starting points for the local search. If 0, all points will be used.
:param local_search_random_points: number of random points to sample before the local search.
:param param_space: a space object containing the search space.
:param fast_addressing_of_data_array: A list containing the points that were already explored.
:param enable_feasible_predictor: whether to use constrained optimization.
:param optimization_function: the function that will be optimized by the local search.
:param optimization_function_parameters: a dictionary containing the parameters that will be passed to the optimization function.
:param optimization_objective: the name given to the scalarized values.
:param previous_points: previous points that have already been evaluated.
:return: all points evaluted and the best point found by the local search.
"""
t0 = datetime.datetime.now()
input_params = param_space.get_input_parameters()
input_param_objects = param_space.get_input_parameters_objects()
tmp_fast_addressing_of_data_array = copy.deepcopy(fast_addressing_of_data_array)

# CMA-ES works better if optimizing between normalized ranges
param_min, param_max = {}, {}
normalizer = {}
unnormalizer = {}
for param in input_params:
param_min[param], param_max[param] = (
input_param_objects[param].get_min(),
input_param_objects[param].get_max(),
)
normalizer[param] = lambda x, input_param: (x - param_min[input_param]) / (
param_max[input_param] - param_min[input_param]
)
unnormalizer[param] = (
lambda x, input_param: x * (param_max[input_param] - param_min[input_param])
+ param_min[input_param]
)

best_previous = get_min_configurations(
data_array, cma_es_starting_points, optimization_objective
)

concatenation_keys = input_params
cma_es_configurations = {}
cma_es_configurations = concatenate_data_dictionaries(
cma_es_configurations, best_previous, concatenation_keys
)
# Get mode or random
param_mode = param_space.get_prior_mode()
cma_es_configurations = concatenate_data_dictionaries(
cma_es_configurations, param_mode, concatenation_keys
)

# Passing the dictionary with ** expands the key-value pairs into function parameters
# The acquisition functions return a tuple with two lists, cma wants only the first element of the first list
cmaes_black_box = lambda x: optimization_function(
configurations=[
{
param: unnormalizer[param](x[param_idx], param)
for param_idx, param in enumerate(input_params)
}
],
**optimization_function_parameters
)[0][0]
best_points = []
best_point_vals = []

for configuration_idx in range(
len(cma_es_configurations[list(cma_es_configurations.keys())[0]])
):
x0 = [
normalizer[param](cma_es_configurations[param][configuration_idx], param)
for param in input_params
]
with open(logfile, "a") as f, contextlib.redirect_stdout(f):
es = cma.CMAEvolutionStrategy(x0, sigma, {"bounds": [0, 1]})
es.optimize(cmaes_black_box)

best_points.append(es.result.xbest)
best_point_vals.append(es.result.fbest)

best_configuration_idx = np.argmin(best_point_vals)
best_configuration = {
param: unnormalizer[param](
best_points[best_configuration_idx][param_idx], param
)
for param_idx, param in enumerate(input_params)
}
configuration_string = param_space.get_unique_hash_string_from_values(
best_configuration
)
# If the best configuration has already been evaluated before, remove it and get the next best configuration
if configuration_string in fast_addressing_of_data_array:
del best_point_vals[best_configuration_idx]
del best_points[best_configuration_idx]
best_configuration_idx = np.argmin(best_point_vals)
best_configuration = {
param: unnormalizer[param](best[param_idx], param)
for param_idx, param in enumerate(input_params)
}
configuration_string = param_space.get_unique_hash_string_from_values(
best_configuration
)

sys.stdout.write_to_logfile(
("CMA-ES time %10.4f sec\n" % ((datetime.datetime.now() - t0).total_seconds()))
)

return best_configuration
71 changes: 56 additions & 15 deletions hypermapper/prior_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from hypermapper import models
from hypermapper.local_search import local_search
from hypermapper.utility_functions import dict_list_to_matrix
from hypermapper.cma_es import cma_es
except ImportError:
if os.getenv("HYPERMAPPER_HOME"): # noqa
warnings.warn(
Expand Down Expand Up @@ -51,6 +52,7 @@
from hypermapper import models
from hypermapper.local_search import local_search
from hypermapper.utility_functions import dict_list_to_matrix
from hypermapper.cma_es import cma_es


def compute_probability_from_prior(configurations, param_space, objective_weights):
Expand Down Expand Up @@ -178,6 +180,15 @@ def compute_EI_from_posteriors(
:param posterior_normalization_limits:
:param debug: whether to run in debug mode.
"""
param_objects = param_space.get_input_parameters_objects()
for parameter in param_space.get_input_parameters():
param_min, param_max = (
param_objects[parameter].get_min(),
param_objects[parameter].get_max(),
)
for configuration in configurations:
configuration[parameter] = min(configuration[parameter], param_max)
configuration[parameter] = max(configuration[parameter], param_min)
user_prior_t0 = datetime.datetime.now()
prior_good = compute_probability_from_prior(
configurations, param_space, objective_weights
Expand Down Expand Up @@ -336,6 +347,8 @@ def compute_EI_from_posteriors(

good_bad_ratios = good_bad_ratios + feasibility_indicator
good_bad_ratios = -1 * good_bad_ratios
good_bad_ratios[good_bad_ratios == float("inf")] = sys.maxsize
good_bad_ratios[good_bad_ratios == float("-inf")] = -1 * sys.maxsize
good_bad_ratios = list(good_bad_ratios)

sys.stdout.write_to_logfile(
Expand Down Expand Up @@ -366,6 +379,7 @@ def prior_guided_optimization(
objective_limits,
classification_model=None,
profiling=None,
acquisition_function_optimizer="local_search",
):
"""
Run a prior-guided bayesian optimization iteration.
Expand All @@ -379,8 +393,6 @@ def prior_guided_optimization(
:param objective_limits: estimated minimum and maximum limits for each objective.
:param classification_model: feasibility classifier for constrained optimization.
"""
local_search_starting_points = config["local_search_starting_points"]
local_search_random_points = config["local_search_random_points"]
scalarization_key = config["scalarization_key"]
number_of_cpus = config["number_of_cpus"]

Expand Down Expand Up @@ -419,17 +431,46 @@ def prior_guided_optimization(
float("-inf"),
]

_, best_configuration = local_search(
local_search_starting_points,
local_search_random_points,
param_space,
fast_addressing_of_data_array,
False, # set feasibility to false, we handle it inside the acquisition function
compute_EI_from_posteriors,
function_parameters,
scalarization_key,
number_of_cpus,
previous_points=data_array,
profiling=profiling,
)
if acquisition_function_optimizer == "local_search":
local_search_starting_points = config["local_search_starting_points"]
local_search_random_points = config["local_search_random_points"]
_, best_configuration = local_search(
local_search_starting_points,
local_search_random_points,
param_space,
fast_addressing_of_data_array,
False, # set feasibility to false, we handle it inside the acquisition function
compute_EI_from_posteriors,
function_parameters,
scalarization_key,
number_of_cpus,
previous_points=data_array,
profiling=profiling,
)
elif acquisition_function_optimizer == "cma_es":
logfile = deal_with_relative_and_absolute_path(
config["run_directory"], config["log_file"]
)
sigma = config["cma_es_sigma"]
cma_es_starting_points = config["cma_es_starting_points"]
cma_es_random_points = config["cma_es_random_points"]
best_configuration = cma_es(
param_space,
data_array,
fast_addressing_of_data_array,
scalarization_key,
logfile,
compute_EI_from_posteriors,
function_parameters,
cma_es_random_points=cma_es_random_points,
cma_es_starting_points=cma_es_starting_points,
sigma=sigma,
)
else:
print(
"Unrecognized acquisition function optimizer:",
acquisition_function_optimizer,
)
raise SystemExit

return best_configuration
Loading

0 comments on commit 730d3aa

Please sign in to comment.