Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ethen8181
GitHub Repository: ethen8181/machine-learning
Path: blob/master/ga/ga.py
1470 views
1
import random
2
import numpy as np
3
from collections import namedtuple
4
5
6
class GA:
7
"""
8
Genetic Algorithm for a simple optimization problem
9
10
Parameters
11
----------
12
generation : int
13
number of iteration to train the algorithm
14
15
pop_size : int
16
number of chromosomes in the population
17
18
chromo_size : int
19
number of possible values (genes) per chromosome
20
21
low, high : int
22
lower_bound and upper_bound possible value of the randomly generated chromosome
23
24
retain_rate : float 0 ~ 1
25
the fraction of the best chromosome to retain. used to mate
26
the children for the next generation
27
28
mutate_rate : float 0 ~ 1
29
the probability that each chromosome will mutate
30
"""
31
def __init__( self, generation, pop_size, chromo_size, low, high,
32
retain_rate, mutate_rate ):
33
self.low = low
34
self.high = high
35
self.pop_size = pop_size
36
self.chromo_size = chromo_size
37
self.generation = generation
38
self.retain_len = int(pop_size * retain_rate)
39
self.mutate_rate = mutate_rate
40
self.info = namedtuple( 'info', ['cost', 'chromo'] )
41
42
43
def fit(self, target):
44
"""
45
target : int
46
the targeted solution
47
"""
48
49
# randomly generate the initial population, and evaluate its cost
50
array_size = self.pop_size, self.chromo_size
51
pop = np.random.randint(self.low, self.high + 1, array_size)
52
graded_pop = self._compute_cost( pop, target )
53
54
# store the best chromosome and its cost for each generation,
55
# so we can get an idea of when the algorithm converged
56
self.generation_history = []
57
for _ in range(self.generation):
58
graded_pop, generation_best = self._evolve(graded_pop, target)
59
self.generation_history.append(generation_best)
60
61
self.best = self.generation_history[self.generation - 1]
62
self.is_fitted = True
63
return self
64
65
66
def _compute_cost(self, pop, target):
67
"""
68
combine the cost and chromosome into one list and sort them
69
in ascending order
70
"""
71
cost = np.abs( np.sum(pop, axis = 1) - target )
72
graded = [ self.info( c, list(p) ) for p, c in zip(pop, cost) ]
73
graded = sorted(graded)
74
return graded
75
76
77
def _evolve(self, graded_pop, target):
78
"""
79
core method that does the crossover, mutation to generate
80
the possibly best children for the next generation
81
"""
82
83
# retain the best chromos (number determined by the retain_len)
84
graded_pop = graded_pop[:self.retain_len]
85
parent = [ p.chromo for p in graded_pop ]
86
87
# generate the children for the next generation
88
children = []
89
while len(children) < self.pop_size:
90
child = self._crossover(parent)
91
child = self._mutate(child)
92
children.append(child)
93
94
# evaluate the children chromosome and retain the overall best,
95
# overall simply means the best from the parent and the children, where
96
# the size retained is determined by the population size
97
graded_children = self._compute_cost(children, target)
98
graded_pop.extend(graded_children)
99
graded_pop = sorted(graded_pop)
100
graded_pop = graded_pop[:self.pop_size]
101
102
# also return the current generation's best chromosome and its cost
103
generation_best = graded_pop[0]
104
return graded_pop, generation_best
105
106
107
def _crossover(self, parent):
108
"""
109
mate the children by randomly choosing two parents and mix
110
the first half element of one parent with the later half
111
element of the other
112
"""
113
index1, index2 = random.sample( range(self.retain_len), k = 2 )
114
male, female = parent[index1], parent[index2]
115
pivot = len(male) // 2
116
child = male[:pivot] + female[pivot:]
117
return child
118
119
120
def _mutate(self, child):
121
"""
122
randomly change one element of the chromosome if it
123
exceeds the user-specified threshold (mutate_rate)
124
"""
125
if self.mutate_rate > random.random():
126
idx_to_mutate = random.randrange(self.chromo_size)
127
child[idx_to_mutate] = random.randint(self.low, self.high)
128
129
return child
130
131
132