Skip to content

Commit 789f0a8

Browse files
committed
Make ann_evolve.py work again
1 parent 8a231d1 commit 789f0a8

File tree

6 files changed

+119
-88
lines changed

6 files changed

+119
-88
lines changed

LICENSE.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 Martin Jones, Georgios Koutsovoulos, Marc Harper
3+
Copyright (c) 2015 Martin Jones, Georgios Koutsovoulos, Marc Harper,
4+
Vincent Knight
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

ann_evolve.py

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,93 +3,93 @@
33
44
Original code by Martin Jones @mojones:
55
https://gist.github.com/mojones/b809ba565c93feb8d44becc7b93e37c6
6+
7+
Usage:
8+
ann_evolve.py [-h] [-g GENERATIONS] [-u MUTATION_RATE] [-b BOTTLENECK]
9+
[-d mutation_distance] [-i PROCESSORS] [-o OUTPUT_FILE]
10+
[-k STARTING_POPULATION]
11+
12+
Options:
13+
-h --help show this
14+
-g GENERATIONS how many generations to run the program for [default: 100]
15+
-u MUTATION_RATE mutation rate i.e. probability that a given value will flip [default: 0.1]
16+
-d MUTATION_DISTANCE amount of change a mutation will cause [default: 0.1]
17+
-b BOTTLENECK number of individuals to keep from each generation [default: 10]
18+
-i PROCESSORS number of processors to use [default: 1]
19+
-o OUTPUT_FILE file to write statistics to [default: ann_out.csv]
20+
-k STARTING_POPULATION starting population size for the simulation [default: 5]
621
"""
722

23+
824
from __future__ import division
925

1026
import copy
1127
import random
28+
from multiprocessing import Pool
1229
from statistics import mean, pstdev
1330

14-
import axelrod
15-
from axelrod.strategies.ann import split_weights
16-
31+
from docopt import docopt
1732

18-
19-
20-
# def split_weights(weights, input_values, hidden_layer_size):
21-
# number_of_input_to_hidden_weights = input_values * hidden_layer_size
22-
# number_of_hidden_bias_weights = hidden_layer_size
23-
# number_of_hidden_to_output_weights = hidden_layer_size
24-
#
25-
# input2hidden = []
26-
# for i in range(0, number_of_input_to_hidden_weights, input_values):
27-
# input2hidden.append(weights[i:i + input_values])
28-
#
29-
# hidden2output = weights[
30-
# number_of_input_to_hidden_weights:number_of_input_to_hidden_weights + number_of_hidden_to_output_weights]
31-
# bias = weights[
32-
# number_of_input_to_hidden_weights + number_of_hidden_to_output_weights:]
33-
#
34-
# return (input2hidden, hidden2output, bias)
33+
import axelrod as axl
34+
from axelrod.strategies.ann import ANN, split_weights
35+
from axelrod_utils import mean, pstdev
3536

3637

3738
def get_random_weights(number):
3839
return [random.uniform(-1, 1) for _ in range(number)]
3940

4041

41-
def score_single(my_strategy_factory, other_strategy_factory, iterations=200,
42-
debug=False):
43-
if other_strategy_factory.classifier['stochastic']:
42+
def score_single(my_strategy_factory, other_strategy_factory, length=200):
43+
if other_strategy_factory().classifier['stochastic']:
4444
repetitions = 10
4545
else:
4646
repetitions = 1
4747
all_scores = []
4848
for _ in range(repetitions):
4949
me = my_strategy_factory()
5050
other = other_strategy_factory()
51-
me.set_tournament_attributes(length=iterations)
52-
other.set_tournament_attributes(length=iterations)
51+
me.set_match_attributes(length=length)
52+
other.set_match_attributes(length=length)
5353

54-
g = axelrod.Game()
55-
for _ in range(iterations):
54+
g = axl.Game()
55+
for _ in range(length):
5656
me.play(other)
57-
# print(me.history)
5857
iteration_score = sum([g.score(pair)[0] for pair in
59-
zip(me.history, other.history)]) / iterations
58+
zip(me.history, other.history)]) / length
6059
all_scores.append(iteration_score)
60+
return sum(all_scores)
6161

62-
def score_all_weights(population):
63-
return sorted(pool.map(score_weights, population), reverse=True)
64-
62+
def score_for(my_strategy_factory, other_strategies, iterations=200):
63+
my_scores = map(
64+
lambda x: score_single(my_strategy_factory, x, iterations),
65+
other_strategies)
66+
my_average_score = sum(my_scores) / len(my_scores)
67+
return my_average_score
6568

66-
def score_weights(weights):
69+
def score_weights(weights, strategies, input_values=17, hidden_layer_size=10):
6770
in2h, h2o, bias = split_weights(weights, input_values, hidden_layer_size)
6871
return (score_for(lambda: ANN(in2h, h2o, bias), strategies), weights)
6972

73+
from itertools import repeat
7074

71-
def score_for(my_strategy_factory, other_strategies=strategies, iterations=200,
72-
debug=False):
73-
my_scores = map(
74-
lambda x: score_single(my_strategy_factory, x, iterations, debug=debug),
75-
other_strategies)
76-
my_average_score = sum(my_scores) / len(my_scores)
77-
return (my_average_score)
78-
75+
def score_all_weights(population, strategies):
76+
# args = (population, strategies)
77+
results = pool.starmap(score_weights, zip(population, repeat(strategies)))
78+
return sorted(results, reverse=True)
79+
# return sorted(pool.map(score_weights, *args), reverse=True)
7980

8081
def evolve(starting_weights, mutation_rate, mutation_distance, generations,
81-
bottleneck, starting_pop, output_file):
82+
bottleneck, strategies, output_file):
8283

8384
current_bests = starting_weights
8485

8586
for generation in range(generations):
8687

8788
with open(output_file, "a") as output:
8889

89-
weights_to_copy = [x[1] for x in current_bests]
90-
90+
# weights_to_copy = [x[1] for x in current_bests]
91+
weights_to_copy = current_bests[0:3]
9192
copies = []
92-
9393
for w1 in weights_to_copy:
9494
for w2 in weights_to_copy:
9595
crossover = random.randrange(len(w1))
@@ -107,12 +107,12 @@ def evolve(starting_weights, mutation_rate, mutation_distance, generations,
107107

108108
# map the population to get a list of (score, weights) tuples
109109
# this list will be sorted by score, best weights first
110-
results = score_all_weights(population)
110+
results = score_all_weights(population, strategies)
111111

112-
current_bests = results[0:bottleneck]
112+
current_bests = results[0: bottleneck]
113113

114114
# get all the scores for this generation
115-
scores = [score for score, table in results]
115+
scores = [score for score, weights in results]
116116

117117
for value in [generation, results[0][1], results[0][0],
118118
mean(scores), pstdev(scores), mutation_rate,
@@ -124,3 +124,26 @@ def evolve(starting_weights, mutation_rate, mutation_distance, generations,
124124
mutation_distance *= 0.99
125125

126126
return current_bests
127+
128+
129+
if __name__ == '__main__':
130+
arguments = docopt(__doc__, version='ANN Evolver 0.1')
131+
# set up the process pool
132+
pool = Pool(processes=int(arguments['-i']))
133+
# Vars for the genetic algorithm
134+
mutation_rate = float(arguments['-u'])
135+
generations = int(arguments['-g'])
136+
bottleneck = int(arguments['-b'])
137+
mutation_distance = float(arguments['-d'])
138+
starting_population = int(arguments['-k'])
139+
output_file = arguments['-o']
140+
141+
starting_weights = [get_random_weights(190) for _ in range(starting_population)]
142+
143+
strategies = [s for s in axl.all_strategies
144+
if not s().classifier['long_run_time']]
145+
146+
evolve(starting_weights, mutation_rate, mutation_distance, generations,
147+
bottleneck, strategies, output_file)
148+
149+

axelrod_utils.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import division
2-
import axelrod
2+
from operator import itemgetter
33

4-
axl = axelrod
4+
import axelrod as axl
55

66
def mean(data):
77
"""Return the sample arithmetic mean of data."""
@@ -33,7 +33,7 @@ def score_single(me, other, iterations=200):
3333
Return the average score per turn for a player in a single match against
3434
an opponent.
3535
"""
36-
g = axelrod.Game()
36+
g = axl.Game()
3737
for _ in range(iterations):
3838
me.play(other)
3939
return sum([g.score(pair)[0] for pair in zip(me.history, other.history)]) / iterations
@@ -79,11 +79,9 @@ def do_table(table):
7979
Take a lookup table dict, construct a lambda factory for it, and return
8080
a tuple of the score and the table itself
8181
"""
82-
fac = lambda: axelrod.LookerUp(lookup_table=table)
82+
fac = lambda: axl.LookerUp(lookup_table=table)
8383
return (score_for(fac), table)
8484

85-
from operator import itemgetter, attrgetter, methodcaller
86-
8785
def score_tables(tables, pool):
8886
"""Use a multiprocessing Pool to take a bunch of tables and score them"""
8987
results = list(pool.map(do_table, tables))

lookup_evolve.py

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
-i PROCESSORS number of processors to use [default: 1]
1717
-o OUTPUT_FILE file to write statistics to [default: evolve.csv]
1818
-z INITIAL_POPULATION_FILE file to read an initial population from [default: None]
19-
20-
21-
2219
"""
20+
2321
from __future__ import division
2422
import itertools
2523
import random
26-
import copy
2724
from multiprocessing import Pool
2825

2926
from docopt import docopt
@@ -70,80 +67,95 @@ def evolve(starting_tables, mutation_rate, generations, bottleneck, pool, plys,
7067
7168
"""
7269

73-
# current_bests is a list of 2-tuples, each of which consists of a score and a lookup table
74-
# initially the collection of best tables are the ones supplied to start with
70+
# Current_bests is a list of 2-tuples, each of which consists of a score
71+
# and a lookup table initially the collection of best tables are the ones
72+
# supplied to start with
7573
current_bests = starting_tables
7674

7775
keys = list(sorted(table_keys(plys, start_plys)))
7876

7977
for generation in range(generations):
8078

81-
# because this is a long-running process we'll just keep appending to the output file
82-
# so we can monitor it while it's running
79+
# Because this is a long-running process we'll just keep appending to
80+
# the output file so we can monitor it while it's running
8381
with open(output_file, "a") as output:
8482
print("doing generation " + str(generation))
8583

86-
# the tables at the start of this generation are the best ones from the
87-
# previous generation (i.e. the second element of each tuple) plus a bunch
88-
# of random ones
89-
tables_to_copy = [x[1] for x in current_bests] + get_random_tables(plys, start_plys, starting_pop)
84+
# The tables at the start of this generation are the best ones from
85+
# the previous generation (i.e. the second element of each tuple)
86+
# plus a bunch of random ones
87+
tables_to_copy = [x[1] for x in current_bests] + \
88+
get_random_tables(plys, start_plys, starting_pop)
9089

91-
# set up new list to hold the tables that we are going to want to score
90+
# Set up new list to hold the tables that we are going to want to
91+
# score
9292
copies = crossover(tables_to_copy, mutation_rate, keys=keys)
9393

9494
# Mutations
9595
for c in copies:
96-
# flip each value with a probability proportional to the mutation rate
96+
# Flip each value with a probability proportional to the
97+
# mutation rate
9798
for history, move in c.items():
9899
if random.random() < mutation_rate:
99100
c[history] = 'C' if move == 'D' else 'D'
100101

101-
# the population of tables we want to consider includes the recombined, mutated copies, plus the originals
102+
# The population of tables we want to consider includes the
103+
# recombined, mutated copies, plus the originals
102104
population = copies + tables_to_copy
103105

104-
# map the population to get a list of (score, table) tuples
105-
# this list will be sorted by score, best tables first
106+
# Map the population to get a list of (score, table) tuples
107+
# This list will be sorted by score, best tables first
106108
results = axelrod_utils.score_tables(population, pool)
107109

108110
# keep the user informed
109111
print("generation " + str(generation))
110112

111-
# the best tables from this generation become the starting tables for the next generation
113+
# The best tables from this generation become the starting tables
114+
# for the next generation
112115
current_bests = results[0: bottleneck]
113116

114117
# get all the scores for this generation
115118
scores = [score for score, table in results]
116119

117-
# write the generation number, identifier of current best table, score of current best table, mean score, and SD of scores to the output file
118-
for value in [generation, axelrod_utils.id_for_table(results[0][1]), results[0][0], axelrod_utils.mean(scores), axelrod_utils.pstdev(scores)]:
120+
# write the generation number, identifier of current best table,
121+
# score of current best table, mean score, and SD of scores to the
122+
# output file
123+
for value in [generation,
124+
axelrod_utils.id_for_table(results[0][1]),
125+
results[0][0], axelrod_utils.mean(scores),
126+
axelrod_utils.pstdev(scores)]:
119127
output.write(str(value) + ",")
120128
output.write("\n")
121129

122-
return (current_bests)
130+
return current_bests
123131

124132
def table_keys(plys, opponent_start_plys):
125133
"""Return key for given size of table"""
126134

127-
# generate all the possible recent histories for the player and opponent
135+
# Generate all the possible recent histories for the player and opponent
128136
player_histories = [''.join(x) for x in itertools.product('CD', repeat=plys)]
129137
opponent_histories = [''.join(x) for x in itertools.product('CD', repeat=plys)]
130138

131-
# also generate all the possible opponent starting plays
139+
# Also generate all the possible opponent starting plays
132140
opponent_starts = [''.join(x) for x in itertools.product('CD', repeat=opponent_start_plys)]
133141

134-
# the list of keys for the lookup table is just the product of these three lists
135-
lookup_table_keys = list(itertools.product(opponent_starts,player_histories, opponent_histories))
142+
# The list of keys for the lookup table is just the product of these three
143+
# lists
144+
lookup_table_keys = list(itertools.product(opponent_starts,
145+
player_histories,
146+
opponent_histories))
136147

137148
return lookup_table_keys
138149

139150

140151
def get_random_tables(plys, opponent_start_plys, number):
141152
"""Return randomly-generated lookup tables"""
142153

143-
# the list of keys for the lookup table is just the product of these three lists
154+
# The list of keys for the lookup table is just the product of these three
155+
# lists
144156
lookup_table_keys = table_keys(plys, opponent_start_plys)
145157

146-
# to get a pattern, we just randomly pick between C and D for each key
158+
# To get a pattern, we just randomly pick between C and D for each key
147159
patterns = [''.join([random.choice("CD") for _ in lookup_table_keys]) for i in range(number)]
148160

149161
# zip together the keys and the patterns to give a table
@@ -185,4 +197,5 @@ def get_random_tables(plys, opponent_start_plys, number):
185197
real_starting_tables = axelrod_utils.score_tables(starting_tables, pool)
186198

187199
# kick off the evolve function
188-
evolve(real_starting_tables, mutation_rate, generations, bottleneck, pool, plys, start_plys, starting_pop, arguments['-o'])
200+
evolve(real_starting_tables, mutation_rate, generations, bottleneck, pool,
201+
plys, start_plys, starting_pop, arguments['-o'])

run_ann.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python ann_evolve.py -i 4 -u 0.5 -g 20 -b 10 -d 0.1 -k 5

run.sh renamed to run_lookerup.sh

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ python lookup_evolve.py -p 1 -s 2 -g 100000 -k 20 -u 0.1 -b 20 -i 2 -o evolve1-
88

99
python lookup_evolve.py -p 2 -s 1 -g 100000 -k 20 -u 0.1 -b 20 -i 2 -o evolve2-1.csv
1010

11-
12-
1311
python lookup_evolve.py -p 1 -s 1 -g 100000 -k 10 -u 0.1 -b 10 -i 4 -o evolve1-1.csv
1412

1513
python lookup_evolve.py -p 1 -s 1 -g 100000 -k 10 -u 0.1 -b 10 -i 4 -o evolve1-1.csv
1614

17-
18-
19-
2015
python lookup_evolve.py -p 3 -s 3 -g 100000 -k 20 -u 0.001 -b 20 -i 4 -o evolve3-3.csv

0 commit comments

Comments
 (0)