From e7ac0e23f4d65303a3422a544fed8bd482b03c14 Mon Sep 17 00:00:00 2001 From: RyleyGG Date: Tue, 6 Oct 2020 17:55:17 -0400 Subject: [PATCH] Optimizations/updates 1. Deleted duplicate functions in EasyGA 2. Added new index-dependent fitness example 3. GA now auto-sorts by best fitness immediately after the fitness is calculated across the board 4. Removed 'selected' status flag from the Chromosome flag 5. Added mating_pool attribute to the population 6. Changed other code to be in line with 4 and 5 7. Optimized tournament selection method --- src/EasyGA.py | 53 +++++------ src/crossover/crossover_methods.py | 5 +- src/fitness_function/fitness_examples.py | 12 +++ .../chromosome_structure/chromosome.py | 1 - .../population_structure/population.py | 1 + src/run_testing.py | 2 +- src/selection/selection_methods.py | 91 +++++-------------- 7 files changed, 61 insertions(+), 104 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index 4f27075..159c986 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -18,17 +18,20 @@ class GA: """Initialize the GA.""" # Initilization variables self.chromosome_length = 10 - self.population_size = 100 + self.population_size = 150 self.chromosome_impl = None self.gene_impl = None self.population = None + self.target_fitness_type = 'maximum' + + self.parent_ratio = 0.1 # Termination varibles self.current_generation = 0 self.generation_goal = 50 self.current_fitness = 0 - self.generation_goal = 100 - self.fitness_goal = 3 + self.generation_goal = 250 + self.fitness_goal = 9 # Mutation variables self.mutation_rate = 0.10 @@ -37,7 +40,7 @@ class GA: # Defualt EastGA implimentation structure self.initialization_impl = Initialization_Methods().random_initialization - self.fitness_function_impl = Fitness_Examples().is_it_5 + self.fitness_function_impl = Fitness_Examples().index_dependent_values # Selects which chromosomes should be automaticly moved to the next population self.survivor_selection_impl = Selection_Methods().Survivor_Selection().remove_two_worst # Methods for accomplishing parent-selection -> Crossover -> Mutation @@ -54,6 +57,7 @@ class GA: 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.parent_selection_impl(self) next_population = self.crossover_impl(self) @@ -62,6 +66,7 @@ class GA: self.population = next_population self.set_all_fitness(self.population.chromosome_list) + self.population.set_all_chromosomes(self.sort_by_best_fitness()) number_of_generations -= 1 self.current_generation += 1 @@ -94,34 +99,26 @@ class GA: # Set the chromosomes fitness using the fitness function chromosome.set_fitness(self.fitness_function_impl(chromosome)) - 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 sort_by_best_fitness(self, chromosome_set = None): - 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))): - # 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.parent_selection_impl(self) - next_population = self.crossover_impl(self) - next_population = self.survivor_selection_impl(self, next_population) - next_population.set_all_chromosomes(self.mutation_impl(self, next_population.get_all_chromosomes())) + if chromosome_set == None: + chromosome_set = self.population.get_all_chromosomes() - self.population = next_population - self.set_all_fitness(self.population.chromosome_list) + chromosome_set_temp = chromosome_set + not_sorted_check = 0 + while (not_sorted_check != len(chromosome_set_temp)): + not_sorted_check = 0 + for i in range(len(chromosome_set_temp)): + if ((i + 1 < len(chromosome_set_temp)) and (chromosome_set_temp[i + 1].fitness > chromosome_set_temp[i].fitness)): + temp = chromosome_set[i] + chromosome_set_temp[i] = chromosome_set[i + 1] + chromosome_set_temp[i + 1] = temp + else: + not_sorted_check += 1 - number_of_generations -= 1 - self.current_generation += 1 + chromosome_set = chromosome_set_temp - def active(self): - """Returns if the ga should terminate base on the termination implimented""" - return self.termination_impl(self) + return chromosome_set def make_gene(self,value): return create_gene(value) diff --git a/src/crossover/crossover_methods.py b/src/crossover/crossover_methods.py index 4e2b1d1..48e55bf 100644 --- a/src/crossover/crossover_methods.py +++ b/src/crossover/crossover_methods.py @@ -14,10 +14,7 @@ class Crossover_Methods: """Single point crossover is when a "point" is selected and the genetic make up of the two parent chromosomes are "Crossed" or better known as swapped""" - crossover_pool = [] - for i in range(ga.population_size): - if ga.population.get_all_chromosomes()[i].selected: - crossover_pool.append(ga.population.get_all_chromosomes()[i]) + crossover_pool = ga.population.mating_pool new_population = Population() for i in range(len(crossover_pool)): diff --git a/src/fitness_function/fitness_examples.py b/src/fitness_function/fitness_examples.py index 8e29245..1354e9e 100644 --- a/src/fitness_function/fitness_examples.py +++ b/src/fitness_function/fitness_examples.py @@ -14,3 +14,15 @@ class Fitness_Examples: fitness += 1 return fitness + + def index_dependent_values(self, chromosome): + """A very simple case test function - If the chromosomes gene value is a 5 add one + to the chromosomes overall fitness value.""" + # Overall fitness value + fitness = 0 + # For each gene in the chromosome + for i in range(len(chromosome.gene_list)): + if (chromosome.gene_list[i].value == i+1): + fitness += 1 + + return fitness diff --git a/src/initialization/chromosome_structure/chromosome.py b/src/initialization/chromosome_structure/chromosome.py index 3a1978c..f9e16e6 100644 --- a/src/initialization/chromosome_structure/chromosome.py +++ b/src/initialization/chromosome_structure/chromosome.py @@ -6,7 +6,6 @@ class Chromosome: else: self.gene_list = genes self.fitness = None - self.selected = False def add_gene(self, gene, index = -1): if index == -1: diff --git a/src/initialization/population_structure/population.py b/src/initialization/population_structure/population.py index 0f414a9..94bdcb7 100644 --- a/src/initialization/population_structure/population.py +++ b/src/initialization/population_structure/population.py @@ -7,6 +7,7 @@ class Population: else: self.chromosome_list = chromosomes self.fitness = None + self.mating_pool = [] def get_closet_fitness(self,value): # Get the chomosome that has the closets fitness to the value defined diff --git a/src/run_testing.py b/src/run_testing.py index 390986b..2381627 100644 --- a/src/run_testing.py +++ b/src/run_testing.py @@ -13,4 +13,4 @@ ga.gene_impl = [random.randrange,1,100] ga.evolve() # Print the current population -ga.population.print_all() +ga.population.print_all() \ No newline at end of file diff --git a/src/selection/selection_methods.py b/src/selection/selection_methods.py index 201d468..953b117 100644 --- a/src/selection/selection_methods.py +++ b/src/selection/selection_methods.py @@ -12,68 +12,33 @@ class Selection_Methods: class Parent_Selection: class Tournament: def with_replacement(self, ga): - tournament_size = int(len(ga.population.get_all_chromosomes())/10) #currently hard-coded for purposes of the example. + tournament_size = int(len(ga.population.get_all_chromosomes())*ga.parent_ratio/10) if tournament_size < 3: - tournament_size = int(len(ga.population.get_all_chromosomes())/3) - parent_ratio = 0.25 - - #selection_probability is the likelihood that a chromosome will be selected. - #best chromosome in a tournament is given a selection probablity of selection_probability - #2nd best is given probability of selection_probability*(1-selection_probability) - #3rd best is given probability of selection_probability*(1-selection_probability)**2 + tournament_size = int(len(ga.population.get_all_chromosomes())*ga.parent_ratio/3) + + # Probability used for determining if a chromosome should enter the mating pool. selection_probability = 0.95 - total_selected = 0 #Total Chromosomes selected - - while (total_selected < parent_ratio*ga.population_size): - #create & gather tournament group - tournament_group = [] - - for i in range(tournament_size): - tournament_group.append(random.choice(ga.population.get_all_chromosomes())) + + # Repeat tournaments until the mating pool is large enough. + while (len(ga.population.mating_pool) < len(ga.population.get_all_chromosomes())*ga.parent_ratio): - #Sort the tournament contenders based on their fitness - #currently hard-coded to only consider higher fitness = better; can be changed once this impl is agreed on - #also currently uses bubble sort because its easy - tournament_group_temp = tournament_group - not_sorted_check = 0 - while (not_sorted_check != len(tournament_group_temp)): - not_sorted_check = 0 - for i in range(len(tournament_group_temp)): - if ((i + 1 < len(tournament_group_temp)) and (tournament_group_temp[i + 1].fitness > tournament_group_temp[i].fitness)): - temp = tournament_group[i] - tournament_group_temp[i] = tournament_group[i + 1] - tournament_group_temp[i + 1] = temp - else: - not_sorted_check += 1 - - tournament_group = tournament_group_temp - - #After sorting by fitness, randomly select a chromosome based on selection_probability - selected_chromosome_tournament_index = 0 - for i in range(tournament_size): - random_num = random.uniform(0,1) - - #ugly implementation but its functional - if i == 0: - if random_num <= selection_probability: - tournament_group[i].selected = True - total_selected += 1 - selected_chromosome_tournament_index = i - break - else: - if random_num <= selection_probability*((1-selection_probability)**(i-1)): - tournament_group[i].selected = True - total_selected += 1 - selected_chromosome_tournament_index = i - break + # 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)]) + + # 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) + # 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+1): + ga.population.mating_pool.append(tournament_group[index]) class Survivor_Selection: def repeated_crossover(self, ga, next_population): #Might be cheating? I don't know honestly - RG while len(next_population.get_all_chromosomes()) < ga.population_size: - crossover_pool = [] - for i in range(ga.population_size): - if ga.population.get_all_chromosomes()[i].selected: - crossover_pool.append(ga.population.get_all_chromosomes()[i]) + crossover_pool = ga.population.mating_pool split_point = random.randint(0,ga.chromosome_length) chromosome_list = [] @@ -95,23 +60,9 @@ class Selection_Methods: return next_population def remove_two_worst(self, ga, next_population): - - #Bubble sorting by highest fitness - temp_population = ga.population - not_sorted_check = 0 - while (not_sorted_check != len(temp_population.get_all_chromosomes())): - not_sorted_check = 0 - for i in range(len(temp_population.get_all_chromosomes())): - if ((i + 1 < len(temp_population.get_all_chromosomes())) and (temp_population.get_all_chromosomes()[i + 1].fitness > temp_population.get_all_chromosomes()[i].fitness)): - temp = temp_population.get_all_chromosomes()[i] - temp_population.get_all_chromosomes()[i] = ga.population.get_all_chromosomes()[i + 1] - temp_population.get_all_chromosomes()[i + 1] = temp - else: - not_sorted_check += 1 - iterator = 0 while len(next_population.get_all_chromosomes()) < ga.population_size: - next_population.add_chromosome(temp_population.get_all_chromosomes()[iterator]) + next_population.add_chromosome(ga.population.get_all_chromosomes()[iterator]) iterator += 1 return next_population