-
Notifications
You must be signed in to change notification settings - Fork 237
/
optimizer.py
182 lines (134 loc) · 5.34 KB
/
optimizer.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
Class that holds a genetic algorithm for evolving a network.
Credit:
A lot of those code was originally inspired by:
http://lethain.com/genetic-algorithms-cool-name-damn-simple/
"""
from functools import reduce
from operator import add
import random
from network import Network
class Optimizer():
"""Class that implements genetic algorithm for MLP optimization."""
def __init__(self, nn_param_choices, retain=0.4,
random_select=0.1, mutate_chance=0.2):
"""Create an optimizer.
Args:
nn_param_choices (dict): Possible network paremters
retain (float): Percentage of population to retain after
each generation
random_select (float): Probability of a rejected network
remaining in the population
mutate_chance (float): Probability a network will be
randomly mutated
"""
self.mutate_chance = mutate_chance
self.random_select = random_select
self.retain = retain
self.nn_param_choices = nn_param_choices
def create_population(self, count):
"""Create a population of random networks.
Args:
count (int): Number of networks to generate, aka the
size of the population
Returns:
(list): Population of network objects
"""
pop = []
for _ in range(0, count):
# Create a random network.
network = Network(self.nn_param_choices)
network.create_random()
# Add the network to our population.
pop.append(network)
return pop
@staticmethod
def fitness(network):
"""Return the accuracy, which is our fitness function."""
return network.accuracy
def grade(self, pop):
"""Find average fitness for a population.
Args:
pop (list): The population of networks
Returns:
(float): The average accuracy of the population
"""
summed = reduce(add, (self.fitness(network) for network in pop))
return summed / float((len(pop)))
def breed(self, mother, father):
"""Make two children as parts of their parents.
Args:
mother (dict): Network parameters
father (dict): Network parameters
Returns:
(list): Two network objects
"""
children = []
for _ in range(2):
child = {}
# Loop through the parameters and pick params for the kid.
for param in self.nn_param_choices:
child[param] = random.choice(
[mother.network[param], father.network[param]]
)
# Now create a network object.
network = Network(self.nn_param_choices)
network.create_set(child)
# Randomly mutate some of the children.
if self.mutate_chance > random.random():
network = self.mutate(network)
children.append(network)
return children
def mutate(self, network):
"""Randomly mutate one part of the network.
Args:
network (dict): The network parameters to mutate
Returns:
(Network): A randomly mutated network object
"""
# Choose a random key.
mutation = random.choice(list(self.nn_param_choices.keys()))
# Mutate one of the params.
network.network[mutation] = random.choice(self.nn_param_choices[mutation])
return network
def evolve(self, pop):
"""Evolve a population of networks.
Args:
pop (list): A list of network parameters
Returns:
(list): The evolved population of networks
"""
# Get scores for each network.
graded = [(self.fitness(network), network) for network in pop]
# Sort on the scores.
graded = [x[1] for x in sorted(graded, key=lambda x: x[0], reverse=True)]
# Get the number we want to keep for the next gen.
retain_length = int(len(graded)*self.retain)
# The parents are every network we want to keep.
parents = graded[:retain_length]
# For those we aren't keeping, randomly keep some anyway.
for individual in graded[retain_length:]:
if self.random_select > random.random():
parents.append(individual)
# Now find out how many spots we have left to fill.
parents_length = len(parents)
desired_length = len(pop) - parents_length
children = []
# Add children, which are bred from two remaining networks.
while len(children) < desired_length:
# Get a random mom and dad.
male = random.randint(0, parents_length-1)
female = random.randint(0, parents_length-1)
# Assuming they aren't the same network...
if male != female:
male = parents[male]
female = parents[female]
# Breed them.
babies = self.breed(male, female)
# Add the children one at a time.
for baby in babies:
# Don't grow larger than desired length.
if len(children) < desired_length:
children.append(baby)
parents.extend(children)
return parents