def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__(traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.best_individual = None self.best_fitness = None sample_individual = self.optimizee_create_individual() # Generate parameter dictionary based on optimizee_param_grid self.param_list = {} _, optimizee_individual_param_spec = dict_to_list(sample_individual, get_dict_spec=True) self.optimizee_individual_dict_spec = optimizee_individual_param_spec optimizee_param_grid = parameters.param_grid # Assert validity of optimizee_param_grid assert set(sample_individual.keys()) == set(optimizee_param_grid.keys()), \ "The Parameters of optimizee_param_grid don't match those of the optimizee individual" for param_name, param_type, param_length in optimizee_individual_param_spec: param_lower_bound, param_upper_bound, param_n_steps = optimizee_param_grid[param_name] if param_type == DictEntryType.Scalar: self.param_list[param_name] = np.linspace(param_lower_bound, param_upper_bound, param_n_steps + 1) elif param_type == DictEntryType.Sequence: curr_param_list = np.linspace(param_lower_bound, param_upper_bound, param_n_steps + 1) curr_param_list = np.meshgrid(*([curr_param_list] * param_length), indexing='ij') curr_param_list = [x.ravel() for x in curr_param_list] curr_param_list = np.stack(curr_param_list, axis=-1) self.param_list[param_name] = curr_param_list self.param_list = cartesian_product(self.param_list, tuple(sorted(optimizee_param_grid.keys()))) self.size = len(self.param_list[list(self.param_list.keys())[0]]) # Adding the bounds information to the trajectory traj.f_add_parameter_group('grid_spec') for param_name, param_grid_spec in optimizee_param_grid.items(): traj.grid_spec.f_add_parameter(param_name + '.lower_bound', param_grid_spec[0]) traj.grid_spec.f_add_parameter(param_name + '.uper_bound', param_grid_spec[1]) traj.f_add_parameter('n_iteration', 1, comment='Grid search does only 1 iteration') #: The current generation number self.g = 0 # Expanding the trajectory grouped_params_dict = {'individual.' + key: value for key, value in self.param_list.items()} final_params_dict = {'generation': [self.g], 'ind_idx': range(self.size)} final_params_dict.update(grouped_params_dict) traj.f_expand(cartesian_product(final_params_dict, [('ind_idx',) + tuple(grouped_params_dict.keys()), 'generation'])) #: The population (i.e. list of individuals) to be evaluated at the next iteration self.eval_pop = None
def bounding_wrapper(*args, **kwargs): if self.optimizee_bounding_func is None: return func(*args, **kwargs) else: # Deap Functions modify individuals in-place, Hence we must do the same result_individuals_deap = func(*args, **kwargs) result_individuals = [list_to_dict(x, self.optimizee_individual_dict_spec) for x in result_individuals_deap] bounded_individuals = [self.optimizee_bounding_func(x) for x in result_individuals] for i, deap_indiv in enumerate(result_individuals_deap): deap_indiv[:] = dict_to_list(bounded_individuals[i]) # print("Bounded Individual: {}".format(bounded_individuals)) return result_individuals_deap
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ noisy_step, temp_decay, n_iteration, stop_criterion = \ traj.noisy_step, traj.temp_decay, traj.n_iteration, traj.stop_criterion old_eval_pop = self.eval_pop.copy() self.eval_pop.clear() temperature = self.T temperature_end = 0 self.T = self.cooling(temperature, self.cooling_schedule, temp_decay, temperature_end, n_iteration) logger.info(" Evaluating %i individuals" % len(fitnesses_results)) assert len(fitnesses_results) == traj.n_parallel_runs weighted_fitness_list = [] for i, (run_index, fitness) in enumerate(fitnesses_results): weighted_fitness = sum( f * w for f, w in zip(fitness, self.optimizee_fitness_weights)) weighted_fitness_list.append(weighted_fitness) # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx individual = old_eval_pop[ind_index] # Accept or reject the new solution current_fitness_value_i = self.current_fitness_value_list[i] r = self.random_state.rand() p = np.exp((weighted_fitness - current_fitness_value_i) / self.T) # Accept if r < p or weighted_fitness >= current_fitness_value_i: self.current_fitness_value_list[i] = weighted_fitness self.current_individual_list[i] = np.array( dict_to_list(individual)) traj.f_add_result('$set.$.individual', individual) # Watchout! if weighted fitness is a tuple/np array it should be converted to a list first here traj.f_add_result('$set.$.fitness', weighted_fitness) current_individual = self.current_individual_list[i] new_individual = list_to_dict( current_individual + self.random_state.randn(current_individual.size) * noisy_step * self.T, self.optimizee_individual_dict_spec) if self.optimizee_bounding_func is not None: new_individual = self.optimizee_bounding_func(new_individual) logger.debug( "Current best fitness for individual %d is %.2f. New individual is %s", i, self.current_fitness_value_list[i], new_individual) self.eval_pop.append(new_individual) logger.debug("Current best fitness within population is %.2f", max(self.current_fitness_value_list)) traj.v_idx = -1 # set the trajectory back to default logger.info("-- End of generation {} --".format(self.g)) # ------- Create the next generation by crossover and mutation -------- # # not necessary for the last generation if self.g < n_iteration - 1 and stop_criterion > max( self.current_fitness_value_list): fitnesses_results.clear() self.g += 1 # Update generation counter self._expand_trajectory(traj)
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func # The following parameters are recorded traj.f_add_parameter( 'n_parallel_runs', parameters.n_parallel_runs, comment= 'Number of parallel simulated annealing runs / Size of Population') traj.f_add_parameter('noisy_step', parameters.noisy_step, comment='Size of the random step') traj.f_add_parameter( 'temp_decay', parameters.temp_decay, comment='A temperature decay parameter (multiplicative)') traj.f_add_parameter('n_iteration', parameters.n_iteration, comment='Number of iteration to perform') traj.f_add_parameter('stop_criterion', parameters.stop_criterion, comment='Stopping criterion parameter') traj.f_add_parameter('seed', np.uint32(parameters.seed), comment='Seed for RNG') _, self.optimizee_individual_dict_spec = dict_to_list( self.optimizee_create_individual(), get_dict_spec=True) # Note that this array stores individuals as an np.array of floats as opposed to Individual-Dicts # This is because this array is used within the context of the simulated annealing algorithm and # Thus needs to handle the optimizee individuals as vectors self.current_individual_list = [ np.array(dict_to_list(self.optimizee_create_individual())) for _ in range(parameters.n_parallel_runs) ] self.random_state = np.random.RandomState(parameters.seed) # The following parameters are NOT recorded self.T = 1. # Initialize temperature self.g = 0 # the current generation # Keep track of current fitness value to decide whether we want the next individual to be accepted or not self.current_fitness_value_list = [-np.Inf ] * parameters.n_parallel_runs new_individual_list = [ list_to_dict( ind_as_list + self.random_state.normal( 0.0, parameters.noisy_step, ind_as_list.size) * traj.noisy_step * self.T, self.optimizee_individual_dict_spec) for ind_as_list in self.current_individual_list ] if optimizee_bounding_func is not None: new_individual_list = [ self.optimizee_bounding_func(ind) for ind in new_individual_list ] self.eval_pop = new_individual_list self._expand_trajectory(traj) self.cooling_schedule = parameters.cooling_schedule
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ n_iteration, smoothing, temp_decay = \ traj.n_iteration, traj.smoothing, traj.temp_decay stop_criterion, n_elite = traj.stop_criterion, traj.n_elite weighted_fitness_list = [] #************************************************************************************************************** # Storing run-information in the trajectory # Reading fitnesses and performing distribution update #************************************************************************************************************** for run_index, fitness in fitnesses_results: # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx traj.f_add_result('$set.$.individual', self.eval_pop[ind_index]) traj.f_add_result('$set.$.fitness', fitness) weighted_fitness_list.append(np.dot(fitness, self.optimizee_fitness_weights)) traj.v_idx = -1 # set trajectory back to default weighted_fitness_list = np.array(weighted_fitness_list).ravel() # Performs descending arg-sort of weighted fitness fitness_sorting_indices = list(reversed(np.argsort(weighted_fitness_list))) # Sorting the data according to fitness sorted_population = self.eval_pop_asarray[fitness_sorting_indices] sorted_fitness = np.asarray(weighted_fitness_list)[fitness_sorting_indices] # Elite individuals are with performance better than or equal to the (1-rho) quantile. # See original describtion of cross entropy for optimization elite_individuals = sorted_population[:n_elite] self.best_individual_in_run = sorted_population[0] self.best_fitness_in_run = sorted_fitness[0] self.gamma = sorted_fitness[n_elite - 1] logger.info("-- End of generation %d --", self.g) logger.info(" Evaluated %d individuals", len(fitnesses_results)) logger.info(' Best Fitness: %.4f', self.best_fitness_in_run) logger.info(' Average Fitness: %.4f', np.mean(sorted_fitness)) logger.debug(' Calculated gamma: %.4f', self.gamma) #************************************************************************************************************** # Storing Generation Parameters / Results in the trajectory #************************************************************************************************************** # These entries correspond to the generation that has been simulated prior to this post-processing run # Documentation of algorithm parameters for the current generation # # generation - The index of the evaluated generation # gamma - The fitness threshold inferred from the evaluated generation # (This is used in sampling the next generation) # T - Temperature used to select non-elite elements among the individuals # of the evaluated generation # best_fitness_in_run - The highest fitness among the individuals in the # evaluated generation # pop_size - Population size generation_result_dict = { 'generation': self.g, 'gamma': self.gamma, 'T': self.T, 'best_fitness_in_run': self.best_fitness_in_run, 'average_fitness_in_run': np.mean(sorted_fitness), 'pop_size': self.pop_size } generation_name = 'generation_{}'.format(self.g) traj.results.generation_params.f_add_result_group(generation_name) traj.results.generation_params.f_add_result( generation_name + '.algorithm_params', generation_result_dict, comment="These are the parameters that correspond to the algorithm, look at the source code" " for `CrossEntropyOptimizer::post_process()` for comments documenting these" " parameters") # new distribution fit individuals_to_be_fitted = elite_individuals # Temperature dependent sampling of non elite individuals if temp_decay > 0: # Keeping non-elite samples with certain probability dependent on temperature (like Simulated Annealing) non_elite_selection_probs = np.clip(np.exp((weighted_fitness_list[n_elite:] - self.gamma) / self.T), a_min=0.0, a_max=1.0) non_elite_selected_indices = self.random_state.binomial(1, non_elite_selection_probs).astype(bool) non_elite_eval_pop_asarray = sorted_population[n_elite:][non_elite_selected_indices] individuals_to_be_fitted = np.concatenate((elite_individuals, non_elite_eval_pop_asarray)) # Fitting New distribution parameters. self.distribution_results = self.current_distribution.fit(individuals_to_be_fitted, smoothing) #Add the results of the distribution fitting to the trajectory traj.results.generation_params.f_add_result( generation_name + '.distribution_params', self.distribution_results, comment="These are the parameters of the distribution inferred from the currently evaluated" " generation") #************************************************************************************************************** # Create the next generation by sampling the inferred distribution #************************************************************************************************************** # Note that this is only done in case the evaluated run is not the last run fitnesses_results.clear() self.eval_pop.clear() # check if to stop if self.g < n_iteration - 1 and self.best_fitness_in_run < stop_criterion: #Sample from the constructed distribution self.eval_pop_asarray = self.current_distribution.sample(self.pop_size) self.eval_pop = [list_to_dict(ind_asarray, self.optimizee_individual_dict_spec) for ind_asarray in self.eval_pop_asarray] # Clip to boundaries if self.optimizee_bounding_func is not None: self.eval_pop = [self.optimizee_bounding_func(individual) for individual in self.eval_pop] self.eval_pop_asarray = np.array([dict_to_list(x) for x in self.eval_pop]) self.g += 1 # Update generation counter self.T *= temp_decay self._expand_trajectory(traj)
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func __, self.optimizee_individual_dict_spec = dict_to_list( optimizee_create_individual(), get_dict_spec=True) traj.f_add_parameter('seed', parameters.seed, comment='Seed for RNG') traj.f_add_parameter('popsize', parameters.popsize, comment='Population size') # 185 traj.f_add_parameter('CXPB', parameters.CXPB, comment='Crossover term') traj.f_add_parameter('MUTPB', parameters.MUTPB, comment='Mutation probability') traj.f_add_parameter('n_iteration', parameters.NGEN, comment='Number of generations') traj.f_add_parameter('indpb', parameters.indpb, comment='Mutation parameter') traj.f_add_parameter('tournsize', parameters.tournsize, comment='Selection parameter') # ------- Create and register functions with DEAP ------- # # delay_rate, slope, std_err, max_fraction_active creator.create("FitnessMax", base.Fitness, weights=self.optimizee_fitness_weights) creator.create("Individual", list, fitness=creator.FitnessMax) toolbox = base.Toolbox() # Structure initializers toolbox.register("individual", tools.initIterate, creator.Individual, lambda: dict_to_list(optimizee_create_individual())) toolbox.register("population", tools.initRepeat, list, toolbox.individual) # Operator registering # This complex piece of code is only necessary because we're using the # DEAP framework and would like to decorate the DEAP mutation operator def bounding_decorator(func): def bounding_wrapper(*args, **kwargs): if self.optimizee_bounding_func is None: return func(*args, **kwargs) else: # Deap Functions modify individuals in-place, Hence we must do the same result_individuals_deap = func(*args, **kwargs) result_individuals = [ list_to_dict(x, self.optimizee_individual_dict_spec) for x in result_individuals_deap ] bounded_individuals = [ self.optimizee_bounding_func(x) for x in result_individuals ] for i, deap_indiv in enumerate(result_individuals_deap): deap_indiv[:] = dict_to_list(bounded_individuals[i]) print("Bounded Individual: {}".format(bounded_individuals)) return result_individuals_deap return bounding_wrapper toolbox.register("mate", tools.cxBlend, alpha=parameters.matepar) toolbox.decorate("mate", bounding_decorator) toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=parameters.mutpar, indpb=traj.indpb) toolbox.decorate("mutate", bounding_decorator) toolbox.register("select", tools.selTournament, tournsize=traj.tournsize) # ------- Initialize Population and Trajectory -------- # # NOTE: The Individual object implements the list interface. self.pop = toolbox.population(n=traj.popsize) self.eval_pop_inds = [ind for ind in self.pop if not ind.fitness.valid] self.eval_pop = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in self.eval_pop_inds ] self.g = 0 # the current generation self.toolbox = toolbox # the DEAP toolbox self.hall_of_fame = HallOfFame(20) self._expand_trajectory(traj)
def main(): name = "FIT-TEMPS" root_dir_path = os.path.dirname(os.path.abspath(sys.argv[0])) paths = Paths(name, dict(run_num="test"), root_dir_path=root_dir_path) print("All output logs can be found in directory ", paths.logs_path) traj_file = os.path.join(paths.output_dir_path, "data.h5") print(traj_file) os.makedirs(paths.output_dir_path, exist_ok=True) print("Trajectory file is: {}".format(traj_file)) trajectories = load_last_trajs( os.path.join(paths.output_dir_path, 'per_gen_trajectories')) if len(trajectories): k = trajectories['generation'] traj = trajectories[k] else: traj = name # Create an environment that handles running our simulation # This initializes an environment env = Environment( trajectory=traj, filename=traj_file, file_title="{} data".format(name), comment="{} data".format(name), add_time=bool(1), automatic_storing=bool(1), log_stdout=bool(0), # Sends stdout to logs multiprocessing=MULTIPROCESSING, ) create_shared_logger_data(logger_names=["bin", "optimizers"], log_levels=["INFO", "INFO"], log_to_consoles=[True, True], sim_name=name, log_directory=paths.logs_path) configure_loggers() # trajectories = load_last_trajs(os.path.join(paths.root_dir_path,'trajectories')) # env.trajectory.individuals[0] = trajectories # Get the trajectory from the environment traj = env.trajectory if len(trajectories) == 0: # Set JUBE params traj.f_add_parameter_group("JUBE_params", "Contains JUBE parameters") # Scheduler parameters # Name of the scheduler # traj.f_add_parameter_to_group("JUBE_params", "scheduler", "Slurm") # Command to submit jobs to the schedulers # traj.f_add_parameter_to_group("JUBE_params", "submit_cmd", "sbatch") # Template file for the particular scheduler traj.f_add_parameter_to_group("JUBE_params", "job_file", "job.run") # Number of nodes to request for each run traj.f_add_parameter_to_group("JUBE_params", "nodes", "1") # Requested time for the compute resources traj.f_add_parameter_to_group("JUBE_params", "walltime", "00:10:00") # MPI Processes per node traj.f_add_parameter_to_group("JUBE_params", "ppn", "1") # CPU cores per MPI process traj.f_add_parameter_to_group("JUBE_params", "cpu_pp", "1") # Threads per process traj.f_add_parameter_to_group("JUBE_params", "threads_pp", "1") # Type of emails to be sent from the scheduler traj.f_add_parameter_to_group("JUBE_params", "mail_mode", "ALL") # Email to notify events from the scheduler traj.f_add_parameter_to_group("JUBE_params", "mail_address", "*****@*****.**") # Error file for the job traj.f_add_parameter_to_group("JUBE_params", "err_file", "stderr") # Output file for the job traj.f_add_parameter_to_group("JUBE_params", "out_file", "stdout") # JUBE parameters for multiprocessing. Relevant even without scheduler. # MPI Processes per job traj.f_add_parameter_to_group("JUBE_params", "tasks_per_job", "1") # The execution command run_filename = os.path.join(paths.root_dir_path, "run_files/run_optimizee.py") command = "python3 {}".format(run_filename) if ON_JEWELS and not USE_MPI: # -N num nodes # -t exec time (mins) # -n num sub-procs command = "srun -t 15 -N 1 -n 4 -c 1 --gres=gpu:1 {}".format( command) elif USE_MPI: command = "MPIEXEC_TIMEOUT={} mpiexec -bind-to socket -np 1 {}".format( 60, command) traj.f_add_parameter_to_group("JUBE_params", "exec", command) # Ready file for a generation traj.f_add_parameter_to_group( "JUBE_params", "ready_file", os.path.join(paths.root_dir_path, "readyfiles/ready_w_")) # Path where the job will be executed traj.f_add_parameter_to_group("JUBE_params", "work_path", paths.root_dir_path) ### Maybe we should pass the Paths object to avoid defining paths here and there traj.f_add_parameter_to_group("JUBE_params", "paths_obj", paths) csv = open('./temperature/temperature-anomaly.csv', 'r') temps = [] years = [] for i, line in enumerate(csv): sp = line.split(',') if sp[0] == 'Global': temps.append(float(sp[3])) years.append(float(sp[2])) if sp[0].startswith('Northern'): break csv.close() traj.f_add_parameter_group("simulation", "Contains JUBE parameters") traj.f_add_parameter_to_group("simulation", 'target', temps) # ms traj.f_add_parameter_to_group("simulation", 'years', years) ## Innerloop simulator optimizee = FitOptimizee(traj, 1234) # Prepare optimizee for jube runs JUBE_runner.prepare_optimizee(optimizee, paths.root_dir_path) _, dict_spec = dict_to_list(optimizee.create_individual(), get_dict_spec=True) # step_size = np.asarray([config.ATTR_STEPS[k] for (k, spec, length) in dict_spec]) fit_weights = [ 1.0, ] # 0.1] num_generations = 5000 population_size = 200 # population_size = 5 # if len(trajectories): # traj.individuals = trajectories_to_individuals( # trajectories, population_size, optimizee) parameters = GeneticAlgorithmParameters( seed=None, popsize=population_size, CXPB=0.5, # probability of mating 2 individuals MUTPB=0.8, # probability of individual to mutate NGEN=num_generations, indpb=0.1, # probability of "gene" to mutate tournsize=population_size, # number of best individuals to mate matepar=0.5, # how much to mix two genes when mating mutpar=1. #2.0/4.0, #standard deviations for normal distribution ) optimizer = GeneticAlgorithmOptimizer( traj, optimizee_create_individual=optimizee.create_individual, optimizee_fitness_weights=fit_weights, parameters=parameters, optimizee_bounding_func=optimizee.bounding_func, percent_hall_of_fame=0.05, percent_elite=0.5, ) # Add post processing ### guess this is where we want to split results from multiple runs? env.add_postprocessing(optimizer.post_process) # Run the simulation with all parameter combinations env.run(optimizee.simulate) ## Outerloop optimizer end optimizer.end(traj) # Finally disable logging and close all log-files env.disable_logging()
def main(): name = "L2L-OMNIGLOT" root_dir_path = os.path.dirname(os.path.abspath(sys.argv[0])) paths = Paths(name, dict(run_num="test"), root_dir_path=root_dir_path) print("All output logs can be found in directory ", paths.logs_path) traj_file = os.path.join(paths.output_dir_path, "data.h5") os.makedirs(paths.output_dir_path, exist_ok=True) print("Trajectory file is: {}".format(traj_file)) trajectories = load_last_trajs( os.path.join(paths.output_dir_path, 'per_gen_trajectories')) if len(trajectories): k = trajectories['generation'] traj = trajectories[k] else: traj = name # Create an environment that handles running our simulation # This initializes an environment env = Environment( trajectory=traj, filename=traj_file, file_title="{} data".format(name), comment="{} data".format(name), add_time=bool(1), automatic_storing=bool(1), log_stdout=bool(0), # Sends stdout to logs multiprocessing=MULTIPROCESSING, ) create_shared_logger_data(logger_names=["bin", "optimizers"], log_levels=["INFO", "INFO"], log_to_consoles=[True, True], sim_name=name, log_directory=paths.logs_path) configure_loggers() # Get the trajectory from the environment traj = env.trajectory # Set JUBE params traj.f_add_parameter_group("JUBE_params", "Contains JUBE parameters") # Scheduler parameters # Name of the scheduler # traj.f_add_parameter_to_group("JUBE_params", "scheduler", "Slurm") # Command to submit jobs to the schedulers # traj.f_add_parameter_to_group("JUBE_params", "submit_cmd", "sbatch") # Template file for the particular scheduler traj.f_add_parameter_to_group("JUBE_params", "job_file", "job.run") # Number of nodes to request for each run traj.f_add_parameter_to_group("JUBE_params", "nodes", "1") # Requested time for the compute resources traj.f_add_parameter_to_group("JUBE_params", "walltime", "00:01:00") # MPI Processes per node traj.f_add_parameter_to_group("JUBE_params", "ppn", "1") # CPU cores per MPI process traj.f_add_parameter_to_group("JUBE_params", "cpu_pp", "1") # Threads per process traj.f_add_parameter_to_group("JUBE_params", "threads_pp", "1") # Type of emails to be sent from the scheduler traj.f_add_parameter_to_group("JUBE_params", "mail_mode", "ALL") # Email to notify events from the scheduler traj.f_add_parameter_to_group("JUBE_params", "mail_address", "*****@*****.**") # Error file for the job traj.f_add_parameter_to_group("JUBE_params", "err_file", "stderr") # Output file for the job traj.f_add_parameter_to_group("JUBE_params", "out_file", "stdout") # JUBE parameters for multiprocessing. Relevant even without scheduler. # MPI Processes per job traj.f_add_parameter_to_group("JUBE_params", "tasks_per_job", "1") # The execution command run_filename = os.path.join(paths.root_dir_path, "run_files", "run_optimizee.py") command = "python3 {}".format(run_filename) if ON_JEWELS and not USE_MPI: # -N num nodes # -t exec time (mins) # -n num sub-procs command = "srun -n 1 -c {} --gres=gpu:1 {}".format(NUM_SIMS, command) elif USE_MPI: # -timeout <seconds> # command = "MPIEXEC_TIMEOUT={} " # "mpiexec -bind-to socket -np 1 {}".format(60*240, command) command = "mpiexec -bind-to socket -np 1 {}".format(command) traj.f_add_parameter_to_group("JUBE_params", "exec", command) # Ready file for a generation traj.f_add_parameter_to_group( "JUBE_params", "ready_file", os.path.join(paths.root_dir_path, "readyfiles/ready_w_")) # Path where the job will be executed traj.f_add_parameter_to_group("JUBE_params", "work_path", paths.root_dir_path) # Maybe we should pass the Paths object to avoid # defining paths here and there traj.f_add_parameter_to_group("JUBE_params", "paths_obj", paths) traj.f_add_parameter_group("simulation", "Contains JUBE parameters") traj.f_add_parameter_to_group("simulation", 'num_sims', NUM_SIMS) # ms traj.f_add_parameter_to_group("simulation", 'on_juwels', ON_JEWELS) traj.f_add_parameter_to_group("simulation", 'steps', config.STEPS) # ms traj.f_add_parameter_to_group("simulation", 'duration', config.DURATION) # ms traj.f_add_parameter_to_group("simulation", 'sample_dt', config.SAMPLE_DT) # ms traj.f_add_parameter_to_group( "simulation", # rows, cols 'input_shape', config.INPUT_SHAPE) traj.f_add_parameter_to_group( "simulation", # rows, cols 'input_divs', config.INPUT_DIVS) traj.f_add_parameter_to_group("simulation", 'input_layers', config.N_INPUT_LAYERS) traj.f_add_parameter_to_group("simulation", 'num_classes', config.N_CLASSES) traj.f_add_parameter_to_group("simulation", 'samples_per_class', config.N_SAMPLES) traj.f_add_parameter_to_group("simulation", 'test_per_class', config.N_TEST) traj.f_add_parameter_to_group("simulation", 'num_epochs', config.N_EPOCHS) traj.f_add_parameter_to_group("simulation", 'total_per_class', config.TOTAL_SAMPLES) traj.f_add_parameter_to_group("simulation", 'kernel_width', config.KERNEL_W) traj.f_add_parameter_to_group("simulation", 'kernel_pad', config.PAD) traj.f_add_parameter_to_group("simulation", 'output_size', config.OUTPUT_SIZE) traj.f_add_parameter_to_group("simulation", 'use_gabor', config.USE_GABOR_LAYER) # traj.f_add_parameter_to_group("simulation", # 'expand', config.EXPANSION_RANGE[0]) # traj.f_add_parameter_to_group("simulation", # 'conn_dist', config.CONN_DIST) traj.f_add_parameter_to_group("simulation", 'prob_noise', config.PROB_NOISE_SAMPLE) traj.f_add_parameter_to_group("simulation", 'noisy_spikes_path', paths.root_dir_path) # db_path = '/home/gp283/brainscales-recognition/codebase/images_to_spikes/omniglot/spikes' db_path = os.path.abspath('../omniglot_output_%d' % config.INPUT_SHAPE[0]) traj.f_add_parameter_to_group("simulation", 'spikes_path', db_path) # dbs = [ name for name in os.listdir(db_path) # if os.path.isdir(os.path.join(db_path, name)) ] # print(dbs) dbs = [ 'Mkhedruli_-Georgian-', 'Tagalog', 'Ojibwe_-Canadian_Aboriginal_Syllabics-', 'Asomtavruli_-Georgian-', 'Balinese', 'Japanese_-katakana-', 'Malay_-Jawi_-_Arabic-', 'Armenian', 'Burmese_-Myanmar-', 'Arcadian', 'Futurama', 'Cyrillic', 'Alphabet_of_the_Magi', 'Sanskrit', 'Braille', 'Bengali', 'Inuktitut_-Canadian_Aboriginal_Syllabics-', 'Syriac_-Estrangelo-', 'Gujarati', 'Korean', 'Early_Aramaic', 'Japanese_-hiragana-', 'Anglo-Saxon_Futhorc', 'N_Ko', 'Grantha', 'Tifinagh', 'Blackfoot_-Canadian_Aboriginal_Syllabics-', 'Greek', 'Hebrew', 'Latin' ] # dbs = ['Alphabet_of_the_Magi'] # dbs = ['Futurama'] # dbs = ['Latin'] # dbs = ['Braille'] # dbs = ['Blackfoot_-Canadian_Aboriginal_Syllabics-', 'Gujarati', 'Syriac_-Estrangelo-'] dbs = ['Futurama'] #, 'Braille'] # dbs = ['Cyrillic', 'Futurama', 'Braille'] if config.DEBUG: dbs = ['Braille'] #dbs = ['Braille'] traj.f_add_parameter_to_group("simulation", 'database', dbs) # Innerloop simulator grad_desc = OPTIMIZER == GRADDESC optimizee = OmniglotOptimizee(traj, 1234, grad_desc) # Prepare optimizee for jube runs JUBE_runner.prepare_optimizee(optimizee, paths.root_dir_path) _, dict_spec = dict_to_list(optimizee.create_individual(), get_dict_spec=True) # step_size = np.asarray( # [config.ATTR_STEPS[k] for (k, spec, length) in dict_spec]) step_size = tuple( [config.ATTR_STEPS[k] for (k, spec, length) in dict_spec]) fit_weights = [ 1.0, ] # 0.1] optimizer_seed = config.SEED if OPTIMIZER == GRADDESC: n_random_steps = 100 n_iteration = 1000 parameters = RMSPropParameters(learning_rate=0.0001, exploration_step_size=step_size, n_random_steps=n_random_steps, momentum_decay=0.5, n_iteration=n_iteration, stop_criterion=np.inf, seed=optimizer_seed) optimizer = GradientDescentOptimizer( traj, optimizee_create_individual=optimizee.create_individual, optimizee_fitness_weights=fit_weights, parameters=parameters, optimizee_bounding_func=optimizee.bounding_func) elif OPTIMIZER == EVOSTRAT: parameters = EvolutionStrategiesParameters( learning_rate=0.0001, noise_std=step_size, mirrored_sampling_enabled=True, fitness_shaping_enabled=True, pop_size=50, # couples n_iteration=1000, stop_criterion=np.inf, seed=optimizer_seed) optimizer = EvolutionStrategiesOptimizer( traj, optimizee_create_individual=optimizee.create_individual, optimizee_fitness_weights=fit_weights, parameters=parameters, optimizee_bounding_func=optimizee.bounding_func) else: num_generations = 1000 if ON_JEWELS: nodes = 12 gpus_per_node = 4 population_size = gpus_per_node * nodes else: population_size = 20 #population_size = 5 # population_size = 5 p_hof = 0.2 if population_size < 50 else 0.1 p_bob = 0.2 # last_trajs = load_last_trajs(os.path.join( # paths.output_dir_path, 'per_gen_trajectories')) # last_trajs = load_last_trajs(os.path.join( # paths.root_dir_path, 'trajectories')) # if len(last_trajs): # traj.individuals = trajectories_to_individuals( # last_trajs, population_size, optimizee) attr_steps = [config.ATTR_STEPS[k[0]] for k in dict_spec] parameters = GeneticAlgorithmParameters( seed=optimizer_seed, popsize=population_size, CXPB=0.5, # probability of mating 2 individuals # note: moved from 0.8 to 0.6 mutpb to see if it removes bouncing MUTPB=0.7, # probability of individual to mutate NGEN=num_generations, indpb=0.1, # probability of "gene" to mutate # number of best individuals to mate tournsize=population_size, matepar=0.5, # how much to mix two genes when mating # standard deviations for normal distribution mutpar=attr_steps, ) optimizer = GeneticAlgorithmOptimizer( traj, optimizee_create_individual=optimizee.create_individual, optimizee_fitness_weights=fit_weights, parameters=parameters, optimizee_bounding_func=optimizee.bounding_func, percent_hall_of_fame=p_hof, percent_elite=p_bob, ) # Add post processing env.add_postprocessing(optimizer.post_process) # Run the simulation with all parameter combinations env.run(optimizee.simulate) # Outerloop optimizer end optimizer.end(traj) # Finally disable logging and close all log-files env.disable_logging()
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ n_iteration, stop_criterion, learning_rate = \ traj.n_iteration, traj.stop_criterion, traj.learning_rate noise_std, fitness_shaping_enabled = \ traj.noise_std, traj.fitness_shaping_enabled weighted_fitness_list = [] # ********************************************************************* # Storing run-information in the trajectory # Reading fitnesses and performing distribution update # ********************************************************************* for run_index, fitness in fitnesses_results: # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx traj.f_add_result('$set.$.individual', self.eval_pop[ind_index]) traj.f_add_result('$set.$.fitness', fitness) weighted_fitness_list.append( np.dot(fitness, self.optimizee_fitness_weights)) traj.v_idx = -1 # set trajectory back to default weighted_fitness_list = np.array(weighted_fitness_list).ravel() # NOTE: It is necessary to clear the finesses_results to clear the data # in the reference, and del # ^ is used to make sure it's not used in the rest of this function fitnesses_results.clear() del fitnesses_results # Last fitness is for the previous `current_individual_arr` weighted_fitness_list = weighted_fitness_list[:-1] current_individual_fitness = weighted_fitness_list[-1] # Performs descending arg-sort of weighted fitness fitness_sorting_indices = list( reversed(np.argsort(weighted_fitness_list))) # Sorting the data according to fitness sorted_population = self.eval_pop_arr[fitness_sorting_indices] sorted_fitness = np.asarray( weighted_fitness_list)[fitness_sorting_indices] sorted_perturbations = self.current_perturbations[ fitness_sorting_indices] self.best_individual_in_run = sorted_population[0] self.best_fitness_in_run = sorted_fitness[0] logger.info("-- End of generation %d --", self.g) logger.info(" Evaluated %d individuals", len(weighted_fitness_list) + 1) logger.info(' Best Fitness: %.4f', self.best_fitness_in_run) logger.info(' Average Fitness: %.4f', np.mean(sorted_fitness)) # ********************************************************************* # Storing Generation Parameters / Results in the trajectory # ********************************************************************* # These entries correspond to the generation that has been simulated # prior to this post-processing run # Documentation of algorithm parameters for the current generation # # generation - The index of the evaluated generation # best_fitness_in_run - The highest fitness among the individuals in the # evaluated generation # pop_size - Population size generation_result_dict = { 'generation': self.g, 'best_fitness_in_run': self.best_fitness_in_run, 'current_individual_fitness': current_individual_fitness, 'average_fitness_in_run': np.mean(sorted_fitness), 'pop_size': self.pop_size } generation_name = 'generation_{}'.format(self.g) traj.results.generation_params.f_add_result_group(generation_name) traj.results.generation_params.f_add_result( generation_name + '.algorithm_params', generation_result_dict, comment="These are the parameters that correspond to the " "algorithm. Look at the source code for " "`EvolutionStrategiesOptimizer::post_process()` " "for comments documenting these parameters") if fitness_shaping_enabled: sorted_utilities = [] n_individuals = len(sorted_fitness) for i in range(n_individuals): u = max(0., np.log((n_individuals / 2) + 1) - np.log(i + 1)) sorted_utilities.append(u) sorted_utilities = np.array(sorted_utilities) sorted_utilities /= np.sum(sorted_utilities) sorted_utilities -= (1. / n_individuals) # assert np.sum(sorted_utilities) == 0., \ # "Sum of utilities is not 0, but %.4f" % np.sum(sorted_utilities) fitnesses_to_fit = sorted_utilities else: fitnesses_to_fit = sorted_fitness assert len(fitnesses_to_fit) == len(sorted_perturbations) sum_fits = np.sum( [f * e for f, e in zip(fitnesses_to_fit, sorted_perturbations)], axis=0) weight = len(fitnesses_to_fit) * np.asarray(noise_std)**2 self.current_individual_arr += learning_rate * (sum_fits / weight) # ********************************************************************* # Create the next generation by sampling the inferred distribution # ********************************************************************* # Note that this is only done in case the evaluated run is not the # last run self.eval_pop.clear() # check if to stop max_g = n_iteration - 1 if self.g < max_g and self.best_fitness_in_run < stop_criterion: self.current_perturbations = self._get_perturbations(traj) perturbated = \ self.current_individual_arr + self.current_perturbations current_eval_pop_arr = perturbated.tolist() self.eval_pop[:] = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in current_eval_pop_arr ] self.eval_pop.append( list_to_dict(self.current_individual_arr, self.optimizee_individual_dict_spec)) # Bounding function has to be applied AFTER the individual has been # converted to a dict if self.optimizee_bounding_func is not None: self.eval_pop[:] = [ self.optimizee_bounding_func(ind) for ind in self.eval_pop ] self.eval_pop_arr[:] = np.asarray( [dict_to_list(ind) for ind in self.eval_pop]) self.g += 1 # Update generation counter self._expand_trajectory(traj)
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ old_eval_pop = self.eval_pop.copy() self.eval_pop.clear() logger.info(" Evaluating %i individuals" % len(fitnesses_results)) assert len(fitnesses_results) - 1 == traj.n_random_steps # We need to collect the directions of the random steps along with # the fitness evaluated there fitnesses = np.zeros(traj.n_random_steps) dx = np.zeros((traj.n_random_steps, len(self.current_individual))) weighted_fitness_list = [] for i, (run_index, fitness) in enumerate(fitnesses_results): # We need to convert the current run index into an ind_idx # (index of individual within one generation traj.v_idx = run_index ind_index = traj.par.ind_idx individual = old_eval_pop[ind_index] traj.f_add_result('$set.$.individual', individual) traj.f_add_result('$set.$.fitness', fitness) weighted_fitness = np.dot(fitness, self.optimizee_fitness_weights) weighted_fitness_list.append(weighted_fitness) # The last element of the list is the evaluation of the individual # obtained via gradient descent if i == len(fitnesses_results) - 1: self.current_fitness = weighted_fitness else: fitnesses[i] = weighted_fitness dx[i] = np.array( dict_to_list(individual)) - self.current_individual traj.v_idx = -1 # set the trajectory back to default # Performs descending arg-sort of weighted fitness fitness_sorting_indices = list( reversed(np.argsort(weighted_fitness_list))) old_eval_pop_as_array = np.array( [dict_to_list(x) for x in old_eval_pop]) # Sorting the data according to fitness sorted_population = old_eval_pop_as_array[fitness_sorting_indices] sorted_fitness = np.asarray( weighted_fitness_list)[fitness_sorting_indices] logger.info("-- End of generation %d --", self.g) logger.info(" Evaluated %d individuals", len(fitnesses_results)) logger.info(' Average Fitness: %.4f', np.mean(sorted_fitness)) logger.info(" Current fitness is %.2f", self.current_fitness) logger.info(' Best Fitness: %.4f', sorted_fitness[0]) logger.info(" Best individual is %s", sorted_population[0]) curr_ind_dict = list_to_dict(self.current_individual, self.optimizee_individual_dict_spec) generation_result_dict = { 'generation': self.g, 'current_fitness': self.current_fitness, 'best_fitness_in_run': sorted_fitness[0], 'average_fitness_in_run': np.mean(sorted_fitness), 'current_individual': curr_ind_dict, } generation_name = 'generation_{}'.format(self.g) traj.results.generation_params.f_add_result_group(generation_name) traj.results.generation_params.f_add_result( generation_name + '.algorithm_params', generation_result_dict) logger.info("-- End of iteration {}, current fitness is {} --".format( self.g, self.current_fitness)) max_g = traj.n_iteration - 1 if self.g < max_g and traj.stop_criterion > self.current_fitness: # Create new individual using the appropriate gradient descent self.update_function( traj, np.dot(np.linalg.pinv(dx), fitnesses - self.current_fitness)) current_individual_dict = list_to_dict( list(self.current_individual), self.optimizee_individual_dict_spec) if self.optimizee_bounding_func is not None: current_individual_dict = self.optimizee_bounding_func( current_individual_dict) self.current_individual = np.array( dict_to_list(current_individual_dict)) # Explore the neighbourhood in the parameter space of the # current individual new_individual_list = [ list_to_dict( self.current_individual + self.random_state.normal(0.0, traj.exploration_step_size, self.current_individual.size), self.optimizee_individual_dict_spec) for _ in range(traj.n_random_steps) ] if self.optimizee_bounding_func is not None: new_individual_list = [ self.optimizee_bounding_func(ind) for ind in new_individual_list ] new_individual_list.append(current_individual_dict) fitnesses_results.clear() self.eval_pop = new_individual_list self.g += 1 # Update generation counter self._expand_trajectory(traj)
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func _, self.optimizee_individual_dict_spec = dict_to_list( self.optimizee_create_individual(), get_dict_spec=True) exploration_step_size = parameters.exploration_step_size if isinstance(exploration_step_size, dict): exploration_step_size = dict_to_list(exploration_step_size) traj.f_add_parameter('learning_rate', parameters.learning_rate, comment='Value of learning rate') traj.f_add_parameter('exploration_step_size', exploration_step_size, comment='Standard deviation of the random steps') traj.f_add_parameter('n_random_steps', parameters.n_random_steps, comment='Amount of random steps taken for ' 'calculating the gradient') traj.f_add_parameter('n_iteration', parameters.n_iteration, comment='Number of iteration to perform') traj.f_add_parameter('stop_criterion', parameters.stop_criterion, comment='Stopping criterion parameter') traj.f_add_parameter('seed', np.uint32(parameters.seed), comment='Optimizer random seed') self.random_state = np.random.RandomState(seed=traj.par.seed) # Note that this array stores individuals as an np.array of floats as # opposed to Individual-Dicts # This is because this array is used within the context of the # gradient descent algorithm and thus needs to handle the optimizee # individuals as vectors self.current_individual = np.array( dict_to_list(self.optimizee_create_individual())) # Depending on the algorithm used, initialize the necessary variables self.update_function = None if type(parameters) is ClassicGDParameters: self.init_classic_gd(parameters, traj) elif type(parameters) is StochasticGDParameters: self.init_stochastic_gd(parameters, traj) elif type(parameters) is AdamParameters: self.so_moment = 0.0 self.delta = 0.0 self.init_adam(parameters, traj) elif type(parameters) is RMSPropParameters: self.so_moment = 0.0 self.fo_moment = 0.0 self.init_rmsprop(parameters, traj) else: raise Exception( 'Class of the provided "parameters" argument is not among ' 'the supported types') # Added a generation-wise parameter logging traj.results.f_add_result_group('generation_params', comment='This contains the optimizer ' 'parameters that are common ' 'across a generation') # Explore the neighbourhood in the parameter space of current # individual new_individual_list = [ list_to_dict((self.current_individual + self.random_state.normal( 0.0, exploration_step_size, self.current_individual.size)).tolist(), self.optimizee_individual_dict_spec) for _ in range(parameters.n_random_steps) ] # Also add the current individual to determine it's fitness new_individual_list.append( list_to_dict(list(self.current_individual), self.optimizee_individual_dict_spec)) if optimizee_bounding_func is not None: new_individual_list = [ self.optimizee_bounding_func(ind) for ind in new_individual_list ] # Storing the fitness of the current individual self.current_fitness = -np.Inf self.g = 0 self.eval_pop = new_individual_list self._expand_trajectory(traj)
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.best_individual = None self.best_fitness = None sample_individual = self.optimizee_create_individual() # Generate parameter dictionary based on optimizee_param_grid self.param_list = {} _, optimizee_individual_param_spec = dict_to_list(sample_individual, get_dict_spec=True) self.optimizee_individual_dict_spec = optimizee_individual_param_spec optimizee_param_grid = parameters.param_grid # Assert validity of optimizee_param_grid assert set(sample_individual.keys()) == set(optimizee_param_grid.keys()), \ "The Parameters of optimizee_param_grid don't match those of the optimizee individual" for param_name, param_type, param_length in optimizee_individual_param_spec: param_lower_bound, param_upper_bound, param_n_steps = optimizee_param_grid[ param_name] if param_type == DictEntryType.Scalar: self.param_list[param_name] = np.linspace( param_lower_bound, param_upper_bound, param_n_steps) elif param_type == DictEntryType.Sequence: curr_param_list = np.linspace(param_lower_bound, param_upper_bound, param_n_steps) curr_param_list = np.meshgrid(*([curr_param_list] * param_length), indexing='ij') curr_param_list = [x.ravel() for x in curr_param_list] curr_param_list = np.stack(curr_param_list, axis=-1) self.param_list[param_name] = curr_param_list self.param_list = cartesian_product( self.param_list, tuple(sorted(optimizee_param_grid.keys()))) # Adding the bounds information to the trajectory traj.f_add_parameter_group('grid_spec') for param_name, param_grid_spec in optimizee_param_grid.items(): traj.f_add_parameter_to_group('grid_spec', param_name + '.lower_bound', param_grid_spec[0]) traj.f_add_parameter_to_group('grid_spec', param_name + '.upper_bound', param_grid_spec[1]) traj.f_add_parameter_to_group('grid_spec', param_name + '.step', param_grid_spec[2]) # Expanding the trajectory self.param_list = {('individual.' + key): value for key, value in self.param_list.items()} k0 = list(self.param_list.keys())[0] self.param_list['generation'] = [0] self.param_list['ind_idx'] = np.arange(len(self.param_list[k0])) traj.f_expand(self.param_list) traj.par['n_iteration'] = 1 #: The current generation number self.g = 0 #: The population (i.e. list of individuals) to be evaluated at the next iteration self.eval_pop = None # self.param_list # Storing the fitness of the current individual self.current_fitness = -np.Inf self.traj = traj
def spawn(): x = self.optimizee_create_individual() return dict_to_list(self.optimizee_bounding_func(x))
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func # If a parameter is set to `None`, use default value as described in Wierstra et al. (2014) if parameters.learning_rate_mu is None: learning_rate_mu = 1. else: learning_rate_mu = parameters.learning_rate_mu if parameters.learning_rate_sigma is None: learning_rate_sigma = (3 + np.log(len(parameters.mu))) / ( 5. * np.sqrt(len(parameters.mu))) else: learning_rate_sigma = parameters.learning_rate_sigma if parameters.pop_size is None: pop_size = 4 + int(np.floor(3 * np.log(len(parameters.mu)))) else: pop_size = parameters.pop_size if pop_size < 1: raise ValueError("pop_size needs to be greater than 0") # The following parameters are recorded traj.f_add_parameter('learning_rate_mu', learning_rate_mu, comment='Learning rate mu') traj.f_add_parameter('learning_rate_sigma', learning_rate_sigma, comment='Learning rate mu') traj.f_add_parameter('mu', parameters.mu, comment='Initial mean of search distribution') traj.f_add_parameter( 'sigma', parameters.sigma, comment='Initial standard deviation of search distribution') traj.f_add_parameter('mirrored_sampling_enabled', parameters.mirrored_sampling_enabled, comment='Flag to enable mirrored sampling') traj.f_add_parameter('fitness_shaping_enabled', parameters.fitness_shaping_enabled, comment='Flag to enable fitness shaping') traj.f_add_parameter( 'pop_size', pop_size, comment='Number of minimal individuals simulated in each run') traj.f_add_parameter('n_iteration', parameters.n_iteration, comment='Number of iterations to run') traj.f_add_parameter( 'stop_criterion', parameters.stop_criterion, comment='Stop if best individual reaches this fitness') traj.f_add_parameter( 'seed', np.uint32(parameters.seed), comment='Seed used for random number generation in optimizer') self.random_state = np.random.RandomState(traj.parameters.seed) self.current_individual_arr, self.optimizee_individual_dict_spec = dict_to_list( self.optimizee_create_individual(), get_dict_spec=True) traj.f_add_derived_parameter( 'dimension', self.current_individual_arr.shape, comment='The dimension of the parameter space of the optimizee') # Added a generation-wise parameter logging traj.results.f_add_result_group( 'generation_params', comment='This contains the optimizer parameters that are' ' common across a generation') # The following parameters are recorded as generation parameters i.e. once per generation self.g = 0 # the current generation self.pop_size = pop_size # Population size is dynamic in FACE self.best_fitness_in_run = -np.inf self.best_individual_in_run = None # Set initial parameters of search distribution self.mu = traj.mu self.sigma = traj.sigma # Generate initial distribution self.current_perturbations = self._get_perturbations(traj) current_eval_pop_arr = ( self.mu + self.sigma * self.current_perturbations).tolist() self.eval_pop = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in current_eval_pop_arr ] # Bounding function has to be applied AFTER the individual has been converted to a dict if optimizee_bounding_func is not None: self.eval_pop = [ self.optimizee_bounding_func(ind) for ind in self.eval_pop ] self.eval_pop_arr = np.array( [dict_to_list(ind) for ind in self.eval_pop]) self._expand_trajectory(traj)
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ n_iteration, stop_criterion, fitness_shaping_enabled = \ traj.n_iteration, traj.stop_criterion, traj.fitness_shaping_enabled weighted_fitness_list = [] # ************************************************************************************************************** # Storing run-information in the trajectory # Reading fitnesses and performing distribution update # ************************************************************************************************************** for run_index, fitness in fitnesses_results: # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx traj.f_add_result('$set.$.individual', self.eval_pop[ind_index]) traj.f_add_result('$set.$.fitness', fitness) weighted_fitness_list.append( np.dot(fitness, self.optimizee_fitness_weights)) traj.v_idx = -1 # set trajectory back to default weighted_fitness_list = np.array(weighted_fitness_list).ravel() # NOTE: It is necessary to clear the finesses_results to clear the data in the reference, and del # is used to make sure it's not used in the rest of this function fitnesses_results.clear() del fitnesses_results # Last fitness is for the previous `current_individual_arr` weighted_fitness_list = weighted_fitness_list[:-1] current_individual_fitness = weighted_fitness_list[-1] # Performs descending arg-sort of weighted fitness fitness_sorting_indices = list( reversed(np.argsort(weighted_fitness_list))) # Sorting the data according to fitness sorted_population = self.eval_pop_arr[fitness_sorting_indices] sorted_fitness = np.asarray( weighted_fitness_list)[fitness_sorting_indices] sorted_perturbations = self.current_perturbations[ fitness_sorting_indices] self.best_individual_in_run = sorted_population[0] self.best_fitness_in_run = sorted_fitness[0] logger.info("-- End of generation %d --", self.g) logger.info(" Evaluated %d individuals", len(weighted_fitness_list) + 1) logger.info(' Best Fitness: %.4f', self.best_fitness_in_run) logger.info(' Average Fitness: %.4f', np.mean(sorted_fitness)) # ************************************************************************************************************** # Storing Generation Parameters / Results in the trajectory # ************************************************************************************************************** # These entries correspond to the generation that has been simulated prior to this post-processing run # Documentation of algorithm parameters for the current generation # # generation - The index of the evaluated generation # best_fitness_in_run - The highest fitness among the individuals in the # evaluated generation # pop_size - Population size generation_result_dict = { 'generation': self.g, 'best_fitness_in_run': self.best_fitness_in_run, 'current_individual_fitness': current_individual_fitness, 'average_fitness_in_run': np.mean(sorted_fitness), 'pop_size': self.pop_size } generation_name = 'generation_{}'.format(self.g) traj.results.generation_params.f_add_result_group(generation_name) traj.results.generation_params.f_add_result( generation_name + '.algorithm_params', generation_result_dict, comment="These are the parameters that correspond to the algorithm. " "Look at the source code for `EvolutionStrategiesOptimizer::post_process()` " "for comments documenting these parameters") traj.results.generation_params.f_add_result( generation_name + '.distribution_params', { 'mu': self.mu.copy(), 'sigma': self.sigma.copy() }, comment= "These are the parameters of the distribution that underlies the" " currently evaluated generation") if fitness_shaping_enabled: fitnesses_to_fit = self._compute_utility(sorted_fitness) else: fitnesses_to_fit = sorted_fitness assert len(fitnesses_to_fit) == len(sorted_perturbations) # ************************************************************************************************************** # Update the parameters of the search distribution using the natural gradient in natural coordinates # ************************************************************************************************************** self.mu += traj.learning_rate_mu * traj.sigma * np.dot( fitnesses_to_fit, sorted_perturbations) self.sigma *= np.exp( traj.learning_rate_sigma / 2. * np.dot(fitnesses_to_fit, sorted_perturbations**2 - 1.)) # ************************************************************************************************************** # Create the next generation by sampling the inferred distribution # ************************************************************************************************************** # Note that this is only done in case the evaluated run is not the last run self.eval_pop.clear() # check if to stop if self.g < n_iteration - 1 and self.best_fitness_in_run < stop_criterion: self.current_perturbations = self._get_perturbations(traj) current_eval_pop_arr = ( self.mu + self.sigma * self.current_perturbations).tolist() self.eval_pop = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in current_eval_pop_arr ] # Bounding function has to be applied AFTER the individual has been converted to a dict if self.optimizee_bounding_func is not None: self.eval_pop = [ self.optimizee_bounding_func(ind) for ind in self.eval_pop ] self.eval_pop_arr = np.array( [dict_to_list(ind) for ind in self.eval_pop]) self.g += 1 # Update generation counter self._expand_trajectory(traj)
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ n_elite, n_iteration, smoothing, temp_decay, min_pop_size, max_pop_size = \ traj.n_elite, traj.n_iteration, traj.smoothing, traj.temp_decay, traj.min_pop_size, traj.max_pop_size stop_criterion, n_expand = traj.stop_criterion, traj.n_expand weighted_fitness_list = [] # ************************************************************************************************************** # Storing run-information in the trajectory # Reading fitnesses and performing distribution update # ************************************************************************************************************** for run_index, fitness in fitnesses_results: # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx traj.f_add_result('$set.$.individual', self.eval_pop[ind_index]) traj.f_add_result('$set.$.fitness', fitness) weighted_fitness_list.append( np.dot(fitness, self.optimizee_fitness_weights)) traj.v_idx = -1 # set trajectory back to default # Performs descending arg-sort of weighted fitness fitness_sorting_indices = list( reversed(np.argsort(weighted_fitness_list))) generation_name = 'generation_{}'.format(self.g) # Sorting the data according to fitness sorted_population = self.eval_pop_asarray[fitness_sorting_indices] sorted_fitess = np.asarray( weighted_fitness_list)[fitness_sorting_indices] # Elite individuals are with performance better than or equal to the (1-rho) quantile. # See original describtion of cross entropy for optimization elite_individuals = sorted_population[:n_elite] previous_best_fitness = self.best_fitness_in_run self.best_individual_in_run = sorted_population[0] self.best_fitness_in_run = sorted_fitess[0] previous_gamma = self.gamma self.gamma = sorted_fitess[n_elite - 1] logger.info("-- End of generation %d --", self.g) logger.info(" Evaluated %d individuals", len(fitnesses_results)) logger.info(' Best Fitness: %.4f', self.best_fitness_in_run) logger.debug(' Calculated gamma: %.4f', self.gamma) # ************************************************************************************************************** # Storing Generation Parameters / Results in the trajectory # ************************************************************************************************************** # These entries correspond to the generation that has been simulated prior to this post-processing run traj.results.generation_params.f_add_result( generation_name + '.g', self.g, comment='The index of the evaluated generation') traj.results.generation_params.f_add_result( generation_name + '.gamma', self.gamma, comment='The fitness threshold inferred from the evaluated ' 'generation (This is used in sampling the next generation') traj.results.generation_params.f_add_result( generation_name + '.T', self.T, comment='Temperature used to select non-elite elements among the' 'individuals of the evaluated generation') traj.results.generation_params.f_add_result( generation_name + '.best_fitness_in_run', self.best_fitness_in_run, comment='The highest fitness among the individuals in the ' 'evaluated generation') traj.results.generation_params.f_add_result(generation_name + '.pop_size', self.pop_size, comment='Population size') # Check stopping if self.g >= n_iteration or self.best_fitness_in_run >= stop_criterion: return expand = True if self.best_fitness_in_run > previous_best_fitness or self.gamma > previous_gamma: # shrink population size self.pop_size = (self.pop_size + min_pop_size) // 2 # new distribution fit individuals_to_be_fitted = elite_individuals # Temperature dependent sampling of non elite individuals if temp_decay > 0: # Keeping non-elite samples with certain probability dependent on temperature (like Simulated Annealing) non_elite_selection_probs = np.clip(np.exp( (weighted_fitness_list[n_elite:] - self.gamma) / self.T), amin=0.0, a_max=1.0) non_elite_selected_indices = self.random_state.binomial( 1, p=non_elite_selection_probs) non_elite_eval_pop_asarray = sorted_population[n_elite:][ non_elite_selected_indices] individuals_to_be_fitted = np.concatenate( (elite_individuals, non_elite_eval_pop_asarray)) # Fitting New distribution parameters. self.distribution_results = self.current_distribution.fit( individuals_to_be_fitted, smoothing) elif self.pop_size + n_expand <= max_pop_size: # Increase pop size by one, resample, FACE part logger.info(' FACE increase population size by %d', n_expand) self.pop_size += n_expand else: # Stop algorithm expand = False logger.warning(' Possibly diverged') # Add the results of the distribution fitting to the trajectory for parameter_key, parameter_value in self.distribution_results.items( ): traj.results.generation_params.f_add_result( generation_name + '.' + parameter_key, parameter_value) # ************************************************************************************************************** # Create the next generation by sampling the inferred distribution # ************************************************************************************************************** # Note that this is only done in case the evaluated run is not the last run fitnesses_results.clear() self.eval_pop.clear() if expand: # Sample from the constructed distribution self.eval_pop_asarray = self.current_distribution.sample( self.pop_size) self.eval_pop = [ list_to_dict(ind_asarray, self.optimizee_individual_dict_spec) for ind_asarray in self.eval_pop_asarray ] # Clip to boundaries if self.optimizee_bounding_func is not None: self.eval_pop = [ self.optimizee_bounding_func(individual) for individual in self.eval_pop ] self.eval_pop_asarray = np.array( [dict_to_list(x) for x in self.eval_pop]) self.g += 1 # Update generation counter self.T *= temp_decay self._expand_trajectory(traj)
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func if parameters.min_pop_size < 1: raise Exception("min_pop_size needs to be greater than 0") if parameters.max_pop_size < parameters.min_pop_size: raise Exception( "max_pop_size needs to be greater or equal to min_pop_size") if parameters.n_elite > parameters.min_pop_size: raise Exception("n_elite exceeds min_pop_size") if parameters.temp_decay < 0 or parameters.temp_decay > 1: raise Exception("temp_decay not in range") if parameters.smoothing >= 1 or parameters.smoothing < 0: raise Exception("smoothing has to be in interval [0, 1)") if parameters.seed is None: raise Exception("The 'seed' must be set") # The following parameters are recorded traj.f_add_parameter( 'min_pop_size', parameters.min_pop_size, comment='Number of minimal individuals simulated in each run') traj.f_add_parameter('max_pop_size', parameters.max_pop_size, comment='Maximal individuals in population') traj.f_add_parameter( 'n_elite', parameters.n_elite, comment='Number of individuals to be considered as elite') traj.f_add_parameter('n_iteration', parameters.n_iteration, comment='Number of iterations to run') traj.f_add_parameter( 'n_expand', parameters.n_expand, comment='Expanding of population size in case of FACE') traj.f_add_parameter( 'stop_criterion', parameters.stop_criterion, comment='Stop if best individual reaches this fitness') traj.f_add_parameter('smoothing', parameters.smoothing, comment='Weight of old parameters in smoothing') traj.f_add_parameter('temp_decay', parameters.temp_decay, comment='Decay factor for temperature') traj.f_add_parameter('seed', np.uint32(parameters.seed), comment='Random seed used by optimizer') self.random_state = np.random.RandomState(seed=traj.par.seed) temp_indiv, self.optimizee_individual_dict_spec = dict_to_list( self.optimizee_create_individual(), get_dict_spec=True) traj.f_add_derived_parameter( 'dimension', len(temp_indiv), comment='The dimension of the parameter space of the optimizee') # Added a generation-wise parameter logging traj.results.f_add_result_group( 'generation_params', comment='This contains the optimizer parameters that are' ' common across a generation') # The following parameters are recorded as generation parameters i.e. once per generation self.g = 0 # the current generation # This is the value above which the samples are considered elite in the # current generation self.gamma = -np.inf self.T = 1 # This is the temperature used to filter evaluated samples in this run self.pop_size = parameters.min_pop_size # Population size is dynamic in FACE self.best_fitness_in_run = -np.inf # The first iteration does not pick the values out of the Gaussian distribution. It picks randomly # (or at-least as randomly as optimizee_create_individual creates individuals) # Note that this array stores individuals as an np.array of floats as opposed to Individual-Dicts # This is because this array is used within the context of the cross entropy algorithm and # Thus needs to handle the optimizee individuals as vectors current_eval_pop = [ self.optimizee_create_individual() for _ in range(parameters.min_pop_size) ] if optimizee_bounding_func is not None: current_eval_pop = [ self.optimizee_bounding_func(ind) for ind in current_eval_pop ] self.eval_pop = current_eval_pop self.eval_pop_asarray = np.array( [dict_to_list(x) for x in self.eval_pop]) # Max Likelihood self.current_distribution = parameters.distribution self.current_distribution.init_random_state(self.random_state) self.current_distribution.fit(self.eval_pop_asarray) self._expand_trajectory(traj)
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__(traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func # The following parameters are recorded traj.f_add_parameter('n_parallel_runs', parameters.n_parallel_runs, comment='Number of parallel simulated annealing runs / Size of Population') traj.f_add_parameter('noisy_step', parameters.noisy_step, comment='Size of the random step') traj.f_add_parameter('n_iteration', parameters.n_iteration, comment='Number of iteration to perform') traj.f_add_parameter('stop_criterion', parameters.stop_criterion, comment='Stopping criterion parameter') traj.f_add_parameter('seed', parameters.seed, comment='Seed for RNG') cooling_schedules_string = '' bounds_list = [] decay_list = [] schedules_list = [] for i in range(0,traj.n_parallel_runs): bounds_list.append(str(parameters.temperature_bounds[i,:])) bounds_list.append(' ') decay_list.append(str(parameters.decay_parameters[i])) decay_list.append(' ') schedules_list.append(str(parameters.cooling_schedules[i])) schedules_list.append(' ') temperature_bounds_string = ''.join(bounds_list) decay_parameters_string = ''.join(decay_list) cooling_schedules_string = ''.join(schedules_list) traj.f_add_parameter('temperature_bounds', temperature_bounds_string, comment='The max and min temperature of the respective schedule') traj.f_add_parameter('decay_parameters', decay_parameters_string, comment='The one parameter, most schedules need') traj.f_add_parameter('cooling_schedules', cooling_schedules_string, comment='The used cooling schedule') _, self.optimizee_individual_dict_spec = dict_to_list(self.optimizee_create_individual(), get_dict_spec=True) # Note that this array stores individuals as an np.array of floats as opposed to Individual-Dicts # This is because this array is used within the context of the simulated annealing algorithm and # Thus needs to handle the optimizee individuals as vectors self.current_individual_list = [np.array(dict_to_list(self.optimizee_create_individual())) for _ in range(parameters.n_parallel_runs)] traj.f_add_result('fitnesses', [], comment='Fitnesses of all individuals') self.T_all = parameters.temperature_bounds[:,0] # Initialize temperature self.T = 1 self.g = 0 # the current generation self.cooling_schedules = parameters.cooling_schedules self.decay_parameters = parameters.decay_parameters self.temperature_bounds = parameters.temperature_bounds # Keep track of current fitness value to decide whether we want the next individual to be accepted or not self.current_fitness_value_list = [-np.Inf] * parameters.n_parallel_runs new_individual_list = [ list_to_dict(ind_as_list + np.random.normal(0.0, parameters.noisy_step, ind_as_list.size) * traj.noisy_step, self.optimizee_individual_dict_spec) for ind_as_list in self.current_individual_list ] if optimizee_bounding_func is not None: new_individual_list = [self.optimizee_bounding_func(ind) for ind in new_individual_list] self.eval_pop = new_individual_list self._expand_trajectory(traj) #initialize container for the indices of the parallel runs self.parallel_indices = [] for i in range(0,traj.n_parallel_runs): self.parallel_indices.append(i) self.available_cooling_schedules = AvailableCoolingSchedules # assert if all cooling schedules are among the known cooling schedules schedule_known = True # start off as True - if any schdule is unknown gets False for i in range(np.size(self.cooling_schedules)): schedule_known = schedule_known and self.cooling_schedules[i] in AvailableCoolingSchedules assert schedule_known, print("Warning: Unknown cooling schedule")
def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func if parameters.pop_size < 1: raise Exception("pop_size needs to be greater than 0") # The following parameters are recorded traj.f_add_parameter('learning_rate', parameters.learning_rate, comment='Learning rate') traj.f_add_parameter('noise_std', parameters.noise_std, comment='Standard deviation of noise') traj.f_add_parameter('mirrored_sampling_enabled', parameters.mirrored_sampling_enabled, comment='Flag to enable mirrored sampling') traj.f_add_parameter('fitness_shaping_enabled', parameters.fitness_shaping_enabled, comment='Flag to enable fitness shaping') traj.f_add_parameter( 'pop_size', parameters.pop_size, comment='Number of minimal individuals simulated in each run') traj.f_add_parameter('n_iteration', parameters.n_iteration, comment='Number of iterations to run') traj.f_add_parameter( 'stop_criterion', parameters.stop_criterion, comment='Stop if best individual reaches this fitness') traj.f_add_parameter( 'seed', np.uint32(parameters.seed), comment='Seed used for random number generation in optimizer') self.random_state = np.random.RandomState(traj.parameters.seed) self.current_individual_arr, self.optimizee_individual_dict_spec = \ dict_to_list( self.optimizee_create_individual(), get_dict_spec=True) noise_std_shape = np.array(parameters.noise_std).shape ind_shape = self.current_individual_arr.shape assert noise_std_shape == () or noise_std_shape == ind_shape traj.f_add_derived_parameter( 'dimension', self.current_individual_arr.shape, comment='The dimension of the parameter space of the optimizee') # Added a generation-wise parameter logging traj.results.f_add_result_group( 'generation_params', comment='This contains the optimizer parameters that are' ' common across a generation') # The following parameters are recorded as generation parameters # i.e. once per generation self.g = 0 # the current generation # Population size is dynamic in FACE self.pop_size = parameters.pop_size self.best_fitness_in_run = -np.inf self.best_individual_in_run = None # The first iteration does not pick the values out of the Gaussian # distribution. It picks randomly (or at-least # as randomly as optimizee_create_individual creates individuals) # Note that this array stores individuals as an np.array of floats as # opposed to Individual-Dicts. # This is because this array is used within the context of the cross # entropy algorithm and thus needs to handle the optimizee individuals # as vectors self.current_perturbations = self._get_perturbations(traj) current_eval_pop_arr = (self.current_individual_arr + self.current_perturbations).tolist() self.eval_pop = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in current_eval_pop_arr ] self.eval_pop.append( list_to_dict(self.current_individual_arr, self.optimizee_individual_dict_spec)) # Bounding function has to be applied AFTER the individual has been # converted to a dict if optimizee_bounding_func is not None: self.eval_pop[:] = [ self.optimizee_bounding_func(ind) for ind in self.eval_pop ] self.eval_pop_arr = np.array( [dict_to_list(ind) for ind in self.eval_pop]) self._expand_trajectory(traj)
def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ noisy_step, n_iteration, stop_criterion = \ traj.noisy_step, traj.n_iteration, traj.stop_criterion cooling_schedules = self.cooling_schedules decay_parameters = self.decay_parameters temperature_bounds = self.temperature_bounds old_eval_pop = self.eval_pop.copy() self.eval_pop.clear() temperature = self.T_all for i in range(0,traj.n_parallel_runs): self.T_all[self.parallel_indices[i]] = self.cooling(temperature[self.parallel_indices[i]], cooling_schedules[self.parallel_indices[i]], decay_parameters[self.parallel_indices[i]], temperature_bounds[self.parallel_indices[i],:], n_iteration) logger.info(" Evaluating %i individuals" % len(fitnesses_results)) assert len(fitnesses_results) == traj.n_parallel_runs weighted_fitness_list = [] for i, (run_index, fitness) in enumerate(fitnesses_results): self.T = self.T_all[self.parallel_indices[i]] weighted_fitness = sum(f * w for f, w in zip(fitness, self.optimizee_fitness_weights)) weighted_fitness_list.append(weighted_fitness) # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx individual = old_eval_pop[ind_index] # Accept or reject the new solution current_fitness_value_i = self.current_fitness_value_list[i] r = np.random.rand() p = np.exp((weighted_fitness - current_fitness_value_i) / self.T) # Accept if r < p or weighted_fitness >= current_fitness_value_i: self.current_fitness_value_list[i] = weighted_fitness self.current_individual_list[i] = np.array(dict_to_list(individual)) traj.f_add_result('$set.$.individual', individual) # Watchout! if weighted fitness is a tuple/np array it should be converted to a list first here traj.f_add_result('$set.$.fitness', weighted_fitness) current_individual = self.current_individual_list[i] new_individual = list_to_dict(current_individual + np.random.randn(current_individual.size) * noisy_step * self.T, self.optimizee_individual_dict_spec) if self.optimizee_bounding_func is not None: new_individual = self.optimizee_bounding_func(new_individual) logger.debug("Current best fitness for individual %d is %.2f. New individual is %s", i, self.current_fitness_value_list[i], new_individual) self.eval_pop.append(new_individual) # the parallel tempering swapping starts here for i in range(0,traj.n_parallel_runs): #make a random choice from all the other parallel runs compare_indices = [] for j in range(0,traj.n_parallel_runs): compare_indices.append(i) compare_indices.remove(i) random_choice = random.choice(compare_indices) #random variable with unit distribution betwwen 0 and 1 random_variable = np.random.rand() #swap if criterion is met if (self.metropolis_hasting(self.current_fitness_value_list[i], self.current_fitness_value_list[random_choice], self.T_all[i], self.T_all[random_choice]) > random_variable): temp = self.parallel_indices[i] self.parallel_indices[i] = self.parallel_indices[random_choice] self.parallel_indices[random_choice] = temp logger.debug("Current best fitness within population is %.2f", max(self.current_fitness_value_list)) traj.v_idx = -1 # set the trajectory back to default logger.info("-- End of generation {} --".format(self.g)) # ------- Create the next generation by crossover and mutation -------- # # not necessary for the last generation if self.g < n_iteration - 1 and stop_criterion > max(self.current_fitness_value_list): fitnesses_results.clear() self.g += 1 # Update generation counter self._expand_trajectory(traj)