Skip to content

Commit 49ef509

Browse files
drvinceknightmarcharper
authored andcommitted
Move the mutation rate from class to algorithm. (#38)
* Move the mutation rate from class to algorithm. If there was a specific reason for having the mutation rate in the params class let me know but I've moved it to better reflect that it's a property of the algorithm. This is more in line with the fact that the representation we write to file does not hold the mutation rate for example. This doesn't stop having algorithms that would have different mutation rates for different parameter instances (it's just that the algorithm would have to keep track of it). * Add mutation rate arg to base class. * Correct test. * Remove rescaling of mutation rate. * Move to params_kwargs which are handled by alg. * Change probability of initial state mutation. * Adjust scripts. * Add ability to not overwrite mutation probability.
1 parent 9b5d80e commit 49ef509

File tree

10 files changed

+109
-62
lines changed

10 files changed

+109
-62
lines changed

bin/ann_evolve.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def parse_repr(cls, s):
140140

141141
# Vars for the genetic algorithm
142142
population = int(arguments['--population'])
143-
mutation_rate = float(arguments['--mu'])
143+
mutation_probability = float(arguments['--mu'])
144144
generations = int(arguments['--generations'])
145145
bottleneck = int(arguments['--bottleneck'])
146146
output_filename = arguments['--output']
@@ -156,9 +156,13 @@ def parse_repr(cls, s):
156156
num_features = int(arguments['--features'])
157157
num_hidden = int(arguments['--hidden'])
158158
mutation_distance = float(arguments['--mu_distance'])
159-
param_args = [num_features, num_hidden, mutation_rate, mutation_distance]
159+
param_kwargs = {"num_features": num_features,
160+
"num_hidden": num_hidden,
161+
"mutation_distance": mutation_distance}
160162

161163
objective = prepare_objective(name, turns, noise, repetitions, nmoran)
162-
population = Population(ANNParams, param_args, population, objective,
163-
output_filename, bottleneck, processes=processes)
164+
population = Population(ANNParams, param_kwargs, population, objective,
165+
output_filename, bottleneck,
166+
mutation_probability,
167+
processes=processes)
164168
population.run(generations)

bin/fsm_evolve.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
# Vars for the genetic algorithm
3838
population = int(arguments['--population'])
39-
mutation_rate = float(arguments['--mu'])
39+
mutation_probability = float(arguments['--mu'])
4040
generations = int(arguments['--generations'])
4141
bottleneck = int(arguments['--bottleneck'])
4242
output_filename = arguments['--output']
@@ -50,9 +50,10 @@
5050

5151
# FSM
5252
num_states = int(arguments['--states'])
53-
param_args = [num_states, mutation_rate]
53+
param_kwargs = {"num_states": num_states}
5454

5555
objective = prepare_objective(name, turns, noise, repetitions, nmoran)
56-
population = Population(FSMParams, param_args, population, objective,
57-
output_filename, bottleneck, processes=processes)
56+
population = Population(FSMParams, param_kwargs, population, objective,
57+
output_filename, bottleneck, mutation_probability,
58+
processes=processes)
5859
population.run(generations)

bin/hmm_evolve.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def parse_matrix(sm):
215215

216216
# Vars for the genetic algorithm
217217
population = int(arguments['--population'])
218-
mutation_rate = float(arguments['--mu'])
218+
mutation_probability = float(arguments['--mu'])
219219
generations = int(arguments['--generations'])
220220
bottleneck = int(arguments['--bottleneck'])
221221
output_filename = arguments['--output']
@@ -229,9 +229,11 @@ def parse_matrix(sm):
229229

230230
# FSM
231231
num_states = int(arguments['--states'])
232-
param_args = [num_states, mutation_rate]
232+
param_kwargs = {"num_states": num_states}
233233

234234
objective = prepare_objective(name, turns, noise, repetitions, nmoran)
235-
population = Population(HMMParams, param_args, population, objective,
236-
output_filename, bottleneck, processes=processes)
235+
population = Population(HMMParams, param_kwargs, population, objective,
236+
output_filename, bottleneck,
237+
mutation_probability,
238+
processes=processes)
237239
population.run(generations)

bin/pso_evolve.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@
5454
plays = int(arguments['--plays'])
5555
op_plays = int(arguments['--op_plays'])
5656
op_start_plays = int(arguments['--op_start_plays'])
57-
param_args = [plays, op_plays, op_start_plays]
57+
param_kwargs = {"plays": plays,
58+
"op_plays": op_plays,
59+
"op_state_plays": op_start_plays}
5860

5961
objective = prepare_objective(name, turns, noise, repetitions, nmoran)
6062

61-
pso = PSO(GamblerParams, param_args, objective=objective,
63+
pso = PSO(GamblerParams, param_kwargs, objective=objective,
6264
population=population, generations=generations)
6365

6466
xopt, fopt = pso.swarm()

src/axelrod_dojo/algorithms/genetic_algorithm.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
class Population(object):
1313
"""Population class that implements the evolutionary algorithm."""
14-
def __init__(self, params_class, params_args, size, objective, output_filename,
15-
bottleneck=None, opponents=None, processes=1, weights=None,
14+
def __init__(self, params_class, params_kwargs, size, objective, output_filename,
15+
bottleneck=None, mutation_probability=.1, opponents=None,
16+
processes=1, weights=None,
1617
sample_count=None, population=None):
1718
self.params_class = params_class
1819
self.bottleneck = bottleneck
20+
1921
if processes == 0:
2022
processes = cpu_count()
2123
self.pool = Pool(processes=processes)
@@ -33,12 +35,16 @@ def __init__(self, params_class, params_args, size, objective, output_filename,
3335
self.opponents_information = [
3436
PlayerInfo(p.__class__, p.init_kwargs) for p in opponents]
3537
self.generation = 0
36-
self.params_args = params_args
38+
39+
self.params_kwargs = params_kwargs
40+
if "mutation_probability" not in self.params_kwargs:
41+
self.params_kwargs["mutation_probability"] = mutation_probability
3742

3843
if population is not None:
3944
self.population = population
4045
else:
41-
self.population = [params_class(*params_args) for _ in range(self.size)]
46+
self.population = [params_class(**params_kwargs)
47+
for _ in range(self.size)]
4248

4349
self.weights = weights
4450
self.sample_count = sample_count
@@ -95,7 +101,7 @@ def evolve(self):
95101
p.mutate()
96102
self.population.append(p)
97103
# Add random variants
98-
random_params = [self.params_class(*self.params_args)
104+
random_params = [self.params_class(**self.params_kwargs)
99105
for _ in range(self.bottleneck // 2)]
100106
params_to_modify = [params.copy() for params in self.population]
101107
params_to_modify += random_params

src/axelrod_dojo/algorithms/particle_swarm_optimization.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88

99
class PSO(object):
1010
"""PSO class that implements a particle swarm optimization algorithm."""
11-
def __init__(self, params_class, params_args, objective, opponents=None,
11+
def __init__(self, params_class, params_kwargs, objective, opponents=None,
1212
population=1, generations=1, debug=True, phip=0.8, phig=0.8,
1313
omega=0.8, weights=None, sample_count=None, processes=1):
1414

1515
self.params_class = params_class
16-
self.params_args = params_args
16+
self.params_kwargs = params_kwargs
1717
self.objective = objective
1818
if opponents is None:
1919
self.opponents_information = [
@@ -36,7 +36,7 @@ def __init__(self, params_class, params_args, objective, opponents=None,
3636

3737
def swarm(self):
3838

39-
params = self.params_class(*self.params_args)
39+
params = self.params_class(**self.params_kwargs)
4040
lb, ub = params.create_vector_bounds()
4141

4242
def objective_function(vector):

src/axelrod_dojo/archetypes/fsm.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@ def copy_lists(rows):
1818

1919
class FSMParams(Params):
2020

21-
def __init__(self, num_states, mutation_rate=None, rows=None,
22-
initial_state=0, initial_action=C):
21+
def __init__(self, num_states, rows=None,
22+
initial_state=0, initial_action=C,
23+
mutation_probability=0):
2324
self.PlayerClass = FSMPlayer
2425
self.num_states = num_states
25-
if mutation_rate is None:
26-
self.mutation_rate = 1 / (2 * num_states)
27-
else:
28-
self.mutation_rate = mutation_rate
26+
self.mutation_probability = mutation_probability
2927
if rows is None:
3028
self.randomize()
3129
else:
@@ -40,8 +38,8 @@ def player(self):
4038
return player
4139

4240
def copy(self):
43-
return FSMParams(self.num_states, self.mutation_rate,
44-
self.rows, self.initial_state, self.initial_action)
41+
return FSMParams(self.num_states, self.rows,
42+
self.initial_state, self.initial_action)
4543

4644
@staticmethod
4745
def random_params(num_states):
@@ -64,11 +62,11 @@ def randomize(self):
6462
self.initial_action = initial_action
6563

6664
@staticmethod
67-
def mutate_rows(rows, mutation_rate):
65+
def mutate_rows(rows, mutation_probability):
6866
randoms = np.random.random(len(rows))
6967
# Flip each value with a probability proportional to the mutation rate
7068
for i, row in enumerate(rows):
71-
if randoms[i] < mutation_rate:
69+
if randoms[i] < mutation_probability:
7270
row[3] = row[3].flip()
7371
# Swap Two Nodes?
7472
if random.random() < 0.5:
@@ -83,10 +81,10 @@ def mutate_rows(rows, mutation_rate):
8381
return rows
8482

8583
def mutate(self):
86-
self.rows = self.mutate_rows(self.rows, self.mutation_rate)
87-
if random.random() < self.mutation_rate / 10:
84+
self.rows = self.mutate_rows(self.rows, self.mutation_probability)
85+
if random.random() < self.mutation_probability:
8886
self.initial_action = self.initial_action.flip()
89-
if random.random() < self.mutation_rate / (10 * self.num_states):
87+
if random.random() < self.mutation_probability / self.num_states:
9088
self.initial_state = randrange(self.num_states)
9189
# Change node size?
9290

@@ -101,8 +99,11 @@ def crossover_rows(rows1, rows2):
10199
def crossover(self, other):
102100
# Assuming that the number of states is the same
103101
new_rows = self.crossover_rows(self.rows, other.rows)
104-
return FSMParams(self.num_states, self.mutation_rate, new_rows,
105-
self.initial_state, self.initial_action)
102+
return FSMParams(num_states=self.num_states,
103+
mutation_probability=self.mutation_probability,
104+
rows=new_rows,
105+
initial_state=self.initial_state,
106+
initial_action=self.initial_action)
106107

107108
@staticmethod
108109
def repr_rows(rows):
@@ -136,7 +137,7 @@ def parse_repr(cls, s):
136137

137138
rows.append(row)
138139
num_states = len(rows) // 2
139-
return cls(num_states, 0.1, rows, initial_state, initial_action)
140+
return cls(num_states, rows, initial_state, initial_action)
140141

141142
def receive_vector(self, vector):
142143
"""Receives a vector and creates an instance attribute called
@@ -145,13 +146,13 @@ def receive_vector(self, vector):
145146

146147
def vector_to_instance(self):
147148
"""Turns the attribute vector in to a FSM player instance.
148-
149+
149150
The vector has three parts. The first is used to define the next state
150151
(for each of the player's states - for each opponents action).
151-
152+
152153
The second part is the player's next moves (for each state - for
153154
each opponent's actions).
154-
155+
155156
Finally, a probability to determine the player's first move."""
156157

157158
num_states = int((len(self.vector) - 1) / 4)
@@ -175,4 +176,4 @@ def create_vector_bounds(self):
175176
lb = [0] * size
176177
ub = [1] * size
177178

178-
return lb, ub
179+
return lb, ub

tests/algorithms/test_particle_swarm_optimization.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ def test_pso_with_gambler(self):
5959
num_plays = 1
6060
num_op_plays = 1
6161
num_op_start_plays = 1
62-
params_args = [num_plays, num_op_plays, num_op_start_plays]
62+
params_kwargs = {"plays": num_plays,
63+
"op_plays": num_op_plays,
64+
"op_start_plays": num_op_start_plays}
6365
population = 10
6466
generations = 100
6567
opponents = [axl.Cooperator() for _ in range(5)]
@@ -69,7 +71,7 @@ def test_pso_with_gambler(self):
6971
noise=noise,
7072
repetitions=repetitions)
7173

72-
pso = PSO(GamblerParams, params_args, objective=objective, debug=False,
74+
pso = PSO(GamblerParams, params_kwargs, objective=objective, debug=False,
7375
opponents=opponents, population=population, generations=generations)
7476

7577
axl.seed(0)
@@ -90,7 +92,7 @@ def test_pso_with_fsm(self):
9092
noise = 0
9193
repetitions = 5
9294
num_states = 4
93-
params_args = [num_states]
95+
params_kwargs = {"num_states": num_states}
9496
population = 10
9597
generations = 100
9698
opponents = [axl.Defector() for _ in range(5)]
@@ -100,7 +102,7 @@ def test_pso_with_fsm(self):
100102
noise=noise,
101103
repetitions=repetitions)
102104

103-
pso = PSO(FSMParams, params_args, objective=objective, debug=False,
105+
pso = PSO(FSMParams, params_kwargs, objective=objective, debug=False,
104106
opponents=opponents, population=population, generations=generations)
105107

106108
axl.seed(0)

tests/archetypes/test_fsm.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def test_init(self):
1414
fsm_params = FSMParams(num_states=num_states, rows=rows)
1515
self.assertEqual(fsm_params.PlayerClass, axl.FSMPlayer)
1616
self.assertEqual(fsm_params.num_states, num_states)
17-
self.assertEqual(fsm_params.mutation_rate, 1 / (2 * num_states))
1817
self.assertEqual(fsm_params.rows, rows)
1918
self.assertEqual(fsm_params.initial_action, C)
2019
self.assertEqual(fsm_params.initial_state, 0)
@@ -48,8 +47,6 @@ def test_copy(self):
4847
# Have same attributes
4948
self.assertEqual(fsm_params.PlayerClass, copy_fsm_params.PlayerClass)
5049
self.assertEqual(fsm_params.num_states, copy_fsm_params.num_states)
51-
self.assertEqual(fsm_params.mutation_rate,
52-
copy_fsm_params.mutation_rate)
5350
self.assertEqual(fsm_params.rows, copy_fsm_params.rows)
5451
self.assertEqual(fsm_params.initial_action,
5552
copy_fsm_params.initial_action)
@@ -113,8 +110,6 @@ def test_crossover(self):
113110

114111
self.assertEqual(fsm_params.PlayerClass, fsm_params1.PlayerClass)
115112
self.assertEqual(fsm_params.num_states, fsm_params1.num_states)
116-
self.assertEqual(fsm_params.mutation_rate,
117-
fsm_params1.mutation_rate)
118113
self.assertEqual(fsm_params.initial_action,
119114
fsm_params1.initial_action)
120115
self.assertEqual(fsm_params.initial_state,

0 commit comments

Comments
 (0)