NEAT Implemenation

The first step in our project was to create a basic NEAT implementation. NEAT stands for NeuroEvolution of Augmenting Topologies, which is a form of evolutionary machine learning developed by researchers at MIT. The result is a fast but structured machine learning algorithm.

Our Code:

NEAT Model Class:

1[NEAT]
2fitness_criterion     = max
3fitness_threshold     = 100000
4pop_size              = 10
5reset_on_extinction   = True
6
7[DefaultGenome]
8# node activation options
9activation_default      = sigmoid
10activation_mutate_rate  = 0.05
11activation_options      = sigmoid gauss 
12#abs clamped cube exp gauss hat identity inv log relu sigmoid sin softplus square tanh
13
14# node aggregation options
15aggregation_default     = random
16aggregation_mutate_rate = 0.05
17aggregation_options     = sum product min max mean median maxabs
18
19# node bias options
20bias_init_mean          = 0.05
21bias_init_stdev         = 1.0
22bias_max_value          = 30.0
23bias_min_value          = -30.0
24bias_mutate_power       = 0.5
25bias_mutate_rate        = 0.7
26bias_replace_rate       = 0.1
27
28# genome compatibility options
29compatibility_disjoint_coefficient = 1.0
30compatibility_weight_coefficient   = 0.5
31
32# connection add/remove rates
33conn_add_prob           = 0.5
34conn_delete_prob        = 0.1
35
36# connection enable options
37enabled_default         = True
38enabled_mutate_rate     = 0.2
39
40feed_forward            = False
41#initial_connection      = unconnected
42initial_connection      = partial_nodirect 0.5
43
44# node add/remove rates
45node_add_prob           = 0.5
46node_delete_prob        = 0.5
47
48# network parameters
49num_hidden              = 0
50num_inputs              = 1120
51num_outputs             = 12
52
53# node response options
54response_init_mean      = 1.0
55response_init_stdev     = 0.05
56response_max_value      = 30.0
57response_min_value      = -30.0
58response_mutate_power   = 0.1
59response_mutate_rate    = 0.75
60response_replace_rate   = 0.1
61
62# connection weight options
63weight_init_mean        = 0.1
64weight_init_stdev       = 1.0
65weight_max_value        = 30
66weight_min_value        = -30
67weight_mutate_power     = 0.5
68weight_mutate_rate      = 0.8
69weight_replace_rate     = 0.1
70
71[DefaultSpeciesSet]
72compatibility_threshold = 2.5
73
74[DefaultStagnation]
75species_fitness_func = max
76max_stagnation       = 50
77species_elitism      = 0
78
79[DefaultReproduction]
80elitism            = 1
81survival_threshold = 0.3

NEAT Agent Class:

1import sys
2import os
3
4script_dir = os.path.dirname(__file__)
5
6sys.path.append(script_dir + '/../agents')
7sys.path.append(script_dir + '/../interface')
8sys.path.append(script_dir + '/../learning')
9
10from agent_base import *
11from action_space import *
12from train_neat import *
13
14class NeatAgent(AgentBase):
15
16  def load(self, filename):
17    None
18    # TODO:
19
20  def save(self, filename):
21    None
22    # TODO:
23    
24  def train(self):
25    train_neat()
26
27  def decide(self, obs, info) -> list:
28    buttons = ActionSpace.move_right()	
29
30    return buttons
31
32  # Returns name of agent as a string
33  def name(self) -> str:
34    return "NeatAgent"
35
36  def to_string(self) -> str:
37    return self.name()

NEAT Traing Class:

1##---------------Sources-------------------------##
2# Neat NN Implementation: https://gitlab.com/lucasrthompson/Sonic-Bot-In-OpenAI-and-NEAT
3# DeepQ Image Processing for GymRetro:  https://github.com/deepanshut041/Reinforcement-Learning 
4# Helper Functions for Gym Retro: https://github.com/moversti/sonicNEAT 
5##-----------------------------------------------##
6
7import retro
8import numpy as np
9import cv2
10import neat
11import pickle
12import sys
13import os
14
15from configparser import Interpolation
16from inspect import getsourcefile
17
18from vision.greyImageViewer import GreyImageViewer
19from vision.controllerViewer import ControllerViewer
20
21script_dir = os.path.dirname(__file__)
22sys.path.append(script_dir + '/../agents')
23sys.path.append(script_dir + '/../interface')
24
25# Trains a NEAT NN.
26def train_neat():
27    env = retro.make(game="SonicTheHedgehog-Genesis", state="GreenHillZone.Act1", scenario="contest", record='.')
28    imgarray = []
29    xpos_end = 0
30
31    SEE_NETWORK_INPUT=True
32
33    resume = True
34    restore_file = "neat-checkpoint-32"
35
36    viewer = GreyImageViewer()
37    controllerViewer = ControllerViewer()
38
39    def eval_genomes(genomes, config):
40        for genome_id, genome in genomes:
41            ob = env.reset()
42            ac = env.action_space.sample()
43
44            inx, iny, inc = env.observation_space.shape
45
46            inx = int(inx / 8)
47            iny = int(iny / 8)
48
49            net = neat.nn.recurrent.RecurrentNetwork.create(genome, config)
50
51            current_max_fitness = 0
52            fitness_current = 0
53            frame = 0
54            counter = 0
55            xpos = 0
56
57            done = False
58
59            while not done:
60
61                env.render()
62                frame += 1
63                ob = cv2.resize(ob, (inx, iny))
64                #ob = cv2.blur(ob, (3,3))
65                ob = cv2.cvtColor(ob, cv2.COLOR_BGR2GRAY)
66
67                if SEE_NETWORK_INPUT:
68                    img = ob.copy()
69                    dst = (img.shape[0] * 8, img.shape[1] * 8)
70                    img = cv2.resize(img, dst, interpolation=cv2.INTER_NEAREST)
71                    img = np.flipud(img)
72                    viewer.imshow(img)
73
74                ob = np.reshape(ob, (inx, iny))
75
76                imgarray = np.ndarray.flatten(ob)
77
78                nnOutput = net.activate(imgarray)
79                ac = env.action_to_array(nnOutput)
80                # print(ac)
81                controllerViewer.actionshow(ac)
82                ob, rew, done, info = env.step(nnOutput)
83
84                xpos = info['x']
85
86                if xpos >= 60000:
87                    fitness_current += 10000000
88                    done = True
89
90                fitness_current += rew
91
92                if fitness_current > current_max_fitness:
93                    current_max_fitness = fitness_current
94                    counter = 0
95                else:
96                    counter += 1
97
98                if done or counter == 250:
99                    done = True
100                    print(genome_id, fitness_current)
101
102                genome.fitness = fitness_current
103
104    # Get directory of current script. This directory is also the one contain 'config-feedforward'
105    config_dir = os.path.dirname(getsourcefile(lambda:0))   
106
107    config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
108                      neat.DefaultSpeciesSet, neat.DefaultStagnation,
109                      'source/models/neat-feedforward')
110    if resume == True:
111        p = neat.Checkpointer.restore_checkpoint(restore_file)
112    else:
113        p = neat.Population(config)
114
115    p.add_reporter(neat.StdOutReporter(True))
116    stats = neat.StatisticsReporter()
117    p.add_reporter(stats)
118    p.add_reporter(neat.Checkpointer(1, filename_prefix='neat-checkpoint-'))
119
120    winner = p.run(eval_genomes, 1)
121
122    with open('winnerTEST.pkl', 'wb') as output:
123        pickle.dump(winner, output, 1)
124
125if __name__ == "__main__":
126    train_neat()