Source code for l2l.optimizers.optimizer
from collections import namedtuple
from l2l.utils.tools import cartesian_product
from l2l import get_grouped_dict
OptimizerParameters = namedtuple('OptimizerParamters', [])
[docs]class Optimizer:
"""
This is the base class for the Optimizers i.e. the outer loop algorithms. These algorithms generate parameters, \
give them to the inner loop to be evaluated, and with the resulting fitness modify the parameters in some way.
:param ~l2l.utils.trajectory.Trajectory traj: Use this trajectory to store the parameters of the specific runs.
The parameters should be initialized based on the values in :param parameters:
:param optimizee_create_individual: A function which when called returns one instance of parameter (or "individual")
:param optimizee_fitness_weights: The weights which should be multiplied with the fitness returned from the
:class:`~l2l.optimizees.optimizee.Optimizee` -- one for each element of the fitness (fitness can be
multi-dimensional). If some element is negative, the Optimizer minimizes that element of fitness instead of
maximizing. By default, the `Optimizer` maximizes all fitness dimensions.
:param parameters: A named tuple containing the parameters for the Optimizer class
"""
def __init__(self, traj,
optimizee_create_individual,
optimizee_fitness_weights,
optimizee_bounding_func,
parameters):
# Creating Placeholders for individuals and results that are about to be explored
traj.f_add_parameter('generation', 0, comment='Current generation')
traj.f_add_parameter('ind_idx', 0, comment='Index of individual')
# Initializing basic variables
self.optimizee_create_individual = optimizee_create_individual
self.optimizee_fitness_weights = optimizee_fitness_weights
self.optimizee_bounding_func = optimizee_bounding_func
self.parameters = parameters
#: The current generation number
self.g = None
#: The population (i.e. list of individuals) to be evaluated at the next iteration
self.eval_pop = None
[docs] def post_process(self, traj, fitnesses_results):
"""
This is the key function of this class. Given a set of :obj:`fitnesses_results`, and the :obj:`traj`, it uses
the fitness to decide on the next set of parameters to be evaluated. Then it fills the :attr:`.Optimizer.eval_pop` with the
list of parameters it wants evaluated at the next simulation cycle, increments :attr:`.Optimizer.g` and calls
:meth:`._expand_trajectory`
:param ~l2l.utils.trajectory.Trajectory traj: The trajectory that contains the parameters and the
individual that we want to simulate. The individual is accessible using `traj.individual` and parameter e.g.
param1 is accessible using `traj.param1`
:param list fitnesses_results: This is a list of fitness results that contain tuples run index and the fitness.
It is of the form `[(run_idx, run), ...]`
"""
# NOTE: Always remember to keep the following two lines.
# TODO: Set eval_pop to the values of parameters you want to evaluate in the next cycle
# self.eval_pop = ...
self.g += 1
self._expand_trajectory(traj)
[docs] def end(self, traj):
"""
Run any code required to clean-up, print final individuals etc.
:param ~l2l.utils.trajectory.Trajectory traj: The trajectory that contains the parameters and the
individual that we want to simulate. The individual is accessible using `traj.individual` and parameter e.g.
param1 is accessible using `traj.param1`
"""
pass
[docs] def _expand_trajectory(self, traj):
"""
Add as many explored runs as individuals that need to be evaluated. Furthermore, add the individuals as explored
parameters.
:param ~l2l.utils.trajectory.Trajectory traj: The trajectory that contains the parameters and the
individual that we want to simulate. The individual is accessible using `traj.individual` and parameter e.g.
param1 is accessible using `traj.param1`
:return:
"""
grouped_params_dict = get_grouped_dict(self.eval_pop)
grouped_params_dict = {'individual.' + key: val for key, val in grouped_params_dict.items()}
final_params_dict = {'generation': [self.g],
'ind_idx': range(len(self.eval_pop))}
final_params_dict.update(grouped_params_dict)
# We need to convert them to lists or write our own custom IndividualParameter ;-)
# Note the second argument to `cartesian_product`: This is for only having the cartesian product
# between ``generation x (ind_idx AND individual)``, so that every individual has just one
# unique index within a generation.
traj.f_expand(cartesian_product(final_params_dict,
[('ind_idx',) + tuple(grouped_params_dict.keys()), 'generation']))