-
Notifications
You must be signed in to change notification settings - Fork 267
/
genetic_algorithm.py
161 lines (135 loc) · 4.91 KB
/
genetic_algorithm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
Genetic algorithm example
In this example we will create a generic genetic algorithm
that can be applied to solve many problems.
"""
from abc import ABC, abstractmethod
from copy import deepcopy
from enum import Enum
from heapq import nlargest
from random import choices, random, randrange
from statistics import mean
class Chromosome(ABC):
@abstractmethod
def fitness(self):
pass
@classmethod
@abstractmethod
def random_instance(cls):
pass
@abstractmethod
def crossover(self, other):
pass
@abstractmethod
def mutate(self):
pass
SelectionType = Enum("SelectionType", "ROULETTE TOURNAMENT")
class GeneticAlgorithm:
def __init__(
self,
initial_population,
threshold,
max_generations=100,
mutation_chance=0.01,
crossover_chance=0.7,
selection_type=SelectionType.TOURNAMENT,
):
self._population = initial_population
self._threshold = threshold
self._max_generations = max_generations
self._mutation_chance = mutation_chance
self._crossover_chance = crossover_chance
self._selection_type = selection_type
self._fitness_key = type(self._population[0]).fitness
def _pick_roulette(self, wheel):
"""Choose 2 random participants based on probability (fitness)."""
return tuple(choices(self._population, weights=wheel, k=2))
def _pick_tournament(self, num_participants):
"""Choose the 2 best participants from a random list."""
participants = choices(self._population, k=num_participants)
return tuple(nlargest(2, participants, key=self._fitness_key))
def _reproduce_and_replace(self):
"""Create a new generation of population."""
new_population = list()
while len(new_population) < len(self._population):
if self._selection_type == SelectionType.ROULETTE:
parents = self._pick_roulette([p.fitness() for p in self._population])
else:
parents = self._pick_tournament(len(self._population) // 2)
# Try to perform a crossover with parents
if random() < self._crossover_chance:
new_population.extend(parents[0].crossover(parents[1]))
else:
new_population.extend(parents)
if len(new_population) > len(self._population):
new_population.pop()
self._population = new_population
def _mutate(self):
"""Try to perform a mutation for each individual."""
for individual in self._population:
if random() < self._mutation_chance:
individual.mutate()
def run(self):
"""Main function responsible for running the algorithm."""
best = max(self._population, key=self._fitness_key)
for generation in range(self._max_generations):
if best.fitness() >= self._threshold:
return best
print(
f"Generation {generation} Best {best.fitness()} "
f"Avg {mean(map(self._fitness_key, self._population))}"
)
self._reproduce_and_replace()
self._mutate()
highest = max(self._population, key=self._fitness_key)
if highest.fitness() > best.fitness():
best = highest
return best
# Now, let's use the generic algorithm to solve a simple equation.
# In this example we want to maximize the following equation:
# 6x - x^2 + 4y - y^2
# Tip: the answer should be: x = 3 and y = 2
class SimpleEquation(Chromosome):
def __init__(self, x, y):
self.x = x
self.y = y
def fitness(self):
"""Calculate the equation."""
return 6 * self.x - self.x * self.x + 4 * self.y - self.y * self.y
@classmethod
def random_instance(cls):
"""Create a new random object of SimpleEquation."""
return SimpleEquation(randrange(100), randrange(100))
def crossover(self, other):
"""Combine two SimpleEquation parents."""
child1 = deepcopy(self)
child2 = deepcopy(other)
child1.y = other.y
child2.y = self.y
return child1, child2
def mutate(self):
"""Mutate the current SimpleEquation object."""
# Try to mutate X
if random() < 0.5:
if random() < 0.5:
self.x += 1
else:
self.y -= 1
else:
if random() < 0.5:
self.y += 1
else:
self.y -= 1
def __str__(self):
return f"X: {self.x} Y: {self.y} Fitness: {self.fitness()}"
if __name__ == "__main__":
initial_population = [SimpleEquation.random_instance() for _ in range(20)]
ga = GeneticAlgorithm(
initial_population=initial_population,
threshold=13.0,
max_generations=100,
mutation_chance=0.1,
crossover_chance=0.7,
)
result = ga.run()
print(result)