def evaluate_and_evolve( self, pop: Population, n: int = 1, parallel=True, save_interval: int = 1, ): """ Evaluate the population on the same set of games. :param pop: Population object :param n: Number of generations :param parallel: Parallel the code (disable parallelization for debugging purposes) :param save_interval: Indicates how often a population gets saved """ multi_env = get_multi_env(pop=pop, game_config=self.game_config) msg = f"Repetitive evaluating on games: {self.games} for {n} iterations" pop.log(msg, print_result=False) # Iterate and evaluate over the games saved = True for iteration in range(n): # Set and randomize the games multi_env.set_games(self.games, noise=True) # Prepare the generation's reporters for the generation pop.reporters.start_generation(gen=pop.generation, logger=pop.log) # Fetch the dictionary of genomes genomes = list(iteritems(pop.population)) if parallel: # Initialize the evaluation-pool pool = mp.Pool(mp.cpu_count() - self.unused_cpu) manager = mp.Manager() return_dict = manager.dict() for genome in genomes: pool.apply_async(func=multi_env.eval_genome, args=(genome, return_dict)) pool.close() # Close the pool pool.join( ) # Postpone continuation until everything is finished else: return_dict = dict() for genome in tqdm(genomes, desc="sequential training"): multi_env.eval_genome(genome, return_dict) # Calculate the fitness from the given return_dict fitness = calc_pop_fitness( fitness_cfg=pop.config.evaluation, game_cfg=self.game_config.game, game_obs=return_dict, gen=pop.generation, ) for i, genome in genomes: genome.fitness = fitness[i] # Gather and report statistics best = None for g in itervalues(pop.population): if best is None or g.fitness > best.fitness: best = g pop.reporters.post_evaluate(population=pop.population, species=pop.species, best_genome=best, logger=pop.log) # Update the population's best_genome genomes = sorted(pop.population.items(), key=lambda x: x[1].fitness, reverse=True) pop.best_fitness[pop.generation] = genomes[0][1].fitness pop.best_genome_hist[pop.generation] = genomes[0] pop.best_genome = best # Let population evolve pop.evolve() # End generation pop.reporters.end_generation(population=pop.population, name=str(pop), species_set=pop.species, logger=pop.log) # Save the population if (iteration + 1) % save_interval == 0: pop.save() saved = True else: saved = False # Make sure that last iterations saves if not saved: pop.save()
def reproduce(self, config, species, pop_size, generation): """ Handles creation of genomes, either from scratch or by sexual or asexual reproduction from parents. """ # TODO: I don't like this modification of the species and stagnation objects, # because it requires internal knowledge of the objects. # Filter out stagnated species, collect the set of non-stagnated # species members, and compute their average adjusted fitness. # The average adjusted fitness scheme (normalized to the interval # [0, 1]) allows the use of negative fitness values without # interfering with the shared fitness scheme. all_fitnesses = [] remaining_species = [] for stag_sid, stag_s, stagnant in self.stagnation.update( species, generation): if stagnant: self.reporters.species_stagnant(stag_sid, stag_s) else: all_fitnesses.extend(m.fitness for m in itervalues(stag_s.members)) remaining_species.append(stag_s) # The above comment was not quite what was happening - now getting fitnesses # only from members of non-stagnated species. # No species left. if not remaining_species: species.species = {} return {} # was [] # Find minimum/maximum fitness across the entire population, for use in # species adjusted fitness computation. min_fitness = min(all_fitnesses) max_fitness = max(all_fitnesses) # Do not allow the fitness range to be zero, as we divide by it below. # TODO: The ``1.0`` below is rather arbitrary, and should be configurable. fitness_range = max(1.0, max_fitness - min_fitness) for afs in remaining_species: # Compute adjusted fitness. msf = mean([m.fitness for m in itervalues(afs.members)]) af = (msf - min_fitness) / fitness_range afs.adjusted_fitness = af adjusted_fitnesses = [s.adjusted_fitness for s in remaining_species] avg_adjusted_fitness = mean(adjusted_fitnesses) # type: float self.reporters.info( "Average adjusted fitness: {:.3f}".format(avg_adjusted_fitness)) # Compute the number of new members for each species in the new generation. previous_sizes = [len(s.members) for s in remaining_species] min_species_size = self.reproduction_config.min_species_size # Isn't the effective min_species_size going to be max(min_species_size, # self.reproduction_config.elitism)? That would probably produce more accurate tracking # of population sizes and relative fitnesses... doing. TODO: document. min_species_size = max(min_species_size, self.reproduction_config.elitism) spawn_amounts = self.compute_spawn(adjusted_fitnesses, previous_sizes, pop_size, min_species_size) new_population = {} species.species = {} for spawn, s in zip(spawn_amounts, remaining_species): # If elitism is enabled, each species always at least gets to retain its elites. spawn = max(spawn, self.reproduction_config.elitism) assert spawn > 0 # The species has at least one member for the next generation, so retain it. old_members = list(iteritems(s.members)) s.members = {} species.species[s.key] = s # Sort members in order of descending fitness. old_members.sort(reverse=True, key=lambda x: x[1].fitness) # Transfer elites to new generation. if self.reproduction_config.elitism > 0: for i, m in old_members[:self.reproduction_config.elitism]: new_population[i] = m spawn -= 1 if spawn <= 0: continue # Only use the survival threshold fraction to use as parents for the next generation. repro_cutoff = int( math.ceil(self.reproduction_config.survival_threshold * len(old_members))) # Use at least two parents no matter what the threshold fraction result is. repro_cutoff = max(repro_cutoff, 2) old_members = old_members[:repro_cutoff] # Randomly choose parents and produce the number of offspring allotted to the species. while spawn > 0: spawn -= 1 parent1_id, parent1 = random.choice(old_members) parent2_id, parent2 = random.choice(old_members) # Note that if the parents are not distinct, crossover will produce a # genetically identical clone of the parent (but with a different ID). # gid = next(self.genome_indexer) # child = config.genome_type(gid) # child.configure_crossover(parent1, parent2, config.genome_config) # child.mutate(config.genome_config) # new_population[gid] = child # self.ancestors[gid] = (parent1_id, parent2_id) if parent1.fitness > parent2.fitness: parent1.mutate(config.genome_config) new_population[parent1_id] = parent1 self.ancestors[parent1_id] = (parent1_id, parent1_id) else: parent2.mutate(config.genome_config) new_population[parent2_id] = parent2 self.ancestors[parent2_id] = (parent2_id, parent2_id) ''' Check cppnon sets of both parents If they're the same, perfrom crossover of non-cppnon-nodes and connections Else, Find parent with highest fitness Transfer those nodes to child perfrom crossover of non-cppnon-nodes and connections ''' return new_population
def run(self, fitness_function, n=None): """ Runs NEAT's genetic algorithm for at most n generations. If n is None, run until solution is found or extinction occurs. The user-provided fitness_function must take only two arguments: 1. The population as a list of (genome id, genome) tuples. 2. The current configuration object. The return value of the fitness function is ignored, but it must assign a Python float to the `fitness` member of each genome. The fitness function is free to maintain external state, perform evaluations in parallel, etc. It is assumed that fitness_function does not modify the list of genomes, the genomes themselves (apart from updating the fitness member), or the configuration object. """ if self.config.no_fitness_termination and (n is None): raise RuntimeError("Cannot have no generational limit with no fitness termination") k = 0 while n is None or k < n: if not (self.state): print("Training: break") self.reached_limit = False break k += 1 self.reporters.start_generation(self.generation) # Evaluate all genomes using the user-provided function. fitness_function(list(iteritems(self.population)), self.config) # Gather and report statistics. best = None for g in itervalues(self.population): if best is None or g.fitness > best.fitness: best = g self.reporters.post_evaluate(self.config, self.population, self.species, best) # Track the best genome ever seen. if self.best_genome is None or best.fitness > self.best_genome.fitness: self.best_genome = best if not self.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.fitness_criterion(g.fitness for g in itervalues(self.population)) if fv >= self.config.fitness_threshold: self.reporters.found_solution(self.config, self.generation, best) self.reached_limit = False break # Create the next generation from the current generation. self.population = self.reproduction.reproduce(self.config, self.species, self.config.pop_size, self.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new(self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. self.species.speciate(self.config, self.population, self.generation) self.reporters.end_generation(self.config, self.population, self.species) self.generation += 1 if self.config.no_fitness_termination: self.reporters.found_solution(self.config, self.generation, self.best_genome) return self.best_genome
def run(): # Load the config file, which is assumed to live in # the same directory as this script. local_dir = os.path.dirname(__file__) config_path = os.path.join(local_dir, my_config) config = neat.Config(LanderGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path) pop = neat.Population(config) stats = neat.StatisticsReporter() pop.add_reporter(stats) pop2 = neat.Population(config) stats2 = neat.StatisticsReporter() pop.add_reporter(stats) pop2.add_reporter(stats2) pop.add_reporter(neat.StdOutReporter(True)) # Checkpoint every 25 generations or 900 seconds. rep = neat.Checkpointer(25, 900) pop.add_reporter(rep) # Training set index avg_score_v = -10000000.0 avg_score_v_ant = avg_score_v avg_score = avg_score_v # asigna un gen_best para poder cargar los demás desde syn for g in itervalues(pop.population): gen_best = g g.fitness = -10000000.0 # Run until the winner from a generation is able to solve the environment # or the user interrupts the process. ec = PooledErrorCompute() temp = 0 best_fitness = -2000.0 pop_size = len(pop.population) # sets the nuber of continuous iterations num_iterations = round(200 / len(pop.population)) + 1 while 1: try: if temp > 0: # Calcula training y validation fitness best_genomes = stats.best_unique_genomes(3) solved = True best_scores = [] observation = env_t.reset() score = 0.0 step = 0 gen_best_nn = neat.nn.FeedForwardNetwork.create( gen_best, config) while 1: step += 1 output = gen_best_nn.activate(nn_format(observation)) action = (np.argmax(output[0:2]), output[3], output[4], output[5]) # buy,sell or observation, reward, done, info = env_t.step(action) score += reward env_t.render() if done: break ec.episode_score.append(score) ec.episode_length.append(step) best_scores.append(score) avg_score = sum(best_scores) / len(best_scores) print("Training Set Score =", score, " avg_score=", avg_score) # Calculate the real-validation set score best_genomes = stats.best_unique_genomes(3) solved = True best_scores = [] observation = env_v.reset() score = 0.0 step = 0 gen_best_nn = neat.nn.FeedForwardNetwork.create( gen_best, config) while 1: step += 1 output = gen_best_nn.activate(nn_format(observation)) action = (np.argmax(output[0:2]), output[3], output[4], output[5]) # buy,sell or observation, reward, done, info = env_v.step(action) score += reward #env_v.render() if done: break best_scores.append(score) avg_score_v = sum(best_scores) / len(best_scores) print("Validation Set Score = ", avg_score_v) print( "*********************************************************" ) # Calcula el best_fitness (PARA SYNC)como el promedio del score de training y el promedio del fitness de los reps. reps_local = [] reps = [gen_best] accum = 0.0 countr = 0 for sid, s in iteritems(pop.species.species): if pop.population[ s.representative.key].fitness is not None: accum = accum + pop.population[ s.representative.key].fitness countr = countr + 1 if countr > 0: best_fitness = (3 * avg_score + (accum / countr)) / 4 else: best_fitness = (avg_score) #FIN de calculo de real validation if temp >= 0: # TODO: FUNCION DE SINCRONIZACION CON SINGULARITY # Lee en pop2 el último checkpoint desde syn # Hace request de getLastParam(process_hash,use_current) a syn TODO: HACER PROCESS CONFIGURABLE Y POR HASH no por id res = requests.get( my_url + "/processes/1?username=harveybc&pass_hash=$2a$04$ntNHmofQoMoajG89mTEM2uSR66jKXBgRQJnCgqfNN38aq9UkN4Y6q&process_hash=ph" ) cont = res.json() print('\ncurrent_block_performance =', cont['result'][0]['current_block_performance']) print('\nlast_optimum_id =', cont['result'][0]['last_optimum_id']) last_optimum_id = cont['result'][0]['last_optimum_id'] # Si el perf reportado pop2_champion_fitness > pop1_champion_fitness de validation training print("\nPerformance = ", best_fitness) print( "*********************************************************" ) if cont['result'][0][ 'current_block_performance'] > best_fitness: # hace request GetParameter(id) res_p = requests.get( my_url + "/parameters/" + str(last_optimum_id) + "?username=harveybc&pass_hash=$2a$04$ntNHmofQoMoajG89mTEM2uSR66jKXBgRQJnCgqfNN38aq9UkN4Y6q&process_hash=ph" ) cont_param = res_p.json() # descarga el checkpoint del link de la respuesta si cont.parameter_link print('Parameter Downloaded') print('\nmigrations =') if cont_param['result'][0]['parameter_link'] is not None: genom_data = requests.get( cont_param['result'][0]['parameter_link']).content with open('remote_reps', 'wb') as handler: handler.write(genom_data) handler.close() # carga genom descargado en nueva población pop2 with open('remote_reps', 'rb') as f: remote_reps = pickle.load(f) # OP.MIGRATION: Reemplaza el peor de la especie pop1 más cercana por el nuevo chmpion de pop2 como http://neo.lcc.uma.es/Articles/WRH98.pdf # para cada elemento de remote_reps, busca el closer, si remote fitness > local, lo reemplaza for i in range(len(remote_reps)): closer = None min_dist = None # initialize for less fit search less_fit = None less_fitness = 10000 for g in itervalues(pop.population): if g not in remote_reps: dist = g.distance(remote_reps[i], config.genome_config) if dist is None: dist = 100000000 else: dist = 100000000 # do not count already migrated remote_reps if closer is None or min_dist is None: closer = deepcopy(g) min_dist = dist if dist < min_dist: closer = deepcopy(g) min_dist = dist if g.fitness is None: g.fitness = -10 if g.fitness < less_fitness: less_fitness = g.fitness less_fit = deepcopy(g) # For the best genom in position 0 if i == 0: tmp_genom = deepcopy(remote_reps[i]) # Hack: overwrites original genome key with the replacing one tmp_genom.key = closer.key pop.population[closer.key] = deepcopy( tmp_genom) print(" gen_best=", closer.key) pop.best_genome = deepcopy(tmp_genom) gen_best = deepcopy(tmp_genom) else: # si el remote fitness>local, reemplazar el remote de pop2 en pop1 if closer is None: # busca el pop con el menor fitness closer = less_fit if closer is not None: if closer not in remote_reps: if closer.fitness is None: closer.fitness = less_fitness if closer.fitness is not None and remote_reps[ i].fitness is not None: if remote_reps[ i].fitness > closer.fitness: tmp_genom = deepcopy( remote_reps[i]) # Hack: overwrites original genome key with the replacing one tmp_genom.key = closer.key pop.population[ closer.key] = deepcopy( tmp_genom) print("Replaced=", closer.key) # actualiza gen_best y best_genome al remoto pop.best_genome = deepcopy( tmp_genom) gen_best = deepcopy(tmp_genom) if closer.fitness is None: tmp_genom = deepcopy( remote_reps[i]) # Hack: overwrites original genome key with the replacing one tmp_genom.key = len( pop.population) + 1 pop.population[ tmp_genom.key] = tmp_genom print( "Created Por closer.fitness=NONE : ", tmp_genom.key) # actualiza gen_best y best_genome al remoto pop.best_genome = deepcopy( tmp_genom) gen_best = deepcopy(tmp_genom) else: #si closer está en remote_reps es porque no hay ningun otro cercano así que lo adiciona tmp_genom = deepcopy(remote_reps[i]) # Hack: overwrites original genome key with the replacing one tmp_genom.key = len(pop.population) + 1 pop.population[ tmp_genom.key] = tmp_genom print( "Created por Closer in rempte_reps=", tmp_genom.key) # actualiza gen_best y best_genome al remoto pop.best_genome = deepcopy(tmp_genom) gen_best = deepcopy(tmp_genom) #ejecuta speciate pop.species.speciate(config, pop.population, pop.generation) print("\nSpeciation after migration done") # Si el perf reportado es menor pero no igual al de pop1 if cont['result'][0][ 'current_block_performance'] < best_fitness: # Obtiene remote_reps # hace request GetParameter(id) remote_reps = None res_p = requests.get( my_url + "/parameters/" + str(last_optimum_id) + "?username=harveybc&pass_hash=$2a$04$ntNHmofQoMoajG89mTEM2uSR66jKXBgRQJnCgqfNN38aq9UkN4Y6q&process_hash=ph" ) cont_param = res_p.json() # descarga el checkpoint del link de la respuesta si cont.parameter_link print('\nNEW OPTIMUM - cont_param =', cont_param) #print('\nmigrations =') if cont_param['result'][0]['parameter_link'] is not None: genom_data = requests.get( cont_param['result'][0]['parameter_link']).content with open('remote_reps', 'wb') as handler: handler.write(genom_data) handler.close() # carga genom descargado en nueva población pop2 with open('remote_reps', 'rb') as f: remote_reps = pickle.load(f) #Guarda los mejores reps reps_local = [] reps = [gen_best] # Para cada especie, adiciona su representative a reps for sid, s in iteritems(pop.species.species): #print("\ns=",s) if s.representative not in reps_local: reps_local.append( pop.population[s.representative.key]) reps_local[len(reps_local) - 1] = deepcopy( pop.population[s.representative.key]) # TODO: Conservar los mejores reps, solo reemplazarlos por los mas cercanos if remote_reps is None: for l in reps_local: reps.append(l) reps[len(reps) - 1] = deepcopy(l) else: # para cada reps_local l for l in reps_local: # busca el closer a l en reps_remote for i in range(len(remote_reps)): closer = None min_dist = None for g in reps_local: if g not in remote_reps: dist = g.distance( remote_reps[i], config.genome_config) else: dist = 100000000 # do not count already migrated remote_reps if closer is None or min_dist is None: closer = deepcopy(g) min_dist = dist if dist < min_dist: closer = deepcopy(g) min_dist = dist # si closer is in reps if closer in reps: # adiciona l a reps si ya no estaba en reps if l not in reps: reps.append(l) reps[len(reps) - 1] = deepcopy(l) # sino else: # si l tiene más fitness que closer, if closer.fitness is not None and l.fitness is not None: if l.fitness > pop.population[ closer.key].fitness: # adiciona l a reps si ya no estaba en reps if l not in reps: reps.append(l) reps[len(reps) - 1] = deepcopy(l) # sino else: # adiciona closer a reps si ya no estaba en reps if l not in reps: reps.append( pop.population[closer.key]) reps[len(reps) - 1] = deepcopy( pop.population[closer.key]) # Guarda checkpoint de los representatives de cada especie y lo copia a ubicación para servir vía syn. # rep.save_checkpoint(config,pop,neat.DefaultSpeciesSet,rep.current_generation) print("\nreps=", reps) filename = '{0}{1}'.format("reps-", rep.current_generation) with open(filename, 'wb') as f: pickle.dump(reps, f) # # Hace request de CreateParam a syn form_data = { "process_hash": "ph", "app_hash": "ah", "parameter_link": my_url + "/genoms/" + filename, "parameter_text": pop.best_genome.key, "parameter_blob": "", "validation_hash": "", "hash": "h", "performance": best_fitness, "redir": "1", "username": "******", "pass_hash": "$2a$04$ntNHmofQoMoajG89mTEM2uSR66jKXBgRQJnCgqfNN38aq9UkN4Y6q" } # TODO: COLOCAR DIRECCION CONFIGURABLE res = requests.post( my_url + "/parameters?username=harveybc&pass_hash=$2a$04$ntNHmofQoMoajG89mTEM2uSR66jKXBgRQJnCgqfNN38aq9UkN4Y6q&process_hash=ph", data=form_data) res_json = res.json() # TODO FIN: FUNCION DE SINCRONIZACION CON SINGULARITY temp = temp + 1 # EVALUATE THE GENOMES WITH THE SUBSET TRAINING DATASET gen_best = pop.run(ec.evaluate_genomes, num_iterations) # TODO: # VERIFY IF THERE ARE PENDING EVALUATIONS # EVALUATE NUM_EVALUATIONS PENDING EVALUATIONS #print(gen_best) #visualize.plot_stats(stats, ylog=False, view=False, filename="fitness.svg") #plt.plot(ec.episode_score, 'g-', label='score') #plt.plot(ec.episode_length, 'b-', label='length') #plt.grid() #plt.legend(loc='best') #plt.savefig("scores.svg") #plt.close() #mfs = sum(stats.get_fitness_mean()[-5:]) / 5.0 #print("Average mean fitness over last 5 generations: {0}".format(mfs)) #mfs = sum(stats.get_fitness_stat(min)[-5:]) / 5.0 #print("Average min fitness over last 3 generations: {0}".format(mfs)) if avg_score < 2000000000: solved = False if solved: print("Solved.") # Save the winners. for n, g in enumerate(best_genomes): name = 'winner-{0}'.format(n) with open(name + '.pickle', 'wb') as f: pickle.dump(g, f) visualize.draw_net(config, g, view=False, filename=name + "-net.gv") visualize.draw_net(config, g, view=False, filename=name + "-net-enabled.gv", show_disabled=False) visualize.draw_net(config, g, view=False, filename=name + "-net-enabled-pruned.gv", show_disabled=False, prune_unused=True) break except KeyboardInterrupt: print("User break.") break env.close()
def __init__(self, name, **default_dict): self.name = name for n, default in iteritems(default_dict): self._config_items[n] = [self._config_items[n][0], default] for n in iterkeys(self._config_items): setattr(self, n + "_name", self.config_item_name(n))
def process(): global program_state, p global config global obj_topo, road_topo global CAR_ROW_START, CAR_COL_START, CAR_SHAPE_COL, sand_stall global frame, new_distance, distance_stall, old_distance, base_distance global old_time, new_time, base_time global fitness_val, genome_fitness, generation_fitness, generation_fitness_index, generation_average_fitness global genome_rank, generation_rank, generation_rank_index global generation_count, genome_count, genome_list, genome, current_net, best_genome global key_press global winner print(program_state) # Stop the data processing if the agent is currently training if program_state == STATE_BUFFERING: time.sleep(0.1) pass elif program_state == STATE_BEGIN: program_state = STATE_BUFFERING # Report the current generation p.reporters.start_generation(p.generation) genome_list = list(iteritems(p.population)) program_state = STATE_PRESSING_F1 time.sleep(0.1) pass elif program_state == STATE_PRESSING_F1: time.sleep(0.1) pass elif program_state == STATE_RESETTED: program_state = STATE_BUFFERING # Grab the genome if there is still genome to check if genome_count < NUM_GENOMES: _, genome = genome_list[genome_count] current_net = neat.nn.FeedForwardNetwork.create(genome, config) program_state = STATE_EVALUATING else: genome_count = 0 program_state = STATE_POST_PROCESSING pass # Sleep for the stability of the algorithm time.sleep(0.1) elif program_state == STATE_EVALUATING: old_distance = new_distance old_time = new_time # Get the data from the request start = time.time() data = request.body.read().decode('utf8') data = json.loads(data) # pixels = data["Pixels"] speed = data["Speed"] new_time = data["Time"] if new_time > BASE_TIME: new_time = 0 new_distance = data["Distance"] if new_distance == BASE_DISTANCE: new_distance = 0 rank = data["Rank"] nitro = data["Nitro"] # Determine a set of background color, we will try to remove this color from the game. road_topo = topo_from_original_array(pixels)[: TOPO_SHAPE[0] // 2, :] end = time.time() # Calculate decision from current net input_arr = road_topo.flatten() key_press = generate_key_presses(current_net, input_arr) + "0" # Don't press F1 to reset # Update frame counter and distance_stall and time if new_distance < old_distance: base_distance += BASE_DISTANCE if new_distance == old_distance: distance_stall += 1 else: distance_stall = 0 frame += 1 if new_time < old_time: base_time += BASE_TIME if not road_topo[CAR_ROW_START + SHAPE_ROW, CAR_COL_START: CAR_COL_START + SHAPE_COL].any(): sand_stall += 1 else: sand_stall = 0 fitness_val = (base_distance + new_distance) * DIST_C \ - (base_time + new_time) * TIME_C \ - rank * RANK_C \ + BASE_FITNESS # Print necessary information os.system('cls') print("Current generation: " + str(generation_count) + "/" + str(NUM_GENERATIONS)) print("----") print_topo(road_topo) print(end - start) print_key_presses(key_press, BUTTONS) print("Speed: " + str(speed)) print("Time: " + str(base_time + new_time)) print("Distance: " + str(base_distance + new_distance)) print("Rank: " + str(rank)) print("Nitro: " + str(nitro)) print("Current genome: " + str(genome_count + 1) + "/" + str(NUM_GENOMES)) print("Fitness value: " + str(fitness_val)) print('--------------') print('| Gen | Best Agent | Score | Rank | Avg Score |') print('|----------------------------------------------------|') for i in range(len(generation_fitness)): print("| {:3d} | {:10d} | {:10d} | {:4d} | {:11.3f} |".format( \ i, generation_fitness_index[i], generation_fitness[i], generation_rank[i], generation_average_fitness[i])) # Stop the running if stall limit is reached if distance_stall > STALL_LIMIT: genome.fitness = fitness_val print("- Evaluated Genome " + str(genome_count) + ": " + str(fitness_val)) # Add a bunch of statistical variables genome_fitness.append(fitness_val) genome_rank.append(rank) # Reset reset_variables() genome_count += 1 program_state = STATE_PRESSING_F1 # Return something cuz a post go with a get pass elif program_state == STATE_POST_PROCESSING: program_state = STATE_BUFFERING # Gather and report statistics. best = None for g in itervalues(p.population): if best is None or g.fitness > best.fitness: best = g p.reporters.post_evaluate(p.config, p.population, p.species, best) # Track the best gen ome ever seen. if p.best_genome is None or best.fitness > p.best_genome.fitness: p.best_genome = best # End if the fitness threshold is reached. fv = p.fitness_criterion(g.fitness for g in itervalues(p.population)) if fv >= p.config.fitness_threshold: p.reporters.found_solution(p.config, p.generation, best) program_state = STATE_RETURN_BEST # Create the next generation from the current generation. p.population = p.reproduction.reproduce(p.config, p.species, p.config.pop_size, p.generation) # Check for complete extinction. if not p.species.species: p.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if p.config.reset_on_extinction: p.population = p.reproduction.create_new(p.config.genome_type, p.config.genome_config, p.config.pop_size) else: raise neat.CompleteExtinctionException() # Divide the new population into species. p.species.speciate(p.config, p.population, p.generation) p.reporters.end_generation(p.config, p.population, p.species) p.generation += 1 # Add the best genome value into the statistics generation_fitness.append(max(genome_fitness)) generation_average_fitness.append(np.average(genome_fitness)) generation_fitness_index.append(np.argmax(genome_fitness)) genome_fitness = [] generation_rank.append(min(genome_rank)) generation_rank_index.append(np.argmin(genome_rank)) genome_rank = [] # Increment generation count and switch state np.savetxt("best_genome_by_generation.npy",generation_fitness_index) generation_count += 1 if generation_count == NUM_GENERATIONS: program_state = STATE_RETURN_BEST else: program_state = STATE_BEGIN pass elif program_state == STATE_RETURN_BEST: best_genome = p.best_genome program_state = STATE_TEST_BEST pass elif program_state == STATE_TEST_BEST: print("Should start testing stuff now") pass return("Finished")
def reproduce(self, config, species, pop_size, generation): """ Handles creation of genomes, either from scratch or by sexual or asexual reproduction from parents. """ # TODO: I don't like this modification of the species and stagnation objects, # because it requires internal knowledge of the objects. # Filter out stagnated species, collect the set of non-stagnated # species members, and compute their average adjusted fitness. # The average adjusted fitness scheme (normalized to the interval # [0, 1]) allows the use of negative fitness values without # interfering with the shared fitness scheme. all_fitnesses = [] remaining_species = [] old_population = [] for stag_sid, stag_s, stagnant in self.stagnation.update( species, generation): old_population.append(itervalues(stag_s.members)) if stagnant: self.reporters.species_stagnant(stag_sid, stag_s) else: all_fitnesses.extend(m.fitness for m in itervalues(stag_s.members)) remaining_species.append(stag_s) # The above comment was not quite what was happening - now getting fitnesses # only from members of non-stagnated species. # No species left. if not remaining_species: species.species = {} return {} # was [] # Find minimum/maximum fitness across the entire population, for use in # species adjusted fitness computation. min_fitness = min(all_fitnesses) max_fitness = max(all_fitnesses) # Do not allow the fitness range to be zero, as we divide by it below. # TODO: The ``1.0`` below is rather arbitrary, and should be configurable. fitness_range = max(1.0, max_fitness - min_fitness) for afs in remaining_species: # Compute adjusted fitness. msf = mean([m.fitness for m in itervalues(afs.members)]) af = (msf - min_fitness) / fitness_range afs.adjusted_fitness = af adjusted_fitnesses = [s.adjusted_fitness for s in remaining_species] avg_adjusted_fitness = mean(adjusted_fitnesses) # type: float self.reporters.info( "Average adjusted fitness: {:.3f}".format(avg_adjusted_fitness)) # Compute the number of new members for each species in the new generation. previous_sizes = [len(s.members) for s in remaining_species] min_species_size = self.reproduction_config.min_species_size # Isn't the effective min_species_size going to be max(min_species_size, # self.reproduction_config.elitism)? That would probably produce more accurate tracking # of population sizes and relative fitnesses... doing. TODO: document. min_species_size = max(min_species_size, self.reproduction_config.elitism) spawn_amounts = self.compute_spawn(adjusted_fitnesses, previous_sizes, pop_size, min_species_size) # Bandit Update # TODO Reconsider reward scheme to be invariant of actual fitness value # Percentage improvement to fitness? # Fitness improvement as a fraction of best improvement? - seems good, then one mutation is always 1, rest are < 1 # How to reward or perceive "failure"? Same as fraction but for decreases? # 1 if best arm in generation else 0? if generation > 0: mutation_delta = [] for mutant, old_fitness, mutations in self.records[-1]: fit_delta = mutant.fitness - old_fitness for m in mutations: mutation_delta.append((m, fit_delta)) # print(mutation_delta) self.bandit.update_all(mutation_delta) # if m not in mutations_deltas: # mutations_deltas[m] = [fit_delta] # else: # mutations_deltas[m].append(fit_delta) # for mutations, deltas in mutations_deltas.items(): # self.bandit.update(mutations, deltas) # len deltas can be removed, the number of rewards is the number of plays # print([(g.key, g.fitness-f, m) for g,f,m in self.records[-1]]) self.reporters.post_reproduction(config, self.bandit, None) self.records.append([]) new_population = {} species.species = {} for spawn, s in zip(spawn_amounts, remaining_species): # If elitism is enabled, each species always at least gets to retain its elites. spawn = max(spawn, self.reproduction_config.elitism) assert spawn > 0 # The species has at least one member for the next generation, so retain it. old_members = list(iteritems(s.members)) s.members = {} species.species[s.key] = s # Sort members in order of descending fitness. old_members.sort(reverse=True, key=lambda x: x[1].fitness) # Transfer elites to new generation. if self.reproduction_config.elitism > 0: for i, m in old_members[:self.reproduction_config.elitism]: new_population[i] = m spawn -= 1 if spawn <= 0: continue # Only use the survival threshold fraction to use as parents for the next generation. repro_cutoff = int( math.ceil(self.reproduction_config.survival_threshold * len(old_members))) # Use at least two parents no matter what the threshold fraction result is. repro_cutoff = max(repro_cutoff, 2) old_members = old_members[:repro_cutoff] # Randomly choose parents and produce the number of offspring allotted to the species. while spawn > 0: spawn -= 1 parent1_id, parent1 = choice(old_members) parent2_id, parent2 = (parent1_id, parent1) gid = next(self.genome_indexer) child = config.genome_type(gid) child.configure_crossover(parent1, parent2, config.genome_config) mutation_directives = self.bandit.play(generation) child.mutate(config.genome_config, mutation_directives) new_population[gid] = child self.ancestors[gid] = (parent1_id, parent2_id) self.records[-1].append( (child, parent1.fitness, mutation_directives)) return new_population
def get_population(self): return list(iteritems(self.population))
def speciate(self, config: Config, population, generation, logger=None): """ Place genomes into species by genetic similarity. :note: This method assumes the current representatives of the species are from the old generation, and that after speciation has been performed, the old representatives should be dropped and replaced with representatives from the new generation. """ assert isinstance(population, dict) unspeciated = set( iterkeys(population)) # Initially the full population distances = GenomeDistanceCache( config.genome) # Cache memorizing distances between genomes # distances.warm_up(population) # No warm-up since only comparison with representatives! (expensive!) new_representatives = dict( ) # Updated representatives for each specie (least difference with previous) members = dict( ) # Dictionary containing for each specie its corresponding members # Update the representatives for each specie as the genome closest to the previous representative for sid, s in iteritems(self.species): candidates = [] for gid in unspeciated: genome = population[gid] d = distances(s.representative, genome) candidates.append((d, genome)) # Sort the genomes based on their distance towards the specie and take the closest genome sorted_repr = sorted(candidates, key=lambda x: x[0]) new_repr = sorted_repr[0][1] new_representatives[sid] = new_repr.key members[sid] = [new_repr.key] unspeciated.remove(new_repr.key) # Partition the population into species based on their genetic similarity while unspeciated: # Pull unspeciated genome gid = unspeciated.pop() genome = population[gid] # Find the species with the most similar representative specie_distance = [] candidates = [] for sid, rid in iteritems(new_representatives): rep = population[rid] d = distances(rep, genome) specie_distance.append((d, sid)) if d < config.population.get_compatibility_threshold( n_species=len(new_representatives)): candidates.append((d, sid)) # There are species close enough; add genome to most similar specie if candidates: _, sid = min(candidates, key=lambda x: x[0]) members[sid].append(gid) else: # No specie is similar enough, create new specie with this genome as its representative sid = next(self.indexer) new_representatives[sid] = gid members[sid] = [gid] # Update the species collection based on the newly defined speciation self.genome_to_species = dict() for sid, rid in iteritems(new_representatives): s = self.species.get(sid) # One of the newly defined species if s is None: s = Species(sid, generation) self.species[sid] = s # Append the newly added members to the genome_to_species mapping specie_members = members[sid] for gid in specie_members: self.genome_to_species[gid] = sid # Update the specie's current representative and members member_dict = dict((genome_id, population[genome_id]) for genome_id in specie_members) s.update(representative=population[rid], members=member_dict) # Report over the current species (distances) self.reporters.info( f'Genetic distance:' f'\n\t- Maximum: {max(itervalues(distances.distances)):.5f}' f'\n\t- Mean: {mean(itervalues(distances.distances)):.5f}' f'\n\t- Minimum: {min(itervalues(distances.distances)):.5f}' f'\n\t- Standard deviation: {stdev(itervalues(distances.distances)):.5f}', logger=logger, print_result=False, ) print( f'Maximum genetic distance: {round(max(itervalues(distances.distances)), 3)}' ) # Print reduced version # Report over the most complex genome most_complex = sorted([(g.size(), gid) for gid, g in population.items()], key=lambda x: x[0], reverse=True)[0] gid = most_complex[1] size = most_complex[0] specie_id = self.genome_to_species[gid] distance = distances(population[gid], self.species[specie_id].representative) self.reporters.info( f"Most complex genome '{gid}' " f"of size {size} " f"belongs to specie '{specie_id}' " f"with distance to representative of {round(distance, 3)}", logger=logger, )
def run(self, fitness_function, self2, n=None): if self.config.no_fitness_termination and (n is None): raise RuntimeError( "Cannot have no generational limit with no fitness termination" ) if self2.config.no_fitness_termination and (n is None): raise RuntimeError( "Cannot have no generational limit with no fitness termination" ) k = 0 while n is None or k < n: k += 1 self.reporters.start_generation(self.generation) self2.reporters.start_generation(self2.generation) # Evaluate all genomes using the user-provided function. fitness_function(list(iteritems(self.population)), list(iteritems(self2.population)), self.config) # Gather and report statistics. best1 = None for g in itervalues(self.population): if best1 is None or g.fitness > best1.fitness: best1 = g self.reporters.post_evaluate(self.config, self.population, self.species, best1) best2 = None for g in itervalues(self2.population): if best2 is None or g.fitness > best2.fitness: best2 = g self2.reporters.post_evaluate(self2.config, self2.population, self2.species, best2) # Track the best genome ever seen. if self.best_genome is None or best1.fitness > self.best_genome.fitness: self.best_genome = best1 if not self.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.fitness_criterion( g.fitness for g in itervalues(self.population)) if fv >= self.config.fitness_threshold: self.reporters.found_solution(self.config, self.generation, best1) break #Do the same with self2 <--- balances out config version if self2.best_genome is None or best2.fitness > self2.best_genome.fitness: self2.best_genome = best2 if not self2.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self2.fitness_criterion( g.fitness for g in itervalues(self2.population)) if fv >= self2.config.fitness_threshold: self2.reporters.found_solution(self2.config, self2.generation, best2) break # Create the next generation from the current generation. self.population = self.reproduction.reproduce( self.config, self.species, self.config.pop_size, self.generation) self2.population = self2.reproduction.reproduce( self2.config, self2.species, self2.config.pop_size, self2.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new( self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() if not self2.species.species: self2.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self2.config.reset_on_extinction: self2.population = self2.reproduction.create_new( self2.config.genome_type, self2.config.genome_config, self2.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. self.species.speciate(self.config, self.population, self.generation) self2.species.speciate(self2.config, self2.population, self2.generation) self.reporters.end_generation(self.config, self.population, self.species) self2.reporters.end_generation(self2.config, self2.population, self2.species) self.generation += 1 self2.generation += 1 if self.config.no_fitness_termination: self.reporters.found_solution(self.config, self.generation, self.best_genome) if self2.config.no_fitness_termination: self2.reporters.found_solution(self2.config, self2.generation, self2.best_genome) return (self.best_genome, self2.best_genome)
output = net.activate(input_arr) s = "" for val in output: if val < THRESHOLD: s += "0" else: s += "1" return s # --------------- # PREPARE NETWORK # --------------- # best_agents = np.load("./data/from_tung/best_genome_by_generation.npy") p = neat.Checkpointer.restore_checkpoint('./data/from_tung/neat-checkpoint-' + str(CHOSEN_GEN)) genome_list = list(iteritems(p.population)) _, genome = genome_list[CHOSEN_GENOME] current_net = neat.nn.FeedForwardNetwork.create(genome, p.config) visualize.draw_net(p.config, genome, filename="Generation_" + str(CHOSEN_GEN) + "_Genome_" + str(CHOSEN_GENOME), fmt="pdf") # -------------- # NEURAL NETWORK # -------------- while (True): r = requests.get("http://127.0.0.1:37979/get_input") data = r.json()
def speciate(self, config, population): """ Place genomes into species by genetic similarity. Note that this method assumes the current representatives of the species are from the old generation, and that after speciation has been performed, the old representatives should be dropped and replaced with representatives from the new generation. If you violate this assumption, you should make sure other necessary parts of the code are updated to reflect the new behavior. """ assert type(population) is dict compatibility_threshold = config.species_set_config[ 'compatibility_threshold'] # Reset all species member lists. for s in itervalues(self.species): s.members.clear() self.to_species.clear() # Partition population into species based on genetic similarity. distances = [] for key, individual in iteritems(population): # Find the species with the most similar representative. min_distance = sys.float_info.max closest_species = None closest_species_id = None for sid, s in iteritems(self.species): rep = s.representative distance = individual.distance(rep, config.genome_config) distances.append(distance) compatible = distance < compatibility_threshold if compatible and distance < min_distance: closest_species = s closest_species_id = sid min_distance = distance if closest_species is not None: closest_species.add(key, individual) self.to_species[key] = closest_species_id else: # No species is similar enough, create a new species for this individual. sid = self.indexer.get_next() self.species[sid] = Species(sid, key, individual) self.to_species[key] = sid self.reporters.info('Mean genetic distance {0}, std dev {1}'.format( mean(distances), stdev(distances))) # Only keep non-empty species. empty_species_ids = [] for sid, s in iteritems(self.species): if not s.members: empty_species_ids.append(sid) for sid in empty_species_ids: del self.species[sid] # Select a random current member as the new representative. for s in itervalues(self.species): s.representative = random.choice(list(s.members.values()))
selection_type = sys.argv[1] base_dir = sys.argv[2] read_state_sequences = dict() with open(base_dir + 'state_usages.csv', 'r') as csv_file: csv_reader = csv.reader(csv_file) for row in csv_reader: if selection_type == 'scenario': key = int(int(filter(str.isdigit, row[0]))) index = int(row[1]) elif selection_type == 'controller': key = int(row[1]) index = int(int(filter(str.isdigit, row[0]))) else: print('Unknown selection type') exit(1) if key not in read_state_sequences: read_state_sequences[key] = [] read_state_sequences[key].append((index, int(row[2]), [int(x) for x in row[3:]])) for key, state_sequences in iteritems(read_state_sequences): visualizer = StateUsageVisualizer(base_dir, selection_type, key) visualizer.state_sequences = state_sequences # Cheat to not have to append them all. visualizer.finalize()
def distance(self, other, config) """ Returns the genetic distance between this genome and the other. This distance value is used to compute genome compatibility for speciation. """ # Compute node gene distance component. node_distance = 0.0 if self.nodes or other.nodes: disjoint_nodes = 0 for k2 in iterkeys(other.nodes): if k2 not in self.nodes: disjoint_nodes += 1 for k1, n1 in iteritems(self.nodes): n2 = other.nodes.get(k1) if n2 is None: disjoint_nodes += 1 else: # Homologous genes compute their own distance value. node_distance += n1.distance(n2, config) max_nodes = max(len(self.nodes), len(other.nodes)) node_distance = (node_distance + (config.compatibility_disjoint_coefficient * disjoint_nodes)) / max_nodes # Compute connection gene differences. connection_distance = 0.0 if self.connections.keys() != [None] or other.connections.keys() != [None]:
def reproduce(self, config: Config, species: DefaultSpecies, generation: int, logger=None): """Handles creation of genomes, either from scratch or by sexual or asexual reproduction from parents.""" # Check is one of the species has become stagnant (i.e. must be removed) remaining_fitness = [] remaining_species = [] for stag_sid, stag_s, stagnant in self.stagnation.update( config=config, species_set=species, gen=generation): # If specie is stagnant, then remove if stagnant: self.reporters.species_stagnant(stag_sid, stag_s, logger=logger) # Add the specie to remaining_species and save each of its members' fitness else: remaining_fitness.extend(m.fitness for m in itervalues(stag_s.members)) remaining_species.append(stag_s) # If no species is left then force hard-reset if not remaining_species: species.species = dict() return dict() # Calculate the adjusted fitness, normalized by the minimum fitness across the entire population for specie in remaining_species: # Adjust a specie's fitness in a fitness sharing manner. A specie's fitness gets normalized by the number of # members it has, this to ensure that a better performing specie does not takes over the population # A specie's fitness is determined by its most fit genome specie_fitness = max([m.fitness for m in specie.members.values()]) specie_size = len(specie.members) specie.adjusted_fitness = specie_fitness / max( specie_size, config.population.min_specie_size) # Minimum specie-size is defined by the number of elites and the minimal number of genomes in a population spawn_amounts = self.compute_spawn( adjusted_fitness=[s.adjusted_fitness for s in remaining_species], previous_sizes=[len(s.members) for s in remaining_species], pop_size=config.population.pop_size, min_species_size=max(config.population.min_specie_size, config.population.genome_elitism)) # Setup the next generation by filling in the new species with their elites and offspring new_population = dict() species.species = dict() for spawn_amount, specie in zip(spawn_amounts, remaining_species): # If elitism is enabled, each species will always at least gets to retain its elites spawn_amount = max(spawn_amount, config.population.genome_elitism) assert spawn_amount > 0 # Get all the specie's old (evaluated) members old_members = list(iteritems( specie.members)) # Temporarily save members of last generation specie.members = dict() # Reset members species.species[specie.key] = specie # Sort members in order of descending fitness (i.e. most fit members in front) old_members.sort(reverse=True, key=lambda x: x[1].fitness) # Make sure that all the specie's elites are added to the new generation if config.population.genome_elitism > 0: # Add the specie's elites to the global population for i, m in old_members[:config.population.genome_elitism]: new_population[i] = m spawn_amount -= 1 # Add the specie's past elites as well if requested for i in range( min(len(specie.elite_list), config.population.genome_elite_stagnation - 1)): gid, g = specie.elite_list[-(i + 1)] if gid not in new_population: # Only add genomes not yet present in the population new_population[gid] = g spawn_amount -= 1 # Update the specie's elite_list specie.elite_list.append(old_members[0]) # Check if the specie has the right to add more genomes to the population if spawn_amount <= 0: continue # Only use the survival threshold fraction to use as parents for the next generation, use at least all the # elite of a population as parents reproduction_cutoff = max( round(config.population.parent_selection * len(old_members)), config.population.genome_elitism) # Since asexual reproduction, at least one parent must be chosen reproduction_cutoff = max(reproduction_cutoff, 1) parents = old_members[:reproduction_cutoff] # Add the elites again to the parent-set such that these have a greater likelihood of being chosen parents += old_members[:config.population.genome_elitism] # Fill the specie with offspring based, which is a mutation of the chosen parent while spawn_amount > 0: spawn_amount -= 1 # Init genome dummy (values are overwritten later) gid = next(self.genome_indexer) child: Genome = Genome(gid, num_outputs=config.genome.num_outputs, bot_config=config.bot) # Choose the parents, note that if the parents are not distinct, crossover will produce a genetically # identical clone of the parent (but with a different ID) p1_id, p1 = choice(parents) child.connections = copy.deepcopy(p1.connections) child.nodes = copy.deepcopy(p1.nodes) # Mutate the child child.mutate(config.genome) # Ensure that the child is connected while len(child.get_used_connections()) == 0: child.mutate_add_connection(config.genome) # Add the child to the global population new_population[gid] = child return new_population
def create(genome, config): """ Receives a genome and returns its phenotype (a GRUNetwork). """ genome_config = config.genome_config required = required_for_output(genome_config.input_keys, genome_config.output_keys, genome.connections, genome.nodes) connections = [ cg.key for cg in itervalues(genome.connections) if cg.enabled ] node_evals = [] node_inputs = {} for cg in itervalues(genome.connections): if not cg.enabled: continue i, o = cg.key if o not in required and i not in required: continue if o not in node_inputs: node_inputs[o] = [(i, cg.weight)] else: node_inputs[o].append((i, cg.weight)) node_evals = [] gate_list = set() # Add the gates first for proper computation order for node_key, inputs in iteritems(node_inputs): ng = genome.nodes[node_key] if type(ng) is GRUNodeGene: for gate_key in [ng.read_gate, ng.forget_gate]: if type(gate_key) is int and gate_key not in gate_list: gate_list = gate_list.union(gate_key) gate_g = genome.nodes[gate_key] activation_function = genome_config.activation_defs.get( gate_g.activation) aggregation_function = genome_config.aggregation_function_defs.get( gate_g.aggregation) node_evals.append( (gate_key, activation_function, aggregation_function, gate_g.bias, gate_g.response, inputs, [gate_g.read_gate, gate_g.forget_gate] if type(gate_g) is GRUNodeGene else None)) for node_key, inputs in iteritems(node_inputs): if node_key not in gate_list: ng = genome.nodes[node_key] activation_function = genome_config.activation_defs.get( ng.activation) aggregation_function = genome_config.aggregation_function_defs.get( ng.aggregation) node_evals.append((node_key, activation_function, aggregation_function, ng.bias, ng.response, inputs, [ng.read_gate, ng.forget_gate] if type(ng) is GRUNodeGene else None)) nw = GRUNetwork(genome_config.input_keys, genome_config.output_keys, node_evals, gate_list) nw.genome = genome return nw
def reproduce(self, config, species, pop_size, generation): # TODO: I don't like this modification of the species and stagnation objects, # because it requires internal knowledge of the objects. # Find minimum/maximum fitness across the entire population, for use in # species adjusted fitness computation. all_fitnesses = [] for sid, s in iteritems(species.species): all_fitnesses.extend(m.fitness for m in itervalues(s.members)) min_fitness = min(all_fitnesses) max_fitness = max(all_fitnesses) # Do not allow the fitness range to be zero, as we divide by it below. fitness_range = max(1.0, max_fitness - min_fitness) # Filter out stagnated species, collect the set of non-stagnated # species members, and compute their average adjusted fitness. # The average adjusted fitness scheme (normalized to the interval # [0, 1]) allows the use of negative fitness values without # interfering with the shared fitness scheme. num_remaining = 0 species_fitness = [] avg_adjusted_fitness = 0.0 for sid, s, stagnant in self.stagnation.update(species, generation): if stagnant: self.reporters.species_stagnant(sid, s) else: num_remaining += 1 # Compute adjusted fitness. msf = mean([m.fitness for m in itervalues(s.members)]) s.adjusted_fitness = (msf - min_fitness) / fitness_range species_fitness.append((sid, s, s.fitness)) avg_adjusted_fitness += s.adjusted_fitness # No species left. if 0 == num_remaining: species.species = {} return [] avg_adjusted_fitness /= len(species_fitness) self.reporters.info( "Average adjusted fitness: {:.3f}".format(avg_adjusted_fitness)) # Compute the number of new individuals to create for the new generation. spawn_amounts = [] for sid, s, sfitness in species_fitness: spawn = len(s.members) if sfitness > avg_adjusted_fitness: spawn = max(spawn + 2, spawn * 1.1) else: spawn = max(spawn * 0.9, 2) spawn_amounts.append(spawn) # Normalize the spawn amounts so that the next generation is roughly # the population size requested by the user. total_spawn = sum(spawn_amounts) norm = pop_size / total_spawn spawn_amounts = [int(round(n * norm)) for n in spawn_amounts] new_population = {} species.species = {} for spawn, (sid, s, sfitness) in zip(spawn_amounts, species_fitness): # If elitism is enabled, each species always at least gets to retain its elites. spawn = max(spawn, self.elitism) if spawn <= 0: continue # The species has at least one member for the next generation, so retain it. old_members = list(iteritems(s.members)) s.members = {} species.species[sid] = s # Sort members in order of descending fitness. old_members.sort(reverse=True, key=lambda x: x[1].fitness) # Transfer elites to new generation. if self.elitism > 0: for i, m in old_members[:self.elitism]: new_population[i] = m spawn -= 1 if spawn <= 0: continue # Only use the survival threshold fraction to use as parents for the next generation. repro_cutoff = int( math.ceil(self.survival_threshold * len(old_members))) # Use at least two parents no matter what the threshold fraction result is. repro_cutoff = max(repro_cutoff, 2) old_members = old_members[:repro_cutoff] # Randomly choose parents and produce the number of offspring allotted to the species. while spawn > 0: spawn -= 1 parent1_id, parent1 = random.choice(old_members) parent2_id, parent2 = random.choice(old_members) # Note that if the parents are not distinct, crossover will produce a # genetically identical clone of the parent (but with a different ID). gid = self.genome_indexer.get_next() child = config.genome_type(gid) child.configure_crossover(parent1, parent2, config.genome_config) child.mutate(config.genome_config) new_population[gid] = child self.ancestors[gid] = (parent1_id, parent2_id) return new_population
def build_population_nn(self): nn = {} for index, pop in list(iteritems(self.population.population)): nn[index] = self.create_net(pop) self.rt_population_nn = nn
def run(self, fitness_function, n=None): # Variables needed to save archive on each update winner_name_prefix = "archive_checkpoint_" + self.winner_name if self.config.no_fitness_termination and (n is None): raise RuntimeError("Cannot have no generational limit with no fitness termination") k = 0 while n is None or k < n: k += 1 self.reporters.start_generation(self.generation) not_evaluated = {} evaluated = [] # len(population) = 2*pop_size for gid, g in self.population.items(): if g.fitness is None: not_evaluated[gid] = g else: evaluated.append((gid, g)) # Evaluate all genomes using the user-provided function. fitness_function(list(iteritems(not_evaluated)), self.config) self.KNNdistances(self.population, self.novelty_archive, self.n_neighbors) if len(evaluated) != 0: self.species.speciate(self.config, self.population, self.generation) dim = 0 max_spec_dim = 0 max_sid = -1 for sid, s in iteritems(self.species.species): s.members = self.get_best_n_members(s.members, math.ceil(len(s.members) / 2)) d = len(s.members) if d > max_spec_dim: max_spec_dim = d max_sid = sid dim += d diff = dim - self.config.pop_size if diff > 0 and diff > max_spec_dim: s = self.species.species[max_sid] s.members = self.get_best_n_members(s.members, len(s.members) - diff) new_population = {} for sid, s in iteritems(self.species.species): for gid, g in s.members.items(): new_population[gid] = g self.population = new_population self.species.speciate(self.config, self.population, self.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new(self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() # Gather and report statistics. best = None for g in itervalues(self.population): if best is None or g.dist > best.dist: best = g if g.dist > self.novelty_threshold: if g not in self.novelty_archive: self.novelty_archive.append(g) print("Distanza di aggiunta: ", g.dist) self.n_add_archive += 1 self.last_archive_modified = self.generation with open(winner_name_prefix, "wb") as f: pickle.dump(self.novelty_archive, f) self.reporters.post_evaluate(self.config, self.population, self.species, best) if not self.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.fitness_criterion(g.dist for g in itervalues(self.population)) if fv >= self.config.fitness_threshold: self.reporters.found_solution(self.config, self.generation, best) break # Create the next generation from the current generation. self.population = self.reproduction.reproduce(self.config, self.species, self.config.pop_size, self.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new(self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. self.species.speciate(self.config, self.population, self.generation) self.reporters.end_generation(self.config, self.population, self.species) time_diff = self.generation - self.last_archive_modified if time_diff > 60: self.novelty_threshold -= self.novelty_threshold * 0.05 if self.n_add_archive > 4 and time_diff <= 30: self.novelty_threshold += self.novelty_threshold * 0.05 self.n_add_archive = 0 if time_diff > 30: self.n_add_archive = 0 self.reporters.info("Novelty's archive size: {}\n".format(len(self.novelty_archive))) if len(self.novelty_archive) > 0: self.reporters.info("Archive's best: {}".format( max(self.novelty_archive, key=lambda x: x.fitness[0]).fitness[0])) else: self.reporters.info("Archive's best: 0") self.generation += 1 if self.config.no_fitness_termination: self.reporters.found_solution(self.config, self.generation, self.best_genome) return self.novelty_archive
def rt_iterate(self): self.population.reporters.start_generation(self.population.generation) # Gather and report statistics. best = None worst = None for g in itervalues(self.population.population): if not hasattr(g, 'birth'): g.birth = self.population.generation if g.fitness is None: continue if best is None or g.fitness > best.fitness: best = g if (worst is None or g.fitness < worst.fitness) and \ self.population.generation - g.birth > self.population.config.stagnation_config.max_stagnation: worst = g population_with_fitness = dict([ p for p in iteritems(self.population.population) if p[1].fitness is not None ]) if population_with_fitness: self.population.reporters.post_evaluate(self.config, population_with_fitness, self.population.species, best) # Track the best genome ever seen. if self.population.best_genome is None or best.fitness > self.population.best_genome.fitness: self.population.best_genome = best if not self.population.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.population.fitness_criterion( g.fitness for g in itervalues(population_with_fitness)) if fv >= self.population.config.fitness_threshold: self.population.reporters.found_solution( self.population.config, self.population.generation, best) return # remove worst genome and replace with new one if worst is not None: new_genomes = self.population.reproduction.reproduce( self.population.config, self.population.species, 1, self.population.generation) new_key = np.max( [g.key for g in itervalues(self.population.population)]) + 1 for g in itervalues(new_genomes): del self.population.population[worst.key] g.key = new_key self.population.population[new_key] = g print(new_key) break # Divide the new population into species. if population_with_fitness: self.population.species.speciate(self.population.config, population_with_fitness, self.population.generation) # self.population.reporters.end_generation(self.population.config, self.population.population, # self.population.species) self.population.generation += 1
from MyCheckpoint import MyCheckpointer from GenomeVisualizer import GenomeVisualizer root_path = '../logs/meandist_collision100/1525807840' generation = 1029 top_x = 5 config_path = '{}/neat_config'.format(root_path) pop_path = '{}/pop-gen-{}'.format(root_path, generation) neat_conf = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path) pop = MyCheckpointer.restore_checkpoint(pop_path) genomes = list(iteritems(pop.population)) def cond(item): return item[1].fitness sorted_genoms = sorted(genomes, reverse=True, key=cond) genomes = sorted_genoms[0:top_x] for i in range(0, len(genomes)): genome_viz_file = '{}/genome-gen-{}-top-{}-viz'.format( root_path, generation, i) GenomeVisualizer.draw_net(neat_conf, genomes[i][1], False,
def neat_manual_looping(pop, generation_limit=None): if pop.config.no_fitness_termination and (generation_limit is None): raise RuntimeError( "Cannot have no generational limit with no fitness termination") yield k = 0 while generation_limit is None or k < generation_limit: k += 1 pop.reporters.start_generation(pop.generation) genomes = list(iteritems(pop.population)) finesses = yield genomes for (genome_id, genome), fitness in zip(genomes, finesses): genome.fitness = fitness # Gather and report statistics. best = None for g in itervalues(pop.population): if best is None or g.fitness > best.fitness: best = g pop.reporters.post_evaluate(pop.config, pop.population, pop.species, best) # Track the best genome ever seen. if pop.best_genome is None or best.fitness > pop.best_genome.fitness: pop.best_genome = best if not pop.config.no_fitness_termination: # End if the fitness threshold is reached. fv = pop.fitness_criterion(g.fitness for g in itervalues(pop.population)) if fv >= pop.config.fitness_threshold: pop.reporters.found_solution(pop.config, pop.generation, best) break # Create the next generation from the current generation. pop.population = pop.reproduction.reproduce(pop.config, pop.species, pop.config.pop_size, pop.generation) # Check for complete extinction. if not pop.species.species: pop.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if pop.config.reset_on_extinction: pop.population = pop.reproduction.create_new( pop.config.genome_type, pop.config.genome_config, pop.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. pop.species.speciate(pop.config, pop.population, pop.generation) pop.reporters.end_generation(pop.config, pop.population, pop.species) pop.generation += 1 if pop.config.no_fitness_termination: pop.reporters.found_solution(pop.config, pop.generation, pop.best_genome)
def train( population: Population, game_config: Config, games: list, iterations: int, unused_cpu: int = 0, save_interval: int = 10, ): """Train the population on the requested number of iterations. Manual adaptation of main's train().""" population.log("\n===> TRAINING <===\n") multi_env = get_multi_env(pop=population, game_config=game_config) msg = f"Repetitive evaluating on games: {games} for {iterations} iterations" population.log(msg, print_result=False) # Iterate and evaluate over the games saved = True for iteration in range(iterations): # Set and randomize the games multi_env.set_games(games, noise=True) # Prepare the generation's reporters for the generation population.reporters.start_generation(gen=population.generation, logger=population.log) # Fetch the dictionary of genomes genomes = list(iteritems(population.population)) # Initialize the evaluation-pool pool = mp.Pool(mp.cpu_count() - unused_cpu) manager = mp.Manager() return_dict = manager.dict() for genome in genomes: pool.apply_async(func=multi_env.eval_genome, args=(genome, return_dict)) pool.close() # Close the pool pool.join() # Postpone continuation until everything is finished # Calculate the fitness from the given return_dict fitness = calc_pop_fitness( fitness_cfg=population.config.evaluation, game_cfg=game_config.game, game_obs=return_dict, gen=population.generation, ) for i, genome in genomes: genome.fitness = fitness[i] # Gather and report statistics best = None for g in itervalues(population.population): if best is None or g.fitness > best.fitness: best = g population.reporters.post_evaluate(population=population.population, species=population.species, best_genome=best, logger=population.log) # Update the population's best_genome genomes = sorted(population.population.items(), key=lambda x: x[1].fitness, reverse=True) population.best_fitness[population.generation] = genomes[0][1].fitness population.best_genome_hist[population.generation] = genomes[0] population.best_genome = best # Let population evolve population.evolve() # Update the genomes such all have one hidden node for g in population.population.values(): n_hidden, _ = g.size() while n_hidden < 1: g.mutate_add_connection(population.config.genome) n_hidden, _ = g.size() # End generation population.reporters.end_generation(population=population.population, name=str(population), species_set=population.species, logger=population.log) # Save the population if (iteration + 1) % save_interval == 0: population.save() saved = True else: saved = False # Make sure that last iterations saves if not saved: population.save()
def reproduce(self, config, species, pop_size, generation, exp, syllabus): all_fitnesses = [] for sid, s in iteritems(species.species): all_fitnesses.extend(m.fitness for m in itervalues(s.members)) min_fitness = min(all_fitnesses) max_fitness = max(all_fitnesses) fitness_range = max(1.0, max_fitness - min_fitness) num_remaining = 0 species_fitness = [] avg_adjusted_fitness = 0.0 for sid, s, stagnant in self.stagnation.update(species, generation): if stagnant: self.reporters.species_stagnant(sid, s) else: num_remaining += 1 msf = mean([m.fitness for m in itervalues(s.members)]) s.adjusted_fitness = (msf - min_fitness) / fitness_range species_fitness.append((sid, s, s.fitness)) avg_adjusted_fitness += s.adjusted_fitness if 0 == num_remaining: species.species = {} return [] avg_adjusted_fitness /= len(species_fitness) self.reporters.info( "Average adjusted fitness: {:.3f}".format(avg_adjusted_fitness)) spawn_amounts = [] for sid, s, sfitness in species_fitness: spawn = len(s.members) if sfitness > avg_adjusted_fitness: spawn = max(spawn + 2, spawn * 1.1) else: spawn = max(spawn * 0.9, 2) spawn_amounts.append(spawn) total_spawn = sum(spawn_amounts) norm = pop_size / total_spawn spawn_amounts = [int(round(n * norm)) for n in spawn_amounts] new_population = {} species.species = {} learning_function = None if exp.learning_function == 'BP': learning_function = backpropagation elif exp.learning_function == 'BT': learning_function = batch has_learning = not learning_function is None if has_learning: (writer, lessons) = syllabus if exp.syllabus_source == 'EXP': lessons_to_learn = random.sample( lessons, min(exp.syllabus_size, len(lessons))) elif exp.syllabus_source == 'RND': lessons_to_learn = lessons for spawn, (sid, s, sfitness) in zip(spawn_amounts, species_fitness): spawn = max(spawn, self.elitism) if spawn <= 0: continue old_members = list(iteritems(s.members)) s.members = {} species.species[sid] = s old_members.sort(reverse=True, key=lambda x: x[1].fitness) if self.elitism > 0: for i, m in old_members[:self.elitism]: new_population[i] = m spawn -= 1 if spawn <= 0: continue repro_cutoff = int( math.ceil(self.survival_threshold * len(old_members))) repro_cutoff = max(repro_cutoff, 2) old_members = old_members[:repro_cutoff] if has_learning and (exp.learning_target == 'Ambos' or exp.learning_target == 'Pais'): for (_, g) in old_members: if g != writer: learning_function(g, lessons_to_learn, exp.learning_rate) while spawn > 0: spawn -= 1 parent1_id, parent1 = random.choice(old_members) parent2_id, parent2 = random.choice(old_members) gid = self.genome_indexer.next() child = config.genome_type(gid) child.configure_crossover(parent1, parent2, config.genome_config) child.mutate(config.genome_config) child.net = FeedForwardNetwork.create(child, self.config) if has_learning and (exp.learning_target == 'Ambos' or exp.learning_target == 'Filhos'): if exp.children_inclusion == 'Inicial' or ( exp.children_inclusion == 'Tardia' and generation >= exp.generations / 2): learning_function(child, lessons_to_learn, exp.learning_rate) new_population[gid] = child self.ancestors[gid] = (parent1_id, parent2_id) return new_population
def speciate(self, config, population, generation): """ Place genomes into species by genetic similarity. Note that this method assumes the current representatives of the species are from the old generation, and that after speciation has been performed, the old representatives should be dropped and replaced with representatives from the new generation. If you violate this assumption, you should make sure other necessary parts of the code are updated to reflect the new behavior. """ assert isinstance(population, dict) compatibility_threshold = self.species_set_config.compatibility_threshold # Find the best representatives for each existing species. unspeciated = set(iterkeys(population)) distances = GenomeDistanceCache(config.genome_config) new_representatives = {} new_members = {} for sid, s in iteritems(self.species): candidates = [] for gid in unspeciated: g = population[gid] d = distances(s.representative, g) candidates.append((d, g)) # The new representative is the genome closest to the current representative. ignored_rdist, new_rep = min(candidates, 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 population into species based on genetic similarity. while unspeciated: gid = unspeciated.pop() g = population[gid] # Find the species with the most similar representative. candidates = [] for sid, rid in iteritems(new_representatives): rep = population[rid] d = distances(rep, g) if d < compatibility_threshold: candidates.append((d, sid)) if candidates: ignored_sdist, sid = min(candidates, key=lambda x: x[0]) new_members[sid].append(gid) else: # No species is similar enough, create a new species, using # this genome as its representative. sid = next(self.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): s = self.species.get(sid) if s is None: s = Species(sid, generation) self.species[sid] = s members = new_members[sid] for gid in members: self.genome_to_species[gid] = sid member_dict = dict((gid, population[gid]) for gid in members) s.update(population[rid], member_dict) gdmean = mean(itervalues(distances.distances)) gdstdev = stdev(itervalues(distances.distances)) self.reporters.info( 'Mean genetic distance {0:.3f}, standard deviation {1:.3f}'.format( gdmean, gdstdev))
def single_evaluation(multi_env, parallel: bool, pop: Population, unused_cpu: int): """ Perform a single evaluation-iteration. :param multi_env: Environment used to execute the game-simulation in :param parallel: Boolean indicating if training happens in parallel or not :param pop: Population used to evaluate on :param unused_cpu: Number of CPU-cores not used during evaluation """ # Prepare the generation's reporters for the generation pop.reporters.start_generation(gen=pop.generation, logger=pop.log) # Fetch the dictionary of genomes genomes = list(iteritems(pop.population)) if parallel: pbar = tqdm(total=len(genomes), desc="parallel training") # Initialize the evaluation-pool pool = mp.Pool(mp.cpu_count() - unused_cpu) manager = mp.Manager() return_dict = manager.dict() def cb(*_): """Update progressbar after finishing a single genome's evaluation.""" pbar.update() for genome in genomes: pool.apply_async(func=multi_env.eval_genome, args=(genome, return_dict), callback=cb) pool.close() # Close the pool pool.join() # Postpone continuation until everything is finished pbar.close() # Close the progressbar else: return_dict = dict() for genome in tqdm(genomes, desc="sequential training"): multi_env.eval_genome(genome, return_dict) # Calculate the fitness from the given return_dict try: fitness = calc_pop_fitness( fitness_config=pop.config.evaluation, game_observations=return_dict, game_params=multi_env.get_game_params(), generation=pop.generation, ) for i, genome in genomes: genome.fitness = fitness[i] except Exception: # TODO: Fix! Sometimes KeyError in fitness (path problem) pop.log( f"Exception at fitness calculation: \n{traceback.format_exc()}", print_result=False) warnings.warn( f"Exception at fitness calculation: \n{traceback.format_exc()}") # Set fitness to zero for genomes that have no fitness set yet for i, genome in genomes: if not genome.fitness: genome.fitness = 0.0 # Gather and report statistics best = None for g in itervalues(pop.population): if best is None or g.fitness > best.fitness: best = g pop.reporters.post_evaluate(population=pop.population, species=pop.species, best_genome=best, logger=pop.log) # Update the population's best_genome genomes = sorted(pop.population.items(), key=lambda x: x[1].fitness, reverse=True) pop.best_genome_hist[ pop.generation] = genomes[:pop.config.population.genome_elitism] if pop.best_genome is None or best.fitness > pop.best_genome.fitness: pop.best_genome = best # Let population evolve pop.evolve() # End generation pop.reporters.end_generation(population=pop.population, name=str(pop), species_set=pop.species, logger=pop.log)
def run(self, fitness_function, n=None): # Variables needed to save archive on each update winner_name_prefix = "archive_checkpoint_" + self.winner_name if self.config.no_fitness_termination and (n is None): raise RuntimeError( "Cannot have no generational limit with no fitness termination" ) k = 0 while n is None or k < n: k += 1 self.reporters.start_generation(self.generation) # Evaluate all genomes using the user-provided function. fitness_function(list(iteritems(self.population)), self.config) self.KNNdistances(self.population, self.novelty_archive, self.n_neighbors) # Gather and report statistics. best = None for g in itervalues(self.population): if best is None or g.dist > best.dist: best = g if g.dist > self.novelty_threshold: if g not in self.novelty_archive: self.novelty_archive.append(g) print("Distanza di aggiunta: ", g.dist) self.n_add_archive += 1 self.last_archive_modified = self.generation with open(winner_name_prefix, "wb") as f: pickle.dump(self.novelty_archive, f) self.reporters.post_evaluate(self.config, self.population, self.species, best) # if self.last_genome_added is None: # self.last_genome_added = best # Track the best genome ever seen. # if self.last_genome_added.fitness != best.fitness and best.dist > self.novelty_threshold: # print("Distanza di aggiunta: ", best.dist) # self.last_genome_added = best # self.last_archive_modified = self.generation # self.n_add_archive += 1 # self.novelty_archive.append(best) # with open(winner_name_prefix, "wb") as f: # pickle.dump(self.novelty_archive, f) if not self.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.fitness_criterion( g.dist for g in itervalues(self.population)) if fv >= self.config.fitness_threshold: self.reporters.found_solution(self.config, self.generation, best) break # Create the next generation from the current generation. self.population = self.reproduction.reproduce( self.config, self.species, self.config.pop_size, self.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new( self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. self.species.speciate(self.config, self.population, self.generation) self.reporters.end_generation(self.config, self.population, self.species) time_diff = self.generation - self.last_archive_modified if time_diff > 60: self.novelty_threshold -= self.novelty_threshold * 0.05 if self.n_add_archive > 4 and time_diff <= 30: self.novelty_threshold += self.novelty_threshold * 0.05 self.n_add_archive = 0 if time_diff > 30: self.n_add_archive = 0 self.reporters.info("Novelty's archive size: {}\n".format( len(self.novelty_archive))) self.generation += 1 if self.config.no_fitness_termination: self.reporters.found_solution(self.config, self.generation, self.best_genome) return self.novelty_archive
def run(self, fitness_function, n): """ Runs NEAT's genetic algorithm for at most n generations. The user-provided fitness_function should take two arguments, a list of all genomes in the population, and its return value is ignored. This function is free to maintain external state, perform evaluations in parallel, and probably any other thing you want. Each individual genome's fitness member must be set to a floating point value after this function returns. It is assumed that fitness_function does not modify the list of genomes, the genomes themselves (apart from updating the fitness member), or the configuration object. """ for g in range(n): self.generation += 1 self.reporters.start_generation(self.generation) # Evaluate all individuals in the population using the user-provided function. # TODO: Add an option to only evaluate each genome once, to reduce number of # fitness evaluations in cases where the fitness is known to be the same if the # genome doesn't change--in these cases, evaluating unmodified elites in each # generation is a waste of time. The user can always take care of this in their # fitness function in the time being if they wish. fitness_function(list(iteritems(self.population)), self.config) # Gather and report statistics. best = None for g in itervalues(self.population): if best is None or g.fitness > best.fitness: best = g self.reporters.post_evaluate(self.config, self.population, self.species, best) # Track the best genome ever seen. if self.best_genome is None or best.fitness > self.best_genome.fitness: self.best_genome = best # End if the fitness threshold is reached. if best.fitness >= self.config.max_fitness_threshold: self.reporters.found_solution(self.config, self.generation, best) break # Create the next generation from the current generation. self.population = self.reproduction.reproduce( self.config, self.species, self.config.pop_size) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new( self.config.pop_size) else: raise CompleteExtinctionException() # Update species age. # TODO: Wouldn't it be easier to remember creation time? for s in itervalues(self.species.species): s.age += 1 # Divide the new population into species. self.species.speciate(self.config, self.population) self.reporters.end_generation(self.config, self.population, self.species) return self.best_genome
def reproduce(self, config, species, pop_size, generation): """ Handles creation of genomes, either from scratch or by sexual or asexual reproduction from parents. """ # TODO: I don't like this modification of the species and stagnation objects, # because it requires internal knowledge of the objects. # Filter out stagnated species, collect the set of non-stagnated # species members, and compute their average adjusted fitness. # The average adjusted fitness scheme (normalized to the interval # [0, 1]) allows the use of negative fitness values without # interfering with the shared fitness scheme. all_fitnesses = [] remaining_species = [] for stag_sid, stag_s, stagnant in self.stagnation.update( species, generation): if stagnant: self.reporters.species_stagnant(stag_sid, stag_s) else: all_fitnesses.extend(m.fitness for m in itervalues(stag_s.members)) remaining_species.append(stag_s) # The above comment was not quite what was happening - now getting fitnesses # only from members of non-stagnated species. # No species left. if not remaining_species: species.species = {} return {} # was [] self.calculate_adjusted_fitnesses(remaining_species, all_fitnesses) adjusted_fitnesses = [s.adjusted_fitness for s in remaining_species] avg_adjusted_fitness = mean(adjusted_fitnesses) # type: float self.reporters.info( "Average adjusted fitness: {:.3f}".format(avg_adjusted_fitness)) # Compute the number of new members for each species in the new generation. previous_sizes = [len(s.members) for s in remaining_species] min_species_size = self.reproduction_config.min_species_size # Isn't the effective min_species_size going to be max(min_species_size, # self.reproduction_config.elitism)? That would probably produce more accurate tracking # of population sizes and relative fitnesses... doing. TODO: document. min_species_size = max(min_species_size, self.reproduction_config.elitism) spawn_amounts = self.compute_spawn(adjusted_fitnesses, previous_sizes, pop_size, min_species_size) new_population = {} species.species = {} for spawn, s in zip(spawn_amounts, remaining_species): # If elitism is enabled, each species always at least gets to retain its elites. spawn = max(spawn, self.reproduction_config.elitism) assert spawn > 0 # The species has at least one member for the next generation, so retain it. old_members = list(iteritems(s.members)) s.members = {} species.species[s.key] = s # Sort members in order of descending fitness. old_members.sort(reverse=True, key=lambda x: x[1].fitness) # Transfer elites to new generation. if self.reproduction_config.elitism > 0: for i, m in old_members[:self.reproduction_config.elitism]: new_population[i] = m spawn -= 1 if spawn <= 0: continue repro_cutoff = self.calculated_cutoff(len(old_members)) old_members = old_members[:repro_cutoff] # Randomly choose parents and produce the number of offspring allotted to the species. while spawn > 0: spawn -= 1 gid, child = self.generate_child(old_members, config) new_population[gid] = child return new_population
def run(self, fitness_function, n=None): #Variables needed to save winner winner_interval = 10 winnername_prefix = "winner_checkpoint_" last_winner_checkpoint = 0 if self.config.no_fitness_termination and (n is None): raise RuntimeError( "Cannot have no generational limit with no fitness termination" ) k = 0 while n is None or k < n: k += 1 start_time_gen = time.time() self.reporters.start_generation(self.generation) # Evaluate all genomes using the user-provided function. fitness_function(list(iteritems(self.population)), self.config) if self.initial_best: self.species.speciate(self.config, self.population, self.generation) with open("output.txt", "a") as f: f.write("Gen: " + str(k) + " tempo: " + str(round(time.time() - start_time_gen, 3)) + " sec\n") start_time_gen = time.time() # Gather and report statistics. best = None for g in itervalues(self.population): if best is None or g.fitness > best.fitness: best = g self.reporters.post_evaluate(self.config, self.population, self.species, best) # Track the best genome ever seen. if self.best_genome is None or best.fitness > self.best_genome.fitness: self.best_genome = best if not self.config.no_fitness_termination: # End if the fitness threshold is reached. fv = self.fitness_criterion( g.fitness for g in itervalues(self.population)) if fv >= self.config.fitness_threshold: self.reporters.found_solution(self.config, self.generation, best) break # Create the next generation from the current generation. self.population = self.reproduction.reproduce( self.config, self.species, self.config.pop_size, self.generation) # Check for complete extinction. if not self.species.species: self.reporters.complete_extinction() # If requested by the user, create a completely new population, # otherwise raise an exception. if self.config.reset_on_extinction: self.population = self.reproduction.create_new( self.config.genome_type, self.config.genome_config, self.config.pop_size) else: raise CompleteExtinctionException() # Divide the new population into species. self.species.speciate(self.config, self.population, self.generation) self.reporters.end_generation(self.config, self.population, self.species) self.generation += 1 #Code to save the best after winner_interval generations if winner_interval is not None: dg = self.generation - last_winner_checkpoint if dg >= winner_interval: filename = '{0}{1}'.format(winnername_prefix, self.generation) last_winner_checkpoint = self.generation with open(filename, 'wb') as f: pickle.dump(self.best_genome, f) with open("output.txt", "a") as f: f.write("Gen: " + str(k) + " tempo: " + str(round(time.time() - start_time_gen, 3)) + " sec\n") if self.config.no_fitness_termination: self.reporters.found_solution(self.config, self.generation, self.best_genome) return self.best_genome