Breakout DQN : Fichier d'entrainement

Suite du sujet Introduction à l'apprentissage par renforcement profond :

Fonctionnement de l’émulateur

Actions disponibles

On utilisera OpenAI Gym. Il existe différentes versions du casse-brique, on utilisera Breakout-Deterministicv4.
Cette version nous accorde 4 actions différentes représentées par un entier :
0 correspond à l’action FIRE, l’émulateur est une copie du jeu original et dans ce dernier, la manette possédait un bouton FIRE qu’on devait actionner pour relancer la balle après chaque perte de vie.
1 correspond à l’action NOOP, l’agent se contente d’attendre.
2 correspond à l’action RIGHT
3 correspond à l’action LEFT

Pour vérifier les actions qui sont disponibles dans un environnement, il faut utiliser le code ci-dessous:

import gym
env = gym.make('BreakoutDeterministic-v4')
print(env.unwrapped.get_action_meanings())#noop fire right left

Déroulement d’une partie

A chaque étape du jeu, l’agent choisit une action a_t et l’émulateur nous renvoie 4 variables :
-une frame du jeu : taille 210*160
-une récompense r_{t} : valeur comprise entre 0 et 7
-un booléen qui indique si on a perdu la partie
-Une variable “info” qui permet de récupérer le nombre de vie restantes.

Le code d’entrainement

Le code de départ

import gym

#version du simulateur
env = gym.make("BreakoutDeterministic-v4")

#variables
game_over=False #on a perdu nos 5 vies
games=0 #nombre de parties
steps=0 #nombre d'étapes

while True:
      #nouvelle partie
      games += 1
      frame=env.reset()
      game_over=False
      while (game_over != True):
             env.render()
             action = env.action_space.sample() #notre agent choisi une action au hasard
             frame, reward, game_over, info = env.step(action)
             steps += 1

Actuellement, notre agent n’effectue que des actions aléatoires, on va donc lui demander de choisir les prochaines actions.

Mise en place de la politique \Large{\epsilon}-greedy

Durant les 500 premières étapes de jeu, l’agent jouera uniquement des actions aléatoires afin de remplir la mémoire de rejeu. A la fin de celles-ci, l’agent aura une probabilité \large{\epsilon} de faire une action aléatoire plutôt que de choisir celle qu’il considère comme la meilleure action. La probabilité \large{\epsilon} décroit linéairement jusqu’à 1 000 000 d’étapes, au-delà elle sera fixée à 10%.

#à ajouter dans leur zone respective
#dépendance
import numpy as np
from random import randint
import breakout_agent

#Hyperparamètres
ONLY_EXPLORATION_END=500
TRAINING_END=1000000 #nombre d'étapes du jeu
EPSILON_END=0.1
EPSILON_DECAY=(1-EPSILON_END)/TRAINING_END

#variables
agent=breakout_agent.DQNAgent()
games=0 #nombre de parties
epsilon=1 #probabilité de faire une action au hasard
score=0

def epsilon_greedy(steps,TRAINING_END,EPSILON_END,EPSILON_DECAY):
    if(steps<TRAINING_END):
        epsilon=1-steps*EPSILON_DECAY
    else:
        epsilon=EPSILON_END
    return epsilon

while True:
      games += 1
      frame=env.reset()
      game_over=False
      while (game_over != True):
             env.render()
             #choix de l'action
             epsilon=epsilon_greedy(steps,TRAINING_END,EPSILON_END,EPSILON_DECAY)
             if (steps<REPLAY_MEMORY_SIZE_MIN) or (np.random.rand()<epsilon):
                      action=env.action_space.sample()
             else:
                      state=replay_memory.GetLastState()
                      action=agent.GetAction(state)
             #l'agent effectue l'action
             frame_prime, reward, game_over, info = env.step(action)
             #on met à jour le score
             score += reward
             #on met à jour le nombre d'étape
             steps += 1

Ajoutons l’expérience à la mémoire de rejeu

Pour que notre agent réalise que la perte d’une vie est néfaste, on considérera que celle-ci induit la fin du jeu. On ne relancera pas une nouvelle partie, on indiquera seulement à notre agent sa défaite. Pour accomplir cela, on utilisera la variable “info” dans le code ci-dessus. Cette variable est un dictionnaire qui contient le nombre de vies restantes.

#à ajouter dans leur zone respective
#dépendance
import breakout_memory

#Hyperparamètres:
REPLAY_MEMORY_SIZE=1000000

#variables
replay_memory=breakout_memory.ReplayMemory(REPLAY_MEMORY_SIZE)
life_before=0
life_after=0
lost_a_life=False
while True:
    #on commence un nouvelle partie
    games += 1
    state=env.reset()
    game_over=False
    life_before=5
    while game_over != True:#tant que la partie continue faire
        [...] le code ne change pas
        steps += 1
        #on regarde si on a perdu une vie
        life_after=info["ale.lives"]
        if((life_after-life_before)==(-1)):
            lost_a_life=True
        else:
            lost_a_life=False
        life_before=life_after
        #on ajoute l'expérience à la mémoire de rejeu
        replay_memory.add_experience(frame_prime, action, reward, lost_a_life)

Apprentissage de notre agent

Une fois la période d’exploration pure terminée (500 étapes), notre agent apprendra toutes les 4 étapes de jeu. On mettra à jour notre DQN cible (target) toutes les 10 000 étapes de jeu.

#à ajouter dans leur zone respective
#Hyperparamètres:
GAMMA=0.99 #taux de rabais
MINIBATCH_SIZE=32
TARGET_UPDATE=10000 #nombres d'étapes

#variables
train_counts=0 #nombre d'entrainements
training=False
while True:
    #on commence un nouvelle partie
    games += 1
    state=env.reset()
    game_over=False
    life_before=5
    while game_over != True:#tant que la partie continue faire
        env.render()
        training=False
        [...] le code ne change pas
        steps += 1
        ##l'agent doit-il apprendre?
        if((steps%4)==0):
            training=True
        else:
            training=False
        [...] le code ne change pas
        #on ajoute l'expérience à la mémoire de rejeu
        replay_memory.add_experience(frame_prime, action, reward, lost_a_life)
        #l'agent apprend toutes les 4 étapes de jeu
        if((steps>=ONLY_EXPLORATION_END) and training):
            minibatch=replay_memory.GetMinibatch(MINIBATCH_SIZE)
            agent.learning(GAMMA,minibatch,MINIBATCH_SIZE)
            train_counts += 1
         #on copie le DQN primaire dans le DQN secondaire toutes les 10k étapes
        if(steps%TARGET_UPDATE==0):
            agent.Online2Target

Partie terminée

Dans cette section, on se contente d’afficher les scores et de sauvegarder notre agent chaque fois qu’il accomplit une bonne performance.

#variables à ajouter
record=0
scrore_mean=0 #moyenne sur les 100 dernières parties
next_level=10 #+10 à chaque fois que le score moyen dépasse cette variable
while True:
    games += 1
    state=env.reset()#on commence un nouvelle partie
    game_over=False
    score=0 #avec un nouveau score
    life_before=5
    while game_over != True:#tant que la partie continue faire
        [...] le code ne change pas
    ##partie terminée
    score_mean=score_mean+score
    #on affiche les scores
    if score>record:
        record=score
        print ("partie :",games, "a fait un score de", score)
    #moyenne sur les 100 dernières parties
    if ((games%100)==0):
        score_mean=score_mean/100.0
        if(score_mean>=next_level):
            filename="dqn-"+str(next_level)
            next_level=next_level+10
            agent.SaveAgent(filename)
        print("partie :",games,"moyenne de", score_mean,"nombre de frames:",steps,"nbr training:",train_counts)
        score_mean=0

Surveillance des valeurs Q

On va procéder à une petite modification du code pour qu’on puisse suivre l’évolution des valeurs q et s’assurer qu’elles n’explosent pas : elles doivent être dans les dizaines.

#variables à ajouter
q_s_a=0 #q(s,a) de l'action choisie
qmax=0 #q(s,a) la plus haute au cours du jeu
action=agent.GetAction(state)

devient

(q_s_a, action)=agent.GetAction(state)
if(q_s_a>qmax):
   qmax=q_s_a

et

print("partie :",games,"moyenne de", score_mean,"nombre de frames:",steps,"nbr training:",train_counts)

devient

print("partie :",games,"moyenne de", score_mean,"nombre de frames:",steps,"nbr training:",train_counts,"q_value max:",qmax)

Et maintenant?

Voici à quoi cela ressemblera une fois que les classes seront écrites (oui j’avais écrit époque à la place de partie):

La première classe que l’on écrira sera celle de la mémoire de rejeu où on mettra en oeuvre la technique appelée Experience Replay.