def generate_offspring(self, parents, generation, pool=None): """ This function generates the offspring from the population :return: Population of offsprings """ offsprings = Population(self.params, init_size=0, name='offsprings') parent_genome = parents['genome'] parent_ids = parents['id'] parent_ancestor = parents['ancestor'] if pool is not None: offs = pool.map(self._generate_off, zip(parent_ids, parent_genome, parent_ancestor)) else: offs = [] for id_gen in zip( parent_ids, parent_genome, parent_ancestor): # Generate offsprings from each parent offs.append(self._generate_off(id_gen)) offsprings.pop = [off for p_off in offs for off in p_off ] # Unpack list of lists and add it to offsprings offs_ids = parents.agent_id + np.array(range( len(offsprings))) # Calculate offs IDs offsprings['id'] = offs_ids # Update offs IDs offsprings['born'] = [generation] * offsprings.size parents.agent_id = max( offs_ids) + 1 # This saves the maximum ID reached till now return offsprings
def evolve(pop): new_pop = Population(0) LEN_POP = 0 '''Keep The Fittest Chromosomes''' for i in range(settings.NUMBER_OF_ELITE_CHROMOSOMES): new_pop.append(pop[i]) LEN_POP += 1 while LEN_POP < settings.POPULATION_SIZE: parent1 = GeneticAlgorithm.select_tournament(pop) parent2 = GeneticAlgorithm.select_tournament(pop) child1, child2 = GeneticAlgorithm.crossover_chromosomes( parent1, parent2) GeneticAlgorithm.mutate_chromosome(child1) GeneticAlgorithm.mutate_chromosome(child2) new_pop.append(child1) LEN_POP += 1 # make sure to not depass the population size if we keep the elite if LEN_POP < settings.POPULATION_SIZE: new_pop.append(child2) LEN_POP += 1 # make sure to not depass the population size if we keep the elite # if len(new_pop.get_chromosomes()) < settings.POPULATION_SIZE: # new_pop.get_chromosomes().append(child2) new_pop.sort(reverse=True) return new_pop
def __init__(self, parameters): self.parameters = parameters self.bd_extractor = BehaviorDescriptor(self.parameters) self.generation = 1 if self.parameters.multiprocesses: global main_pool main_pool = mp.Pool(initializer=self.init_process, processes=self.parameters.multiprocesses) self.evaluator = Evaluator(self.parameters) self.population = Population(self.parameters, init_size=self.parameters.pop_size) self.init_pop = True self.offsprings = None if self.parameters.exp_type == 'NS': self.evolver = NoveltySearch(self.parameters) elif self.parameters.exp_type == 'SIGN': self.evolver = NoveltySearch(self.parameters) elif self.parameters.exp_type == 'CMA-ES': self.evolver = CMAES(self.parameters) # Generate CMA-ES initial population del self.population self.population = Population( self.parameters, init_size=self.parameters.emitter_population) for agent in self.population: agent['genome'] = self.evolver.optimizer.ask() elif self.parameters.exp_type == 'CMA-NS': self.evolver = CMANS(self.parameters) self.reward_archive = self.evolver.rew_archive elif self.parameters.exp_type == 'NSGA-II': self.evolver = NSGAII(self.parameters) elif self.parameters.exp_type == 'SERENE': self.evolver = SERENE(self.parameters) elif self.parameters.exp_type == 'ME': self.evolver = MAPElites(self.parameters) elif self.parameters.exp_type == 'CMA-ME': self.evolver = CMAME(self.parameters) elif self.parameters.exp_type == 'RND': self.evolver = RandomSearch(self.parameters) else: print("Experiment type {} not implemented.".format( self.parameters.exp_type)) raise ValueError
def test_total_fitness(self): """ Population - total fitness """ population = Population(Mock) population += [ _FakeIndividual(0.1), _FakeIndividual(0.5) ] population.calculate_fitness() self.assertEquals(population.total_fitness, 0.6)
def test_average_fitness(self): """ Population - average fitness """ population = Population(Mock) population += [ _FakeIndividual(0.1), _FakeIndividual(0.5) ] population.calculate_fitness() self.assertEquals(population.average_fitness, 0.3)
def test_best_solution(self): """ Population - return best solution """ population = Population(Mock) population += [ _FakeIndividual(0.1), _FakeIndividual(0.5) ] population.calculate_fitness() self.assertEquals(population.best_individual.fitness, 0.5)
def test_best_chromosomes(self): """ Population - elite chromosomes """ population = Population(Mock) population += [ _FakeIndividual(0.5), _FakeIndividual(0.1), _FakeIndividual(0.9), ] population.calculate_fitness() self.assertSequenceEqual( population.best_individuals(2), [population[2], population[0]])
def generate_off(self, generation): """ This function generates the offsprings of the emitter :return: """ offsprings = Population(self._params, init_size=2 * self.pop.size, name='offsprings') off_genomes = [] off_ancestors = [] off_parents = [] for agent in self.pop: # Generate 2 offsprings from each parent off_genomes.append(self.mutate_genome(agent['genome'])) off_genomes.append(self.mutate_genome(agent['genome'])) off_ancestors.append(agent['ancestor'] if agent['ancestor'] is not None else agent['id']) off_ancestors.append(agent['ancestor'] if agent['ancestor'] is not None else agent['id']) off_parents.append(agent['id']) off_parents.append(agent['id']) off_ids = self.pop.agent_id + np.array(range(len(offsprings))) offsprings['genome'] = off_genomes offsprings['id'] = off_ids offsprings['ancestor'] = off_ancestors offsprings['parent'] = off_parents offsprings['born'] = [generation] * offsprings.size self.pop.agent_id = max( off_ids) + 1 # This saves the maximum ID reached till now return offsprings
def generate_offspring(self, parents, generation, pool=None): """ This function generates the offspring from the population :return: Population of offsprings """ offsprings = Population(self.params, init_size=self.params.pop_size, name='offsprings') return offsprings
def run(self, generations=None): """ Runs genetic algorithm for a number of iterations, starting with a randomly created initial population. If iteration count is not specified, algorithm would run until terminated explicitly. Yields current population AND generation number. """ if generations is not None and generations < 1: raise ValueError( "Generation count must be positive, non-zero integer") # Initial random population for generation-0: self._population = Population( self.phenotype, self.population_size, parallelizer=self._parallelizer) self._population.calculate_fitness() # Run specified amount of iterations if generations: for generation in xrange(1, generations + 1): self._population = self._next_population() yield self.population, generation # Run indefintely else: generation = 0 while True: self._population = self._next_population() generation += 1 yield self.population, generation
def _init_pop(self): """ This function initializes the emitter pop around the parent :return: """ pop = Population(self._params, self._pop_size) for agent in pop: agent['genome'] = self.mutate_genome(self._init_mean) return pop
def __init__(self, parameters): self.parameters = parameters self.bd_extractor = BehaviorDescriptor(self.parameters) self.generation = 0 if self.parameters.multiprocesses: global main_pool main_pool = mp.Pool(initializer=self.init_process, processes=self.parameters.multiprocesses) else: self.evaluator = Evaluator(self.parameters) self.evolver = NoveltySearch(self.parameters) self.population = Population(self.parameters, init_size=self.parameters.pop_size) self.offsprings = None self.ns_archive = self.evolver.archive
def _next_population(self): # Start with an empty population new_population = Population( self.phenotype, size=0, parallelizer=self._parallelizer) # Pick best individuals from previous population if necessary new_population += self.population.best_individuals( self.elitism_count) # Form the new population while len(new_population) < self.population_size: # Selection individual1 = self._selection.run(self.population) individual2 = self._selection.run(self.population) # Crossover # Individuals are copied, regardless of whether # crossover actually occurs. offspring1, offspring2 = self._crossover.run( individual1, individual2) # Mutation offspring1.mutate(self.mutation_rate) offspring2.mutate(self.mutation_rate) new_population += [offspring1, offspring2] # If elitism param is odd number, the new population might have got # one extra individual. Remove the worst? if self._remove_extra_individual is True: new_population.calculate_fitness( truncate_if_above=self.population_size) else: new_population.calculate_fitness() return new_population
def __init__(self, parameters, **kwargs): super().__init__(parameters) self.update_criteria = 'novelty' self.rew_archive = Archive(self.params, name='rew_archive') # Instantiated only to extract genome size controller = registered_envs[ self.params.env_name]['controller']['controller']( **registered_envs[self.params.env_name]['controller']) self.genome_size = controller.genome_size self.bounds = self.params.genome_limit * np.ones( (self.genome_size, len(self.params.genome_limit))) self.emitter_pop = Population(self.params, init_size=self.params.emitter_population, name='emitter') self.emitter_based = True self.archive_candidates = {} self.emitters = {} self.emitter_candidate = {}
def create_random_cells(self, n_cells): """ Creation of a basic population for cells, with latitude and longitude """ cells = Population(size=n_cells) latitude_generator = FakerGenerator(method="latitude", seed=next(self.seeder)) longitude_generator = FakerGenerator(method="longitude", seed=next(self.seeder)) cells.create_attribute("latitude", init_gen=latitude_generator) cells.create_attribute("longitude", init_gen=longitude_generator) return cells
def __init__(self, ancestor, mutation_rate, parameters): self.ancestor = ancestor self._init_mean = self.ancestor['genome'] self.id = ancestor['id'] self._mutation_rate = mutation_rate self._params = parameters self._pop_size = self._params.emitter_population self.pop = self._init_pop() self.ns_arch_candidates = Population(self._params, init_size=0, name='ns_arch_cand') # List of lists. Each inner list corresponds to the values obtained during a step # We init with the ancestor reward so it's easier to calculate the improvement self.values = [] self.archived_values = [] self.improvement = 0 self._init_values = None self.archived = [ ] # List containing the number of archived agents at each step self.most_novel = self.ancestor self.steps = 0
class Algorithm(object): def __init__(self, phenotype, crossover, selection, population_size=10, mutation_rate=0.01, elitism_count=0, parallelizer=None): # Classes self.phenotype = phenotype # Parameters self.population_size = population_size self.mutation_rate = mutation_rate self.elitism_count = elitism_count # GA operator instances self._crossover = crossover self._selection = selection # Etc self._parallelizer = parallelizer self._population = None # Odd elitist size - selection/crossover/mutation is done in pairs # so eventually the new population will get one extra individual # which needs to be dealt with if self.elitism_count % 2 == 1: self._remove_extra_individual = True else: self._remove_extra_individual = False @property def population(self): return self._population def _next_population(self): # Start with an empty population new_population = Population( self.phenotype, size=0, parallelizer=self._parallelizer) # Pick best individuals from previous population if necessary new_population += self.population.best_individuals( self.elitism_count) # Form the new population while len(new_population) < self.population_size: # Selection individual1 = self._selection.run(self.population) individual2 = self._selection.run(self.population) # Crossover # Individuals are copied, regardless of whether # crossover actually occurs. offspring1, offspring2 = self._crossover.run( individual1, individual2) # Mutation offspring1.mutate(self.mutation_rate) offspring2.mutate(self.mutation_rate) new_population += [offspring1, offspring2] # If elitism param is odd number, the new population might have got # one extra individual. Remove the worst? if self._remove_extra_individual is True: new_population.calculate_fitness( truncate_if_above=self.population_size) else: new_population.calculate_fitness() return new_population def run(self, generations=None): """ Runs genetic algorithm for a number of iterations, starting with a randomly created initial population. If iteration count is not specified, algorithm would run until terminated explicitly. Yields current population AND generation number. """ if generations is not None and generations < 1: raise ValueError( "Generation count must be positive, non-zero integer") # Initial random population for generation-0: self._population = Population( self.phenotype, self.population_size, parallelizer=self._parallelizer) self._population.calculate_fitness() # Run specified amount of iterations if generations: for generation in xrange(1, generations + 1): self._population = self._next_population() yield self.population, generation # Run indefintely else: generation = 0 while True: self._population = self._next_population() generation += 1 yield self.population, generation
class Searcher(object): """ This class creates the instance of the NS algorithm and everything related """ def __init__(self, parameters): self.parameters = parameters self.bd_extractor = BehaviorDescriptor(self.parameters) self.generation = 0 if self.parameters.multiprocesses: global main_pool main_pool = mp.Pool(initializer=self.init_process, processes=self.parameters.multiprocesses) else: self.evaluator = Evaluator(self.parameters) self.evolver = NoveltySearch(self.parameters) self.population = Population(self.parameters, init_size=self.parameters.pop_size) self.offsprings = None self.ns_archive = self.evolver.archive def init_process(self): """ This function is used to initialize the pool so each process has its own instance of the evaluator :return: """ global evaluator evaluator = Evaluator(self.parameters) def _feed_eval(self, agent): """ This function feeds the agent to the evaluator and returns the updated agent :param agent: :return: """ global evaluator eval_agent = evaluator( agent, self.bd_extractor.__call__ ) # Is passing the call better than init the evaluator with the bd inside??? return eval_agent def evaluate_in_env(self, pop, pool=None): """ This function evaluates the population in the environment by passing it to the parallel evaluators. :return: """ if self.parameters.verbose: print('Evaluating {} in environment.'.format(pop.name)) if self.parameters.multiprocesses: pop.pop = pool.map( self._feed_eval, pop.pop ) # As long as the ID is fine, the order of the element in the list does not matter else: for i in range(pop.size): if self.parameters.verbose: print(".", end='') # The end prevents the newline pop[i] = self.evaluator(pop[i], self.bd_extractor) if self.parameters.verbose: print() def generational_step(self): """ This function performs all the calculations needed for one generation. Generates offsprings, evaluates them and the parents in the environment, calculates the performance metrics, updates archive and population and finally saves offsprings, population and archive. :return: time taken for running the generation """ global main_pool start_time = timer() # NB if you pass a pool here the offsprings are not well generated. This is probably due to the fact that # the sampling for the mutation are done in parallel so multiple offsprings will have same mutations. self.offsprings = self.evolver.generate_offspring( self.population, pool=None) # Generate offsprings # Evaluate population and offsprings in the environment self.evaluate_in_env(self.population, pool=main_pool) self.evaluate_in_env(self.offsprings, pool=main_pool) # Do evolution stuff #TODO maybe can wrap these 3 functions into a single ea.step function self.evolver.evaluate_performances( self.population, self.offsprings, pool=main_pool) # Calculate novelty/fitness/curiosity etc self.evolver.update_archive(self.offsprings) self.evolver.update_population(self.population, self.offsprings) self.generation += 1 # Save pop, archive and off self.population.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) self.evolver.archive.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) self.offsprings.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) return timer() - start_time def load_generation(self, generation, path): """ This function loads the population, the offsprings and the archive at a given generation, so it can restart the search from there. :param generation: :param path: experiment path :return: """ self.generation = generation self.population.load( os.path.join(path, 'population_gen_{}.pkl'.format(self.generation))) self.offsprings.load( os.path.join(path, 'offsprings_gen_{}.pkl'.format(self.generation))) self.evolver.archive.load( os.path.join(path, 'archive_gen_{}.pkl'.format(self.generation))) def close(self): """ This function closes the pool and deletes everything. :return: """ if self.parameters.multiprocesses: global main_pool main_pool.close() main_pool.join()
def __init__(self, evaluate): self.evaluate = evaluate self.population = Population() self.starting_compatibility_threshold = config.compatibility_threshold
def reset(self): self.population = Population() config.compatibility_threshold = self.starting_compatibility_threshold
class Searcher(object): """ This class creates the instance of the NS algorithm and everything related """ def __init__(self, parameters): self.parameters = parameters self.bd_extractor = BehaviorDescriptor(self.parameters) self.generation = 1 if self.parameters.multiprocesses: global main_pool main_pool = mp.Pool(initializer=self.init_process, processes=self.parameters.multiprocesses) self.evaluator = Evaluator(self.parameters) self.population = Population(self.parameters, init_size=self.parameters.pop_size) self.init_pop = True self.offsprings = None if self.parameters.exp_type == 'NS': self.evolver = NoveltySearch(self.parameters) elif self.parameters.exp_type == 'SIGN': self.evolver = NoveltySearch(self.parameters) elif self.parameters.exp_type == 'CMA-ES': self.evolver = CMAES(self.parameters) # Generate CMA-ES initial population del self.population self.population = Population( self.parameters, init_size=self.parameters.emitter_population) for agent in self.population: agent['genome'] = self.evolver.optimizer.ask() elif self.parameters.exp_type == 'CMA-NS': self.evolver = CMANS(self.parameters) self.reward_archive = self.evolver.rew_archive elif self.parameters.exp_type == 'NSGA-II': self.evolver = NSGAII(self.parameters) elif self.parameters.exp_type == 'SERENE': self.evolver = SERENE(self.parameters) elif self.parameters.exp_type == 'ME': self.evolver = MAPElites(self.parameters) elif self.parameters.exp_type == 'CMA-ME': self.evolver = CMAME(self.parameters) elif self.parameters.exp_type == 'RND': self.evolver = RandomSearch(self.parameters) else: print("Experiment type {} not implemented.".format( self.parameters.exp_type)) raise ValueError def init_process(self): """ This function is used to initialize the pool so each process has its own instance of the evaluator :return: """ global evaluator evaluator = Evaluator(self.parameters) def _feed_eval(self, agent): """ This function feeds the agent to the evaluator and returns the updated agent :param agent: :return: """ global evaluator if agent['evaluated'] == None: # Agents are evaluated only once agent = evaluator(agent, self.bd_extractor.__call__) return agent def evaluate_in_env(self, pop, pool=None): """ This function evaluates the population in the environment by passing it to the parallel evaluators. :return: """ if self.parameters.verbose: print('Evaluating {} in environment.'.format(pop.name)) if pool is not None: pop.pop = pool.map( self._feed_eval, pop.pop ) # As long as the ID is fine, the order of the element in the list does not matter else: for i in range(pop.size): if self.parameters.verbose: print(".", end='') # The end prevents the newline if pop[i][ 'evaluated'] is None: # Agents are evaluated only once pop[i] = self.evaluator(pop[i], self.bd_extractor) if self.parameters.verbose: print() def _main_search(self, budget_chunk): """ This function performs the main search e.g. NS/NSGA/CMA-ES :return: """ # Only log reward here if NS or NSGA. Emitter based log during the emitter evaluation if self.evolver.emitter_based: log_reward = False else: log_reward = True # Evaluate population in the environment only the first time if self.init_pop: self.evaluate_in_env(self.population, pool=main_pool) self.population['evaluated'] = list( range(self.evolver.evaluated_points, self.evolver.evaluated_points + self.population.size)) self.evolver.evaluated_points += self.population.size self.evolver.evaluation_budget -= self.population.size budget_chunk -= self.population.size self.init_pop = False if not self.evolver.emitter_based: for area in self.population['rew_area']: if area is not None: name = 'rew_area_{}'.format(area) if name not in Logger.data: Logger.data[name] = 0 Logger.data[name] += 1 while budget_chunk > 0 and self.evolver.evaluation_budget > 0: self.offsprings = self.evolver.generate_offspring( self.population, pool=None, generation=self.generation) # Generate offsprings # Evaluate offsprings in the env self.evaluate_in_env(self.offsprings, pool=main_pool) self.offsprings['evaluated'] = list( range(self.evolver.evaluated_points, self.evolver.evaluated_points + self.offsprings.size)) self.evolver.evaluated_points += self.offsprings.size self.evolver.evaluation_budget -= self.offsprings.size budget_chunk -= self.offsprings.size self.evolver.init_emitters(self.population, self.offsprings) # Evaluate performances of pop and off and update archive self.evolver.evaluate_performances( self.population, self.offsprings, pool=main_pool) # Calculate novelty/fitness/curiosity etc # Only update archive using NS stuff. No archive candidates from emitters self.evolver.update_archive(self.population, self.offsprings, generation=self.generation) # Save pop, archive and off if self.generation % 1 == 0: self.population.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) self.evolver.archive.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) self.offsprings.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) # Log reward only if not emitter based if not self.evolver.emitter_based: for area in self.offsprings['rew_area']: if area is not None: name = 'rew_area_{}'.format(area) if name not in Logger.data: Logger.data[name] = 0 Logger.data[name] += 1 # Last thing we do is to update the population self.generation += 1 self.evolver.update_population(self.population, self.offsprings, generation=self.generation) def _emitter_search(self, budget_chunk): """ This function performs the reward search through the emitters :return: """ if self.evolver.emitter_based and ( len(self.evolver.emitters) > 0 or len(self.evolver.emitter_candidate) > 0): self.evolver.emitter_step(self.evaluate_in_env, self.generation, ns_pop=self.population, ns_off=self.offsprings, budget_chunk=budget_chunk, pool=None) self.evolver.rew_archive.save(self.parameters.save_path, 'gen_{}'.format(self.generation)) # Update the performaces due to possible changes in the pop and archive given by the emitters self.evolver.evaluate_performances(self.population, self.offsprings, pool=None) # Update main archive with the archive candidates from the emitters self.evolver.elaborate_archive_candidates(self.generation) def chunk_step(self): """ This function performs all the calculations needed for one generation. Generates offsprings, evaluates them and the parents in the environment, calculates the performance metrics, updates archive and population and finally saves offsprings, population and archive. :return: time taken for running the generation """ global main_pool start_time = timer() print("\nRemaining budget: {}".format(self.evolver.evaluation_budget)) # ------------------- # Base part # ------------------- budget_chunk = self.parameters.chunk_size if self.evolver.evaluation_budget > 0: print("MAIN") self._main_search(budget_chunk) # ------------------- # Emitters part # ------------------- budget_chunk = self.parameters.chunk_size # Starts only if a reward has been found. if self.evolver.evaluation_budget > 0: print("EMITTERS: {}".format(len(self.evolver.emitters))) self._emitter_search(budget_chunk) # ------------------- return timer() - start_time, self.evolver.evaluated_points def load_generation(self, generation, path): """ This function loads the population, the offsprings and the archive at a given generation, so it can restart the search from there. :param generation: :param path: experiment path :return: """ self.generation = generation self.population.load( os.path.join(path, 'population_gen_{}.pkl'.format(self.generation))) self.offsprings.load( os.path.join(path, 'offsprings_gen_{}.pkl'.format(self.generation))) self.evolver.archive.load( os.path.join(path, 'archive_gen_{}.pkl'.format(self.generation))) def close(self): """ This function closes the pool and deletes everything. :return: """ if self.parameters.multiprocesses: global main_pool main_pool.close() main_pool.join() gc.collect()
from core.chromosome import Chromosome from core.population import Population from core.genetic_algorithm import GeneticAlgorithm import core.settings as settings if __name__ == "__main__": generation_number = 0 MAX_FITNESS = 1 population = Population(settings.POPULATION_SIZE) population.print_population(generation_number) while population[0].get_fitness( ) < MAX_FITNESS and generation_number < settings.MAX_GENERATION_NUMBER: generation_number += 1 population = GeneticAlgorithm.evolve(population) population.print_population(generation_number) print(population[0].genes) print("\n---------- all timetables ------------") for c in settings.RAW_DATA["classes"]: print("class : {}".format(c)) print(population[0].get_time_table(c)) print("---------------------------------------------------\n")
def load_population(namespace, population_id, circus): return Population.load_from(population_folder(namespace, population_id), circus)