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:
SimpleArt
2020-10-13 12:48:20 -04:00
parent 5e6f9b0427
commit fb213f04dd
8 changed files with 223 additions and 77 deletions

View File

@ -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)