Example for evolutionary regression with local search via evolution strategiesΒΆ

Example demonstrating the use of Cartesian genetic programming for a regression task that involves numeric constants. Local search via evolution strategies is used to determine numeric leaf values of the graph.

# The docopt str is added explicitly to ensure compatibility with
# sphinx-gallery.
docopt_str = """
  Usage:
    example_local_search_evolution_strategies.py [--max-generations=<N>]

  Options:
    -h --help
    --max-generations=<N>  Maximum number of generations [default: 500]

"""

import functools

import matplotlib.pyplot as plt
import numpy as np
import scipy.constants
from docopt import docopt

import cgp

args = docopt(docopt_str)

We first define the target function. Note that this function contains numeric values which are initially not available as constants to the search.

def f_target(x):
    return np.e * x ** 2 + 1.0 + np.pi

Then we define the objective function for the evolution. It consists of an inner objective which accepts a NumPy-compatible function as its first argument and returns the mean-squared error between the expression represented by a given individual and the target function evaluated on a set of random points. This inner objective is used by the local search to determine appropriate values for Parameter node and the actual objective function to update the fitness of the individual.

def inner_objective(ind, seed):
    """Return a loss for the numpy-compatible function f. Used for
    calculating the fitness of each individual and for the local
    search of numeric leaf values.

    """

    f = ind.to_numpy()
    rng = np.random.RandomState(seed)
    batch_size = 500
    x = rng.uniform(-5, 5, size=batch_size)
    y = f(x)
    return -np.mean((f_target(x) - y) ** 2)


def objective(individual, seed):
    """Objective function of the regression task."""

    if not individual.fitness_is_None():
        return individual

    individual.fitness = inner_objective(individual, seed)

    return individual

Next, we define the parameters for the genome of individuals, the evolutionary algorithm, and the local search.

seed = 1234

genome_params = {
    "n_columns": 36,
    "primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Parameter),
}

ea_params = {"k_local_search": 2}

evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0}

# restrict the number of steps in the local search; since parameter
# values are propagated from parents to offsprings, parameter values
# may be iteratively improved across generations despite the small
# number of steps per generation
local_search_params = {"max_steps": 5}

We then create a Population instance and instantiate the local search and evolutionary algorithm.

pop = cgp.Population(genome_params=genome_params)

# define the function for local search; an instance of the
# EvolutionStrategies can be called with an individual as an argument
# for which the local search is performed
local_search = cgp.local_search.EvolutionStrategies(
    objective=functools.partial(inner_objective, seed=seed + 1234),
    seed=seed + 12345,
    **local_search_params,
)

ea = cgp.ea.MuPlusLambda(**ea_params, local_search=local_search)

We define a recording callback closure for bookkeeping of the progression of the evolution.

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=seed + 123456)

Finally, we call the evolve method to perform the evolutionary search.

pop = cgp.evolve(obj, pop, ea, **evolve_params, print_progress=True, callback=recording_callback)

Out:

[2/500] max fitness: -488.3692156484158
[3/500] max fitness: -3.523375872763281
[4/500] max fitness: -3.269534371772616
[5/500] max fitness: -3.269534371772616
[6/500] max fitness: -3.269534371772616
[7/500] max fitness: -3.269534371772616
[8/500] max fitness: -3.269534371772616
[9/500] max fitness: -3.269534371772616
[10/500] max fitness: -3.269534371772616
[11/500] max fitness: -3.269534371772616
[12/500] max fitness: -3.269534371772616
[13/500] max fitness: -3.269534371772616
[14/500] max fitness: -3.269534371772616
[15/500] max fitness: -3.269534371772616
[16/500] max fitness: -3.269534371772616
[17/500] max fitness: -3.269534371772616
[18/500] max fitness: -3.269534371772616
[19/500] max fitness: -3.269534371772616
[20/500] max fitness: -3.269534371772616
[21/500] max fitness: -3.269534371772616
[22/500] max fitness: -3.269534371772616
[23/500] max fitness: -3.269534371772616
[24/500] max fitness: -3.269534371772616
[25/500] max fitness: -3.269534371772616
[26/500] max fitness: -3.269534371772616
[27/500] max fitness: -3.269534371772616
[28/500] max fitness: -3.269534371772616
[29/500] max fitness: -3.269534371772616
[30/500] max fitness: -3.269534371772616
[31/500] max fitness: -3.269534371772616
[32/500] max fitness: -3.269534371772616
[33/500] max fitness: -3.269534371772616
[34/500] max fitness: -3.269534371772616
[35/500] max fitness: -3.269534371772616
[36/500] max fitness: -3.269534371772616
[37/500] max fitness: -3.269534371772616
[38/500] max fitness: -3.269534371772616
[39/500] max fitness: -3.269534371772616
[40/500] max fitness: -3.269534371772616
[41/500] max fitness: -3.269534371772616
[42/500] max fitness: -3.269534371772616
[43/500] max fitness: -3.269534371772616
[44/500] max fitness: -3.269534371772616
[45/500] max fitness: -3.269534371772616
[46/500] max fitness: -3.269534371772616
[47/500] max fitness: -3.269534371772616
[48/500] max fitness: -3.269534371772616
[49/500] max fitness: -3.269534371772616
[50/500] max fitness: -3.269534371772616
[51/500] max fitness: -3.269534371772616
[52/500] max fitness: -3.269534371772616
[53/500] max fitness: -3.269534371772616
[54/500] max fitness: -3.269534371772616
[55/500] max fitness: -3.269534371772616
[56/500] max fitness: -3.269534371772616
[57/500] max fitness: -3.269534371772616
[58/500] max fitness: -3.269534371772616
[59/500] max fitness: -3.269534371772616
[60/500] max fitness: -3.269534371772616
[61/500] max fitness: -3.269534371772616
[62/500] max fitness: -3.269534371772616
[63/500] max fitness: -3.269534371772616
[64/500] max fitness: -3.269534371772616
[65/500] max fitness: -3.269534371772616
[66/500] max fitness: -3.269534371772616
[67/500] max fitness: -3.269534371772616
[68/500] max fitness: -3.269534371772616
[69/500] max fitness: -3.269534371772616
[70/500] max fitness: -3.269534371772616
[71/500] max fitness: -3.269534371772616
[72/500] max fitness: -3.269534371772616
[73/500] max fitness: -3.269534371772616
[74/500] max fitness: -3.269534371772616
[75/500] max fitness: -3.269534371772616
[76/500] max fitness: -3.269534371772616
[77/500] max fitness: -3.269534371772616
[78/500] max fitness: -3.269534371772616
[79/500] max fitness: -3.269534371772616
[80/500] max fitness: -3.269534371772616
[81/500] max fitness: -3.269534371772616
[82/500] max fitness: -3.269534371772616
[83/500] max fitness: -3.269534371772616
[84/500] max fitness: -3.269534371772616
[85/500] max fitness: -3.269534371772616
[86/500] max fitness: -3.269534371772616
[87/500] max fitness: -3.269534371772616
[88/500] max fitness: -3.269534371772616
[89/500] max fitness: -3.269534371772616
[90/500] max fitness: -3.269534371772616
[91/500] max fitness: -3.269534371772616
[92/500] max fitness: -3.269534371772616
[93/500] max fitness: -3.269534371772616
[94/500] max fitness: -3.269534371772616
[95/500] max fitness: -3.269534371772616
[96/500] max fitness: -3.269534371772616
[97/500] max fitness: -3.269534371772616
[98/500] max fitness: -3.269534371772616
[99/500] max fitness: -3.269534371772616
[100/500] max fitness: -3.269534371772616
[101/500] max fitness: -3.269534371772616
[102/500] max fitness: -3.269534371772616
[103/500] max fitness: -3.269534371772616
[104/500] max fitness: -3.269534371772616
[105/500] max fitness: -3.269534371772616
[106/500] max fitness: -3.269534371772616
[107/500] max fitness: -3.269534371772616
[108/500] max fitness: -3.269534371772616
[109/500] max fitness: -3.269534371772616
[110/500] max fitness: -3.269534371772616
[111/500] max fitness: -3.269534371772616
[112/500] max fitness: -1.1468917684859816
[113/500] max fitness: -0.23546802790711463
[114/500] max fitness: -0.015548376080520178
[115/500] max fitness: -0.015548376080520178
[116/500] max fitness: -0.015548376080520178
[117/500] max fitness: -0.015548376080520178
[118/500] max fitness: -0.015548376080520178
[119/500] max fitness: -0.015548376080520178
[120/500] max fitness: -0.015548376080520178
[121/500] max fitness: -0.015548376080520178
[122/500] max fitness: -0.015548376080520178
[123/500] max fitness: -0.015548376080520178
[124/500] max fitness: -0.015548376080520178
[125/500] max fitness: -0.015548376080520178
[126/500] max fitness: -0.015548376080520178
[127/500] max fitness: -0.015548376080520178
[128/500] max fitness: -0.015548376080520178
[129/500] max fitness: -0.015548376080520178
[130/500] max fitness: -0.015548376080520178
[131/500] max fitness: -0.015548376080520178
[132/500] max fitness: -0.015548376080520178
[133/500] max fitness: -0.015548376080520178
[134/500] max fitness: -0.015548376080520178
[135/500] max fitness: -0.015548376080520178
[136/500] max fitness: -0.015548376080520178
[137/500] max fitness: -0.015548376080520178
[138/500] max fitness: -0.015548376080520178
[139/500] max fitness: -0.015548376080520178
[140/500] max fitness: -0.015548376080520178
[141/500] max fitness: -0.015548376080520178
[142/500] max fitness: -0.015548376080520178
[143/500] max fitness: -0.015548376080520178
[144/500] max fitness: -0.015548376080520178
[145/500] max fitness: -0.015548376080520178
[146/500] max fitness: -0.015548376080520178
[147/500] max fitness: -0.009285467809965631
[148/500] max fitness: -0.0013328541057003737
[149/500] max fitness: -0.00027220146415326836
[150/500] max fitness: -8.051899268065928e-06
[151/500] max fitness: -4.631393123938792e-06
[152/500] max fitness: -2.2010616321260843e-07
[153/500] max fitness: -1.1330884758117508e-08
[154/500] max fitness: -4.0372918264009766e-10
[155/500] max fitness: -4.387319918400777e-11

After finishing the evolution, we plot the result and log the final evolved expression.

width = 9.0
fig = plt.figure(figsize=(width, width / scipy.constants.golden))

ax_fitness = fig.add_subplot(121)
ax_fitness.set_xlabel("Generation")
ax_fitness.set_ylabel("Fitness")
ax_fitness.set_yscale("symlog")

ax_function = fig.add_subplot(122)
ax_function.set_ylabel(r"$f(x)$")
ax_function.set_xlabel(r"$x$")


print(f"Final expression {pop.champion.to_sympy()} with fitness {pop.champion.fitness}")

history_fitness = np.array(history["fitness_parents"])
ax_fitness.plot(np.max(history_fitness, axis=1), label="Champion")
ax_fitness.plot(np.mean(history_fitness, axis=1), label="Population mean")

x = np.linspace(-5.0, 5, 100).reshape(-1, 1)
f = pop.champion.to_func()
y = [f(xi) for xi in x]
ax_function.plot(x, f_target(x), lw=2, label="Target")
ax_function.plot(x, y, lw=1, label="Target", marker="x")

plt.savefig("example_local_search_evolution_strategies.pdf", dpi=300)
example local search evolution strategies

Out:

Final expression 2.718282388910569*x_0**2 + 4.1415927164883464 with fitness -4.387319918400777e-11

Total running time of the script: ( 0 minutes 31.332 seconds)

Gallery generated by Sphinx-Gallery