def genome_distance(self, genome0, genome1): ''' Computes genome distance between two genomes ''' node_distance = 0.0 # Determine node distance if genome0.nodes or genome1.nodes: # Number of disjoing nodes between genomes disjoint_nodes = 0 # Count number of disjoint node genes between genomes for genome_1_node_key in iterkeys(genome1.nodes): if genome_1_node_key not in genome0.nodes: disjoint_nodes += 1 # Determine genetic distance between individual node genes for genome_0_node_key, genome_0_node in iteritems(genome0.nodes): genome_1_node = genome1.nodes.get(genome_0_node_key) if genome_1_node is None: disjoint_nodes += 1 else: # Homologous genes compute their own distance value. node_distance += self.node_gene_distance( genome_0_node, genome_1_node) # Find most number of nodes in either genome max_nodes = max(len(genome0.nodes), len(genome1.nodes)) # Determine final node genetic distance node_distance = (node_distance + (self.compatibility_disjoint_coefficient * disjoint_nodes)) / max_nodes # Determine connection gene distance connection_distance = 0.0 if genome0.connections or genome1.connections: disjoint_connections = 0 for genome_1_conn_key in iterkeys(genome1.connections): if genome_1_conn_key not in genome0.connections: disjoint_connections += 1 for genome_0_conn_key, genome_0_conn in iteritems( genome0.connections): genome_1_conn = genome1.connections.get(genome_0_conn_key) if genome_1_conn is None: disjoint_connections += 1 else: # Homologous genes compute their own distance value. connection_distance += self.connection_gene_distance( genome_0_conn, genome_1_conn) max_conn = max(len(genome0.connections), len(genome1.connections)) connection_distance = (connection_distance + (self.compatibility_disjoint_coefficient * disjoint_connections)) / max_conn distance = node_distance + connection_distance return distance
def __init__(self, **values): self.start_date = values.pop('start_date', None) if self.start_date: self.start_date = convert_to_datetime(self.start_date) # Check field names and yank out all None valued fields for key, value in list(iteritems(values)): if key not in self.FIELD_NAMES: raise TypeError('Invalid field name: %s' % key) if value is None: del values[key] self.fields = [] assign_defaults = False for field_name in self.FIELD_NAMES: if field_name in values: exprs = values.pop(field_name) is_default = False assign_defaults = not values elif assign_defaults: exprs = DEFAULT_VALUES[field_name] is_default = True else: exprs = '*' is_default = True field_class = self.FIELDS_MAP[field_name] field = field_class(field_name, exprs, is_default) self.fields.append(field)
def update(self, species_set, generation): ''' Updates species fitness history, checks for stagnated species, and returns a list with stagnant species to remove. species_set -- set containing the species and their ids generation -- the current generation number ''' species_data = [] for sid, species in iteritems(species_set.species): if species.fitness_history: prev_fitness = max(species.fitness_history) else: prev_fitness = -sys.float_info.max species.fitness = self.species_fitness_func( species.get_fitnesses()) species.fitness_history.append(species.fitness) species.adjusted_fitness = None if prev_fitness is None or species.fitness > prev_fitness: species.last_improved = generation species_data.append((sid, species)) # Sort in ascending fitness order. species_data.sort(key=lambda x: x[1].fitness) result = [] species_fitnesses = [] num_non_stagnant_species = len(species_data) for idx, (sid, species) in enumerate(species_data): # Override stagnant state if marking this species as stagnant would # result in the total number of species dropping below the limit stagnant_time = generation - species.last_improved is_stagnant = False if num_non_stagnant_species > self.species_elitism: is_stagnant = stagnant_time >= self.max_stagnation if (len(species_data) - idx) <= self.species_elitism: is_stagnant = False if is_stagnant: num_non_stagnant_species -= 1 result.append((sid, species, is_stagnant)) species_fitnesses.append(species.fitness) return result
def mutate_delete_node(self, gen=None): ''' Mutation for deleting a node gene to the genome. gen -- optional argument for current generation mutation occurs ''' available_nodes = [ k for k in iterkeys(self.nodes) if k not in self.output_keys ] if not available_nodes: return # Choose random node to delete del_key = np.random.choice(available_nodes) # Iterate through all connections and find connections to node conn_to_delete = set() for k, v in iteritems(self.connections): if del_key in v.key: conn_to_delete.add(v.key) for i in conn_to_delete: del self.connections[i] # Delete node key del self.nodes[del_key] return del_key
def run(self, task, goal, generations=None): ''' Run evolution on a given task for a number of generations or until a goal is reached. task -- the task to be solved goal -- the goal to reach for the given task that defines a solution generations -- the max number of generations to run evolution for ''' self.current_gen = 0 reached_goal = False # Plot data best_fitnesses = [] max_complexity = [] min_complexity = [] avg_complexity = [] while self.current_gen < generations and not reached_goal: # Assess fitness of current population task(list(iteritems(self.population))) # Find best genome in current generation and update avg fitness curr_best = None curr_max_complex = None curr_min_complex = None avg_complexities = 0 for genome in itervalues(self.population): avg_complexities += genome.complexity() # Update generation's most fit if curr_best is None or genome.fitness > curr_best.fitness: curr_best = genome # Update generation's most complex if curr_max_complex is None or genome.complexity( ) > curr_max_complex.complexity(): curr_max_complex = genome # Update generation's least complex if curr_min_complex is None or genome.complexity( ) < curr_min_complex.complexity(): curr_min_complex = genome # Update global best genome if possible if self.best_genome is None or curr_best.fitness > self.best_genome.fitness: self.best_genome = curr_best # Update global most and least complex genomes if self.max_complex_genome is None or curr_max_complex.complexity( ) > self.max_complex_genome.complexity(): self.max_complex_genome = curr_max_complex if self.min_complex_genome is None or curr_min_complex.complexity( ) < self.min_complex_genome.complexity(): self.min_complex_genome = curr_min_complex self.max_dict[self.current_gen] = self.max_complex_genome # Reporters report_fitness(self) report_species(self.species, self.current_gen) report_output(self) best_fitnesses.append(self.best_genome.fitness) max_complexity.append(self.max_complex_genome.complexity()) min_complexity.append(self.min_complex_genome.complexity()) avg_complexity.append( (avg_complexities + 0.0) / len(self.population)) self.avg_complex = (avg_complexities + 0.0) / len(self.population) avg_complexities = 0 # Reached fitness goal, we can stop if self.best_genome.fitness >= goal: reached_goal = True # Create new unspeciated popuation based on current population's fitness self.population = self.reproduction.reproduce_with_species( self.species, self.size, self.current_gen) # Check for species extinction (species did not perform well) if not self.species.species: print("!!! Species went extinct !!!") self.population = self.reproduction.create_new_population( self.size) # Speciate new population self.species.speciate(self.population, self.current_gen) self.current_gen += 1 generations = range(self.current_gen) plot_fitness(generations, best_fitnesses) return self.best_genome
def speciate(self,population,generation): ''' Speciates a population. ''' # Compatibility threshold compatibility_threshold = self.threshold # Set of unspeciated members of the population unspeciated = set(iterkeys(population)) # Means of determining distances distances = GenomeDistanceCache() # New representatives and members of species new_representatives = {} new_members = {} # Traverse through set of species from last generation for sid, species in iteritems(self.species): # Candidates for current species representatives candidate_representatives = [] # Traverese genomes in the unspeciated and check their distance # from the current species representative for gid in unspeciated: genome = population[gid] genome_distance = distances(species.representative, genome) candidate_representatives.append((genome_distance, genome)) # The new representative for the current species is the # closest to the current representative _, new_rep = min(candidate_representatives, key=lambda x: x[0]) new_rid = new_rep.key new_representatives[sid] = new_rid new_members[sid] = [new_rid] unspeciated.remove(new_rid) # Partition the population in species based on genetic similarity while unspeciated: gid = unspeciated.pop() genome = population[gid] # Find the species with the most similar representative to the # current genome from the unspeciated set candidate_species = [] # Traverse species and their representatives for sid, rid in iteritems(new_representatives): representative = population[rid] # Determine current genome's distance from representative genome_distance = distances(representative, genome) # If it's below threshold, add it to list for adding to the species if genome_distance < compatibility_threshold: candidate_species.append((genome_distance, sid)) # Add current genome to the species its most genetically similar to if candidate_species: _, sid = min(candidate_species, key=lambda x: x[0]) new_members[sid].append(gid) else: # No species is similar enough so we create a mnew species with # the current genome as its representative sid = next(self.species_indexer) new_representatives[sid] = gid new_members[sid] = [gid] # Update species collection based on new speciation self.genome_to_species = {} for sid, rid in iteritems(new_representatives): # Add species if not existing in current species set s = self.species.get(sid) if s is None: s = Species(sid, generation) self.species[sid] = s # Collect and add members to current species members = new_members[sid] for gid in members: self.genome_to_species[gid] = sid # Update current species members and represenative member_dict = {gid:population[gid] for gid in members} s.update(population[rid], member_dict)
def reproduce_with_species(self, species_set, pop_size, generation): ''' Creates and speciates genomes. species_set -- set of current species pop_size -- population size generation -- current generation ''' all_fitnesses = [] remaining_species = [] # Traverse species and grab fitnesses from non-stagnated species for sid, species, species_is_stagnant in self.stagnation.update( species_set, generation): if species_is_stagnant: print("!!! Species {} Stagnated !!!".format(sid)) # self.reporters.species_stagnant(sid, species) pass else: # Add fitnesses of members of current species all_fitnesses.extend(member.fitness for member in itervalues(species.members)) remaining_species.append(species) # No species if not remaining_species: species_set.species = {} return {} # Find min/max fitness across entire population min_population_fitness = min(all_fitnesses) max_population_fitness = max(all_fitnesses) # Do not allow the fitness range to be zero, as we divide by it below. population_fitness_range = max( 1.0, max_population_fitness - min_population_fitness) # Compute adjusted fitness and record minimum species size for species in remaining_species: # Determine current species average fitness mean_species_fitness = mean( [member.fitness for member in itervalues(species.members)]) max_species_fitness = max( [member.fitness for member in itervalues(species.members)]) # Determine current species adjusted fitness and update it species_adjusted_fitness = ( mean_species_fitness - min_population_fitness) / population_fitness_range species.adjusted_fitness = species_adjusted_fitness species.max_fitness = max_species_fitness adjusted_fitnesses = [ species.adjusted_fitness for species in remaining_species ] avg_adjusted_fitness = mean(adjusted_fitnesses) # Compute the number of new members for each species in the new generation. previous_sizes = [ len(species.members) for species in remaining_species ] min_species_size = max(2, self.species_elitism) spawn_amounts = self.compute_species_sizes(adjusted_fitnesses, previous_sizes, pop_size, min_species_size) new_population = {} species_set.species = {} for spawn, species in zip(spawn_amounts, remaining_species): # If elitism is enabled, each species always at least gets to retain its elites. spawn = max(spawn, self.species_elitism) assert spawn > 0 # The species has at least one member for the next generation, so retain it. old_species_members = list(iteritems(species.members)) # Update species with blank slate species.members = {} # Update species in species set accordingly species_set.species[species.key] = species # Sort old species members in order of descending fitness. old_species_members.sort(reverse=True, key=lambda x: x[1].fitness) # Clone elites to new generation. if self.species_elitism > 0: for member_key, member in old_species_members[:self. species_elitism]: new_population[member_key] = member spawn -= 1 # If the species only has room for the elites, move onto next species if spawn <= 0: continue # Only allow fraction of species members to reproduce reproduction_cutoff = int( ceil(self.species_reproduction_threshold * len(old_species_members))) # Use at least two parents no matter what the threshold fraction result is. reproduction_cutoff = max(reproduction_cutoff, 2) old_species_members = old_species_members[:reproduction_cutoff] # Randomly choose parents and produce the number of offspring allotted to the species. # NOTE: Asexual reproduction for now while spawn > 0: spawn -= 1 parent1_key, parent1 = random.choice(old_species_members) # parent2_key, parent2 = random.choice(old_species_members) child_key = next(self.genome_indexer) child = Genome(child_key) # child.crossover(parent1, parent2) child.copy(parent1, generation) child.mutate(generation) new_population[child_key] = child return new_population