Skip to content

Commit

Permalink
Merge pull request #195 from jakobj/maint/remove-crossover
Browse files Browse the repository at this point in the history
Remove all traces of crossover/breeding pool
  • Loading branch information
mschmidt87 committed Jul 20, 2020
2 parents 2635d53 + 4b0f994 commit d652645
Show file tree
Hide file tree
Showing 11 changed files with 17 additions and 112 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Follow these steps to solve a basic regression problem:
"primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat),
}
ea_params = {"n_offsprings": 10, "n_breeding": 10, "tournament_size": 2, "n_processes": 2}
ea_params = {"n_offsprings": 10, "tournament_size": 2, "n_processes": 2}
evolve_params = {"max_generations": 1000, "min_fitness": 0.0}
Expand Down
24 changes: 6 additions & 18 deletions cgp/ea/mu_plus_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class MuPlusLambda:
def __init__(
self,
n_offsprings: int,
n_breeding: int,
tournament_size: int,
*,
n_processes: int = 1,
Expand All @@ -33,8 +32,6 @@ def __init__(
----------
n_offsprings : int
Number of offspring in each iteration.
n_breeding : int
Number of parents to use for breeding in each iteration.
tournament_size : int
Tournament size in each iteration.
n_processes : int, optional
Expand All @@ -50,13 +47,6 @@ def __init__(
"""
self.n_offsprings = n_offsprings

if n_breeding < n_offsprings:
raise ValueError(
"size of breeding pool must be at least as large "
"as the desired number of offsprings"
)
self.n_breeding = n_breeding

self.tournament_size = tournament_size
self.n_processes = n_processes
self.local_search = local_search
Expand Down Expand Up @@ -136,17 +126,15 @@ def step(
return pop

def _create_new_offspring_generation(self, pop: Population) -> List[IndividualBase]:
# fill breeding pool via tournament selection from parent
# population
breeding_pool: List[IndividualBase] = []
while len(breeding_pool) < self.n_breeding:
# use tournament selection to randomly select individuals from
# parent population
offsprings: List[IndividualBase] = []
while len(offsprings) < self.n_offsprings:
tournament_pool = pop.rng.permutation(pop.parents)[: self.tournament_size]
best_in_tournament = sorted(tournament_pool, key=lambda x: -x.fitness)[0]
breeding_pool.append(best_in_tournament.clone())
offsprings.append(best_in_tournament.clone())

# create offsprings by applying crossover to breeding pool and mutating
# resulting individuals
offsprings = pop.crossover(breeding_pool, self.n_offsprings)
# mutate individuals to create offsprings
offsprings = pop.mutate(offsprings)

for ind in offsprings:
Expand Down
4 changes: 2 additions & 2 deletions cgp/genome.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def randomize(self, rng: np.random.RandomState) -> None:
Parameters
----------
rng : numpy.RandomState
Random number generator instance to use for crossover.
Random number generator instance to use for randomizing.
Returns
----------
Expand Down Expand Up @@ -354,7 +354,7 @@ def mutate(self, mutation_rate: float, rng: np.random.RandomState):
mutation_rate : float
Proportion of genes to be mutated, between 0 and 1.
rng : numpy.random.RandomState
Random number generator instance to use for crossover.
Random number generator instance to use for mutating.
Returns
----------
Expand Down
45 changes: 0 additions & 45 deletions cgp/population.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,51 +105,6 @@ def generate_random_individual(self) -> IndividualBase:
ind.idx = self.get_idx_for_new_individual()
return ind

def crossover(
self, breeding_pool: List[IndividualBase], n_offsprings: int
) -> List[IndividualBase]:
"""Create an offspring population via crossover.
Parameters
----------
breeding_pool : List[IndividualBase]
List of individuals from which the offspring are created.
n_offsprings : int
Number of offspring to be created.
Returns
----------
List[IndividualBase]
List of offspring individuals.
"""
# in principle crossover would rely on a procedure like the
# following:
# offsprings = []
# while len(offsprings) < n_offsprings:
# first_parent, second_parent = self.rng.permutation(breeding_pool)[:2]
# offsprings.append(first_parent.crossover(second_parent, self.rng))

# return offsprings
# however, as cross over tends to disrupt the search in in CGP
# (Miller, 1999) crossover is skipped, instead the best
# individuals from breeding pool are returned.
# reference:
# Miller, J. F. (1999). An empirical study of the efficiency
# of learning boolean functions using a cartesian genetic
# programming approach. In Proceedings of the 1st Annual
# Conference on Genetic and Evolutionary Computation-Volume 2,
# pages 1135–1142. Morgan Kaufmann Publishers Inc.
assert len(breeding_pool) >= n_offsprings

def sort_func(ind: IndividualBase) -> float:
if isinstance(ind.fitness, float):
return ind.fitness
else:
raise ValueError(f"Individual fitness value is of wrong type {type(ind.fitness)}.")

# Sort individuals in descending order
return sorted(breeding_pool, key=sort_func, reverse=True)[:n_offsprings]

def mutate(self, offsprings: List[IndividualBase]) -> List[IndividualBase]:
"""Mutate a list of offspring invididuals.
Expand Down
2 changes: 1 addition & 1 deletion examples/example_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def objective(individual):

params = {
"population_params": {"n_parents": 10, "mutation_rate": 0.05, "seed": 8188211},
"ea_params": {"n_offsprings": 10, "n_breeding": 10, "tournament_size": 1, "n_processes": 1},
"ea_params": {"n_offsprings": 10, "tournament_size": 1, "n_processes": 1},
"genome_params": {
"n_inputs": 1,
"n_outputs": 1,
Expand Down
1 change: 0 additions & 1 deletion examples/example_differential_evo_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ def objective(individual, seed):

ea_params = {
"n_offsprings": 4,
"n_breeding": 4,
"tournament_size": 1,
"n_processes": 1,
"k_local_search": 2,
Expand Down
2 changes: 1 addition & 1 deletion examples/example_evo_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def evolution(f_target):
"primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.Div, cgp.ConstantFloat),
}

ea_params = {"n_offsprings": 10, "n_breeding": 10, "tournament_size": 2, "n_processes": 2}
ea_params = {"n_offsprings": 10, "tournament_size": 2, "n_processes": 2}

evolve_params = {"max_generations": 1000, "min_fitness": 0.0}

Expand Down
2 changes: 1 addition & 1 deletion examples/example_mountain_car.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def evolve(seed):
),
}

ea_params = {"n_offsprings": 4, "n_breeding": 4, "tournament_size": 1, "n_processes": 4}
ea_params = {"n_offsprings": 4, "tournament_size": 1, "n_processes": 4}

evolve_params = {"max_generations": 3000, "min_fitness": 200.0}

Expand Down
2 changes: 1 addition & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def population_params(mutation_rate, rng_seed):

@fixture
def ea_params():
return {"n_offsprings": 5, "n_breeding": 5, "tournament_size": 2}
return {"n_offsprings": 5, "tournament_size": 2}


@fixture
Expand Down
15 changes: 4 additions & 11 deletions test/test_ea_mu_plus_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import cgp


def test_objective_with_label(population_params, genome_params):
def test_objective_with_label(population_params, genome_params, ea_params):
def objective_without_label(individual):
individual.fitness = -2.0
return individual
Expand All @@ -17,7 +17,7 @@ def objective_with_label(individual, label):

pop = cgp.Population(**population_params, genome_params=genome_params)

ea = cgp.ea.MuPlusLambda(1, 2, 1)
ea = cgp.ea.MuPlusLambda(**ea_params)
ea.initialize_fitness_parents(pop, objective_without_label)

ea.step(pop, objective_without_label)
Expand Down Expand Up @@ -84,7 +84,7 @@ def objective(ind):


def test_local_search_is_only_applied_to_best_k_individuals(
population_params, local_search_params
population_params, local_search_params, ea_params,
):

torch = pytest.importorskip("torch")
Expand Down Expand Up @@ -119,7 +119,7 @@ def objective(ind):
cgp.local_search.gradient_based, objective=inner_objective, **local_search_params
)

ea = cgp.ea.MuPlusLambda(5, 5, 1, local_search=local_search, k_local_search=k_local_search)
ea = cgp.ea.MuPlusLambda(**ea_params, local_search=local_search, k_local_search=k_local_search)
ea.initialize_fitness_parents(pop, objective)
ea.step(pop, objective)

Expand All @@ -130,13 +130,6 @@ def objective(ind):
assert pop[idx].genome._parameter_names_to_values["<p1>"] == pytest.approx(1.0)


def test_raise_n_offsprings_less_than_n_breeding():
n_offsprings = 10
n_breeding = 5
with pytest.raises(ValueError):
cgp.ea.MuPlusLambda(n_offsprings, n_breeding, 1)


def test_raise_fitness_has_wrong_type(population_params, genome_params, ea_params):
def objective(individual):
individual.fitness = int(5.0) # should raise error since fitness should be float
Expand Down
30 changes: 0 additions & 30 deletions test/test_population.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,6 @@ def test_champion(population_simple_fitness):
assert pop.champion == pop.parents[-1]


def test_crossover_too_small(population_simple_fitness):
pop = population_simple_fitness
# Breeding pool too small
with pytest.raises(AssertionError):
pop.crossover(pop.parents[:1], 2)


def test_crossover_one_offspring_all_parents(population_simple_fitness):
pop = population_simple_fitness
# Check is best parent is chosen if n_offsprings = 1
offspring = pop.crossover(pop.parents, 1)
assert offspring[0] == pop.champion


def test_crossover_one_offspring_breeding_pool(population_simple_fitness):
pop = population_simple_fitness
# Check is best parent in smaller breeding pool (arbitrarily picking 3 parents)
# is chosen if n_offsprings = 1
offspring = pop.crossover(pop.parents[:3], 1)
assert offspring[0] == max(pop.parents[:3], key=lambda x: x.fitness)


def test_crossover_two_offspring(population_simple_fitness):
pop = population_simple_fitness
# Check if best two parents are chosen if n_offsprings = 2
offspring = pop.crossover(pop.parents, 2)
assert offspring[0] == pop.champion
assert offspring[1] == sorted(pop.parents, key=lambda x: -x.fitness)[1]


def test_mutate(population_params, genome_params):
population_params["mutation_rate"] = 0.5
pop = cgp.Population(**population_params, genome_params=genome_params)
Expand Down

0 comments on commit d652645

Please sign in to comment.