def evolve(runs, steps, env, rep, gens, pop_size, num_nodes, mutate_std, output, gui): """Evolve a controller using a Pitt-style rule system.""" check_rep(rep) print(f"Loading environment '{env}'...") environment = gym.make(env) print(f"\tObservation space:\t{environment.observation_space}") print(f"\tAction space: \t{environment.action_space}") with open(output, 'w') as genomes_file: if rep == 'pitt': representation = pitt_representation(environment, num_rules=num_nodes) elif rep == 'neural': representation = neural_representation(environment, num_hidden_nodes=num_nodes) probes = get_probes(genomes_file, environment, rep) with Client() as dask_client: ea = generational_ea( generations=gens, pop_size=pop_size, # Solve a problem that executes agents in the # environment and obtains fitness from it problem=problems.EnvironmentProblem(runs, steps, environment, 'reward', gui), representation=representation, # The operator pipeline. pipeline=[ ops.tournament_selection, ops.clone, mutate_gaussian(std=mutate_std, hard_bounds=(-1, 1)), ops.evaluate, ops.pool(size=pop_size), #synchronous.eval_pool(client=dask_client, size=pop_size), *probes # Inserting all the probes at the end ]) list(ea)
def test_mutate_gaussian(): """If we apply isotropic Gaussian mutation to a given genome a bunch of different times, the offsprings' genes should follow a Gaussian distribution around their parents' values.""" N = 5000 # We'll sample 5,000 independent genomes gene0_values = [] gene1_values = [] op = ops.mutate_gaussian(std=1.0, expected_num_mutations='isotropic') for _ in range(N): # Set up two parents with fixed genomes, two genes each ind1 = Individual(np.array([0, 0.5])) population = iter([ind1]) # Mutate result = op(population) result = next(result) # Pulse the iterator gene0_values.append(result.genome[0]) gene1_values.append(result.genome[1]) # Use a Kolmogorov-Smirnoff test to verify that the mutations follow a # Gaussian distribution with zero mean and unit variance p_threshold = 0.01 # Gene 0 should follow N(0, 1.0) _, p = stats.kstest(gene0_values, 'norm') print(p) assert (p > p_threshold) # Gene 1 should follow N(0.5, 1.0) gene1_centered_values = [x - 0.5 for x in gene1_values] _, p = stats.kstest(gene1_centered_values, 'norm') print(p) assert (p > p_threshold) # Gene 1 should *not* follow N(0, 1.0) _, p = stats.kstest(gene1_values, 'norm') print(p) assert (p <= p_threshold)
def ea_solve(function, bounds, generations=100, pop_size=2, mutation_std=1.0, maximize=False, viz=False, viz_ylim=(0, 1)): """Provides a simple, top-level interfact that optimizes a real-valued function using a simple generational EA. :param function: the function to optimize; should take lists of real numbers as input and return a float fitness value :param [(float, float)] bounds: a list of (min, max) bounds to define the search space :param int generations: the number of generations to run for :param int pop_size: the population size :param float mutation_std: the width of the mutation distribution :param bool maximize: whether to maximize the function (else minimize) :param bool viz: whether to display a live best-of-generation plot :param (float, float) viz_ylim: initial bounds to use of the plots vertical axis >>> from leap_ec import simple >>> ea_solve(sum, bounds=[(0, 1)]*5) # doctest:+ELLIPSIS generation, bsf 0, ... 1, ... ... 100, ... [..., ..., ..., ..., ...] """ pipeline = [ ops.tournament_selection, ops.clone, mutate_gaussian(std=mutation_std), ops.uniform_crossover(p_swap=0.4), ops.evaluate, ops.pool(size=pop_size) ] if viz: plot_probe = probe.PopulationPlotProbe( context, ylim=viz_ylim, ax=plt.gca()) pipeline.append(plot_probe) ea = generational_ea(generations=generations, pop_size=pop_size, problem=FunctionProblem(function, maximize), representation=Representation( individual_cls=Individual, decoder=IdentityDecoder(), initialize=create_real_vector(bounds=bounds) ), pipeline=pipeline) best_genome = None print('generation, bsf') for g, ind in ea: print(f"{g}, {ind.fitness}") best_genome = ind.genome return best_genome
context['leap']['std'] = 2 # We use the provided context, but we could roll our own if we # wanted to keep separate contexts. E.g., island models may want to have # their own contexts. generation_counter = util.inc_generation(context=context, callbacks=(anneal_std, )) # print initial, random population print_population(parents, generation=0) while generation_counter.generation() < MAX_GENERATIONS: offspring = pipe( parents, ops.random_selection, ops.clone, mutate_gaussian(std=context['leap']['std']), ops.evaluate, ops.pool(size=len(parents) * BROOD_SIZE), # create the brood ops.truncation_selection(size=len(parents), parents=parents)) # mu + lambda parents = offspring generation_counter() # increment to the next generation # Just to demonstrate that we can also get the current generation from # the context print_population(parents, context['leap']['generation'])
# Representation representation=Representation( # Initialize a population of integer-vector genomes initialize=create_real_vector( bounds=[problem.bounds] * l) ), # Operator pipeline pipeline=[ ops.tournament_selection(k=2), ops.clone, # Apply binomial mutation: this is a lot like # additive Gaussian mutation, but adds an integer # value to each gene mutate_gaussian(std=0.2, hard_bounds=[problem.bounds]*l, expected_num_mutations=1), ops.evaluate, ops.pool(size=pop_size), # Some visualization probes so we can watch what happens probe.CartesianPhenotypePlotProbe( xlim=problem.bounds, ylim=problem.bounds, contours=problem), probe.FitnessPlotProbe(), probe.PopulationMetricsPlotProbe( metrics=[ probe.pairwise_squared_distance_metric ], title='Population Diversity'), probe.FitnessStatsCSVProbe(stream=sys.stdout)
pop_size=pop_size, problem=problems, # Fitness function # Representation representation=Representation( individual_cls=Individual, initialize=create_real_vector( bounds=[bounds] * l), decoder=IdentityDecoder() ), # Operator pipeline shared_pipeline=[ ops.tournament_selection, ops.clone, mutate_gaussian( std=0.03, hard_bounds=bounds), ops.evaluate, ops.pool(size=pop_size), ops.migrate(context, topology=topology, emigrant_selector=ops.tournament_selection, replacement_selector=ops.random_selection, migration_gap=5, customs_stamp=problem_stamp(problems)), probe.FitnessStatsCSVProbe(context, stream=sys.stdout, extra_columns={ 'island': get_island(context) }) ], subpop_pipelines=subpop_probes) list(ea) plt.show()
parents = Individual.evaluate_population(parents) # print initial, random population print_population(parents, generation=0) max_generation = MAX_GENERATIONS # We use the provided context, but we could roll our own if we # wanted to keep separate contexts. E.g., island models may want to have # their own contexts. generation_counter = util.inc_generation(context=context) while generation_counter.generation() < max_generation: offspring = pipe( parents, ops.random_selection, ops.clone, mutate_gaussian(std=.1), ops.evaluate, ops.pool(size=len(parents) * BROOD_SIZE), # create the brood ops.insertion_selection(parents=parents)) parents = offspring generation_counter() # increment to the next generation # Just to demonstrate that we can also get the current generation from # the context print_population(parents, context['leap']['generation'])
ea = multi_population_ea( generations=1000, num_populations=topology.number_of_nodes(), pop_size=pop_size, problem=problem, # Fitness function # Representation representation=Representation( individual_cls=Individual, initialize=create_real_vector(bounds=[problem.bounds] * l), decoder=IdentityDecoder()), # Operator pipeline shared_pipeline=[ ops.tournament_selection, ops.clone, mutate_gaussian(std=30, hard_bounds=problem.bounds), ops.evaluate, ops.pool(size=pop_size), ops.migrate(context, topology=topology, emigrant_selector=ops.tournament_selection, replacement_selector=ops.random_selection, migration_gap=50), probe.FitnessStatsCSVProbe( context, stream=sys.stdout, extra_columns={'island': get_island(context)}) ], subpop_pipelines=subpop_probes) list(ea) plt.show()
context['leap']['std'] = 2 # We use the provided context, but we could roll our own if we # wanted to keep separate contexts. E.g., island models may want to have # their own contexts. generation_counter = util.inc_generation(context=context, callbacks=(anneal_std, )) # print initial, random population print_population(parents, generation=0) while generation_counter.generation() < max_generation: offspring = pipe( parents, ops.random_selection, ops.clone, mutate_gaussian(std=context['leap']['std'], expected_num_mutations=1), ops.evaluate, # create the brood ops.pool(size=len(parents) * BROOD_SIZE), # mu + lambda ops.truncation_selection(size=len(parents), parents=parents)) parents = offspring generation_counter() # increment to the next generation # Just to demonstrate that we can also get the current generation from # the context print_population(parents, context['leap']['generation'])
pop_size = 10 ea = multi_population_ea( max_generations=generations, num_populations=topology.number_of_nodes(), pop_size=pop_size, problem=problem, # Fitness function # Representation representation=Representation( individual_cls=Individual, initialize=create_real_vector(bounds=[problem.bounds] * l)), # Operator pipeline shared_pipeline=[ ops.tournament_selection, ops.clone, mutate_gaussian(std=30, expected_num_mutations=1, hard_bounds=problem.bounds), ops.evaluate, ops.pool(size=pop_size), ops.migrate(topology=topology, emigrant_selector=ops.tournament_selection, replacement_selector=ops.random_selection, migration_gap=50), probe.FitnessStatsCSVProbe( stream=sys.stdout, extra_metrics={'island': get_island(context)}) ], subpop_pipelines=subpop_probes) list(ea)
# Evaluate initial population parents = Individual.evaluate_population(parents) # print initial, random population print_population(parents, generation=0) max_generation = MAX_GENERATIONS # Set up a generation counter using the default global context variable generation_counter = util.inc_generation() while generation_counter.generation() < max_generation: offspring = pipe(parents, ops.random_selection, ops.clone, mutate_gaussian(std=.1, expected_num_mutations=1), ops.evaluate, ops.pool( size=len(parents) * BROOD_SIZE), # create the brood ops.insertion_selection(parents=parents)) parents = offspring generation_counter() # increment to the next generation # Just to demonstrate that we can also get the current generation from # the context print_population(parents, context['leap']['generation'])
ea = multi_population_ea( max_generations=generations, num_populations=topology.number_of_nodes(), pop_size=pop_size, problem=problems, # Fitness function # Representation representation=Representation( individual_cls=Individual, initialize=create_real_vector(bounds=[bounds] * l)), # Operator pipeline shared_pipeline=[ ops.tournament_selection, ops.clone, mutate_gaussian(std=0.03, expected_num_mutations=1, hard_bounds=bounds), ops.evaluate, ops.pool(size=pop_size), ops.migrate(topology=topology, emigrant_selector=ops.tournament_selection, replacement_selector=ops.random_selection, migration_gap=5, customs_stamp=problem_stamp(problems)), probe.FitnessStatsCSVProbe( stream=sys.stdout, extra_metrics={'island': get_island(context)}) ], subpop_pipelines=subpop_probes) list(ea)
def ea_solve(function, bounds, generations=100, pop_size=cpu_count(), mutation_std=1.0, maximize=False, viz=False, viz_ylim=(0, 1), hard_bounds=True, dask_client=None): """Provides a simple, top-level interfact that optimizes a real-valued function using a simple generational EA. :param function: the function to optimize; should take lists of real numbers as input and return a float fitness value :param [(float, float)] bounds: a list of (min, max) bounds to define the search space :param int generations: the number of generations to run for :param int pop_size: the population size :param float mutation_std: the width of the mutation distribution :param bool maximize: whether to maximize the function (else minimize) :param bool viz: whether to display a live best-of-generation plot :param bool hard_bounds: if True, bounds are enforced at all times during evolution; otherwise they are only used to initialize the population. :param (float, float) viz_ylim: initial bounds to use of the plots vertical axis :param dask_client: is optional dask Client for enable parallel evaluations The basic call includes instrumentation that prints the best-so-far fitness value of each generation to stdout: >>> from leap_ec.simple import ea_solve >>> ea_solve(sum, bounds=[(0, 1)]*5) # doctest:+ELLIPSIS generation, bsf 0, ... 1, ... ... 100, ... array([..., ..., ..., ..., ...]) When `viz=True`, a live BSF plot will also display: >>> ea_solve(sum, bounds=[(0, 1)]*5, viz=True) # doctest:+ELLIPSIS generation, bsf 0, ... 1, ... ... 100, ... array([..., ..., ..., ..., ...]) .. plot:: from leap_ec.simple import ea_solve ea_solve(sum, bounds=[(0, 1)]*5, viz=True) """ if hard_bounds: mutation_op = mutate_gaussian(std=mutation_std, hard_bounds=bounds, expected_num_mutations='isotropic') else: mutation_op = mutate_gaussian(std=mutation_std, expected_num_mutations='isotropic') pipeline = [ ops.tournament_selection, ops.clone, mutation_op, ops.uniform_crossover(p_swap=0.2), ] # If a dask client is given, then use the synchronous (map/reduce) parallel # evaluation of individuals; else, revert to serial evaluations. if dask_client: pipeline.append( synchronous.eval_pool(client=dask_client, size=pop_size)) else: pipeline.extend([ops.evaluate, ops.pool(size=pop_size)]) if viz: plot_probe = probe.FitnessPlotProbe(ylim=viz_ylim, ax=plt.gca()) pipeline.append(plot_probe) ea = generational_ea(max_generations=generations, pop_size=pop_size, problem=FunctionProblem(function, maximize), representation=Representation( individual_cls=DistributedIndividual, initialize=create_real_vector(bounds=bounds)), pipeline=pipeline) best_genome = None print('generation, bsf') for g, ind in ea: print(f"{g}, {ind.fitness}") best_genome = ind.genome return best_genome
with open('./genomes.csv', 'w') as genomes_file: ea = generational_ea( max_generations=generations, pop_size=pop_size, # Solve a problem that executes agents in the # environment and obtains fitness from it problem=problems.EnvironmentProblem(runs_per_fitness_eval, simulation_steps, environment, 'reward', gui=gui), representation=Representation(initialize=create_real_vector( bounds=([[-1, 1]] * decoder.wrapped_decoder.length)), decoder=decoder), # The operator pipeline. pipeline=[ ops.tournament_selection, ops.clone, mutate_gaussian(std=mutate_std, hard_bounds=(-1, 1), expected_num_mutations=1), ops.evaluate, ops.pool(size=pop_size), *build_probes( genomes_file) # Inserting all the probes at the end ]) list(ea)
print_population(parents, generation=0) # When running the test harness, just run for two generations # (we use this to quickly ensure our examples don't get bitrot) if os.environ.get(test_env_var, False) == 'True': max_generation = 2 else: max_generation = 100 # Set up a generation counter using the default global context variable generation_counter = util.inc_generation() while generation_counter.generation() < max_generation: offspring = pipe(parents, ops.cyclic_selection, ops.clone, mutate_gaussian(std=.1, expected_num_mutations='isotropic'), ops.evaluate, # create the brood ops.pool(size=len(parents) * BROOD_SIZE), # mu + lambda ops.truncation_selection(size=len(parents), parents=parents)) parents = offspring generation_counter() # increment to the next generation # Just to demonstrate that we can also get the current generation from # the context print_population(parents, context['leap']['generation'])