def evolve(inner_objective): def objective(ind): ind.fitness = -inner_objective(ind) return ind if individual_type == "SingleGenome": pop = cgp.Population(**population_params, genome_params=genome_params) elif individual_type == "MultiGenome": pop = cgp.Population(**population_params, genome_params=[genome_params]) else: raise NotImplementedError ea = cgp.ea.MuPlusLambda(**ea_params) history = {} history["fitness_champion"] = [] def recording_callback(pop): history["fitness_champion"].append(pop.champion.fitness) cgp.evolve(objective, pop, ea, **evolve_params, callback=recording_callback) return history
def test_history_recording(population_params, genome_params, ea_params): pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) evolve_params = {"max_generations": 2, "termination_fitness": 1.0} history = {} history["fitness"] = np.empty( (evolve_params["max_generations"], population_params["n_parents"])) history["fitness_champion"] = np.empty(evolve_params["max_generations"]) history["champion"] = [] def recording_callback(pop): history["fitness"][pop.generation] = pop.fitness_parents() history["fitness_champion"][pop.generation] = pop.champion.fitness history["champion"].append(pop.champion) cgp.evolve(objective_history_recording, pop, ea, **evolve_params, callback=recording_callback) assert np.all(history["fitness"] == pytest.approx(1.0)) assert np.all(history["fitness_champion"] == pytest.approx(1.0)) assert "champion" in history
def main(): objective_params = {"n_runs_per_individual": 10} population_params = {"n_parents": 10, "mutation_rate": 0.04, "seed": 42} genome_params = { "n_inputs": 2, "n_outputs": 1, "n_columns": 16, "n_rows": 1, "levels_back": None, "primitives": ( cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat, ), } ea_params = { "n_offsprings": 4, "tournament_size": 1, "n_processes": 1, # was 4 } evolve_params = {"max_generations": 1000, "min_fitness": 0.0} pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) history = {"fitness_parents": [], "fitness_champion": []} def recording_callback(pop): history["fitness_parents"].append(pop.fitness_parents()) history["fitness_champion"].append(pop.champion.fitness) obj = functools.partial( objective, seed=population_params["seed"], n_runs_per_individual=objective_params["n_runs_per_individual"], ) cgp.evolve(pop, obj, ea, **evolve_params, print_progress=True, callback=recording_callback) display_msg( "History: {}".format(history), True, ) display_msg( "Champion: {}".format(pop.champion.to_sympy()), True, )
def test_objective_must_set_valid_fitness(population_params, genome_params, ea_params): def objective(ind): # missing ind.fitness assignement return ind pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) with pytest.raises(RuntimeError): cgp.evolve(objective, pop, ea, max_generations=10) with pytest.raises(RuntimeError): pop.champion.fitness
def evolution(f_target): """Execute CGP on a regression task for a given target function. Parameters ---------- f_target : Callable Target function Returns ------- dict Dictionary containing the history of the evolution Individual Individual with the highest fitness in the last generation """ population_params = {"n_parents": 10, "seed": 818821} genome_params = { "n_inputs": 2, "n_outputs": 1, "n_columns": 12, "n_rows": 2, "levels_back": 5, "primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat), } ea_params = {"n_offsprings": 10, "tournament_size": 2, "mutation_rate": 0.03, "n_processes": 2} evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # create population that will be evolved pop = cgp.Population(**population_params, genome_params=genome_params) # create instance of evolutionary algorithm ea = cgp.ea.MuPlusLambda(**ea_params) # define callback for recording of fitness over generations history = {} history["fitness_parents"] = [] def recording_callback(pop): history["fitness_parents"].append(pop.fitness_parents()) # the objective passed to evolve should only accept one argument, # the individual obj = functools.partial(objective, target_function=f_target, seed=population_params["seed"]) # Perform the evolution cgp.evolve(pop, obj, ea, **evolve_params, print_progress=True, callback=recording_callback) return history, pop.champion
def evolution(data, population_params, genome_params, ea_params, evolve_params, learning_rate, alpha, fitness_mode): """Execute CGP for given target function. Parameters ---------- data: List(dict) Fields in dict: data_train: np.array data_validate: np.array initial_weights: np.array -> pre defined so that every individual (resp learning rule) has the same starting condition pc0: First principal components (Eigenvectors of the co-variance matrix) population_params: dict with n_parents, mutation_rate, seed genome_params: dict with n_inputs, n_outputs, n_columns, n_rows, levels_back, primitives (allowed function gene values) ea_params: dict with n_offsprings, n_breeding, tournament_size, n_processes, evolve_params: dict with max_generations, min_fitness alpha: Hyperparameter weighting the second term of the fitness function fitness_mode: str ("angle" or "variance") Returns ------- dict Dictionary containing the history of the evolution Individual Individual with the highest fitness in the last generation """ pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) history = {} history["fitness_parents"] = [] history["champion_sympy_expression"] = [] def recording_callback(pop): history["fitness_parents"].append(pop.fitness_parents()) history["champion_sympy_expression"].append( pop.champion.to_sympy() ) obj = functools.partial( objective, data=data, learning_rate=learning_rate, alpha=alpha, mode=fitness_mode) cgp.evolve( pop, obj, ea, **evolve_params, print_progress=True, callback=recording_callback, ) return history, pop.champion
def test_finite_max_generations_or_max_objective_calls(population_params, genome_params, ea_params): def objective(individual): individual.fitness = float(individual.idx) return individual pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) evolve_params = { "max_generations": np.inf, "min_fitness": 0, "max_objective_calls": np.inf, } with pytest.raises(ValueError): cgp.evolve(pop, objective, ea, **evolve_params)
def test_min_fitness_deprecation(population_params, genome_params, ea_params): def objective(individual): individual.fitness = 1.0 return individual pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) with pytest.warns(DeprecationWarning): cgp.evolve(pop, objective, ea, min_fitness=2.0, max_generations=10) with pytest.raises(RuntimeError): cgp.evolve(pop, objective, ea, min_fitness=2.0, termination_fitness=1.5, max_generations=10)
def test_speedup_parallel_evolve(population_params, genome_params, ea_params): # use 4 parents and 4 offsprings to achieve even load on 2, 4 # cores population_params["n_parents"] = 4 ea_params["n_offsprings"] = 4 evolve_params = {"max_generations": 5, "min_fitness": np.inf} # Number of calls to objective: Number of parents + (Number of # parents + offspring) * (N_generations - 1) Initially, we need to # compute the fitness for all parents. Then we compute the fitness # for each parents and offspring in each iteration. n_calls_objective = population_params["n_parents"] + ( population_params["n_parents"] + ea_params["n_offsprings"]) * (evolve_params["max_generations"] - 1) np.random.seed(population_params["seed"]) time_per_objective_call = 0.25 # Serial execution for n_processes in [1, 2, 4]: pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params, n_processes=n_processes) t0 = time.time() cgp.evolve(pop, _objective_speedup_parallel_evolve, ea, **evolve_params) T = time.time() - t0 if n_processes == 1: T_baseline = T # assert that total execution time is roughly equal to # number of objective calls x time per call; serves as a # baseline for subsequent parallel evolutions assert T == pytest.approx(n_calls_objective * time_per_objective_call, rel=0.25) else: # assert that multiprocessing roughly follows a linear speedup. assert T == pytest.approx(T_baseline / n_processes, rel=0.25)
def _test_population(population_params, genome_params, ea_params): evolve_params = {"max_generations": 2000, "termination_fitness": -1e-12} pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) history = {} history["max_fitness_per_generation"] = [] def recording_callback(pop): history["max_fitness_per_generation"].append(pop.champion.fitness) obj = functools.partial(_objective_test_population, rng_seed=population_params["seed"]) cgp.evolve(obj, pop, ea, **evolve_params, callback=recording_callback) assert pop.champion.fitness >= evolve_params["termination_fitness"] return history["max_fitness_per_generation"]
def evolve_generations(self, n_generations: int) -> None: for gen_idx in range(n_generations): self.evaluate_generation() f_vals = [ind.fitness for ind in self.phenotypes] print(f"Gen {gen_idx+1}: {f_vals}") self.phenotypes = cgp.evolve(self.phenotypes, cgp.MUT_PB, cgp.MU, cgp.LAMBDA) # evaluate for the last time self.evaluate_generation()
def evolve(seed): objective_params = {"n_runs_per_individual": 3, "n_total_steps": 2000} genome_params = { "n_inputs": 2, "primitives": ( cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat, ConstantFloatZeroPointOne, ConstantFloatTen, ), } ea_params = {"n_processes": 4} evolve_params = { "max_generations": int(args["--max-generations"]), "termination_fitness": 100.0, } pop = cgp.Population(genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) history = {} history["expr_champion"] = [] history["fitness_champion"] = [] def recording_callback(pop): history["expr_champion"].append(pop.champion.to_sympy()) history["fitness_champion"].append(pop.champion.fitness) obj = functools.partial( objective, seed=seed, n_runs_per_individual=objective_params["n_runs_per_individual"], n_total_steps=objective_params["n_total_steps"], ) pop = cgp.evolve(obj, pop, ea, **evolve_params, print_progress=True, callback=recording_callback) return history, pop.champion
def run(self): self.playing = True while self.playing: self._handle_events() self._update() self._draw() self._clock.tick(self._fps) if not self.running: return # one generation finished and perform evolution again # if current score is very low, then we use a large mutation rate pb = MUT_PB if self._max_score < 500: pb = MUT_PB * 3 elif self._max_score < 1000: pb = MUT_PB * 2 elif self._max_score < 2000: pb = MUT_PB * 1.5 elif self._max_score < 5000: pb = MUT_PB * 1.2 self.pop = cgp.evolve(self.pop, pb, MU, LAMBDA)
def run(self): self.playing = True while self.playing: self._handle_events() self._update() self._draw() self._time.tick(self._frames) if not self.running: return # End of generation and spinning up new one # If score is low, mutation rates are increased pb = MUTATE_PROB if self._max_score < 500: pb = MUTATE_PROB * 3 elif self._max_score < 1000: pb = MUTATE_PROB * 2 elif self._max_score < 2000: pb = MUTATE_PROB * 1.5 elif self._max_score < 5000: pb = MUTATE_PROB * 1.2 self.pop_evo = cgp.evolve(self.pop_evo, pb, MUTATION, LAMBDA)
pop = cgp.Population(**params["population_params"], genome_params=params["genome_params"]) ea = cgp.ea.MuPlusLambda(**params["ea_params"]) history = {} history["fitness"] = np.empty((params["evolve_params"]["max_generations"], params["population_params"]["n_parents"])) history["fitness_champion"] = np.empty( params["evolve_params"]["max_generations"]) history["expr_champion"] = [] def recording_callback(pop): history["fitness"][pop.generation] = pop.fitness_parents() history["fitness_champion"][pop.generation] = pop.champion.fitness history["expr_champion"].append(str(pop.champion.to_sympy()[0])) cgp.evolve( pop, objective, ea, **params["evolve_params"], print_progress=True, callback=recording_callback, ) print(pop.champion.fitness, pop.champion.to_sympy()) with open(f"res_{seed_offset}.pkl", "wb") as f: pickle.dump(history, f)
def record_history(pop, run_nr): history["fitness"].append(pop.fitness_parents()) history["fitness_champion"].append(pop.champion.fitness) history["expr_champion"].append( str(pop.champion.to_sympy(simplify=True))) if len(history["fitness"]) % 2 == 0: np.save("fitness_{}.npy".format(run_nr), history["fitness"]) record_history_wrapped = functools.partial(record_history, run_nr=run_nr) print("Starting evolution.") cgp.evolve( pop, objective_wrapped, ea, params["gp_params"]["max_generations"], params["gp_params"]["min_fitness"], callback=record_history_wrapped, print_progress=True, ) print("Evolution finished. Storing results.") # Evolution of fitness values over time np.save("fitness_{}.npy".format(run_nr), np.array(history["fitness"])) # Store champion of this evolutionary run str_tuple = [ str(len(history["fitness_champion"])), str(pop.champion.fitness) ] for i in range(2): str_tuple.append(str(pop.champion.to_sympy()[i][0]))
def evolution(): population_params = {"n_parents": 1, "mutation_rate": 0.05, "seed": 818821} genome_params = { "n_inputs": 1, "n_outputs": 1, "n_columns": 20, "n_rows": 1, "levels_back": None, "primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Parameter), } ea_params = { "n_offsprings": 4, "n_breeding": 4, "tournament_size": 1, "n_processes": 1 } evolve_params = {"max_generations": 2000, "min_fitness": 0.0} # use an uneven number of gradient steps so they can not easily # average out for clipped values local_search_params = {"lr": 1e-3, "gradient_steps": 9} pop = cgp.Population(**population_params, genome_params=genome_params) # define the function for local search; parameters such as the # learning rate and number of gradient steps are fixed via the use # of `partial`; the local_search function should only receive a # population of individuals as input local_search = functools.partial( cgp.local_search.gradient_based, objective=functools.partial(inner_objective, seed=population_params["seed"]), **local_search_params, ) ea = cgp.ea.MuPlusLambda(**ea_params, local_search=local_search) history = {} history["champion"] = [] history["fitness_parents"] = [] def recording_callback(pop): history["champion"].append(pop.champion) history["fitness_parents"].append(pop.fitness_parents()) obj = functools.partial(objective, seed=population_params["seed"]) cgp.evolve( pop, obj, ea, **evolve_params, print_progress=True, callback=recording_callback, ) return history, pop.champion
sympy_expression = pop.champion.to_sympy() except TypeError: sympy_expression = pop.champion.to_sympy(simplify=False) history["expr_champion"].append(sympy_expression) # the objective passed to evolve should only accept one argument, # the individual rng = np.random.RandomState(seed=population_params["seed"]) obj = functools.partial(objective, rng=rng) # Perform the evolution cgp.evolve(pop, obj, ea, **evolve_params, print_progress=True, callback=recording_callback) # %% # After the evolutionary search has ended, we print the expression # with the highest fitness and plot the search progression and target and evolved functions. print( f"Final expression {pop.champion.to_sympy()} with fitness {pop.champion.fitness}" ) fig = plt.figure(1) plt.plot(history["fitness_champion"]) plt.ylim(1.1 * min(history["fitness_champion"]), 5) plt.xlabel("Generation") plt.ylabel("Loss (Fitness)")
"tournament_size": 1, "mutation_rate": 0.05, "n_processes": 1, }, "genome_params": { "n_inputs": 1, "n_outputs": 1, "n_columns": 10, "n_rows": 2, "levels_back": 2, "primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.ConstantFloat), }, "evolve_params": {"max_generations": 200, "termination_fitness": -1e-12}, } # %% # We then create a Population instance and instantiate the evolutionary algorithm. pop = cgp.Population(**params["population_params"], genome_params=params["genome_params"]) ea = cgp.ea.MuPlusLambda(**params["ea_params"]) # %% # Finally, we call the `evolve` method to perform the evolutionary search. cgp.evolve(pop, objective, ea, **params["evolve_params"], print_progress=True) print(f"evolved function: {pop.champion.to_sympy()}")
history["expression_champion"] = [] history["reward_matrix"] = [] champion_history = [] # not in history, since individuals can't be pickled def recording_callback(pop): history["fitness_champion"].append(pop.champion.fitness) history["expression_champion"].append(str(pop.champion.to_sympy())) history["reward_matrix"].append(pop.champion.reward_matrix) champion_history.append(pop.champion.copy()) obj = functools.partial(objective, network_params=network_params, curriculum_params=curriculum_params, seeds=seeds) start = time.time() cgp.evolve(obj, max_time=max_time, ea=ea, pop=pop, print_progress=True, callback=recording_callback) end = time.time() history['validation_fitness'] = [] history['validated_champion_expression'] = [] history['validation_generation'] = [] for generation, champion in enumerate(champion_history): if generation == 0 or history["fitness_champion"][generation] > history["fitness_champion"][generation-1]: seed = int(seeds[-1] +100) reward = calculate_validation_fitness(champion, seed, network_params, curriculum_params) history['validation_fitness'].append(reward) history['validated_champion_expression'].append(str(champion.to_sympy())) history['validation_generation'].append(generation) print(f"Time elapsed:", end - start)
# %% # We define a callback for recording of fitness over generations history = {} history["fitness_champion"] = [] def recording_callback(pop): history["fitness_champion"].append(pop.champion.fitness) # %% # and finally perform the evolution cgp.evolve( pop, [objective_one, objective_two], ea, **evolve_params, print_progress=True, callback=recording_callback ) # %% # After finishing the evolution, we plot the result and log the final # evolved expression. width = 9.0 fig, axes = plt.subplots(1, 2, figsize=(width, width / scipy.constants.golden)) ax_fitness, ax_function = axes[0], axes[1] ax_fitness.set_xlabel("Generation")
def recording_callback(pop): history["fitness_champion"].append(pop.champion.fitness) history_with_reorder = {} history_with_reorder["fitness_champion"] = [] def recording_callback_with_reorder(pop): history_with_reorder["fitness_champion"].append(pop.champion.fitness) # %% # and finally perform the evolution of the two populations cgp.evolve( pop, objective, ea, **evolve_params, print_progress=True, callback=recording_callback, ) cgp.evolve( pop_with_reorder, objective, ea_with_reorder, **evolve_params, print_progress=True, callback=recording_callback_with_reorder, ) # %% # After finishing the evolution, we plot the evolution of the fittest individual # with and without genome reordering
# Next, we set up the evolutionary search. We define a callback for recording # of fitness over generations history = {} history["fitness_champion"] = [] def recording_callback(pop): history["fitness_champion"].append(pop.champion.fitness) # %% # and finally perform the evolution relying on the libraries default # hyperparameters except that we terminate the evolution as soon as one # individual has reached fitness zero. pop = cgp.evolve(objective, termination_fitness=0.0, print_progress=True, callback=recording_callback) # %% # After finishing the evolution, we plot the result and log the final # evolved expression. width = 9.0 fig, axes = plt.subplots(1, 2, figsize=(width, width / scipy.constants.golden)) ax_fitness, ax_function = axes[0], axes[1] ax_fitness.set_xlabel("Generation") ax_fitness.set_ylabel("Fitness") ax_fitness.plot(history["fitness_champion"], label="Champion")
def test_evolve_two_expressions(population_params, ea_params): """Test evolution of multiple expressions simultaneously. """ def _objective(individual): if individual.fitness is not None: return individual def f0(x): return x[0] * (x[0] + x[0]) def f1(x): return (x[0] * x[1]) - x[1] y0 = cgp.CartesianGraph(individual.genome[0]).to_func() y1 = cgp.CartesianGraph(individual.genome[1]).to_func() loss = 0 for _ in range(100): x0 = np.random.uniform(size=1) x1 = np.random.uniform(size=2) loss += float((f0(x0) - y0(x0))**2) loss += float((f1(x1) - y1(x1))**2) individual.fitness = -loss return individual # contains parameters for two distinct CartesianGraphs as list of # two dicts genome_params = [ { "n_inputs": 1, "n_outputs": 1, "n_columns": 4, "n_rows": 2, "levels_back": 2, "primitives": (cgp.Add, cgp.Mul), }, { "n_inputs": 2, "n_outputs": 1, "n_columns": 2, "n_rows": 2, "levels_back": 2, "primitives": (cgp.Sub, cgp.Mul), }, ] evolve_params = {"max_generations": 2000, "min_fitness": -1e-12} np.random.seed(population_params["seed"]) pop = cgp.Population(**population_params, genome_params=genome_params) ea = cgp.ea.MuPlusLambda(**ea_params) cgp.evolve(pop, _objective, ea, **evolve_params) assert abs(pop.champion.fitness) == pytest.approx(0.0)
# %% # We define a callback for recording of fitness over generations history = {} history["fitness_champion"] = [] def recording_callback(pop): history["fitness_champion"].append(pop.champion.fitness) # %% # and finally perform the evolution pop = cgp.evolve(objective, pop, **evolve_params, print_progress=True, callback=recording_callback) # %% # After finishing the evolution, we print the evolved expression and plot the result. expr = pop.champion.to_sympy() print(expr) print(f"--> x<=0: {expr[0]}, \n x> 0: {expr[1]}") width = 9.0 fig, axes = plt.subplots(1, 2, figsize=(width, width / scipy.constants.golden)) ax_fitness, ax_function = axes[0], axes[1] ax_fitness.set_xlabel("Generation") ax_fitness.set_ylabel("Fitness")
""" f = ind.to_numpy() x = np.linspace(-2.0, 2.0, 100) y = f(x) loss = (f_target(x) - y)**2 time.sleep(0.25) # emulate long fitness evaluation return np.mean(loss) def objective(individual): if not individual.fitness_is_None(): return individual individual.fitness = -inner_objective(individual) return individual # %% # Finally, we call the `evolve` method to perform the evolutionary search. pop = cgp.evolve(objective, max_generations=200, termination_fitness=0.0, print_progress=True) print(f"evolved function: {pop.champion.to_sympy()}")