def mate(self, genome1, genome2): """ Mate two genomes based on their fitness. Args: genome1: the first parent genome genome2: the second parent genome Returns: genome3: the resultant child genome """ # we want genome1 to be the most fit if genome2.fitness > genome1.fitness: tempGenome = genome1 genome1 = genome2 genome2 = tempGenome assert genome1.fitness >= genome2.fitness genome3 = Genome(INPUTS, OUTPUTS, self.name, None) possible_genes = set(genome1.genes) | set(genome2.genes) for innov in possible_genes: if random.random() < GENE_DOMINANCE: if innov in genome1.genes: genome3.genes[innov] = genome1.genes[innov] else: if innov in genome2.genes: genome3.genes[innov] = genome2.genes[innov] # creating the nodes genome3.generateNodes() genome3.mutate() return genome3
def make_child(self, mom, dad): child = Genome() for n in range(child.node_count): giver = (mom, dad)[random() > 0.5] child.node_net[n] = giver.node_net[n] child.mutate() return child
class Organism(object): """ Wrapper class that provides fitness metrics. """ def __init__(self, genome): self.genome = Genome(genome) self.policy = Network(self.genome) self.evals = list() def __cmp__(self, other): return cmp(self.fitness, other.fitness) def __str__(self): return '%.3f' % self.fitness def crossover(self, other): """ Return a new organism by recombining the parents. """ return Organism(self.genome.crossover(other.genome)) def mutate(self, frac=0.1, std=1.0, repl=0.25): """ Mutate the organism by mutating its genome. """ self.genome.mutate(frac, std, repl) self.policy = Network(self.genome) def copy(self): """ Return a deep copy of this organism. """ org = Organism(self.genome) org.evals = list(self.evals) return org @property def fitness(self): """ Average return. """ try: return sum(self.evals, 0.) / len(self.evals) except ZeroDivisionError: return 0.
class Organism(object): """ Wrapper class that provides fitness metrics. """ def __init__(self, genome): self.genome = Genome(genome) self.policy = Network(self.genome) self.evals = list() def __cmp__(self, other): return cmp(self.fitness, other.fitness) def __str__(self): return '%.3f' % self.fitness def crossover(self, other): """ Return a new organism by recombining the parents. """ return Organism(self.genome.crossover(other.genome)) def mutate(self, frac=0.1, std=1.0, repl=0.25): """ Mutate the organism by mutating its genome. """ self.genome.mutate(frac, std, repl) self.policy = Network(self.genome) def copy(self): """ Return a deep copy of this organism. """ org = Organism(self.genome) org.evals = list(self.evals) return org @property def fitness(self): """ Average return. """ try: return sum(self.evals, 0.) / len(self.evals) except ZeroDivisionError: return 0.
def create_population(self): """ Creates a genome population using self.pop_size """ self.generation = 0 for genome_num in range(self.pop_size): genome = Genome() genome.mutate() self.genomes.append(genome) txt = self.data_loc(self.generation, genome_num) savetxt(txt, genome.node_net, fmt="%f")
def get_new_population(self, adjusted_species_sizes, remaining_species, species_set, generation_tracker, backprop_mutation): """ Creates the dictionary of the new genomes for the next generation population :param: genetation_tracker: :param adjusted_species_sizes: :param remaining_species: :param species_set: :param new_population: :return: """ new_population = {} for species_size, species in zip(adjusted_species_sizes, remaining_species): assert (species_size > 0) # List of old species members old_species_members = list(species.members.values()) # Reset the members for the current species species.members = {} # Save the species in the species set object species_set.species[species.key] = species # Sort the members into the descending fitness old_species_members.sort(reverse=True, key=lambda x: x.fitness) # Double check that it is descending if len(old_species_members) > 1: assert (old_species_members[0].fitness >= old_species_members[1].fitness) # If we have specified a number of genomes to carry over, carry them over to the new population num_genomes_without_crossover = int( round(species_size * self.config.chance_for_mutation_without_crossover)) if num_genomes_without_crossover > 0: for member in old_species_members[: num_genomes_without_crossover]: # Check if we should carry over a member un-mutated or not if not self.config.keep_unmutated_top_percentage: child = copy.deepcopy(member) child.mutate( reproduction_instance=self, innovation_tracker=self.innovation_tracker, config=self.config, backprop_mutation=backprop_mutation) if not child.check_connection_enabled_amount( ) and not child.check_num_paths( only_add_enabled_connections=True): raise Exception( 'This child has no enabled connections') new_population[child.key] = child self.ancestors[child.key] = () # new_population[member.key] = member species_size -= 1 assert (species_size >= 0) else: # Else we just add the current member to the new population new_population[member.key] = member species_size -= 1 assert (species_size >= 0) # If there are no more genomes for the current species, then restart the loop for the next species if species_size <= 0: continue # Only use the survival threshold fraction to use as parents for the next generation. reproduction_cutoff = int( math.ceil( (1 - self.config.chance_for_mutation_without_crossover) * len(old_species_members))) # Need at least two parents no matter what the previous result reproduction_cutoff = max(reproduction_cutoff, 2) old_species_members = old_species_members[:reproduction_cutoff] # Randomly choose parents and choose whilst there can still be additional genomes for the given species while species_size > 0: species_size -= 1 # TODO: If you don't allow them to mate with themselves then it's a problem because if the species previous # TODO: size is 1, then how can you do with or without crossover? parent_1 = copy.deepcopy(random.choice(old_species_members)) parent_2 = copy.deepcopy(random.choice(old_species_members)) # Has to be a deep copy otherwise the connections which are crossed over are also modified if mutation # occurs on the child. parent_1 = copy.deepcopy(parent_1) parent_2 = copy.deepcopy(parent_2) self.genome_indexer += 1 genome_id = self.genome_indexer child = Genome(key=genome_id) # TODO: Save the parent_1 and parent_2 mutation history as well as what connections they had # Create the genome from the parents num_connections_enabled = child.crossover(genome_1=parent_1, genome_2=parent_2, config=self.config) # If there are no connections enabled we forget about this child and don't add it to the existing # population if num_connections_enabled: child.mutate(reproduction_instance=self, innovation_tracker=self.innovation_tracker, config=self.config, generation_tracker=generation_tracker, backprop_mutation=backprop_mutation) if not child.check_connection_enabled_amount( ) and not child.check_num_paths( only_add_enabled_connections=True): raise Exception( 'This child has no enabled connections') new_population[child.key] = child self.ancestors[child.key] = (parent_1.key, parent_2.key) else: # Else if the crossover resulted in an invalid genome. assert num_connections_enabled == 0 species_size += 1 self.genome_indexer -= 1 return new_population
indiv.run() population.sort(key=lambda x: x.score, reverse=True) generation = 1 print("TOTAL GENERATIONS: ", cfg.NUM_GENERATIONS) while (generation <= cfg.NUM_GENERATIONS): print("CURRENT GENERATION: ", generation) offspring = [] parent_pool = population[:cfg.POPULATION_SIZE // 2] while (len(offspring) < cfg.POPULATION_SIZE * 9 // 10): parent1, parent2 = random.sample(parent_pool, 2) child1 = Genome() child1.uniform_crossover(parent1, parent2) child1.mutate() child1.run() child2 = Genome() child2.scaled_crossover(parent1, parent2) child2.mutate() child2.run() offspring.append(child1) offspring.append(child2) population = population[:cfg.POPULATION_SIZE // 10] + offspring population.sort(key=lambda x: x.score, reverse=True) print("POPULATION_SIZE: ", len(population)) print("BEST GENOME FOR THIS GEN ") population[0].print_self() generation += 1
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
class Agent(): def __init__(self, tile = None, genome = None, trust_parameter = None, performAction = None): self.performAction = performAction if performAction == None: self.performAction = defaultPerformAction if trust_parameter == None: trust_parameter = TRUST_PARAMETER self.trust_parameter = trust_parameter if LEARN_TRUST: self.trust = Trust() self.genome = genome self.tile = None if genome == None: # then generate your own genome self.genome = Genome() # self.statistics = self.genome.statistics self.name = self.genome.name self.available_moves = GAMES_PER_ITER self.fitness = 0 self.ID = returnRandomID() self.parents = [] self.games_played = 0 if tile != None: tile.acceptAgent(self) self.initializeStatistics() def compute_optimality(self): # compares a gene map g to the optimal gene map g = self.genome.gene_map cor = 0 for k in g.keys(): if not len(k): cor += g[k] == COOP_SIGNAL else: cor += int(k[-1]) == g[k] return cor * 1./len(g) def initializeStatistics(self): # instantiates the data structure to hold the statistics # in lieu of the old (gene-centric) way of computing statistics, # now they are updated by gameMaster based on actions that were # actually executed, as well as by the decideAction() function. # Each statistic is intended to be displayed as a ratio, where # the first value is the number of times that situation happend # versus the number of times that situation COULD have happened stats = dict() stats['popular'] = [0, 0] # the frequency with which other agents agree to play with this agent stats['selective'] = [0, 0] # the frequency with which this agent agrees to play with other agents stats['cooperator'] = [0, 0] # the fraction of the time the agent cooperates. stats['defector'] = [0, 0] # the fraction of the time the agent defects stats['quitter'] = [0, 0] # the fraction of the time the agent quits stats['collaborator'] = [0, 0] # cooperated with a cooperator stats['sucker'] = [0, 0] # cooperated against a defector stats['traitor'] = [0, 0] # defected against a cooperator stats['prisoner'] = [0, 0] # defected against a defector stats['cruel'] = [0, 0] # defected following a cooperation stats['nice'] = [0, 0] # cooperated following a cooperation stats['forgiving'] = [0, 0] # cooperated following a defection stats['vengeful'] = [0, 0] # defected following a defection stats['timid'] = [0, 0] # quit following a cooperation stats['retreating'] = [0, 0] # quit following a defection stats['optimality'] = [self.compute_optimality(), 1] # the closeness of the agent's genome to an optimal (tit-for-tat) genome. self.stats = stats # stats_to_increment is a map of stats whose total must be # incremented given the opponent's previous action self.stats_to_increment = {COOP_SIGNAL:['cruel','nice','timid'], DEFECT_SIGNAL:['forgiving','vengeful','retreating']} # stat_update_map is a dict() where # stat_update_map[preceeding_opponent_move][your_signal] = the # stat to increment. stat_update_map = dict() coop_d = {COOP_SIGNAL:'nice',DEFECT_SIGNAL:'cruel',QUIT_SIGNAL:'timid'} defe_d = {COOP_SIGNAL:'forgiving',DEFECT_SIGNAL:'vengeful',QUIT_SIGNAL:'retreating'} stat_update_map[COOP_SIGNAL] = coop_d stat_update_map[DEFECT_SIGNAL] = defe_d self.stat_update_map = stat_update_map def decideAction(self, history): # decides on what action to take given the game's current history action_code = self.genome.getAction(history) signal = self.performAction(self, action_code) self.stats['cooperator'][1] += 1 self.stats['defector'][1] += 1 self.stats['quitter'][1] += 1 if signal == COOP_SIGNAL: self.stats['cooperator'][0] += 1 if signal == DEFECT_SIGNAL: self.stats['defector'][0] += 1 if signal == QUIT_SIGNAL: self.stats['quitter'][0] += 1 if len(history): opp_move = int(history[-1]) for cur_stat in self.stats_to_increment[opp_move]: self.stats[cur_stat][1] += 1 self.stats[self.stat_update_map[opp_move][signal]][0]+=1 return signal def reproduce(self): # returns a child of this agent. childGenome = self.genome.mutate() child = Agent(self.tile, childGenome, self.trust_parameter, self.performAction) #child.name = child.genome.name child.parents = [x for x in self.parents] child.parents.append((self.ID, self.fitness)) if LEARN_TRUST: child.trust = self.trust.reproduce() # no more telepathy! return child def cooperate(self): # returns the cooperate signal return COOP_SIGNAL def defect(self): # returns the defect signal return DEFECT_SIGNAL def quit(self): # returns the quit signal return QUIT_SIGNAL def move(self): # moves the agent. The agent randomly selects a tile from the # set of neighboring tiles (including the current tile) and then # asks the tile if it can move there. if the tile accepts, the # tile-side funciton ('acceptAgent') handles all the # modifications to the source tile, the target tile, and the # agent itself. if self.tile == None: return target = choice([self.tile] + self.tile.neighbors) target.acceptAgent(self) def trustFunction(self, agent): # this returns the probability of playing with the agent # specified by 'agent'. It is given by a modified logistic # function, where the agent has control over point where this # probability crosses 0.5. The scale parameter, however, # is hard-coded # # first, compute the number of different characters between # names. name_diffs = sum([x[0]!=x[1] for x in zip(self.name, agent.name)]) return 1./(1+exp((name_diffs - self.trust_parameter)*1./TRUST_SCALE_FACTOR)) def updateTrust(self, agent, delta_fitness): # updates the agent's trust in accordance with the trust object. self.trust.updateWeights(agent.name, delta_fitness) def decideToPlay(self, agent): # accepts an agent as input and decides whether or not to play # with them if ALWAYS_PLAY: return True if LEARN_TRUST: return self.trust.decideToPlay(agent.name) else: p = self.trustFunction(agent) return random() <= p def incTrustParameter(self): # increments the trust parameter self.trust_parameter += TRUST_INCREMENT_PARAMETER def decTrustParameter(self): # decrements the trust parameter self.trust_parameter -= TRUST_INCREMENT_PARAMETER def die(self): # 'kills' the agent self.tile.removeAgent(self)
def test_genome(self): genome = Genome() genome.mutate()