Merge pull request #13 from danielwilczak101/ryley_beta

Selection/Crossover/Mutation Framework Update
This commit is contained in:
Ryley
2020-10-06 21:12:09 -04:00
committed by GitHub
25 changed files with 414 additions and 81 deletions

View File

@ -4,71 +4,75 @@ from initialization import Population as create_population
from initialization import Chromosome as create_chromosome from initialization import Chromosome as create_chromosome
from initialization import Gene as create_gene from initialization import Gene as create_gene
# Structure Methods # Structure Methods
from fitness_function import Fitness_methods from fitness_function import Fitness_Examples
from initialization import Initialization_methods from initialization import Initialization_Methods
from termination_point import Termination_methods from termination_point import Termination_Methods
# Population Methods # Population Methods
from survivor_selection import Survivor_methods from selection import Selection_Methods
# Manipulation Methods # Manipulation Methods
from parent_selection import Parent_methods from mutation import Mutation_Methods
from mutation import Mutation_methods from crossover import Crossover_Methods
from crossover import Crossover_methods
class GA: class GA:
def __init__(self): def __init__(self):
"""Initialize the GA.""" """Initialize the GA."""
# Initilization variables # Initilization variables
self.chromosome_length = 3 self.chromosome_length = 10
self.population_size = 5 self.population_size = 150
self.chromosome_impl = None self.chromosome_impl = None
self.gene_impl = None self.gene_impl = None
self.population = None self.population = None
# Termination varibles self.target_fitness_type = 'maximum'
self.update_fitness = True
# Selection variables
self.parent_ratio = 0.1
self.selection_probablity = 0.95
# Termination variables
self.current_generation = 0 self.current_generation = 0
self.generation_goal = 3 self.generation_goal = 50
self.current_fitness = 0 self.current_fitness = 0
self.fitness_goal = 3 self.generation_goal = 250
self.fitness_goal = 9
# Mutation variables # Mutation variables
self.mutation_rate = 0.03 self.mutation_rate = 0.10
# Rerun already computed fitness # Default EasyGA implimentation structure
self.update_fitness = False self.initialization_impl = Initialization_Methods().random_initialization
self.fitness_function_impl = Fitness_Examples().index_dependent_values
# Defualt EastGA implimentation structure
self.initialization_impl = Initialization_methods.random_initialization
self.fitness_funciton_impl = Fitness_methods.is_it_5
# Selects which chromosomes should be automaticly moved to the next population # Selects which chromosomes should be automaticly moved to the next population
#self.survivor_selection_impl = Survivor_methods. self.survivor_selection_impl = Selection_Methods().Survivor_Selection().remove_two_worst
# Methods for accomplishing parent-selection -> Crossover -> Mutation # Methods for accomplishing parent-selection -> Crossover -> Mutation
# self.parent_selection_impl = Parent_methods.roulette_selection self.parent_selection_impl = Selection_Methods().Parent_Selection().Roulette().roulette_selection
#self.crossover_impl = Crossover_methods. self.crossover_impl = Crossover_Methods().single_point_crossover
#self.mutation_impl = Mutation_methods. self.mutation_impl = Mutation_Methods().per_gene_mutation
# The type of termination to impliment # The type of termination to impliment
self.termination_impl = Termination_methods.generation_based self.termination_impl = Termination_Methods().generation_based
def evolve_generation(self, number_of_generations = 1): def evolve_generation(self, number_of_generations = 1, consider_termination = True):
"""Evolves the ga the specified number of generations.""" """Evolves the ga the specified number of generations."""
while(number_of_generations > 0): while(number_of_generations > 0 and (consider_termination == False or self.termination_impl(self))):
# If its the first generation then initialize the population # If its the first generation then initialize the population
if(self.current_generation == 0): if self.current_generation == 0:
# Initialize the population
self.initialize_population() self.initialize_population()
# First get the fitness of the population self.set_all_fitness(self.population.chromosome_list)
self.get_population_fitness(self.population.chromosome_list) self.population.set_all_chromosomes(self.sort_by_best_fitness())
# Selection - Triggers flags in the chromosome if its been selected else:
# self.selection_impl(self) self.parent_selection_impl(self)
# Crossover - Takes the flagged chromosome_list and crosses there genetic next_population = self.crossover_impl(self)
# makup to make new offsprings. next_population = self.survivor_selection_impl(self, next_population)
# self.crossover_impl(self) next_population.set_all_chromosomes(self.mutation_impl(self, next_population.get_all_chromosomes()))
# Repopulate - Manipulates the population to some desired way
# self.repopulate_impl(self) self.population = next_population
# Mutation - Manipulates the population very slightly self.set_all_fitness(self.population.chromosome_list)
# self.mutation_impl(self) self.population.set_all_chromosomes(self.sort_by_best_fitness())
# self.parent_selection_impl(self)
# Counter for the local number of generations in evolve_generation
number_of_generations -= 1 number_of_generations -= 1
# Add one to the current overall generation
self.current_generation += 1 self.current_generation += 1
def evolve(self): def evolve(self):
@ -87,18 +91,38 @@ class GA:
implimentation that is currently set""" implimentation that is currently set"""
self.population = self.initialization_impl(self) self.population = self.initialization_impl(self)
def get_population_fitness(self,population): def set_all_fitness(self,chromosome_set):
"""Will get and set the fitness of each chromosome in the population. """Will get and set the fitness of each chromosome in the population.
If update_fitness is set then all fitness values are updated. If update_fitness is set then all fitness values are updated.
Otherwise only fitness values set to None (i.e. uninitialized Otherwise only fitness values set to None (i.e. uninitialized
fitness values) are updated.""" fitness values) are updated."""
# Get each chromosome in the population # Get each chromosome in the population
for chromosome in population:
# If the fitness is not set then get its fitness or if allways getting for chromosome in chromosome_set:
# fitness is turn on then always get the fitness of the chromosome.
if(chromosome.fitness == None or self.update_fitness == True): if(chromosome.fitness == None or self.update_fitness == True):
# Set the chromosomes fitness using the fitness function # Set the chromosomes fitness using the fitness function
chromosome.fitness = self.fitness_funciton_impl(chromosome) chromosome.set_fitness(self.fitness_function_impl(chromosome))
def sort_by_best_fitness(self, chromosome_set = None):
if chromosome_set == None:
chromosome_set = self.population.get_all_chromosomes()
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
chromosome_set = chromosome_set_temp
return chromosome_set
def make_gene(self,value): def make_gene(self,value):
"""Let's the user create a gene.""" """Let's the user create a gene."""

View File

@ -1,2 +1,2 @@
# FROM (. means local) file_name IMPORT function_name # FROM (. means local) file_name IMPORT function_name
from .methods import Crossover_methods from .crossover_methods import Crossover_Methods

View File

@ -0,0 +1,37 @@
import random
from initialization.chromosome_structure.chromosome import Chromosome
from initialization.population_structure.population import Population
class Crossover_Methods:
""" Crossover explination goes here.
Points - Defined as sections between the chromosomes genetic makeup
"""
def __init__(self):
pass
def single_point_crossover(self, ga):
"""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 = ga.population.mating_pool
new_population = Population()
for i in range(len(crossover_pool)):
if i + 1 < len(crossover_pool):
new_gene_set = []
parent_one = crossover_pool[i].get_genes()
parent_two = crossover_pool[i+1].get_genes()
#halfway_point = int(ga.chromosome_length/2)
split_point = random.randint(0,ga.chromosome_length)
new_gene_set.extend(parent_one[0:split_point])
new_gene_set.extend(parent_two[split_point:])
new_chromosome = Chromosome(new_gene_set)
new_population.add_chromosome(new_chromosome)
return new_population
def multi_point_crossover(self, ga,number_of_points = 2):
"""Multi point crossover is when a specific number (More then one) of
"points" are created to merge the genetic makup of the chromosomes."""
pass

View File

View File

@ -1,2 +1,2 @@
# FROM (. means local) file_name IMPORT class name # FROM (. means local) file_name IMPORT class name
from .methods import Fitness_methods from .fitness_examples import Fitness_Examples

View File

@ -0,0 +1,28 @@
class Fitness_Examples:
"""Fitness function examples used"""
def is_it_5(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 gene in chromosome.gene_list:
# Check if its value = 5
if(gene.value == 5):
# If its value is 5 then add one to
# the overal fitness of the chromosome.
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

View File

@ -0,0 +1,12 @@
class test_fitness_funciton:
def get_fitness(self, chromosome):
# For every gene in chromosome
for i in range(len(chromosome.genes)):
# If the gene has a five then add one to the fitness
# Example -> Chromosome = [5],[2],[2],[5],[5] then fitness = 3
if (chromosome.genes[i].get_value == 5):
# Add to the genes fitness
chromosome.genes[i].fitness += 1
# Add to the chromosomes fitness
chromosome.fitness += 1
return chromosome.fitness

View File

@ -1,5 +1,3 @@
# FROM (. means local) file_name IMPORT function_name # FROM (. means local) file_name IMPORT function_name
from .methods import Initialization_methods from .initialization_methods import Initialization_Methods
from .population_structure.population import Population
from .chromosome_structure.chromosome import Chromosome
from .gene_structure.gene import Gene

View File

@ -1,12 +1,10 @@
class Chromosome: class Chromosome:
def __init__(self, gene_list = None): def __init__(self, gene_list = None):
"""Initialize the chromosome based on input gene list, defaulted to an empty list"""
if gene_list is None: if gene_list is None:
self.gene_list = [] self.gene_list = []
else: else:
self.gene_list = gene_list self.gene_list = genes
# The fitness of the overal chromosome
self.fitness = None self.fitness = None
# If the chromosome has been selected then the flag would switch to true # If the chromosome has been selected then the flag would switch to true
self.selected = False self.selected = False
@ -18,11 +16,9 @@ class Chromosome:
self.gene_list.insert(index, gene) self.gene_list.insert(index, gene)
def remove_gene(self, index): def remove_gene(self, index):
"""Remove a gene from the chromosome at the specified index"""
del self.gene_list[index] del self.gene_list[index]
def get_genes(self): def get_genes(self):
"""Return all genes in the chromosome"""
return self.gene_list return self.gene_list
def get_fitness(self): def get_fitness(self):
@ -30,12 +26,10 @@ class Chromosome:
return self.fitness return self.fitness
def set_gene(self, gene, index): def set_gene(self, gene, index):
"""Set a gene at a specific index"""
self.gene_list[index] = gene self.gene_list[index] = gene
def set_genes(self, gene_list): def set_genes(self, genes):
"""Set the entire gene set of the chromosome""" self.gene_list = genes
self.gene_list = gene_list
def set_fitness(self, fitness): def set_fitness(self, fitness):
"""Set the fitness value of the chromosome""" """Set the fitness value of the chromosome"""

View File

@ -0,0 +1,13 @@
# Imported library
import random
def check_values(low,high):
#Check to make sure its not less then zero
assert low > 0 , "The random gene low can not be less then zero"
# Check to make sure the high value is not
# lower than or equal to low and not 0.
assert high > low , "High value can not be smaller then low value"
assert high != 0, "High value can not be zero"
def random_gene():
return random.randint(1,100)

View File

@ -22,7 +22,7 @@ class Gene:
"""Set fitness of the gene""" """Set fitness of the gene"""
self.fitness = fitness self.fitness = fitness
def set_value(self): def set_value(self, value):
"""Set value of the gene""" """Set value of the gene"""
self.value = value self.value = value

View File

@ -0,0 +1,32 @@
# Import the data structure
from .population_structure.population import Population as create_population
from .chromosome_structure.chromosome import Chromosome as create_chromosome
from .gene_structure.gene import Gene as create_gene
class Initialization_Methods:
"""Initialization examples that are used as defaults and examples"""
def random_initialization(self, ga):
"""Takes the initialization inputs and choregraphs them to output the type of population with the given parameters."""
# Create the population object
population = create_population()
# Fill the population with chromosomes
for i in range(ga.population_size):
chromosome = create_chromosome()
#Fill the Chromosome with genes
for j in range(ga.chromosome_length):
# Using the chromosome_impl to set every index inside of the chromosome
if ga.chromosome_impl != None:
# Each chromosome location is specified with its own function
chromosome.add_gene(create_gene(ga.chromosome_impl(j)))
# Will break if chromosome_length != len(lists) in domain
elif ga.gene_impl != None:
function = ga.gene_impl[0]
chromosome.add_gene(create_gene(function(*ga.gene_impl[1:])))
else:
#Exit because either were not specified
print("You did not specify any initialization constraints.")
population.add_chromosome(chromosome)
return population

View File

@ -1,5 +1,4 @@
class Population: class Population:
def __init__(self, chromosome_list = None): def __init__(self, chromosome_list = None):
"""Intiialize the population with fitness of value None, and a set of chromosomes dependant on user-passed parameter""" """Intiialize the population with fitness of value None, and a set of chromosomes dependant on user-passed parameter"""
if chromosome_list is None: if chromosome_list is None:
@ -7,6 +6,7 @@ class Population:
else: else:
self.chromosome_list = chromosome_list self.chromosome_list = chromosome_list
self.fitness = None self.fitness = None
self.mating_pool = []
def get_closet_fitness(self,value): def get_closet_fitness(self,value):
"""Get the chomosome that has the closets fitness to the value defined""" """Get the chomosome that has the closets fitness to the value defined"""
@ -24,18 +24,18 @@ class Population:
def get_all_chromosomes(self): def get_all_chromosomes(self):
"""returns all chromosomes in the population""" """returns all chromosomes in the population"""
return chromosome_list return self.chromosome_list
def get_fitness(self): def get_fitness(self):
"""returns the population's fitness""" """returns the population's fitness"""
return self.fitness return self.fitness
def set_all_chromosomes(self, chromosome_list): def set_all_chromosomes(self, chromosomes):
"""sets the chromosome set of the population""" self.chromosome_list = chromosomes
self.chromosome_list = chromosome_list
def set_chromosome(self, chromosome, index): def set_chromosome(self, chromosome, index = -1):
"""sets a specific chromosome at a specific index""" if index == -1:
index = len(self.chromosomes)-1
self.chromosome_list[index] = chromosome self.chromosome_list[index] = chromosome
def set_fitness(self, fitness): def set_fitness(self, fitness):
@ -43,8 +43,8 @@ class Population:
self.fitness = fitness self.fitness = fitness
def __repr__(self): def __repr__(self):
"""Sets the repr() output format""" for index in range(len(self.chromosomes)):
return ''.join([chromosome.__repr__() for chromosome in self.chromosome_list]) return f'{self.chromosome_list[index]}'
def print_all(self): def print_all(self):
"""Prints information about the population in the following format:""" """Prints information about the population in the following format:"""

View File

View File

@ -1,2 +1,2 @@
# FROM (. means local) file_name IMPORT function_name # FROM (. means local) file_name IMPORT function_name
from .methods import Mutation_methods from .mutation_methods import Mutation_Methods

View File

@ -0,0 +1,42 @@
import random
class Mutation_Methods:
def __init__(self):
pass
def random_mutation(self, ga, chromosome_set = None):
if chromosome_set == None:
chromosome_set = ga.population.get_all_chromosomes()
chromosome_mutate_num = int(len(chromosome_set)*ga.mutation_rate)
temp_population = ga.initialization_impl(ga)
while chromosome_mutate_num > 0:
chromosome_set[random.randint(0,ga.population_size-1)] = temp_population.get_all_chromosomes()[chromosome_mutate_num]
chromosome_mutate_num -= 1
return chromosome_set
def per_gene_mutation(self, ga, chromosome_set = None, gene_mutate_count = 1):
gene_mutate_count_static = int(gene_mutate_count)
if chromosome_set == None:
chromosome_set = ga.population.get_all_chromosomes()
for i in range(len(chromosome_set)):
random_num = random.uniform(0,1)
if (random_num <= ga.mutation_rate):
while gene_mutate_count > 0:
dummy_population = ga.initialization_impl(ga) #Really inefficient, but works for now
random_index = random.randint(0, ga.chromosome_length-1)
chromosome_set[i].get_genes()[random_index] = dummy_population.get_all_chromosomes()[random.randint(0,ga.population_size-1)].get_genes()[random_index]
gene_mutate_count -= 1
gene_mutate_count = int(gene_mutate_count_static)
return chromosome_set

View File

View File

@ -0,0 +1,36 @@
import EasyGA
import random
#1. GA should take in range for gene input
#2. GA should take in index-dependent range for gene input
#3. GA should take in domain input
#4. GA should take in index-dependent domain for gene input
#5. GA should accept mix of range and domain for gene input
# Create the Genetic algorithm
ga = EasyGA.GA()
test_gene_input = [["left", "right"],[1,100],[5.0,10],[22,"up"]]
ga.gene_input_type[1] = "float-range"
ga.gene_input_type[2] = "domain"
ga.initialize(test_gene_input)
ga.population.print_all()
#Example tests
#Note, the following examples assume a chromosome length of 4.
#if the test_gene_input is longer than the chromosomes, it will get truncated at the length of the chromosome
#for example, for chromosomes with length 2, [["left", "right"],[1,100],[5.0,10],[22,"up"]] becomes [["left", "right"],[1,100]]
#if the test_gene_input is shorter than the chromosomes, the remaining elements will be populated with None
#test_gene_input = [1,100]
#test_gene_input = [["left", "right"],[1,100],[5.0,10],[22,"up"]]
#test_gene_input = ["left", "right", "up", "down"]
#test_gene_input = [[1,100],[0,1],[33,35],[5,6]]
#test_gene_input = [["left", "right"], ["up", "down"], ["left", "down"], ["down", "right"]]
#ga.gene_input_type = "float-range"
#ga.gene_input_type[1] = "domain"
#ga.gene_input_type[1] = "float-range"

View File

@ -1,16 +1,15 @@
import EasyGA import EasyGA
import random import random
# Create the Genetic algorithm # Create the Genetic algorithm
ga = EasyGA.GA() ga = EasyGA.GA()
ga.chromosome_length = 3
ga.max_generations = 5
# If the user wants to use a domain
ga.gene_impl = [random.randrange,1,10]
ga.generation = 36
# Run Everyhting ga.gene_impl = [random.randrange,1,100]
# Run Everything
ga.evolve() ga.evolve()
# Print the current population # Print the current population

View File

@ -0,0 +1,2 @@
# FROM (. means local) file_name IMPORT function_name
from .selection_methods import Selection_Methods

View File

@ -0,0 +1,95 @@
import random
from initialization.chromosome_structure.chromosome import Chromosome as create_chromosome
from initialization.gene_structure.gene import Gene as create_gene
from initialization.population_structure.population import Population
from initialization.chromosome_structure.chromosome import Chromosome
class Selection_Methods:
"""Selection is the process by which chromosomes are selected for crossover and eventually, influence the next generation of chromosomes."""
def __init__(self):
pass
class Parent_Selection:
class Tournament:
def with_replacement(self, ga):
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())*ga.parent_ratio/3)
# 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) < len(ga.population.get_all_chromosomes())*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)])
# 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 Roulette:
def roulette_selection(self, ga):
"""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. 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"""
total_fitness = sum(ga.population.chromosome_list[i].get_fitness() for i in range(len(ga.population.chromosome_list)))
rel_fitnesses = []
for chromosome in ga.population.chromosome_list:
if (total_fitness != 0):
rel_fitnesses.append(float(chromosome.fitness)/total_fitness)
probability = [sum(rel_fitnesses[:i+1]) for i in range(len(rel_fitnesses))]
while (len(ga.population.mating_pool) < len(ga.population.get_all_chromosomes())*ga.parent_ratio):
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])
# print (f'Selected chromosome : {i}')
break
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 = ga.population.mating_pool
split_point = random.randint(0,ga.chromosome_length)
chromosome_list = []
for i in range(len(crossover_pool)):
if i + 1 < len(crossover_pool):
new_gene_set = []
parent_one = crossover_pool[i].get_genes()
parent_two = crossover_pool[i+1].get_genes()
new_gene_set.extend(parent_one[0:split_point])
new_gene_set.extend(parent_two[split_point:])
new_chromosome = create_chromosome(new_gene_set)
chromosome_list.append(new_chromosome)
for i in range(len(chromosome_list)):
next_population.add_chromosome(chromosome_list[i])
if len(next_population.get_all_chromosomes()) >= ga.population_size:
break
return next_population
def remove_two_worst(self, ga, next_population):
iterator = 0
while len(next_population.get_all_chromosomes()) < ga.population_size:
next_population.add_chromosome(ga.population.get_all_chromosomes()[iterator])
iterator += 1
return next_population

View File

View File

@ -1,2 +1,2 @@
# FROM (. means local) file_name IMPORT class name # FROM (. means local) file_name IMPORT class name
from .methods import Termination_methods from .termination_methods import Termination_Methods

View File

@ -0,0 +1,21 @@
class Termination_Methods:
"""Example functions that can be used to terminate the the algorithms loop"""
def fitness_based(self, ga):
"""Fitness based approach to terminate when the goal fitness has been reached"""
status = True
if ga.population == None:
return status
for i in range(len(ga.population.get_all_chromosomes())):
if(ga.population.get_all_chromosomes()[i].fitness >= ga.fitness_goal):
status = False
break
return status
def generation_based(self, ga):
"""Generation based approach to terminate when the goal generation has been reached"""
status = True
if(ga.current_generation > ga.generation_goal):
status = False
return status

View File