Skip to content

Commit 60d7949

Browse files
committed
Working versions of pso_evolve.py, lookup_evolve.py, and ann_evolve.py
1 parent 789f0a8 commit 60d7949

File tree

3 files changed

+43
-100
lines changed

3 files changed

+43
-100
lines changed

ann_evolve.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Training ANN strategies.
2+
Training ANN strategies with an evolutionary algorithm.
33
44
Original code by Martin Jones @mojones:
55
https://gist.github.com/mojones/b809ba565c93feb8d44becc7b93e37c6
@@ -13,7 +13,7 @@
1313
-h --help show this
1414
-g GENERATIONS how many generations to run the program for [default: 100]
1515
-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]
16+
-d MUTATION_DISTANCE amount of change a mutation will cause [default: 0.5]
1717
-b BOTTLENECK number of individuals to keep from each generation [default: 10]
1818
-i PROCESSORS number of processors to use [default: 1]
1919
-o OUTPUT_FILE file to write statistics to [default: ann_out.csv]
@@ -38,7 +38,6 @@
3838
def get_random_weights(number):
3939
return [random.uniform(-1, 1) for _ in range(number)]
4040

41-
4241
def score_single(my_strategy_factory, other_strategy_factory, length=200):
4342
if other_strategy_factory().classifier['stochastic']:
4443
repetitions = 10
@@ -57,12 +56,12 @@ def score_single(my_strategy_factory, other_strategy_factory, length=200):
5756
iteration_score = sum([g.score(pair)[0] for pair in
5857
zip(me.history, other.history)]) / length
5958
all_scores.append(iteration_score)
60-
return sum(all_scores)
59+
return sum(all_scores) / repetitions
6160

6261
def score_for(my_strategy_factory, other_strategies, iterations=200):
63-
my_scores = map(
62+
my_scores = list(map(
6463
lambda x: score_single(my_strategy_factory, x, iterations),
65-
other_strategies)
64+
other_strategies))
6665
my_average_score = sum(my_scores) / len(my_scores)
6766
return my_average_score
6867

@@ -73,35 +72,33 @@ def score_weights(weights, strategies, input_values=17, hidden_layer_size=10):
7372
from itertools import repeat
7473

7574
def score_all_weights(population, strategies):
76-
# args = (population, strategies)
7775
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)
76+
return list(sorted(results, reverse=True))
8077

8178
def evolve(starting_weights, mutation_rate, mutation_distance, generations,
8279
bottleneck, strategies, output_file):
8380

84-
current_bests = starting_weights
81+
# Append scores
82+
current_bests = [[0, x] for x in starting_weights]
8583

8684
for generation in range(generations):
87-
8885
with open(output_file, "a") as output:
89-
90-
# weights_to_copy = [x[1] for x in current_bests]
91-
weights_to_copy = current_bests[0:3]
86+
weights_to_copy = [x[1] for x in current_bests]
9287
copies = []
88+
# Crossover
9389
for w1 in weights_to_copy:
9490
for w2 in weights_to_copy:
9591
crossover = random.randrange(len(w1))
9692
new_weights = copy.deepcopy(
9793
w1[0:crossover]) + copy.deepcopy(w2[crossover:])
9894
copies.append(new_weights)
9995

100-
for c in copies:
96+
# Mutate
97+
for _, c in copies:
10198
for i in range(len(c)):
10299
if random.random() < mutation_rate:
103-
c[i] = c[i] * (
104-
1 + (random.uniform(-1, 1) * mutation_distance))
100+
r = 1 + random.uniform(-1, 1) * mutation_distance
101+
c[i] = c[i] * r
105102

106103
population = copies + weights_to_copy
107104

@@ -119,7 +116,7 @@ def evolve(starting_weights, mutation_rate, mutation_distance, generations,
119116
mutation_distance]:
120117
output.write(str(value) + "\t")
121118
output.write("\n")
122-
119+
print("Generation", generation, "| Best Score:", scores[0])
123120
mutation_rate *= 0.99
124121
mutation_distance *= 0.99
125122

@@ -138,9 +135,9 @@ def evolve(starting_weights, mutation_rate, mutation_distance, generations,
138135
starting_population = int(arguments['-k'])
139136
output_file = arguments['-o']
140137

141-
starting_weights = [get_random_weights(190) for _ in range(starting_population)]
138+
starting_weights = [[0, get_random_weights(190)] for _ in range(starting_population)]
142139

143-
strategies = [s for s in axl.all_strategies
140+
strategies = [s for s in axl.strategies
144141
if not s().classifier['long_run_time']]
145142

146143
evolve(starting_weights, mutation_rate, mutation_distance, generations,

axelrod_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def score_for(my_strategy_factory, iterations=200):
4646
run 100 repetitions and take the average to get a good estimate.
4747
"""
4848
scores_for_all_opponents = []
49-
for opponent in axl.ordinary_strategies:
49+
opponents = [s for s in axl.strategies if not s().classifier['long_run_time']]
50+
for opponent in opponents:
5051
if opponent().classifier['stochastic']:
5152
repetitions = 100
5253
else:

pso_evolve.py

Lines changed: 24 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,74 +6,13 @@
66
Based on Martin Jones @mojones original LookerUp code
77
"""
88

9-
import axelrod
9+
from itertools import product
1010

1111
from pyswarm import pso
1212

13-
14-
class Gambler(Player):
15-
16-
name = 'Gambler'
17-
classifier = {
18-
'memory_depth': float('inf'),
19-
'stochastic': True,
20-
'makes_use_of': set(),
21-
'inspects_source': False,
22-
'manipulates_source': False,
23-
'manipulates_state': False
24-
}
25-
26-
@init_args
27-
def __init__(self, lookup_table=None):
28-
"""
29-
If no lookup table is provided to the constructor, then use the TFT one.
30-
"""
31-
Player.__init__(self)
32-
33-
if not lookup_table:
34-
lookup_table = {
35-
('', 'C', 'D') : 0,
36-
('', 'D', 'D') : 0,
37-
('', 'C', 'C') : 1,
38-
('', 'D', 'C') : 1,
39-
}
40-
41-
self.lookup_table = lookup_table
42-
# Rather than pass the number of previous turns (m) to consider in as a
43-
# separate variable, figure it out. The number of turns is the length
44-
# of the second element of any given key in the dict.
45-
self.plays = len(list(self.lookup_table.keys())[0][1])
46-
# The number of opponent starting actions is the length of the first
47-
# element of any given key in the dict.
48-
self.opponent_start_plays = len(list(self.lookup_table.keys())[0][0])
49-
# If the table dictates to ignore the opening actions of the opponent
50-
# then the memory classification is adjusted
51-
if self.opponent_start_plays == 0:
52-
self.classifier['memory_depth'] = self.plays
53-
54-
# Ensure that table is well-formed
55-
for k, v in lookup_table.items():
56-
if (len(k[1]) != self.plays) or (len(k[0]) != self.opponent_start_plays):
57-
raise ValueError("All table elements must have the same size")
58-
59-
60-
def strategy(self, opponent):
61-
# If there isn't enough history to lookup an action, cooperate.
62-
if len(self.history) < max(self.plays, self.opponent_start_plays):
63-
return C
64-
# Count backward m turns to get my own recent history.
65-
history_start = -1 * self.plays
66-
my_history = ''.join(self.history[history_start:])
67-
# Do the same for the opponent.
68-
opponent_history = ''.join(opponent.history[history_start:])
69-
# Get the opponents first n actions.
70-
opponent_start = ''.join(opponent.history[:self.opponent_start_plays])
71-
# Put these three strings together in a tuple.
72-
key = (opponent_start, my_history, opponent_history)
73-
# Look up the action associated with that tuple in the lookup table.
74-
action = float(self.lookup_table[key])
75-
return random_choice(action)
76-
13+
import axelrod as axl
14+
from axelrod import Gambler, init_args
15+
from axelrod_utils import score_single
7716

7817

7918
class TestGambler(Gambler):
@@ -83,7 +22,8 @@ class TestGambler(Gambler):
8322

8423
name = "TestGambler"
8524

86-
def __init__(self,pattern):
25+
@init_args
26+
def __init__(self, pattern):
8727
plays = 2
8828
opponent_start_plays = 2
8929

@@ -102,7 +42,7 @@ def __init__(self,pattern):
10242
Gambler.__init__(self, lookup_table=lookup_table)
10343

10444

105-
def score_for_pattern(my_strategy_factory,pattern, iterations=200):
45+
def score_for_pattern(my_strategy_factory, pattern, iterations=200):
10646
"""
10747
Given a function that will return a strategy,
10848
calculate the average score per turn
@@ -112,29 +52,31 @@ def score_for_pattern(my_strategy_factory,pattern, iterations=200):
11252
a good estimate.
11353
"""
11454
scores_for_all_opponents = []
115-
for opponent in axelrod.ordinary_strategies:
55+
strategies = [s for s in axl.strategies
56+
if not s().classifier['long_run_time']]
57+
for opponent in strategies:
11658

117-
# decide whether we need to sample or not
118-
if opponent.classifier['stochastic']:
59+
# Decide whether we need to sample or not
60+
if opponent().classifier['stochastic']:
11961
repetitions = 100
12062
else:
12163
repetitions = 1
12264
scores_for_this_opponent = []
12365

124-
# calculate an average for this opponent
66+
# Calculate an average for this opponent
12567
for _ in range(repetitions):
12668
me = my_strategy_factory(pattern)
12769
other = opponent()
128-
# make sure that both players know what length the match will be
129-
me.set_tournament_attributes(length=iterations)
130-
other.set_tournament_attributes(length=iterations)
70+
# Make sure that both players know what length the match will be
71+
me.set_match_attributes(length=iterations)
72+
other.set_match_attributes(length=iterations)
13173

13274
scores_for_this_opponent.append(score_single(me, other, iterations))
13375

13476
mean_vs_opponent = sum(scores_for_this_opponent) / len(scores_for_this_opponent)
13577
scores_for_all_opponents.append(mean_vs_opponent)
13678

137-
# calculate the average for all opponents
79+
# Calculate the average for all opponents
13880
overall_average_score = sum(scores_for_all_opponents) / len(scores_for_all_opponents)
13981
return overall_average_score
14082

@@ -152,7 +94,10 @@ def optimizepso(x):
15294
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
15395
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
15496

155-
# The parameters of phip, phig and omega will lead to slower conversion
156-
xopt, fopt = pso(optimizepso, lb, ub, swarmsize=100, maxiter=20, processes=60,
157-
debug=True,
158-
phip=0.8, phig=0.8, omega=0.8)
97+
# There is a multiprocessing version (0.7) of pyswarm available at
98+
# https://github.com/tisimst/pyswarm
99+
# Pip installs version 0.6
100+
xopt, fopt = pso(optimizepso, lb, ub, swarmsize=100, maxiter=20,
101+
debug=True, phip=0.8, phig=0.8, omega=0.8)
102+
print(xopt)
103+
print(fopt)

0 commit comments

Comments
 (0)