-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevo.py
More file actions
111 lines (88 loc) · 3.06 KB
/
Copy pathevo.py
File metadata and controls
111 lines (88 loc) · 3.06 KB
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
from copy import deepcopy
import numpy as np
import random
import math
import ai.chromedino as chromedino
INPUT_SIZE = 3
OUTPUT_SIZE = 3
HIDDEN_LAYERS = 2
LAYER_SIZE = 16
GENOMES_SIZE = 100
EPOCHS = 20
MUTATION_RATE = 0.2
ELITISM = 0.1
# MARK: Genome
class Genome:
def __init__(
self,
all_weights: list, # 3D array
all_biases: list # 2D array
):
self.id = id(self)
self.all_weights = all_weights
self.all_biases = all_biases
self.score = 0
def __lt__(self, other):
return self.score < other.score
def create_genome() -> Genome:
# Three arrays of weights
# 2x16, 16x16, 16x3
# Weights are initialized with a normal distribution
all_weights = [
np.random.normal(size=(INPUT_SIZE, LAYER_SIZE)),
*np.random.normal(size=(
HIDDEN_LAYERS - 1,
LAYER_SIZE,
LAYER_SIZE)
),
np.random.normal(size=(LAYER_SIZE, OUTPUT_SIZE))
]
# Three arrays of biases
# 1x16, 1x16, 1x3
# Biases are initialized with a normal distribution
all_biases = [
*np.random.normal(size=(HIDDEN_LAYERS, LAYER_SIZE)),
np.random.normal(size=(OUTPUT_SIZE))
]
return Genome(all_weights, all_biases)
# MARK: Evolution
def get_fitness(genomes: list, epoch) -> list:
return chromedino.start_game(genomes, epoch)
def select_elite(fitnesses: list[Genome]) -> list[Genome]:
return sorted(fitnesses, reverse=True)[:math.ceil(ELITISM * len(fitnesses))]
def select_parents(fitnesses: list[Genome]) -> list[Genome]:
# Tournament selection
parents = []
for _ in range(GENOMES_SIZE):
tournament = random.sample(fitnesses, 2)
parents.append(max(tournament))
return parents
def mutate(genome: Genome) -> Genome:
for i, weights in enumerate(genome.all_weights):
for row in range(len(weights)):
for col in range(len(weights[row])):
if np.random.rand() < MUTATION_RATE:
genome.all_weights[i][row, col] += np.random.normal()
for i, biases in enumerate(genome.all_biases):
for j in range(len(biases)):
if np.random.rand() < MUTATION_RATE:
genome.all_biases[i][j] += np.random.normal()
return genome
def breed():
genomes = [create_genome() for _ in range(GENOMES_SIZE)]
for epoch in range(EPOCHS):
genomes_with_fitness = get_fitness(genomes, epoch)
parents = select_parents(genomes_with_fitness)
new_genomes = select_elite(genomes_with_fitness)
for _ in range(GENOMES_SIZE - len(new_genomes)):
# Without deepcopy, the same same genome would be mutated
# multiple times, including the genome selected through
# elitism.
new_genome = mutate(deepcopy(random.choice(parents)))
new_genome.score = 0
new_genomes.append(new_genome)
genomes = new_genomes
best_genome = max(genomes)
print(f"Best score {best_genome.score}")
if __name__ == "__main__":
breed()