From 046592a3a87c90450f1cc7b656b674fd63d9cf94 Mon Sep 17 00:00:00 2001 From: SimpleArt <71458112+SimpleArt@users.noreply.github.com> Date: Thu, 3 Dec 2020 17:10:39 -0500 Subject: [PATCH] Split adapting into parts --- src/EasyGA.py | 101 ++++++++++++++++++++++++++++++++++------------ src/attributes.py | 100 ++++++++++++++++++++++++++------------------- 2 files changed, 135 insertions(+), 66 deletions(-) diff --git a/src/EasyGA.py b/src/EasyGA.py index a0328bf..e084719 100644 --- a/src/EasyGA.py +++ b/src/EasyGA.py @@ -66,6 +66,7 @@ class GA(Attributes): # Otherwise evolve the population. else: + self.parent_selection_impl(self) self.crossover_population_impl(self) self.survivor_selection_impl(self) @@ -79,13 +80,17 @@ class GA(Attributes): # Save the population to the database self.save_population() + # Adapt the ga if the generation times the adapt rate + # passes through an integer value. + adapt_counter = self.adapt_rate*self.current_generation + if int(adapt_counter) > int(adapt_counter - self.adapt_rate): + self.adapt() + number_of_generations -= 1 self.current_generation += 1 - self.adapt() - - def evolve(self, number_of_generations = 1, consider_termination = True): + def evolve(self, number_of_generations = 100, consider_termination = True): """Runs the ga until the termination point has been satisfied.""" while self.active(): @@ -99,20 +104,21 @@ class GA(Attributes): def adapt(self): + """Adapts the ga to hopefully get better results.""" + + self.adapt_probabilities() + self.adapt_population() + + + def adapt_probabilities(self): """Modifies the parent ratio and mutation rates based on the adapt rate and percent converged. Attempts to balance out so that a portion of the population gradually approaches the solution. - - Afterwards also performs weighted crossover between - the best chromosome and the rest of the chromosomes, - using negative weights to push away chromosomes that - are too similar and small positive weights to pull - in chromosomes that are too different. """ # Don't adapt - if self.adapt_rate is None or self.adapt_rate <= 0: + if self.adapt_probability_rate is None or self.adapt_probability_rate <= 0: return # Amount of the population desired to converge (default 50%) @@ -123,32 +129,77 @@ class GA(Attributes): tol = lambda i: sqrt(abs(best_chromosome.fitness - self.population[i].fitness)) # Change rates with: - multiplier = 1 + self.adapt_rate - - self.parent_ratio = min(self.percent_converged, self.parent_ratio * multiplier) + multiplier = 1 + self.adapt_probability_rate # Too few converged: cross more and mutate less - if tol(amount_converged//2) > tol(amount_converged//2)*4: + if tol(amount_converged//2) > tol(amount_converged//4)*2: - self.selection_probability = min(0.99, self.selection_probability * multiplier) - self.chromosome_mutation_rate = max(0.05, self.chromosome_mutation_rate / multiplier) - self.gene_mutation_rate = max(0.01, self.gene_mutation_rate / multiplier) + self.selection_probability = min( + self.max_selection_probability, + self.selection_probability * multiplier + ) + + self.chromosome_mutation_rate = max( + self.min_chromosome_mutation_rate, + self.chromosome_mutation_rate / multiplier + ) + + self.gene_mutation_rate = max( + self.min_gene_mutation_rate, + self.gene_mutation_rate / multiplier + ) # Too many converged: cross less and mutate more else: - self.selection_probability = max(0.01, self.selection_probability / multiplier) - self.chromosome_mutation_rate = min(0.25, self.chromosome_mutation_rate * multiplier) - self.gene_mutation_rate = min(0.99, self.gene_mutation_rate * multiplier) + self.selection_probability = max( + self.min_selection_probability, + self.selection_probability / multiplier + ) - # First non-zero tolerance after amount_converged/8 - for i in range(amount_converged//8, len(self.population)): + self.chromosome_mutation_rate = min( + self.max_chromosome_mutation_rate, + self.chromosome_mutation_rate * multiplier + ) + + self.gene_mutation_rate = min( + self.max_gene_mutation_rate, + self.gene_mutation_rate * multiplier + ) + + + def adapt_population(self): + """ + Performs weighted crossover between the best chromosome and + the rest of the chromosomes, using negative weights to push + away chromosomes that are too similar and small positive + weights to pull in chromosomes that are too different. + """ + + # Don't adapt the population. + if self.adapt_population_flag == False: + return + + # Amount of the population desired to converge (default 50%) + amount_converged = round(self.percent_converged*len(self.population)) + + # Difference between best and i-th chromosomes + best_chromosome = self.population[0] + tol = lambda i: sqrt(abs(best_chromosome.fitness - self.population[i].fitness)) + + # First non-zero tolerance after amount_converged/4 + for i in range(amount_converged//4, len(self.population)): if tol(i) > 0: break # First significantly different tolerance for j in range(i, len(self.population)): - if tol(j) > 4*tol(i): + if tol(j) > 2*tol(i): + break + + # Second significantly different tolerance + for k in range(j, len(self.population)): + if tol(k) > 2*tol(j): break # Strongly cross the best chromosome with the worst chromosomes @@ -161,7 +212,7 @@ class GA(Attributes): self, self.population[n], best_chromosome, - min(0.25, (4*tol(n) - tol(j)) / tol(n)) + min(0.25, (2*tol(n) - tol(j)) / tol(n)) ) # If negative weights can't be used, @@ -170,7 +221,7 @@ class GA(Attributes): self.population[n] = self.crossover_individual_impl( self, self.population[n], - self.population[j], + self.population[k], 0.75 ) diff --git a/src/attributes.py b/src/attributes.py index 8048d21..864541b 100644 --- a/src/attributes.py +++ b/src/attributes.py @@ -48,47 +48,55 @@ class Attributes: } def __init__(self, - chromosome_length = 10, - population_size = 10, - chromosome_impl = None, - gene_impl = lambda: random.randint(1, 10), - population = None, - target_fitness_type = 'max', - update_fitness = True, - parent_ratio = 0.10, - selection_probability = 0.50, - tournament_size_ratio = 0.10, - current_generation = 0, - current_fitness = 0, - generation_goal = 15, - fitness_goal = None, - tolerance_goal = None, - percent_converged = 0.50, - chromosome_mutation_rate = 0.15, - gene_mutation_rate = 0.05, - adapt_rate = 0.15, - initialization_impl = Initialization_Methods.random_initialization, - fitness_function_impl = Fitness_Examples.is_it_5, - make_population = create_population, - make_chromosome = create_chromosome, - make_gene = create_gene, - parent_selection_impl = Parent_Selection.Rank.tournament, - crossover_individual_impl = Crossover_Methods.Individual.single_point, - crossover_population_impl = Crossover_Methods.Population.sequential_selection, - survivor_selection_impl = Survivor_Selection.fill_in_best, - mutation_individual_impl = Mutation_Methods.Individual.individual_genes, - mutation_population_impl = Mutation_Methods.Population.random_avoid_best, - termination_impl = Termination_Methods.fitness_generation_tolerance, - Database = sql_database.SQL_Database, - database_name = 'database.db', - sql_create_data_structure = """CREATE TABLE IF NOT EXISTS data ( - id INTEGER PRIMARY KEY, - config_id INTEGER DEFAULT NULL, - generation INTEGER NOT NULL, - fitness REAL, - chromosome TEXT - ); """, - Graph = matplotlib_graph.Matplotlib_Graph + chromosome_length = 10, + population_size = 10, + chromosome_impl = None, + gene_impl = lambda: random.randint(1, 10), + population = None, + target_fitness_type = 'max', + update_fitness = True, + parent_ratio = 0.10, + selection_probability = 0.50, + tournament_size_ratio = 0.10, + current_generation = 0, + current_fitness = 0, + generation_goal = 15, + fitness_goal = None, + tolerance_goal = None, + percent_converged = 0.50, + chromosome_mutation_rate = 0.15, + gene_mutation_rate = 0.05, + adapt_rate = 0.20, + adapt_probability_rate = 0.15, + adapt_population_flag = True, + max_selection_probability = 0.99, + max_chromosome_mutation_rate = 0.20, + max_gene_mutation_rate = None, + min_selection_probability = 0.01, + min_chromosome_mutation_rate = 0.01, + min_gene_mutation_rate = None, + initialization_impl = Initialization_Methods.random_initialization, + fitness_function_impl = Fitness_Examples.is_it_5, + make_population = create_population, + make_chromosome = create_chromosome, + make_gene = create_gene, + parent_selection_impl = Parent_Selection.Rank.tournament, + crossover_individual_impl = Crossover_Methods.Individual.single_point, + crossover_population_impl = Crossover_Methods.Population.sequential_selection, + survivor_selection_impl = Survivor_Selection.fill_in_best, + mutation_individual_impl = Mutation_Methods.Individual.individual_genes, + mutation_population_impl = Mutation_Methods.Population.random_avoid_best, + termination_impl = Termination_Methods.fitness_generation_tolerance, + Database = sql_database.SQL_Database, + database_name = 'database.db', + sql_create_data_structure = """CREATE TABLE IF NOT EXISTS data ( + id INTEGER PRIMARY KEY, + config_id INTEGER DEFAULT NULL, + generation INTEGER NOT NULL, + fitness REAL, + chromosome TEXT + ); """, + Graph = matplotlib_graph.Matplotlib_Graph ): # Initilization variables @@ -113,6 +121,16 @@ class Attributes: self.tolerance_goal = deepcopy(tolerance_goal) self.percent_converged = deepcopy(percent_converged) self.adapt_rate = deepcopy(adapt_rate) + self.adapt_probability_rate = deepcopy(adapt_probability_rate) + self.adapt_population_flag = deepcopy(adapt_population_flag) + + # Bounds on probabilities when adapting + self.max_selection_probability = max_selection_probability + self.max_chromosome_mutation_rate = max_chromosome_mutation_rate + self.max_gene_mutation_rate = gene_mutation_rate if (max_gene_mutation_rate is None) else max_gene_mutation_rate + self.min_selection_probability = min_selection_probability + self.min_chromosome_mutation_rate = min_chromosome_mutation_rate + self.min_gene_mutation_rate = gene_mutation_rate if (min_gene_mutation_rate is None) else min_gene_mutation_rate # Mutation variables self.chromosome_mutation_rate = deepcopy(chromosome_mutation_rate)