Snake KI
3. Agent

Teil 2

Der Agent

Ein sogenannter Agent ist im Reinforcement-Learning die Schnittstelle zwischen dem Neuronalen-Netzwerk / Modell und dem environment. In unserem Fall ist das environment unser Snake spiel. Dieses kann aber in verschiedenen Anwendungen, z.B. dem Autonomen Fahren ein völlig anderes sein.
Der Agent dient also dazu das Modell anhand des environments zu nutzen und zu trainieren.
In unserem Fall ist der Code für den Agenten in der agent.py Datei zu finden. Er funktioniert wiefolgt:
Es gibt eine sogenannte get_state() Methode

def get_state(game: environment.World):
        state = [
            # current direction
            game.snake.orientation[0] == -1,  
            game.snake.orientation[0] == 1,
            game.snake.orientation[1] == 1,
            game.snake.orientation[1] == -1,

            # danger ahead
            game.danger_in_direction(environment.Direction.LEFT) == 1,
            game.danger_in_direction(environment.Direction.FORWARD) == 1,
            game.danger_in_direction(environment.Direction.RIGHT) == 1,

            # food pos
            game.foods[0][0] < game.snake.head.pos[0], # food left
            game.foods[0][0] > game.snake.head.pos[0], # food right
            game.foods[0][1] < game.snake.head.pos[1], # food above
            game.foods[0][1] > game.snake.head.pos[1], # food below
        ]
        return np.array(state, dtype=int)

Diese nimmt als Eingabe das environment und gibt einen sogenannten state zurück. Dieser repräsentiert die Eingabe für das Neuronale Netzwerk. Hier wird also definiert was das Modell von dem environment sieht und basierend auf diesen Informationen berechnet er sich eine statistisch gesehen beste Aktion.
in diesem simplen Fall besteht der state aus 11 Werten die entweder 0 oder 1 sind.
4 Werte welche die Ausrichtung der Schlange repräsentieren
3 Werte ob links, rechts oder geradeaus eine Gefahr herrscht
4 Werte für die relative Positon von dem Apfel

Kommen wir zum Training hierfür sind mehrere Methoden wichtig z.B. die get_action() Methode:

def get_action(self, state):
        self.epsilon = 80 - self.n_games
        final_move = [0,0,0]
        if random.randint(0, 200) < self.epsilon:
            move = random.randint(0, 2)
            final_move[move] = 1
        else:
            state0 = torch.tensor(state, dtype=torch.float)
            prediction = self.model(state0)
            move = torch.argmax(prediction).item()
            final_move[move] = 1
        
        return final_move

Diese ist der sogenannte forward-pass für das Modell. Das heißt, dass der state dem Modell als Eingabewert gegeben wird und eine Aktion oder auch prediction genannt ausgegeben wird.
Zu beachten ist, dass die ersten 80 Spiele, welche das Modell durchtrainiert, Randomness in die Aktionen eingeführt wird. Heißt, dass die Entscheidung was die Schlange macht nicht beim Modell liegt, sondern dem Zufall überlassen wird. Der Grund hierfür ist, dass das Modell noch "unerfahren" ist und durch die Zufälligkeit verschiedene Situationen sammelt, aus denen es lernen kann.

Der nächste Codeblock ist sehr wichtig für das Training des Modells:

def remember(self, state, action, reward, next_state, done):
        self.memory.append((state, action, reward, next_state, done))
    
def train_long_memory(self):
    if len(self.memory) > BATCH_SIZE:
        mini_sample = random.sample(self.memory, BATCH_SIZE) # list of tuples
    else:
        mini_sample = self.memory

    states, actions, rewards, next_states, dones = zip(*mini_sample)
    self.trainer.train_step(states, actions, rewards, next_states, dones)

def train_short_memory(self, state, action, reward, next_state, done):
    self.trainer.train_step(state, action, reward, next_state, done)

In diesem Codeblock sind 3 Methoden.
Die remember Methode wird nach jedem Spielzug aufgerufen. Diese speichert im memory self.memory = deque(maxlen=MAX_MEMORY) # popleft() 5 Werte

  • state -> der ausgangs state
  • action -> die genommene Aktion welche von get_action() kommt
  • reward -> die Belohnung die wir von der step() Methode zurückkriegen
  • next_state -> der state nach dem Zug
  • done -> ob das Spiel nach dem Zug vorbei ist

Da jetzt all diese Situationen im "Gedächtnis" gespeichert sind, können sie in train_long_memory() verwendet werden. Diese Methode nimmt einen Zufälligen Batch aus diesen Situationen und trainiert das Modell auf diesen Batch. (Wie der training step verläuft und wie das Modell insgesamt aussieht, wird im nächsten Teil besprochen)
train_long_memory() wird nach jeder Runde Snake aufgerufen.
Im gegensatz zu train_short_memory(). Diese macht das gleiche wie train_long_memory(), nur mit dem jetzigen state und wird jeden Schritt aufgerufen.

Zuletzt gibt es in der agent.py Datei eine eigenständige Methode die train() heißt.

def train():
    total_score = 0
    record = 0
    agent = Agent()
    game = environment.World()
    while True:
        # get old state
        state_old = Agent.get_state(game)

        # get move
        final_move = agent.get_action(state_old)

        # perform move and get new state
        reward, done, score = game.step(final_move)
        state_new = Agent.get_state(game)

        # train short memory
        agent.train_short_memory(state_old, final_move, reward, state_new, done)

        # remember
        agent.remember(state_old, final_move, reward, state_new, done)

        if done:
            # train long memory, print result
            game.reset()
            agent.n_games += 1
            agent.train_long_memory()

            if score > record:
                record = score
                agent.model.save()

            print('Game', agent.n_games, 'Score', score, 'Record:', record)

Diese besteht aus einem while-True-loop und trainiert das Modell. Hier sieht man die ganzen oben genannten Methoden und wie sie genau genutzt werden.
Als letztes kommt noch das Model