Added more structure methods and some quality of life changes
Overall cleaned up a lot of comments. EasyGA: - Code cleanup. Population: - Added sort_by_best_fitness - Added parent/mating pool methods. - Renamed some methods for consistency. Chromosome: - Added get_gene(index). Parent Selection: - Improved selection methods to use the ga.selection_probability so that the roulette selection actually works well. - Added stochastic selection. Survivor Selection: - Added fill_in_random and fill_in_parents_then_random. Crossover/Mutation: - Cleaned up code.
This commit is contained in:
@ -10,11 +10,11 @@ from fitness_function import Fitness_Examples
|
||||
from initialization import Initialization_Methods
|
||||
from termination_point import Termination_Methods
|
||||
|
||||
# Population Methods
|
||||
from survivor_selection import Survivor_Selection
|
||||
# Parent/Survivor Selection Methods
|
||||
from parent_selection import Parent_Selection
|
||||
from survivor_selection import Survivor_Selection
|
||||
|
||||
# Manipulation Methods
|
||||
# Genetic Operator Methods
|
||||
from mutation import Mutation_Methods
|
||||
from crossover import Crossover_Methods
|
||||
|
||||
@ -39,7 +39,6 @@ class GA:
|
||||
# Termination variables
|
||||
self.current_generation = 0
|
||||
self.current_fitness = 0
|
||||
|
||||
self.generation_goal = 15
|
||||
self.fitness_goal = 9
|
||||
|
||||
@ -53,13 +52,11 @@ class GA:
|
||||
self.make_chromosome = create_chromosome
|
||||
self.make_gene = create_gene
|
||||
|
||||
# Selects which chromosomes should be automaticly moved to the next population
|
||||
self.survivor_selection_impl = Survivor_Selection.fill_in_best
|
||||
|
||||
# Methods for accomplishing parent-selection -> Crossover -> Mutation
|
||||
# Methods for accomplishing Parent-Selection -> Crossover -> Survivor_Selection -> Mutation
|
||||
self.parent_selection_impl = Parent_Selection.Tournament.with_replacement
|
||||
self.crossover_individual_impl = Crossover_Methods.Individual.single_point_crossover
|
||||
self.crossover_population_impl = Crossover_Methods.Population.random_selection
|
||||
self.survivor_selection_impl = Survivor_Selection.fill_in_best
|
||||
self.mutation_individual_impl = Mutation_Methods.Individual.single_gene
|
||||
self.mutation_population_impl = Mutation_Methods.Population.random_selection
|
||||
|
||||
@ -69,57 +66,63 @@ class GA:
|
||||
|
||||
def evolve_generation(self, number_of_generations = 1, consider_termination = True):
|
||||
"""Evolves the ga the specified number of generations."""
|
||||
while(number_of_generations > 0 and (consider_termination == False or self.termination_impl(self))):
|
||||
|
||||
# Evolve the specified number of generations
|
||||
# and if consider_termination flag is set then
|
||||
# also check if termination conditions reached
|
||||
while(number_of_generations > 0 and (not consider_termination or self.termination_impl(self))):
|
||||
|
||||
# If its the first generation then initialize the population
|
||||
if self.current_generation == 0:
|
||||
self.initialize_population()
|
||||
self.set_all_fitness(self.population.chromosome_list)
|
||||
self.population.set_all_chromosomes(self.sort_by_best_fitness(self.population.get_all_chromosomes()))
|
||||
self.set_all_fitness()
|
||||
self.population.sort_by_best_fitness(self)
|
||||
|
||||
# Otherwise evolve the population
|
||||
else:
|
||||
self.set_all_fitness(self.population.chromosome_list)
|
||||
self.population.reset_mating_pool()
|
||||
self.set_all_fitness()
|
||||
self.population.set_all_chromosomes(self.sort_by_best_fitness(self.population.get_all_chromosomes()))
|
||||
self.parent_selection_impl(self)
|
||||
next_population = self.crossover_population_impl(self)
|
||||
next_population = self.survivor_selection_impl(self, next_population)
|
||||
self.population = next_population
|
||||
self.survivor_selection_impl(self, next_population)
|
||||
self.mutation_population_impl(self)
|
||||
self.set_all_fitness(self.population.chromosome_list)
|
||||
self.population.set_all_chromosomes(self.sort_by_best_fitness(self.population.get_all_chromosomes()))
|
||||
|
||||
number_of_generations -= 1
|
||||
|
||||
self.current_generation += 1
|
||||
|
||||
|
||||
def evolve(self):
|
||||
"""Runs the ga until the termination point has been satisfied."""
|
||||
# While the termination point hasnt been reached keep running
|
||||
while(self.active()):
|
||||
self.evolve_generation()
|
||||
|
||||
|
||||
def active(self):
|
||||
"""Returns if the ga should terminate base on the termination implimented"""
|
||||
# Send termination_impl the whole ga class
|
||||
"""Returns if the ga should terminate based on the termination implimented."""
|
||||
return self.termination_impl(self)
|
||||
|
||||
|
||||
def initialize_population(self):
|
||||
"""Initialize the population using the initialization
|
||||
implimentation that is currently set
|
||||
"""Initialize the population using
|
||||
the initialization implimentation
|
||||
that is currently set.
|
||||
"""
|
||||
self.population = self.initialization_impl(self)
|
||||
|
||||
|
||||
def set_all_fitness(self, chromosome_set):
|
||||
def set_all_fitness(self):
|
||||
"""Will get and set the fitness of each chromosome in the population.
|
||||
If update_fitness is set then all fitness values are updated.
|
||||
Otherwise only fitness values set to None (i.e. uninitialized
|
||||
fitness values) are updated."""
|
||||
# Get each chromosome in the population
|
||||
fitness values) are updated.
|
||||
"""
|
||||
|
||||
for chromosome in chromosome_set:
|
||||
if(chromosome.fitness == None or self.update_fitness == True):
|
||||
# Set the chromosomes fitness using the fitness function
|
||||
# Check each chromosome
|
||||
for chromosome in self.population.get_all_chromosomes():
|
||||
|
||||
# Update fitness if needed or asked by the user
|
||||
if(chromosome.get_fitness() is None or self.update_fitness):
|
||||
chromosome.set_fitness(self.fitness_function_impl(chromosome))
|
||||
|
||||
|
||||
|
||||
@ -6,17 +6,22 @@ class Crossover_Methods:
|
||||
"""Methods for selecting chromosomes to crossover"""
|
||||
|
||||
def sequential_selection(ga):
|
||||
"""Select sequential pairs from the mating pool"""
|
||||
"""Select sequential pairs from the mating pool.
|
||||
Every parent is paired with the previous parent.
|
||||
The first parent is paired with the last parent.
|
||||
"""
|
||||
|
||||
mating_pool = ga.population.mating_pool
|
||||
return ga.make_population([ga.crossover_individual_impl(ga, mating_pool[index], mating_pool[index+1]) for index in range(len(mating_pool)-1)])
|
||||
mating_pool = ga.population.get_mating_pool()
|
||||
return ga.make_population([ga.crossover_individual_impl(ga, mating_pool[index], mating_pool[index-1]) for index in range(len(mating_pool))])
|
||||
|
||||
|
||||
def random_selection(ga):
|
||||
"""Select random pairs from the mating pool"""
|
||||
"""Select random pairs from the mating pool.
|
||||
Every parent is paired with a random parent.
|
||||
"""
|
||||
|
||||
mating_pool = ga.population.mating_pool
|
||||
return ga.make_population([ga.crossover_individual_impl(ga, random.choice(mating_pool), random.choice(mating_pool)) for n in mating_pool])
|
||||
mating_pool = ga.population.get_mating_pool()
|
||||
return ga.make_population([ga.crossover_individual_impl(ga, parent, random.choice(mating_pool)) for parent in mating_pool])
|
||||
|
||||
|
||||
class Individual:
|
||||
|
||||
@ -13,7 +13,7 @@ class Mutation_Methods:
|
||||
|
||||
# Randomly apply mutations
|
||||
if random.uniform(0, 1) < ga.mutation_rate:
|
||||
ga.population.set_chromosome(ga.mutation_individual_impl(ga, ga.population.get_all_chromosomes()[index]), index)
|
||||
ga.population.set_chromosome(ga.mutation_individual_impl(ga, ga.population.get_chromosome(index)), index)
|
||||
|
||||
|
||||
class Individual:
|
||||
|
||||
@ -10,26 +10,40 @@ class Parent_Selection:
|
||||
The total number of parents selected is determined by parent_ratio, an attribute to the GA object.
|
||||
"""
|
||||
|
||||
# Error if can't select parents
|
||||
if ga.selection_probability <= 0:
|
||||
print("Selection probability must be greater than 0 to select parents.")
|
||||
return
|
||||
|
||||
# Make sure the population is sorted by fitness
|
||||
ga.population.sort_by_best_fitness(ga)
|
||||
|
||||
# Choose the tournament size.
|
||||
# Use no less than 5 chromosomes per tournament.
|
||||
tournament_size = int(ga.population.size()*ga.tournament_size_ratio)
|
||||
if tournament_size < 5:
|
||||
tournament_size = 5
|
||||
# Probability used for determining if a chromosome should enter the mating pool.
|
||||
selection_probability = ga.selection_probability
|
||||
|
||||
# Repeat tournaments until the mating pool is large enough.
|
||||
while (len(ga.population.mating_pool) < ga.population.size()*ga.parent_ratio):
|
||||
while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio):
|
||||
|
||||
# Generate a random tournament group and sort by fitness.
|
||||
tournament_group = ga.sort_by_best_fitness([random.choice(ga.population.get_all_chromosomes()) for n in range(tournament_size)])
|
||||
tournament_group = sorted([random.randint(0, ga.population.size()-1) for n in range(tournament_size)])
|
||||
|
||||
# For each chromosome, add it to the mating pool based on its rank in the tournament.
|
||||
for index in range(tournament_size):
|
||||
# Probability required is selection_probability * (1-selection_probability) ^ (tournament_size-index+1)
|
||||
|
||||
# Probability required is selection_probability * (1-selection_probability) ^ index
|
||||
# e.g. top ranked fitness has probability: selection_probability
|
||||
# second ranked fitness has probability: selection_probability * (1-selection_probability)
|
||||
# third ranked fitness has probability: selection_probability * (1-selection_probability)^2
|
||||
# etc.
|
||||
if random.uniform(0, 1) < selection_probability * pow(1-selection_probability, index):
|
||||
ga.population.mating_pool.append(tournament_group[index])
|
||||
if random.uniform(0, 1) < ga.selection_probability * pow(1-ga.selection_probability, index):
|
||||
ga.population.set_parent(tournament_group[index])
|
||||
|
||||
# Stop if parent ratio reached
|
||||
if len(ga.population.get_mating_pool()) >= ga.population.size()*ga.parent_ratio:
|
||||
break
|
||||
|
||||
|
||||
class Roulette:
|
||||
@ -40,27 +54,74 @@ class Parent_Selection:
|
||||
that it will be selected. Using the example of a casino roulette wheel.
|
||||
Where the chromosomes are the numbers to be selected and the board size for
|
||||
those numbers are directly proportional to the chromosome's current fitness. Where
|
||||
the ball falls is a randomly generated number between 0 and 1"""
|
||||
the ball falls is a randomly generated number between 0 and 1."""
|
||||
|
||||
# Make sure the population is sorted by fitness
|
||||
ga.population.sort_by_best_fitness(ga)
|
||||
|
||||
# Error if can't select parents
|
||||
if ga.selection_probability <= 0:
|
||||
print("Selection probability must be greater than 0 to select parents.")
|
||||
return
|
||||
|
||||
# Error if not all chromosomes has positive fitness
|
||||
if (ga.population.get_chromosome(0).get_fitness() == 0 or ga.population.get_chromosome(-1).get_fitness() < 0):
|
||||
print("Error using roulette selection, all fitnesses must be positive.")
|
||||
print("Consider using stockastic roulette selection or tournament selection.")
|
||||
return
|
||||
|
||||
# The sum of all the fitnessess in a population
|
||||
total_fitness = sum(ga.population.chromosome_list[i].get_fitness() for i in range(len(ga.population.chromosome_list)))
|
||||
rel_fitnesses = []
|
||||
|
||||
# A list of each chromosome's relative chance of getting chosen
|
||||
for chromosome in ga.population.chromosome_list:
|
||||
if (total_fitness != 0):
|
||||
rel_fitnesses.append(float(chromosome.fitness)/total_fitness)
|
||||
fitness_sum = sum(chromosome.get_fitness() for chromosome in ga.population.get_all_chromosomes())
|
||||
|
||||
# A list of ranges that represent the probability of a chromosome getting chosen
|
||||
probability = [sum(rel_fitnesses[:i+1]) for i in range(len(rel_fitnesses))]
|
||||
probability = [ga.selection_probability]
|
||||
|
||||
# The chance of being selected increases incrementally
|
||||
for chromosome in ga.population.chromosome_list:
|
||||
probability.append(probability[-1]+chromosome.fitness/fitness_sum)
|
||||
|
||||
probability = probability[1:]
|
||||
|
||||
# Loops until it reaches a desired mating pool size
|
||||
while (len(ga.population.mating_pool) < len(ga.population.get_all_chromosomes())*ga.parent_ratio):
|
||||
while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio):
|
||||
|
||||
# Spin the roulette
|
||||
rand_number = random.random()
|
||||
|
||||
# Loop through the list of probabilities
|
||||
for i in range(len(probability)):
|
||||
# If the probability is greater than the random_number, then select that chromosome
|
||||
if (probability[i] >= rand_number):
|
||||
ga.population.mating_pool.append(ga.population.chromosome_list[i])
|
||||
# Find where the roulette landed.
|
||||
for index in range(len(probability)):
|
||||
if (probability[index] >= rand_number):
|
||||
ga.population.set_parent(index)
|
||||
break
|
||||
|
||||
|
||||
def stochastic_selection(ga):
|
||||
"""Stochastic roulette selection works based off of how strong the fitness is of the
|
||||
chromosomes in the population. The stronger the fitness the higher the probability
|
||||
that it will be selected. Instead of dividing the fitness by the sum of all fitnesses
|
||||
and incrementally increasing the chance something is selected, the stochastic method
|
||||
just divides by the highest fitness and selects randomly."""
|
||||
|
||||
# Make sure the population is sorted by fitness
|
||||
ga.population.sort_by_best_fitness(ga)
|
||||
|
||||
# Error if can't select parents
|
||||
if ga.selection_probability <= 0 or ga.selection_probability >= 1:
|
||||
print("Selection probability must be between 0 and 1 to select parents.")
|
||||
return
|
||||
|
||||
# Error if the highest fitness is not positive
|
||||
if ga.population.get_chromosome(0).get_fitness() <= 0:
|
||||
print("Error using stochastic roulette selection, best fitness must be positive.")
|
||||
print("Consider using tournament selection.")
|
||||
return
|
||||
|
||||
# Loops until it reaches a desired mating pool size
|
||||
while (len(ga.population.get_mating_pool()) < ga.population.size()*ga.parent_ratio):
|
||||
|
||||
# Selected chromosome
|
||||
index = random.randint(0, ga.population.size()-1)
|
||||
|
||||
# Probability of becoming a parent is fitness/max_fitness
|
||||
if random.uniform(ga.selection_probability, 1) < ga.population.get_chromosome(index).get_fitness() / ga.population.get_chromosome(0).get_fitness():
|
||||
ga.population.set_parent(index)
|
||||
|
||||
@ -3,8 +3,15 @@ import EasyGA
|
||||
|
||||
# Create the Genetic algorithm
|
||||
ga = EasyGA.GA()
|
||||
ga.population_size = 100
|
||||
ga.generation_goal = 200
|
||||
ga.parent_selection_impl = EasyGA.Parent_Selection.Roulette.stochastic_selection
|
||||
ga.crossover_population_impl = EasyGA.Crossover_Methods.Population.sequential_selection
|
||||
ga.survivor_selection_impl = EasyGA.Survivor_Selection.fill_in_parents_then_random
|
||||
|
||||
ga.evolve()
|
||||
ga.set_all_fitness()
|
||||
ga.population.set_all_chromosomes(ga.sort_by_best_fitness(ga.population.get_all_chromosomes()))
|
||||
|
||||
print(f"Current Generation: {ga.current_generation}")
|
||||
ga.population.print_all()
|
||||
|
||||
@ -24,9 +24,15 @@ class Chromosome:
|
||||
|
||||
|
||||
def remove_gene(self, index):
|
||||
"""Removes the gene at the given index"""
|
||||
del self.gene_list[index]
|
||||
|
||||
|
||||
def get_gene(self, index):
|
||||
"""Returns the gene at the given index"""
|
||||
return gene_list[index]
|
||||
|
||||
|
||||
def get_gene_list(self):
|
||||
return self.gene_list
|
||||
|
||||
|
||||
@ -12,6 +12,11 @@ class Population:
|
||||
self.mating_pool = []
|
||||
|
||||
|
||||
def sort_by_best_fitness(self, ga):
|
||||
"""Sorts the population by fitness"""
|
||||
self.set_all_chromosomes(ga.sort_by_best_fitness(self.chromosome_list))
|
||||
|
||||
|
||||
def size(self):
|
||||
"""Returns the size of the population"""
|
||||
return len(self.chromosome_list)
|
||||
@ -29,28 +34,68 @@ class Population:
|
||||
self.chromosome_list.insert(index, chromosome)
|
||||
|
||||
|
||||
def add_parent(self, chromosome):
|
||||
"""Adds a chromosome to the mating pool"""
|
||||
self.mating_pool.append(chromosome)
|
||||
|
||||
|
||||
def remove_chromosome(self, index):
|
||||
"""removes a chromosome from the indicated index"""
|
||||
"""Removes a chromosome from the indicated index from the population"""
|
||||
del self.chromosome_list[index]
|
||||
|
||||
|
||||
def remove_parent(self, index):
|
||||
"""Removes a parent from the indicated index from the mating pool"""
|
||||
del self.mating_pool[index]
|
||||
|
||||
|
||||
def reset_mating_pool(self):
|
||||
"""Clears the mating pool"""
|
||||
self.mating_pool = []
|
||||
|
||||
|
||||
def get_chromosome(self, index):
|
||||
"""Returns the chromosome at the given index in the population"""
|
||||
return self.chromosome_list[index]
|
||||
|
||||
|
||||
def get_parent(self, index):
|
||||
"""Returns the parent at the given index in the mating pool"""
|
||||
return self.mating_pool[index]
|
||||
|
||||
|
||||
def get_all_chromosomes(self):
|
||||
"""returns all chromosomes in the population"""
|
||||
"""Returns all chromosomes in the population"""
|
||||
return self.chromosome_list
|
||||
|
||||
|
||||
def get_mating_pool(self):
|
||||
"""Returns chromosomes in the mating pool"""
|
||||
return self.mating_pool
|
||||
|
||||
|
||||
def get_fitness(self):
|
||||
"""returns the population's fitness"""
|
||||
"""Returns the population's fitness"""
|
||||
return self.fitness
|
||||
|
||||
|
||||
def set_all_chromosomes(self, chromosomes):
|
||||
self.chromosome_list = chromosomes
|
||||
def set_parent(self, index):
|
||||
"""Sets the index chromosome from the population as a parent"""
|
||||
self.add_parent(self.get_chromosome(index))
|
||||
|
||||
|
||||
def set_chromosome(self, chromosome, index = -1):
|
||||
if index == -1:
|
||||
index = len(self.chromosomes)-1
|
||||
def set_all_chromosomes(self, chromosome_list):
|
||||
"""Sets the chromosome list"""
|
||||
self.chromosome_list = chromosome_list
|
||||
|
||||
|
||||
def set_mating_pool(self, chromosome_list):
|
||||
"""Sets entire mating pool"""
|
||||
self.mating_pool = chromosome_list
|
||||
|
||||
|
||||
def set_chromosome(self, chromosome, index):
|
||||
"""Sets the chromosome at the given index"""
|
||||
self.chromosome_list[index] = chromosome
|
||||
|
||||
|
||||
|
||||
@ -4,5 +4,24 @@ class Survivor_Selection:
|
||||
"""Survivor selection determines which individuals should be brought to the next generation"""
|
||||
|
||||
def fill_in_best(ga, next_population):
|
||||
"""Fills in the next population with the best chromosomes from the last population until the population size is met."""
|
||||
return ga.make_population(ga.population.get_all_chromosomes()[:ga.population.size()-next_population.size()] + next_population.get_all_chromosomes())
|
||||
"""Fills in the next population with the best chromosomes from the last population"""
|
||||
|
||||
ga.population.set_all_chromosomes(ga.population.get_all_chromosomes()[:ga.population.size()-next_population.size()] + next_population.get_all_chromosomes())
|
||||
|
||||
|
||||
def fill_in_random(ga, next_population):
|
||||
"""Fills in the next population with random chromosomes from the last population"""
|
||||
|
||||
ga.population.set_all_chromosomes([
|
||||
random.choice(ga.population.get_all_chromosomes())
|
||||
for n in range(ga.population.size()-next_population.size())]
|
||||
+ next_population.get_all_chromosomes())
|
||||
|
||||
|
||||
def fill_in_parents_then_random(ga, next_population):
|
||||
"""Fills in the next population with all parents followed by random chromosomes from the last population"""
|
||||
|
||||
ga.population.set_all_chromosomes([
|
||||
random.choice(ga.population.get_all_chromosomes())
|
||||
for n in range(ga.population.size()-len(ga.population.get_mating_pool())-next_population.size())]
|
||||
+ ga.population.get_mating_pool() + next_population.get_all_chromosomes())
|
||||
|
||||
Reference in New Issue
Block a user