8000 Restructuring the project to use OOP · rifat17/GeneticAlgorithmPython@48ac5a5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 48ac5a5

Browse files
authored
Restructuring the project to use OOP
Rather than being a script, the ga.py module now holds a class named GA which contains all parameters and methods for running the GA.
1 parent e068af2 commit 48ac5a5

File tree

1 file changed

+173
-42
lines changed

1 file changed

+173
-42
lines changed

ga.py

Lines changed: 173 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,176 @@
11
import numpy
2+
import random
3+
import sys
4+
import matplotlib.pyplot
25

3-
def cal_pop_fitness(equation_inputs, pop):
4-
# Calculating the fitness value of each solution in the current population.
5-
# The fitness function calulates the sum of products between each input and its corresponding weight.
6-
fitness = numpy.sum(pop*equation_inputs, axis=1)
7-
return fitness
8-
9-
def select_mating_pool(pop, fitness, num_parents):
10-
# Selecting the best individuals in the current generation as parents for producing the offspring of the next generation.
11-
parents = numpy.empty((num_parents, pop.shape[1]))
12-
for parent_num in range(num_parents):
13-
max_fitness_idx = numpy.where(fitness == numpy.max(fitness))
14-
max_fitness_idx = max_fitness_idx[0][0]
15-
parents[parent_num, :] = pop[max_fitness_idx, :]
16-
fitness[max_fitness_idx] = -99999999999
17-
return parents
18-
19-
def crossover(parents, offspring_size):
20-
offspring = numpy.empty(offspring_size)
21-
# The point at which crossover takes place between two parents. Usually, it is at the center.
22-
crossover_point = numpy.uint8(offspring_size[1]/2)
23-
24-
for k in range(offspring_size[0]):
25-
# Index of the first parent to mate.
26-
parent1_idx = k%parents.shape[0]
27-
# Index of the second parent to mate.
28-
parent2_idx = (k+1)%parents.shape[0]
29-
# The new offspring will have its first half of its genes taken from the first parent.
30-
offspring[k, 0:crossover_point] = parents[parent1_idx, 0:crossover_point]
31-
# The new offspring will have its second half of its genes taken from the second parent.
32-
offspring[k, crossover_point:] = parents[parent2_idx, crossover_point:]
33-
return offspring
34-
35-
def mutation(offspring_crossover, num_mutations=1):
36-
mutations_counter = numpy.uint8(offspring_crossover.shape[1] / num_mutations)
37-
# Mutation changes a number of genes as defined by the num_mutations argument. The changes are random.
38-
for idx in range(offspring_crossover.shape[0]):
39-
gene_idx = mutations_counter - 1
40-
for mutation_num in range(num_mutations):
6+
class GA:
7+
# Parameters of the genetic algorithm.
8+
sol_per_pop = None # Number of solutions in the population.
9+
num_parents_mating = None # Number of solutions to be selected as parents in the mating pool.
10+
num_generations = None # Number of generations.
11+
pop_size = None # Population size = (number of chromosomes, number of genes per chromosome)
12+
13+
population = None # A NumPy array holding the opulation.
14+
15+
# NumPy arrays holding information about the generations.
16+
best_outputs = [] # A list holding the value of the best solution for each generation.
17+
best_outputs_fitness = [] # A list holding the fitness value of the best solution for each generation.
18+
19+
# Parameters of the function to be optimized.
20+
function_inputs = None # Inputs of the function to be optimized.
21+
function_output = None # Desired outuput of the function.
22+
num_weights = None
23+
24+
# Mutation parameters.
25+
mutation_percent_genes=None
26+
mutation_num_genes=None
27+
mutation_min_val=None
28+
mutation_max_val=None
29+
30+
def __init__(self, num_generations,
31+
sol_per_pop,
32+
num_parents_mating,
33+
function_inputs,
34+
function_output,
35+
mutation_percent_genes=10,
36+
mutation_num_genes=None,
37+
mutation_min_val=-1.0,
38+
mutation_max_val=1.0):
39+
40+
# Parameters of the genetic algorithm.
41+
self.sol_per_pop = sol_per_pop
42+
self.num_parents_mating = num_parents_mating
43+
self.num_generations = num_generations
44+
45+
# Properties of the function to be optimized.
46+
self.function_inputs = function_inputs # Funtion inputs.
47+
self.function_output = function_output # Function output.
48+
self.num_weights = len(self.function_inputs) # Number of parameters in the function.
49+
50+
# Parameters of the mutation operation.
51+
self.mutation_percent_genes = mutation_percent_genes
52+
self.mutation_num_genes = mutation_num_genes
53+
self.mutation_min_val = mutation_min_val
54+
self.mutation_max_val = mutation_max_val
55+
56+
# Initializing the population.
57+
self.initialize_population()
58+
59+
def initialize_population(self):
60+
self.pop_size = (self.sol_per_pop,self.num_weights) # The population will have sol_per_pop chromosome where each chromosome has num_weights genes.
61+
# Creating the initial population randomly.
62+
self.population = numpy.random.uniform(low=-4.0, high=4.0, size=self.pop_size)
63+
64+
def train(self):
65+
for generation in range(self.num_generations):
66+
# Measuring the fitness of each chromosome in the population.
67+
fitness = self.cal_pop_fitness()
68+
69+
# Selecting the best parents in the population for mating.
70+
parents = self.select_mating_pool(fitness)
71+
72+
# Generating next generation using crossover.
73+
offspring_crossover = self.crossover(parents,
74+
offspring_size=(self.pop_size[0]-parents.shape[0], self.num_weights))
75+
76+
# Adding some variations to the offspring using mutation.
77+
offspring_mutation = self.mutation(offspring_crossover)
78+
79+
if (len(offspring_mutation) == 2):
80+
print(offspring_mutation[1])
81+
sys.exit(1)
82+
83+
# Creating the new population based on the parents and offspring.
84+
self.population[0:parents.shape[0], :] = parents
85+
self.population[parents.shape[0]:, :] = offspring_mutation
86+
87+
def cal_pop_fitness(self):
88+
# Calculating the fitness value of each solution in the current population.
89+
# The fitness function calulates the sum of products between each input and its corresponding weight.
90+
outputs = numpy.sum(self.population*self.function_inputs, axis=1)
91+
fitness = 1.0 / numpy.abs(outputs - self.function_output)
92+
93+
# The best result in the current iteration.
94+
self.best_outputs.append(outputs[numpy.where(fitness == numpy.max(fitness))[0][0]])
95+
self.best_outputs_fitness.append(numpy.max(fitness))
96+
97+
return fitness
98+
99+
def select_mating_pool(self, fitness):
100+
# Selecting the best individuals in the current generation as parents for producing the offspring of the next generation.
101+
parents = numpy.empty((self.num_parents_mating, self.population.shape[1]))
102+
for parent_num in range(self.num_parents_mating):
103+
max_fitness_idx = numpy.where(fitness == numpy.max(fitness))
104+
max_fitness_idx = max_fitness_idx[0][0]
105+
parents[parent_num, :] = self.population[max_fitness_idx, :]
106+
fitness[max_fitness_idx] = -99999999999
107+
return parents
108+
109+
def crossover(self, parents, offspring_size):
110+
offspring = numpy.empty(offspring_size)
111+
# The point at which crossover takes place between two parents. Usually, it is at the center.
112+
crossover_point = numpy.uint8(offspring_size[1]/2)
113+
114+
for k in range(offspring_size[0]):
115+
# Index of the first parent to mate.
116+
parent1_idx = k%parents.shape[0]
117+
# Index of the second parent to mate.
118+
parent2_idx = (k+1)%parents.shape[0]
119+
# The new offspring will have its first half of its genes taken from the first parent.
120+
offspring[k, 0:crossover_point] = parents[parent1_idx, 0:crossover_point]
121+
# The new offspring will have its second half of its genes taken from the second parent.
122+
offspring[k, crossover_point:] = parents[parent2_idx, crossover_point:]
123+
return offspring
124+
125+
def mutation(self, offspring):
126+
"""
127+
The mutation() method applies mutation over the offspring.
128+
The parameters are:
129+
-offspring: The offspring to which mutation is applied.
130+
-percent_genes: Defaults to 10 and refers to the percentage of genes to which mutation is applied. Based on the percentage, the number of genes are calculated.
131+
-num_genes: The number of genes to which mutaiton is applied. If None, then the number of genes is calculated based on the argument percent_genes. If not None, then it overrides the value of the argument percent_genes.
132+
-mutation_min_val & mutation_max_val: The range from which random values are selected and added to the selected genes.
133+
"""
134+
if self.mutation_num_genes == None:
135+
if self.mutation_percent_genes <= 0:
136+
return None, "ERROR: The percentage of genes for which mutation is applied must be > 0. Please specify a valid value for the percent_genes argument."
137+
self.mutation_num_genes = numpy.uint32((self.mutation_percent_genes*offspring.shape[1])/100)
138+
# Based on the percentage of genes, if the number of selected genes for mutation is less than the least possible value which is 1, then the number will be set to 1.
139+
if self.mutation_num_genes == 0:
140+
self.mutation_num_genes = 1
141+
if self.mutation_num_genes <= 0:
142+
return None, "ERROR: Number of genes for mutation must be > 0. Please specify a valid value for the mutation_num_genes argument."
143+
else:
144+
self.mutation_num_genes = int(self.mutation_num_genes)
145+
mutation_indices = numpy.array(random.sample(range(0, offspring.shape[1]), self.mutation_num_genes))
146+
# Mutation changes a single gene in each offspring randomly.
147+
for idx in range(offspring.shape[0]):
41148
# The random value to be added to the gene.
42-
random_value = numpy.random.uniform(-1.0, 1.0, 1)
43-
offspring_crossover[idx, gene_idx] = offspring_crossover[idx, gene_idx] + random_value
44-
gene_idx = gene_idx + mutations_counter
45-
return offspring_crossover
149+
random_value = numpy.random.uniform(self.mutation_min_val, self.mutation_max_val, 1)
150+
offspring[idx, mutation_indices] = offspring[idx, mutation_indices] + random_value
151+
return offspring
152+
153+
def best_solution(self):
154+
# Getting the best solution after finishing all generations.
155+
# At first, the fitness is calculated for each solution in the final generation.
156+
fitness = self.cal_pop_fitness()
157+
# Then return the index of that solution corresponding to the best fitness.
158+
best_match_idx = numpy.where(fitness == numpy.max(fitness))
159+
160+
best_solution = self.population[best_match_idx, :][0][0]
161+
best_solution_fitness = fitness[best_match_idx][0]
162+
163+
return best_solution, best_solution_fitness
164+
165+
def plot_result(self):
166+
matplotlib.pyplot.figure()
167+
matplotlib.pyplot.plot(self.best_outputs)
168+
matplotlib.pyplot.xlabel("Iteration")
169+
matplotlib.pyplot.ylabel("Outputs")
170+
matplotlib.pyplot.show()
171+
172+
matplotlib.pyplot.figure()
173+
matplotlib.pyplot.plot(self.best_outputs_fitness)
174+
matplotlib.pyplot.xlabel("Iteration")
175+
matplotlib.pyplot.ylabel("Fitness")
176+
matplotlib.pyplot.show()

0 commit comments

Comments
 (0)
0