Variation de biais pour l’apprentissage en renforcement en profondeur: Comment construire un bot pour Atari avec OpenAI Gym

L’auteur a sélectionné Girls Who Code pour recevoir un don dans le cadre du programme Write for DOnations .

introduction

Reinforcement learning est un sous-domaine de la théorie du contrôle, qui concerne les systèmes de contrôle évoluant dans le temps et englobant de manière générale des applications telles que l’automobile, la robotique et les robots de jeux. Tout au long de ce guide, vous utiliserez l’apprentissage par renforcement pour créer un bot pour les jeux vidéo Atari. Ce bot n’a pas accès aux informations internes sur le jeu. Au lieu de cela, il n’a accès qu’à l’affichage rendu du jeu et à la récompense de cet affichage, ce qui signifie qu’il ne peut voir que ce qu’un joueur humain verrait.

Dans l’apprentissage automatique, un bot est officiellement appelé un agent. Dans le cas de ce tutoriel, un agent est un «joueur» dans le système qui agit conformément à une fonction décisionnelle, appelée politique. Le principal objectif est de développer des agents puissants en les armant de politiques solides. En d’autres termes, notre objectif est de développer des robots intelligents en les dotant de solides capacités décisionnelles.

Vous commencerez ce didacticiel en formant un agent d’apprentissage du renforcement élémentaire qui effectue des actions aléatoires lorsque vous jouez à Space Invaders, le jeu d’arcade classique d’Atari, qui vous servira de base de comparaison. Ensuite, vous explorerez plusieurs autres techniques, notamment Q-learning, https://en.wikipedia.org/wiki/Q-learning#Deep_Q- apprendre [deep Q-learning], et least square - tout en construisant des agents qui jouent à Space Invaders et à Frozen Lake, un environnement de jeu simple inclus dans https: // gym. openai.com/[Gym], une boîte à outils pour l’apprentissage du renforcement publiée par OpenAI. En suivant ce didacticiel, vous comprendrez les concepts fondamentaux qui régissent le choix de la complexité des modèles dans l’apprentissage automatique.

Conditions préalables

Pour compléter ce tutoriel, vous aurez besoin de:

  • Un serveur exécutant Ubuntu 18.04, avec au moins 1 Go de RAM. Ce serveur doit avoir un utilisateur non root avec les privilèges + sudo + configurés, ainsi qu’un pare-feu configuré avec UFW. Vous pouvez le configurer en suivant ce Initial Initial Server Guide de configuration pour Ubuntu 18.04.

  • Un environnement virtuel Python 3 que vous pouvez obtenir en lisant notre guide «https://www.digitalocean.com/community/tutorials/how-to-install-python-3-and-set-up-a-programming-environment- on-an-ubuntu-18-04-server [Comment installer Python 3 et configurer un environnement de programmation sur un serveur Ubuntu 18.04]. ”

Si vous utilisez un ordinateur local, vous pouvez installer Python 3 et configurer un environnement de programmation local en lisant le didacticiel correspondant à votre système d’exploitation via notre https://www.digitalocean.com/community/tutorial_series/how-to. -install-and-setup-a-local-programming-environment-for-python-3 [Série d’installation et de configuration Python].

Étape 1 - Création du projet et installation des dépendances

Afin de configurer l’environnement de développement de vos robots, vous devez télécharger le jeu lui-même et les bibliothèques nécessaires au calcul.

Commencez par créer un espace de travail pour ce projet nommé + AtariBot +:

mkdir ~/AtariBot

Accédez au nouveau répertoire + AtariBot +:

cd ~/AtariBot

Créez ensuite un nouvel environnement virtuel pour le projet. Vous pouvez nommer cet environnement virtuel comme bon vous semble; ici, nous allons le nommer + ataribot +:

python3 -m venv

Activer votre environnement:

source /bin/activate

Sur Ubuntu, à compter de la version 16.04, OpenCV nécessite l’installation de quelques paquets supplémentaires pour fonctionner. Celles-ci incluent CMake, une application qui gère les processus de construction de logiciels, ainsi qu’un gestionnaire de session, diverses extensions et la composition d’images numériques. Exécutez la commande suivante pour installer ces packages:

sudo apt-get install -y cmake libsm6 libxext6 libxrender-dev libz-dev

Ensuite, utilisez + pip + pour installer le paquetage + wheel +, l’implémentation de référence de la norme d’emballage de la roue. Une bibliothèque Python, ce paquetage sert d’extension pour la construction de roues et comprend un outil de ligne de commande permettant de travailler avec les fichiers + .whl +:

python -m pip install wheel

En plus de + wheel +, vous devrez installer les packages suivants:

  • Gym, une bibliothèque Python qui met à disposition divers jeux à la recherche, ainsi que toutes les dépendances des jeux Atari. Développé par OpenAI, Gym propose des repères publics pour chacun des jeux afin que les performances de divers agents et algorithmes puissent être uniformément évaluées.

  • *Tensorflow, une bibliothèque d’apprentissage en profondeur. Cette bibliothèque nous donne la possibilité d’exécuter des calculs plus efficacement. Pour ce faire, il crée notamment des fonctions mathématiques utilisant les abstractions de Tensorflow exécutées exclusivement sur votre GPU.

  • OpenCV, la bibliothèque de vision par ordinateur mentionnée précédemment.

  • SciPy, une bibliothèque informatique scientifique offrant des algorithmes d’optimisation efficaces.

  • NumPy, une bibliothèque d’algèbre linéaire.

Installez chacun de ces packages avec la commande suivante. Notez que cette commande spécifie la version de chaque paquet à installer:

python -m pip install gym==0.9.5 tensorflow==1.5.0 tensorpack==0.8.0 numpy==1.14.0 scipy==1.1.0 opencv-python==3.4.1.15

Ensuite, utilisez à nouveau + pip + pour installer les environnements Atari de Gym, qui incluent une variété de jeux vidéo Atari, y compris Space Invaders:

python -m pip install gym[atari]

Si votre installation du package + gym [atari] + a abouti, votre sortie se terminera comme suit:

OutputInstalling collected packages: atari-py, Pillow, PyOpenGL
Successfully installed Pillow-5.4.1 PyOpenGL-3.1.0 atari-py-0.1.7

Avec ces dépendances installées, vous êtes prêt à créer un agent qui s’exécute de manière aléatoire pour servir de base à la comparaison.

Étape 2 - Création d’un agent aléatoire de base avec un gymnase

Maintenant que le logiciel requis est sur votre serveur, vous allez configurer un agent qui jouera une version simplifiée du jeu classique Atari, Space Invaders. Pour toute expérience, il est nécessaire d’obtenir une base de référence pour vous aider à comprendre les performances de votre modèle. Étant donné que cet agent effectue des actions aléatoires à chaque image, nous nous référons à lui comme à notre agent de base aléatoire. Dans ce cas, comparez-le à cet agent de base pour comprendre les performances de vos agents lors des étapes ultérieures.

Avec Gym, vous conservez votre propre boucle de jeu. Cela signifie que vous gérez chaque étape de l’exécution du jeu: à chaque pas, vous donnez une nouvelle action au + gym + et demandez à + ​​gym + pour le game state. Dans ce didacticiel, l’état du jeu correspond à son apparence à un pas de temps donné et correspond exactement à ce que vous verriez si vous jouiez au jeu.

En utilisant votre éditeur de texte préféré, créez un fichier Python nommé + bot_2_random.py +. Ici, nous allons utiliser + nano +:

nano bot_2_random.py

Démarrez ce script en ajoutant les lignes en surbrillance suivantes. Ces lignes incluent un bloc de commentaires qui explique ce que fera ce script et deux instructions + import + qui permettront d’importer les packages dont ce script aura finalement besoin pour fonctionner:

/AtariBot/bot_2_random.py

Ajoutez une fonction + main +. Dans cette fonction, créez l’environnement de jeu - + SpaceInvaders-v0 + - puis initialisez le jeu à l’aide de + env.reset +:

/AtariBot/bot_2_random.py

. . .
import gym
import random

Ensuite, ajoutez une fonction + env.step +. Cette fonction peut renvoyer les types de valeurs suivants:

  • + state +: Le nouvel état du jeu, après avoir appliqué l’action fournie.

  • + récompense +: L’augmentation du score que l’Etat subit. À titre d’exemple, cela pourrait être le cas lorsqu’une balle a détruit un étranger et que le score augmente de 50 points. Ensuite, + récompense = 50 +. En jouant à un jeu basé sur le score, le but du joueur est de maximiser le score. Ceci est synonyme de maximiser la récompense totale.

  • + done +: Indique si l’épisode est terminé ou non, ce qui se produit généralement lorsqu’un joueur a perdu toutes les vies.

  • + info +: informations supplémentaires que vous allez mettre de côté pour le moment.

Vous utiliserez + récompense + pour compter votre récompense totale. Vous utiliserez également + done + pour déterminer quand le joueur mourra, ce qui arrivera quand + done + retournera + True +.

Ajoutez la boucle de jeu suivante, qui ordonne au jeu de boucler jusqu’à la mort du joueur:

/AtariBot/bot_2_random.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')
   env.reset()

Enfin, lancez la fonction + main +. Incluez une vérification + name + pour vous assurer que + main + ne s’exécute que lorsque vous l’appelez directement avec + python bot_2_random.py +. Si vous n’ajoutez pas la vérification + if +, + main + sera toujours déclenché lorsque le fichier Python sera exécuté, * même lorsque vous importez le fichier *. Par conséquent, il est recommandé de placer le code dans une fonction + main +, exécutée uniquement lorsque + name == 'main ' +.

/AtariBot/bot_2_random.py

. . .
def main():
   . . .
   if done:
       print('Reward %s' % episode_reward)
       break

Enregistrez le fichier et quittez l’éditeur. Si vous utilisez + nano +, appuyez sur les touches + CTRL + X +, + Y +, puis + ENTER +. Ensuite, lancez votre script en tapant:

python bot_2_random.py

Votre programme produira un nombre, semblable au suivant. Notez que chaque fois que vous exécutez le fichier, vous obtiendrez un résultat différent:

OutputMaking new env: SpaceInvaders-v0
Reward:

Ces résultats aléatoires présentent un problème. Pour que vos travaux de recherche puissent bénéficier à d’autres chercheurs et praticiens, vos résultats et vos essais doivent être reproductibles. Pour corriger cela, rouvrez le fichier de script:

nano bot_2_random.py

Après + import random +, ajoutez + random.seed (0) +. Après + env = gym.make ('SpaceInvaders-v0') +, ajoutez + env.seed (0) +. Ensemble, ces lignes «ensemencent» l’environnement avec un point de départ cohérent, garantissant que les résultats seront toujours reproductibles. Votre fichier final correspondra exactement à ce qui suit:

/AtariBot/bot_2_random.py

"""
Bot 2 -- Make a random, baseline agent for the SpaceInvaders game.
"""

import gym
import random



def main():
   env = gym.make('SpaceInvaders-v0')


   env.reset()
   episode_reward = 0
   while True:
       action = env.action_space.sample()
       _, reward, done, _ = env.step(action)
       episode_reward += reward
       if done:
           print('Reward: %s' % episode_reward)
           break


if __name__ == '__main__':
   main()

Enregistrez le fichier et fermez votre éditeur, puis exécutez le script en tapant ce qui suit dans votre terminal:

python bot_2_random.py

Cela produira exactement la récompense suivante:

OutputMaking new env: SpaceInvaders-v0
Reward: 555.0

C’est votre tout premier bot, bien qu’il soit plutôt inintelligent car il ne tient pas compte de l’environnement environnant lorsqu’il prend des décisions. Pour obtenir une estimation plus fiable des performances de votre bot, vous pouvez faire exécuter l’agent par plusieurs épisodes à la fois, en signalant la moyenne des récompenses pour plusieurs épisodes. Pour le configurer, rouvrez d’abord le fichier:

nano bot_2_random.py

Après + random.seed (0) +, ajoutez la ligne en surbrillance suivante qui indique à l’agent de jouer au jeu pendant 10 épisodes:

/AtariBot/bot_2_random.py

. . .
random.seed(0)


. . .

Juste après + env.seed (0) +, commencez une nouvelle liste de récompenses:

/AtariBot/bot_2_random.py

. . .
   env.seed(0)

. . .

Imbriquez tout le code de + env.reset () + à la fin de + main () + dans une boucle + pour, en itérant` + num_episodes + fois. Assurez-vous d’indenter chaque ligne de `+ env.reset () + à + ​​break + de quatre espaces:

/AtariBot/bot_2_random.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')
   env.seed(0)
   rewards = []


       env.reset()
       episode_reward = 0

       while True:
           ...

Juste avant + pause +, actuellement la dernière ligne de la boucle de jeu principale, ajoutez la récompense de l’épisode actuel à la liste de toutes les récompenses:

/AtariBot/bot_2_random.py

. . .
       if done:
           print('Reward: %s' % episode_reward)

           break
. . .

À la fin de la fonction + main +, indiquez la récompense moyenne:

/AtariBot/bot_2_random.py

. . .
def main():
   ...
           print('Reward: %s' % episode_reward)
           break

   . . .

Votre fichier va maintenant s’aligner sur ce qui suit. Veuillez noter que le bloc de code suivant inclut quelques commentaires pour clarifier les parties clés du script:

/AtariBot/bot_2_random.py

"""
Bot 2 -- Make a random, baseline agent for the SpaceInvaders game.
"""

import gym
import random
random.seed(0)  # make results reproducible

num_episodes = 10


def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   for _ in range(num_episodes):
       env.reset()
       episode_reward = 0
       while True:
           action = env.action_space.sample()
           _, reward, done, _ = env.step(action)  # random action
           episode_reward += reward
           if done:
               print('Reward: %d' % episode_reward)
               rewards.append(episode_reward)
               break
   print('Average reward: %.2f' % (sum(rewards) / len(rewards)))


if __name__ == '__main__':
   main()

Enregistrez le fichier, quittez l’éditeur et exécutez le script:

python bot_2_random.py

Ceci imprimera exactement la récompense moyenne suivante:

OutputMaking new env: SpaceInvaders-v0
. . .
Average reward: 163.50

Nous avons maintenant une estimation plus fiable du score de base à battre. Pour créer un agent supérieur, vous devez toutefois comprendre le cadre de l’apprentissage par renforcement. Comment peut-on rendre plus concrète la notion abstraite de «prise de décision»?

Comprendre l’apprentissage par renforcement

Dans tout jeu, le but du joueur est de maximiser son score. Dans ce guide, le score du joueur est appelé son récompense. Pour maximiser leur récompense, le joueur doit pouvoir affiner ses capacités de prise de décision. Formellement, une décision est le processus consistant à regarder le jeu, à observer son état et à choisir une action. Notre fonction décisionnelle s’appelle une politique; une politique accepte un état en tant qu’entrée et «décide» d’une action:

policy: state -> action

Pour construire une telle fonction, nous allons commencer avec un ensemble spécifique d’algorithmes d’apprentissage par renforcement appelé Q-learning algorithms. Pour illustrer cela, considérons l’état initial d’un jeu, que nous appellerons + state0 +: votre vaisseau spatial et les extraterrestres sont tous dans leur position de départ. Ensuite, supposons que nous ayons accès à une "Q-table" magique qui nous dit combien de récompense chaque action rapportera:

state action reward

state0

shoot

10

state0

right

3

state0

left

3

L’action + shoot + maximisera votre récompense, car elle donnera la récompense avec la valeur la plus élevée: 10. Comme vous pouvez le constater, un tableau Q fournit un moyen simple de prendre des décisions, en fonction de l’état observé:

policy: state -> look at Q-table, pick action with greatest reward

Cependant, la plupart des jeux ont trop d’états à énumérer dans un tableau. Dans de tels cas, l’agent Q-learning apprend une Q-function au lieu d’une Q-table. Nous utilisons cette fonction Q de la même manière que nous utilisions précédemment la table Q. La réécriture des entrées de la table en tant que fonctions nous donne ce qui suit:

Q(state0, shoot) = 10
Q(state0, right) = 3
Q(state0, left) = 3

Dans un état particulier, il est facile pour nous de prendre une décision: nous regardons simplement chaque action possible et sa récompense, puis prenons l’action qui correspond à la récompense attendue la plus élevée. En reformulant la politique antérieure de manière plus formelle, nous avons:

policy: state -> argmax_{action} Q(state, action)

Cela répond aux exigences d’une fonction de prise de décision: un état dans le jeu décide d’une action. Cependant, cette solution dépend de la connaissance de + Q (état, action) + pour chaque état et action. Pour estimer `+ Q (état, action) +, considérons les points suivants:

  1. Compte tenu de nombreuses observations sur les états, les actions et les récompenses d’un agent, il est possible d’obtenir une estimation de la récompense pour chaque état et chaque action en prenant une moyenne mobile.

  2. Space Invaders est un jeu avec des récompenses différées: le joueur est récompensé lorsque l’extraterrestre est détruit et non lorsque le joueur tire. Cependant, le joueur qui effectue une action en tirant est le véritable élan de la récompense. D’une manière ou d’une autre, la fonction Q doit attribuer + (state0, shoot) + une récompense positive.

Ces deux idées sont codifiées dans les équations suivantes:

Q(state, action) = (1 - learning_rate) * Q(state, action) + learning_rate * Q_target
Q_target = reward + discount_factor * max_{action'} Q(state', action')

Ces équations utilisent les définitions suivantes:

  • + state +: l’état au pas de temps courant

  • + action +: l’action prise au pas de temps courant

  • + récompense +: la récompense pour le pas de temps actuel

  • + state '+: le nouvel état pour le pas de temps suivant, étant donné que nous avons agi `` + a + `

  • + action '+: toutes les actions possibles

  • + learning_rate +: le taux d’apprentissage

  • + discount_factor +: le facteur de réduction, combien de récompense «se dégrade» au fur et à mesure que nous le propagons

Pour une explication complète de ces deux équations, voir cet article sur Comprendre Q-Learning.

Avec cette compréhension de l’apprentissage par renforcement en tête, il ne reste plus qu’à lancer le jeu et à obtenir ces estimations de la valeur Q pour une nouvelle politique.

Étape 3 - Création d’un agent Q-learning simple pour Frozen Lake

Maintenant que vous avez un agent de base, vous pouvez commencer à créer de nouveaux agents et les comparer à l’original. Dans cette étape, vous allez créer un agent qui utilise Q-learning, une technique d’apprentissage par renforcement utilisée pour enseigner à un agent quelle action entreprendre dans un certain état. Cet agent jouera un nouveau jeu, FrozenLake. La configuration de ce jeu est décrite comme suit sur le site Web de Gym:

_ _ L’hiver est arrivé. Vos amis et vous vous êtes baladés autour d’un frisbee au parc lorsque vous avez lancé un lancer sauvage qui a laissé le frisbee au milieu du lac. La plupart des eaux sont gelées, mais la glace a fondu dans quelques trous. Si vous entrez dans l’un de ces trous, vous tomberez dans l’eau glacée. À l’heure actuelle, il ya une pénurie internationale de frisbee. Il est donc impératif de naviguer sur le lac et de récupérer le disque. Cependant, la glace est glissante et vous ne vous déplacerez pas toujours dans la direction souhaitée.

La surface est décrite à l’aide d’une grille comme celle-ci: _ _

SFFF       (S: starting point, safe)
FHFH       (F: frozen surface, safe)
FFFH       (H: hole, fall to your doom)
HFFG       (G: goal, where the frisbee is located)

Le joueur commence en haut à gauche, indiqué par «+ S », et se dirige vers le but en bas à droite, indiqué par « G ». Les actions disponibles sont * right *, * left *, * up * et * down *. Pour atteindre l'objectif, le score est égal à 1. Il existe un certain nombre de trous, notés ` H +`, et tomber dans un résultat immédiatement conduit à un score de 0.

Dans cette section, vous allez implémenter un agent Q-learning simple. En vous servant de ce que vous avez appris précédemment, vous allez créer un agent qui se situe entre exploration et exploitation. Dans ce contexte, exploration signifie que l’agent agit de manière aléatoire et l’exploitation signifie qu’il utilise ses valeurs Q pour choisir ce qu’il considère être l’action optimale. Vous allez également créer une table pour contenir les valeurs Q, en la mettant à jour progressivement au fur et à mesure que l’agent agit et apprend.

Faites une copie de votre script à partir de l’étape 2:

cp bot_2_random.py bot_3_q_table.py

Puis ouvrez ce nouveau fichier pour édition:

nano bot_3_q_table.py

Commencez par mettre à jour le commentaire en haut du fichier qui décrit le but du script. Comme il ne s’agit que d’un commentaire, cette modification n’est pas nécessaire pour que le script fonctionne correctement, mais elle peut être utile pour garder une trace de son travail:

/AtariBot/bot_3_q_table.py

"""

"""

. . .

Avant d’apporter des modifications fonctionnelles au script, vous devez importer + numpy + pour ses utilitaires d’algèbre linéaire. Juste en dessous de + import gym +, ajoutez la ligne en surbrillance:

/AtariBot/bot_3_q_table.py

"""
Bot 3 -- Build simple q-learning agent for FrozenLake
"""

import gym

import random
random.seed(0)  # make results reproducible
. . .

Sous + random.seed (0) +, ajoutez une valeur de départ pour + numpy +:

/AtariBot/bot_3_q_table.py

. . .
import random
random.seed(0)  # make results reproducible

. . .

Ensuite, rendez les états du jeu accessibles. Mettez à jour la ligne + env.reset () + pour dire ce qui suit, qui stocke l’état initial du jeu dans la variable + state +:

/AtariBot/bot_3_q_table.py

. . .
   for _ in range(num_episodes):
       env.reset()
       . . .

Mettez à jour la ligne + env.step (…​) + pour dire ce qui suit, qui stocke l’état suivant, + state2 +. Vous aurez besoin à la fois du state state et du suivant -` + state2 + `- pour mettre à jour la fonction Q.

/AtariBot/bot_3_q_table.py

       . . .
       while True:
           action = env.action_space.sample()
           , reward, done, _ = env.step(action)
           . . .

Après + récompense + + récompense, ajoutez une ligne mettant à jour la variable` + état`. Ceci maintient la variable + state + mise à jour pour la prochaine itération, car vous vous attendez à ce que + state + reflète l’état actuel:

/AtariBot/bot_3_q_table.py

. . .
       while True:
           . . .
           episode_reward += reward

           if done:
               . . .

Dans le bloc + if done on, supprimez l’instruction` + print` qui affiche la récompense pour chaque épisode. Au lieu de cela, vous obtiendrez la récompense moyenne sur plusieurs épisodes. Le bloc + si fait + ressemblera alors à ceci:

/AtariBot/bot_3_q_table.py

           . . .
           if done:
               rewards.append(episode_reward)
               break
               . . .

Après ces modifications, votre boucle de jeu correspondra à ce qui suit:

/AtariBot/bot_3_q_table.py

. . .
   for _ in range(num_episodes):
       state = env.reset()
       episode_reward = 0
       while True:
           action = env.action_space.sample()
           state2, reward, done, _ = env.step(action)
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward))
               break
               . . .

Ensuite, ajoutez la possibilité pour l’agent de faire un compromis entre exploration et exploitation. Juste avant votre boucle de jeu principale (qui commence par + pour …​ +), créez le tableau Q-value:

/AtariBot/bot_3_q_table.py

. . .

   for _ in range(num_episodes):
     . . .

Ensuite, réécrivez la boucle + for pour afficher le numéro de l’épisode:

/AtariBot/bot_3_q_table.py

. . .
   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for :
     . . .

Dans la boucle de jeu + while True: +, créez + noise +. Le bruit, ou données aléatoires sans signification, est parfois introduit lors de la formation de réseaux neuronaux profonds car il peut améliorer à la fois les performances et la précision du modèle. Notez que plus le bruit est élevé, moins les valeurs de + Q [state,:] + sont importantes. En conséquence, plus le bruit est élevé, plus l’agent est susceptible d’agir indépendamment de sa connaissance du jeu. En d’autres termes, un bruit plus élevé incite l’agent à explorer des actions aléatoires:

/AtariBot/bot_3_q_table.py

       . . .
       while True:

           action = env.action_space.sample()
           . . .

Notez que plus le nombre d’épisodes augmente, plus la quantité de bruit diminue quadratiquement: au fil du temps, l’agent explore de moins en moins car il peut faire confiance à sa propre évaluation de la récompense du jeu et commencer à exploiter ses connaissances.

Mettez à jour la ligne + action + pour que votre agent sélectionne les actions en fonction du tableau de valeurs Q, avec une exploration intégrée:

/AtariBot/bot_3_q_table.py

           . . .
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action =
           state2, reward, done, _ = env.step(action)
           . . .

Votre boucle de jeu principale correspondra alors à ce qui suit:

/AtariBot/bot_3_q_table.py

. . .
   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       state = env.reset()
       episode_reward = 0
       while True:
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action = np.argmax(Q[state, :] + noise)
           state2, reward, done, _ = env.step(action)
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward)
               break
               . . .

Ensuite, vous mettrez à jour votre table de valeurs Q à l’aide de l’équation de mise à jour Bellman, équation largement utilisée dans l’apprentissage automatique pour trouver la stratégie optimale dans un environnement donné.

L’équation de Bellman intègre deux idées très pertinentes pour ce projet. Premièrement, l’exécution répétée d’une action particulière à partir d’un état particulier donnera une bonne estimation de la valeur Q associée à cet état et à cette action. À cette fin, vous augmenterez le nombre d’épisodes que ce bot doit jouer pour obtenir une estimation Q-value plus forte. Deuxièmement, les récompenses doivent se propager dans le temps, de sorte que l’action initiale se voit attribuer une récompense non nulle. Cette idée est la plus claire dans les jeux avec des récompenses différées; Par exemple, dans Space Invaders, le joueur est récompensé lorsque l’extraterrestre est détruit et non lorsque le joueur tire. Cependant, le tireur est le véritable élan pour une récompense. De même, la fonction Q doit attribuer une récompense positive (+ state0 +, '+ shoot + `).

Tout d’abord, mettez à jour + num_episodes + pour qu’il soit égal à 4000:

/AtariBot/bot_3_q_table.py

. . .
np.random.seed(0)

num_episodes =
. . .

Ajoutez ensuite les hyperparamètres nécessaires en haut du fichier sous la forme de deux autres variables:

/AtariBot/bot_3_q_table.py

. . .
num_episodes = 4000


. . .

Calculez la nouvelle valeur Q cible juste après la ligne contenant + env.step (…​) +:

/AtariBot/bot_3_q_table.py

           . . .
           state2, reward, done, _ = env.step(action)

           episode_reward += reward
           . . .

Sur la ligne directement après + Qtarget +, mettez à jour le tableau des valeurs Q en utilisant une moyenne pondérée des anciennes et des nouvelles valeurs Q:

/AtariBot/bot_3_q_table.py

           . . .
           Qtarget = reward + discount_factor * np.max(Q[state2, :])

           episode_reward += reward
           . . .

Vérifiez que votre boucle de jeu principale correspond maintenant à ce qui suit:

/AtariBot/bot_3_q_table.py

. . .
   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       state = env.reset()
       episode_reward = 0
       while True:
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action = np.argmax(Q[state, :] + noise)
           state2, reward, done, _ = env.step(action)
           Qtarget = reward + discount_factor * np.max(Q[state2, :])
           Q[state, action] = (1-learning_rate) * Q[state, action] + learning_rate * Qtarget
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward)
               break
               . . .

Notre logique de formation de l’agent est maintenant terminée. Il ne reste plus qu’à ajouter des mécanismes de rapport.

Même si Python n’applique pas de vérification de type stricte, ajoutez des types à vos déclarations de fonction pour la propreté. En haut du fichier, avant la première ligne lisant + import gym +, importez le type + liste +:

/AtariBot/bot_3_q_table.py

. . .

import gym
. . .

Juste après + learning_rate = 0.9 +, en dehors de la fonction + main +, déclarez l’intervalle et le format des rapports:

/AtariBot/bot_3_q_table.py

. . .
learning_rate = 0.9




def main():
 . . .

Avant la fonction + main +, ajoutez une nouvelle fonction qui va renseigner cette chaîne + report +, en utilisant la liste de toutes les récompenses:

/AtariBot/bot_3_q_table.py

. . .
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'














def main():
 . . .

Changez le jeu en + FrozenLake + au lieu de + SpaceInvaders +:

/AtariBot/bot_3_q_table.py

. . .
def main():
   env = gym.make('')  # create the game
   . . .

Après + rewards.append (…​) +, imprimez la récompense moyenne des 100 derniers épisodes et la récompense moyenne de tous les épisodes:

/AtariBot/bot_3_q_table.py

           . . .
           if done:
               rewards.append(episode_reward)


               . . .

À la fin de la fonction + main () +, rapportez les deux moyennes une fois de plus. Pour ce faire, remplacez la ligne + print ('Récompense moyenne:% .2f'% (somme (récompenses) / len (récompenses)))) + par la ligne mise en surbrillance suivante:

/AtariBot/bot_3_q_table.py

. . .
def main():
   ...
               break

. . .

Enfin, vous avez terminé votre agent Q-learning. Vérifiez que votre script est aligné sur les éléments suivants:

/AtariBot/bot_3_q_table.py

"""
Bot 3 -- Build simple q-learning agent for FrozenLake
"""

from typing import List
import gym
import numpy as np
import random
random.seed(0)  # make results reproducible
np.random.seed(0)  # make results reproducible

num_episodes = 4000
discount_factor = 0.8
learning_rate = 0.9
report_interval = 500
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'


def print_report(rewards: List, episode: int):
   """Print rewards report for current episode
   - Average for last 100 episodes
   - Best 100-episode average across all time
   - Average for all episodes across time
   """
   print(report % (
       np.mean(rewards[-100:]),
       max([np.mean(rewards[i:i+100]) for i in range(len(rewards) - 100)]),
       np.mean(rewards),
       episode))


def main():
   env = gym.make('FrozenLake-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       state = env.reset()
       episode_reward = 0
       while True:
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action = np.argmax(Q[state, :] + noise)
           state2, reward, done, _ = env.step(action)
           Qtarget = reward + discount_factor * np.max(Q[state2, :])
           Q[state, action] = (1-learning_rate) * Q[state, action] + learning_rate * Qtarget
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward)
               if episode % report_interval == 0:
                   print_report(rewards, episode)
               break
   print_report(rewards, -1)

if __name__ == '__main__':
   main()

Enregistrez le fichier, quittez votre éditeur et exécutez le script:

python bot_3_q_table.py

Votre sortie correspondra à ce qui suit:

Output100-ep Average: 0.11 . Best 100-ep Average: 0.12 . Average: 0.03 (Episode 500)
100-ep Average: 0.25 . Best 100-ep Average: 0.24 . Average: 0.09 (Episode 1000)
100-ep Average: 0.39 . Best 100-ep Average: 0.48 . Average: 0.19 (Episode 1500)
100-ep Average: 0.43 . Best 100-ep Average: 0.55 . Average: 0.25 (Episode 2000)
100-ep Average: 0.44 . Best 100-ep Average: 0.55 . Average: 0.29 (Episode 2500)
100-ep Average: 0.64 . Best 100-ep Average: 0.68 . Average: 0.32 (Episode 3000)
100-ep Average: 0.63 . Best 100-ep Average: 0.71 . Average: 0.36 (Episode 3500)
100-ep Average: 0.56 . Best 100-ep Average: 0.78 . Average: 0.40 (Episode 4000)
100-ep Average: 0.56 . Best 100-ep Average: 0.78 . Average: 0.40 (Episode -1)

Vous avez maintenant votre premier bot non-trivial pour les jeux, mais mettons cette récompense moyenne de + 0,78 + en perspective. Selon la page Gym FrozenLake, le fait de "résoudre" le jeu signifie atteindre une moyenne de 100 épisodes de «+0,78 +». De manière informelle, «résoudre» signifie «joue très bien le jeu». Bien qu’en un temps record, l’agent Q-table est capable de résoudre FrozenLake en 4000 épisodes.

Cependant, le jeu peut être plus complexe. Ici, vous avez utilisé une table pour stocker tous les 144 états possibles, mais considérez un tic tac toe dans lequel il y a 19 683 états possibles. De même, considérez les Space Invaders où il y a trop d’états possibles à compter. Une table Q n’est pas durable car les jeux deviennent de plus en plus complexes. Pour cette raison, vous avez besoin d’un moyen d’approximation du tableau de bord. Au fur et à mesure de vos expérimentations à l’étape suivante, vous concevrez une fonction pouvant accepter des états et des actions en tant qu’entrées et générant une valeur Q.

Étape 4 - Construire un agent Q-learning en profondeur pour Frozen Lake

Dans l’apprentissage par renforcement, le réseau de neurones prédit efficacement la valeur de Q en fonction des entrées + state + et + action +, en utilisant un tableau pour stocker toutes les valeurs possibles, mais cela devient instable dans les jeux complexes. L’apprentissage par renforcement en profondeur utilise plutôt un réseau de neurones pour se rapprocher de la fonction Q. Pour plus de détails, voir Comprendre Deep Q-Learning.

Pour vous habituer à Tensorflow, une bibliothèque d’apprentissage en profondeur que vous avez installée à l’étape 1, vous réimplémenterez toute la logique utilisée jusqu’ici avec les abstractions de Tensorflow et vous utiliserez un réseau de neurones. se rapprocher de votre Q-fonction. Cependant, votre réseau de neurones sera extrêmement simple: votre sortie + Q (s) + est une matrice + W + multipliée par votre entrée + s +. C’est ce qu’on appelle un réseau de neurones avec une couche_ pleinement connectée:

Q(s) = Ws

Pour rappel, le but est d’implémenter toute la logique des robots que nous avons déjà construits en utilisant des abstractions Tensorflow. Cela rendra vos opérations plus efficaces, car Tensorflow pourra alors effectuer tous les calculs sur le GPU.

Commencez par dupliquer votre script Q-table à l’étape 3:

cp bot_3_q_table.py bot_4_q_network.py

Puis ouvrez le nouveau fichier avec + nano + ou votre éditeur de texte préféré:

nano bot_4_q_network.py

Commencez par mettre à jour le commentaire en haut du fichier:

/AtariBot/bot_4_q_network.py

"""

"""

. . .

Ensuite, importez le paquet Tensorflow en ajoutant une directive + import juste en dessous de` + import random`. De plus, ajoutez + tf.set_radon_seed (0) + juste en dessous de + np.random.seed (0) +. Cela garantira que les résultats de ce script seront reproductibles dans toutes les sessions:

/AtariBot/bot_4_q_network.py

. . .
import random

random.seed(0)
np.random.seed(0)

. . .

Redéfinissez vos hyperparamètres en haut du fichier pour qu’ils correspondent à ce qui suit et ajoutez une fonction appelée + exploration_probability +, qui renverra la probabilité d’exploration à chaque étape. Rappelez-vous que, dans ce contexte, «exploration» signifie prendre une action aléatoire, par opposition à l’action recommandée par les estimations de la valeur Q:

/AtariBot/bot_4_q_network.py

. . .
num_episodes = 4000
discount_factor =
learning_rate =
report_interval = 500

report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'
. . .

Ensuite, vous allez ajouter une fonction d’encodage one-hot. En bref, le codage one-hot est un processus par lequel les variables sont converties en une forme qui permet aux algorithmes d’apprentissage automatique de faire de meilleures prédictions. Si vous souhaitez en savoir plus sur l’encodage one-hot, vous pouvez consulter Adversarial Exemples dans Computer Vision: Comment construire puis tromper un filtre pour chien basé sur les émotions .

Directement sous + report = …​ +, ajoutez une fonction + one_hot +:

/AtariBot/bot_4_q_network.py

. . .
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'





def print_report(rewards: List, episode: int):
. . .

Ensuite, vous allez réécrire la logique de votre algorithme en utilisant les abstractions de Tensorflow. Avant de faire cela, vous devrez d’abord créer placeholders pour vos données.

Dans votre fonction + main +, directement sous + rewards = [] +, insérez le contenu en surbrillance suivant. Ici, vous définissez des espaces réservés pour votre observation à l’heure * t * (comme + obs_t_ph +) et à l’heure * t + 1 * (comme + obs_tp1_ph +), ainsi que des espaces réservés pour votre action, votre récompense et votre cible Q:

/AtariBot/bot_4_q_network.py

. . .
def main():
   env = gym.make('FrozenLake-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []









   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Directement sous la ligne commençant par + q_target_ph = +, insérez les lignes en surbrillance suivantes. Ce code commence votre calcul en calculant * Q (s, a) * pour tout * a * pour faire + q_current + et * Q (s ’, a’) * pour tout * a ’* pour faire` + q_target + `:

/AtariBot/bot_4_q_network.py

   . . .
   rew_ph = tf.placeholder(shape=(), dtype=tf.float32)
   q_target_ph = tf.placeholder(shape=[1, n_actions], dtype=tf.float32)






   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Encore une fois directement sous la dernière ligne que vous avez ajoutée, insérez le code en surbrillance suivant. Les deux premières lignes sont équivalentes à la ligne ajoutée à l’étape 3 qui calcule + Qtarget +, où + Qtarget = récompense + discount_factor * np.max (Q [state2,:]) +. Les deux lignes suivantes définissent votre perte, tandis que la dernière ligne calcule l’action qui maximise votre valeur Q:

/AtariBot/bot_4_q_network.py

   . . .
   q_current = tf.matmul(obs_t_ph, W)
   q_target = tf.matmul(obs_tp1_ph, W)







   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Après avoir configuré votre algorithme et la fonction de perte, définissez votre optimiseur:

/AtariBot/bot_4_q_network.py

   . . .
   error = tf.reduce_sum(tf.square(q_target_sa - q_current_sa))
   pred_act_ph = tf.argmax(q_current, 1)





   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Ensuite, configurez le corps de la boucle de jeu. Pour ce faire, transmettez les données aux espaces réservés Tensorflow et les abstractions de Tensorflow gèrent le calcul sur le GPU, en renvoyant le résultat de l’algorithme.

Commencez par supprimer l’ancien tableau Q et la logique. Plus précisément, supprimez les lignes qui définissent + Q + (juste avant la boucle + pour +), + noise + (dans la boucle while tant que), + action +, + Qtarget + et + + Q [état, action] + . Renommez `+ state + en + obs_t + et + state2 + en + obs_tp1 + pour les aligner sur les espaces réservés Tensorflow que vous avez définis précédemment. Lorsque vous avez terminé, votre boucle + pour + correspondra à ce qui suit:

/AtariBot/bot_4_q_network.py

   . . .
   # 3. Setup optimization
   trainer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
   update_model = trainer.minimize(error)

   for episode in range(1, num_episodes + 1):
        = env.reset()
       episode_reward = 0
       while True:

           , reward, done, _ = env.step(action)

           episode_reward += reward

           if done:
               ...

Directement au-dessus de la boucle + pour +, ajoutez les deux lignes suivantes en surbrillance. Ces lignes initialisent une session Tensorflow qui gère à son tour les ressources nécessaires pour exécuter des opérations sur le GPU. La deuxième ligne initialise toutes les variables de votre graphe de calcul; Par exemple, initialiser les poids à 0 avant de les mettre à jour. De plus, vous imbriquerez la boucle + pour + dans l’instruction + avec +, donc indentez l’intégralité de la boucle + pour + par quatre espaces:

/AtariBot/bot_4_q_network.py

   . . .
   trainer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
       update_model = trainer.minimize(error)




       for episode in range(1, num_episodes + 1):
           obs_t = env.reset()
           ...

Avant la ligne lisant + obs_tp1, récompense, done, _ = env.step (action) +, insérez les lignes suivantes pour calculer le + action +. Ce code évalue l’espace réservé correspondant et remplace l’action par une action aléatoire avec une certaine probabilité:

/AtariBot/bot_4_q_network.py

           . . .
           while True:





               . . .

Après la ligne contenant + env.step (action) +, insérez ce qui suit pour entraîner le réseau de neurones à estimer votre fonction de valeur Q:

/AtariBot/bot_4_q_network.py

               . . .
               obs_tp1, reward, done, _ = env.step(action)










               episode_reward += reward
               . . .

Votre fichier final correspondra à cette fichier hébergée sur GitHub. Enregistrez le fichier, quittez votre éditeur et exécutez le script:

python bot_4_q_network.py

Votre sortie se terminera exactement comme suit:

Output100-ep Average: 0.11 . Best 100-ep Average: 0.11 . Average: 0.05 (Episode 500)
100-ep Average: 0.41 . Best 100-ep Average: 0.54 . Average: 0.19 (Episode 1000)
100-ep Average: 0.56 . Best 100-ep Average: 0.73 . Average: 0.31 (Episode 1500)
100-ep Average: 0.57 . Best 100-ep Average: 0.73 . Average: 0.36 (Episode 2000)
100-ep Average: 0.65 . Best 100-ep Average: 0.73 . Average: 0.41 (Episode 2500)
100-ep Average: 0.65 . Best 100-ep Average: 0.73 . Average: 0.43 (Episode 3000)
100-ep Average: 0.69 . Best 100-ep Average: 0.73 . Average: 0.46 (Episode 3500)
100-ep Average: 0.77 . Best 100-ep Average: 0.79 . Average: 0.48 (Episode 4000)
100-ep Average: 0.77 . Best 100-ep Average: 0.79 . Average: 0.48 (Episode -1)

Vous venez de former votre tout premier agent Q-learning en profondeur. Pour un jeu aussi simple que FrozenLake, votre agent Q-learning approfondi a nécessité 4000 entraînements. Imaginez si le jeu était beaucoup plus complexe. Combien d’échantillons d’entraînement faudrait-il pour former? En fin de compte, l’agent pourrait nécessiter des millions d’échantillons. Le nombre d’échantillons requis est appelé «complexité d’échantillon», un concept exploré plus en détail dans la section suivante.

Comprendre les compromis biais-variance

De manière générale, la complexité de l’échantillon est en contradiction avec la complexité du modèle dans l’apprentissage automatique:

  1. * Complexité du modèle *: On veut un modèle suffisamment complexe pour résoudre leur problème. Par exemple, un modèle aussi simple qu’une ligne n’est pas suffisamment complexe pour prédire la trajectoire d’une voiture.

  2. * Complexité de l’échantillon *: On voudrait un modèle qui ne nécessite pas beaucoup d’échantillons. Cela peut être dû au fait qu’ils ont un accès limité aux données étiquetées, une puissance de calcul insuffisante, une mémoire limitée, etc.

Supposons que nous ayons deux modèles, un simple et un extrêmement complexe. Pour que les deux modèles atteignent la même performance, biais-variance nous indique que le modèle extrêmement complexe nécessitera de manière exponentielle plus d’échantillons à entraîner. Exemple: votre agent Q-learning basé sur un réseau de neurones a nécessité 4000 épisodes pour résoudre FrozenLake. L’ajout d’une deuxième couche à l’agent de réseau neuronal quadruple le nombre d’épisodes d’entraînement nécessaires. Avec des réseaux de neurones de plus en plus complexes, cette division ne fait que grandir. Pour maintenir le même taux d’erreur, l’augmentation de la complexité du modèle augmente la complexité de l’échantillon de manière exponentielle. De même, la diminution de la complexité de l’échantillon diminue la complexité du modèle. Nous ne pouvons donc pas maximiser la complexité du modèle et minimiser la complexité de l’échantillon selon les souhaits de notre cœur.

Nous pouvons toutefois tirer parti de notre connaissance de ce compromis. Pour une interprétation visuelle des mathématiques à l’origine de la décomposition bias-variance, voir Comprendre le compromis biais-variance. À un niveau élevé, la décomposition biais-variance est une décomposition de «l’erreur vraie» en deux composantes: biais et variance. Nous nous référons à «erreur vraie» en tant que «erreur moyenne carrée» (MSE), qui est la différence attendue entre nos étiquettes prédites et les étiquettes vraies. Ce qui suit est un graphique montrant l’évolution de «l’erreur vraie» à mesure que la complexité du modèle augmente:

image: https: //assets.digitalocean.com/articles/atari_bias_variance/Graph01.png [Courbe d’erreur quadratique moyenne]

Étape 5 - Construction d’un agent des moindres carrés pour Frozen Lake

La méthode des moindres carrés, également appelée régression linéaire, est un moyen d’analyse de régression largement utilisé dans les domaines des mathématiques et de la science des données. En apprentissage machine, il est souvent utilisé pour trouver le modèle linéaire optimal de deux paramètres ou jeux de données.

À l’étape 4, vous avez créé un réseau de neurones pour calculer les valeurs Q. Au lieu d’un réseau de neurones, vous utiliserez dans cette étape ridge regression, une variante des moindres carrés, pour calculer ce vecteur de valeurs-Q. L’espoir est qu’avec un modèle aussi simple que les moindres carrés, la résolution du jeu nécessitera moins d’épisodes d’entraînement.

Commencez par dupliquer le script de l’étape 3:

cp bot_3_q_table.py bot_5_ls.py

Ouvrez le nouveau fichier:

nano bot_5_ls.py

Encore une fois, mettez à jour le commentaire en haut du fichier décrivant ce que fera ce script:

/AtariBot/bot_4_q_network.py

"""

"""

. . .

Avant le bloc d’import en haut de votre fichier, ajoutez deux autres importations pour la vérification de type:

/AtariBot/bot_5_ls.py

. . .


from typing import List
import gym
. . .

Dans votre liste d’hyperparamètres, ajoutez un autre hyperparamètre, + w_lr +, pour contrôler le taux d’apprentissage de la deuxième fonction Q. En outre, mettez à jour le nombre d’épisodes sur 5 000 et le facteur de réduction sur + 0,85 +. En modifiant les hyperparamètres + num_episodes + et + + discount_factor + `en valeurs plus grandes, l’agent sera en mesure de générer une performance plus solide:

/AtariBot/bot_5_ls.py

. . .
num_episodes =
discount_factor =
learning_rate = 0.9

report_interval = 500
. . .

Avant votre fonction + print_report +, ajoutez la fonction d’ordre supérieur suivante. Il retourne un lambda - une fonction anonyme - qui fait abstraction du modèle:

/AtariBot/bot_5_ls.py

. . .
report_interval = 500
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'





def print_report(rewards: List, episode: int):
   . . .

Après + makeQ +, ajoutez une autre fonction, + initialize +, qui initialise le modèle à l’aide de valeurs distribuées normalement:

/AtariBot/bot_5_ls.py

. . .
def makeQ(model: np.array) -> Callable[[np.array], np.array]:
   """Returns a Q-function, which takes state -> distribution over actions"""
   return lambda X: X.dot(model)







def print_report(rewards: List, episode: int):
   . . .

Après le bloc + initialize +, ajoutez une méthode + train + qui calcule la solution de forme fermée de régression de crête, puis pondère l’ancien modèle avec le nouveau. Il retourne à la fois le modèle et la fonction Q abstraite:

/AtariBot/bot_5_ls.py

. . .
def initialize(shape: Tuple):
   ...
   return W, Q









def print_report(rewards: List, episode: int):
   . . .

Après + train +, ajoutez une dernière fonction, + one_hot +, pour effectuer un encodage one-hot pour vos états et actions:

/AtariBot/bot_5_ls.py

. . .
def train(X: np.array, y: np.array, W: np.array) -> Tuple[np.array, Callable]:
   ...
   return W, Q





def print_report(rewards: List, episode: int):
   . . .

Ensuite, vous devrez modifier la logique d’apprentissage. Dans le script précédent que vous avez écrit, la table Q a été mise à jour à chaque itération. Ce script, cependant, va collecter des échantillons et des étiquettes à chaque pas de temps et former un nouveau modèle toutes les 10 étapes. De plus, au lieu de gérer une table Q ou un réseau de neurones, il utilisera un modèle des moindres carrés pour prédire les valeurs Q.

Allez dans la fonction + main + et remplacez la définition de la table Q (+ Q = np.zeros (…​) +) par ce qui suit:

/AtariBot/bot_5_ls.py

. . .
def main():
   ...
   rewards = []




   for episode in range(1, num_episodes + 1):
       . . .

Faites défiler la liste avant la boucle + pour +. Directement au-dessous, ajoutez les lignes suivantes qui réinitialisent les listes + states + et + labels + s’il y a trop d’informations stockées:

/AtariBot/bot_5_ls.py

. . .
def main():
   ...
   for episode in range(1, num_episodes + 1):


           . . .

Modifiez la ligne directement après celle-ci, qui définit + state = env.reset () +, afin qu’elle devienne la suivante. Cela va immédiatement encoder l’état à chaud, car toutes ses utilisations nécessiteront un vecteur à chaud:

/AtariBot/bot_5_ls.py

. . .
   for episode in range(1, num_episodes + 1):
       if len(states) >= 10000:
           states, labels = [], []
       state =
. . .

Avant la première ligne de votre boucle de jeu principale + while +, modifiez la liste des + États +:

/AtariBot/bot_5_ls.py

. . .
   for episode in range(1, num_episodes + 1):
       ...
       episode_reward = 0
       while True:

           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           . . .

Mettez à jour le calcul pour + action +, réduisez la probabilité de bruit et modifiez l’évaluation de la fonction Q:

/AtariBot/bot_5_ls.py

. . .
       while True:
           states.append(state)


           state2, reward, done, _ = env.step(action)
           . . .

Ajoutez une version one-hot de + state2 + et modifiez l’appel de la fonction Q dans votre définition de + Qtarget + comme suit:

/AtariBot/bot_5_ls.py

. . .
       while True:
           ...
           state2, reward, done, _ = env.step(action)


           Qtarget = reward + discount_factor * np.max()
           . . .

Supprimez la ligne qui met à jour + Q [state, action] = …​ + et remplacez-la par les lignes suivantes. Ce code prend la sortie du modèle actuel et met à jour uniquement la valeur de cette sortie qui correspond à l’action en cours. En conséquence, les valeurs Q pour les autres actions n’entraînent pas de perte:

/AtariBot/bot_5_ls.py

. . .
           state2 = one_hot(state2, n_obs)
           Qtarget = reward + discount_factor * np.max(Q(state2))




           episode_reward += reward
           . . .

Juste après + state = state2 +, ajoutez une mise à jour périodique au modèle. Cela entraîne votre modèle tous les 10 pas de temps:

/AtariBot/bot_5_ls.py

. . .
           state = state2


           if done:
           . . .

Vérifiez à nouveau que votre fichier correspond à du code source. Enregistrez ensuite le fichier, quittez l’éditeur et exécutez le script:

python bot_5_ls.py

Cela produira les éléments suivants:

Output100-ep Average: 0.17 . Best 100-ep Average: 0.17 . Average: 0.09 (Episode 500)
100-ep Average: 0.11 . Best 100-ep Average: 0.24 . Average: 0.10 (Episode 1000)
100-ep Average: 0.08 . Best 100-ep Average: 0.24 . Average: 0.10 (Episode 1500)
100-ep Average: 0.24 . Best 100-ep Average: 0.25 . Average: 0.11 (Episode 2000)
100-ep Average: 0.32 . Best 100-ep Average: 0.31 . Average: 0.14 (Episode 2500)
100-ep Average: 0.35 . Best 100-ep Average: 0.38 . Average: 0.16 (Episode 3000)
100-ep Average: 0.59 . Best 100-ep Average: 0.62 . Average: 0.22 (Episode 3500)
100-ep Average: 0.66 . Best 100-ep Average: 0.66 . Average: 0.26 (Episode 4000)
100-ep Average: 0.60 . Best 100-ep Average: 0.72 . Average: 0.30 (Episode 4500)
100-ep Average: 0.75 . Best 100-ep Average: 0.82 . Average: 0.34 (Episode 5000)
100-ep Average: 0.75 . Best 100-ep Average: 0.82 . Average: 0.34 (Episode -1)

Rappelons que, selon la page Gym FrozenLake, le fait de "résoudre" le jeu signifie atteindre une moyenne de 0,78 sur 100 épisodes. Ici, l’agent atteint une moyenne de 0,82, ce qui signifie qu’il a été capable de résoudre le jeu en 5000 épisodes. Bien que cela ne résolve pas le jeu en moins d’épisodes, cette méthode de base des moindres carrés permet toujours de résoudre un jeu simple avec à peu près le même nombre d’épisodes d’entraînement. Bien que la complexité de vos réseaux de neurones augmente, vous avez montré que des modèles simples suffisaient pour FrozenLake.

Avec cela, vous avez exploré trois agents Q-learning: un utilisant une table Q, un autre un réseau de neurones et un troisième des moindres carrés. Ensuite, vous allez créer un agent d’apprentissage en renforcement profond pour un jeu plus complexe: Space Invaders.

Étape 6 - Création d’un agent Deep Q-learning pour Space Invaders

Supposons que vous ayez réglé parfaitement la complexité du modèle et de l’échantillon de l’algorithme Q-learning précédent, que vous ayez choisi une méthode de réseau neuronal ou de méthode des moindres carrés. En fin de compte, cet agent Q-learning inintelligent fonctionne encore mal sur des jeux plus complexes, même avec un nombre particulièrement élevé d’épisodes d’entraînement. Cette section couvre deux techniques pouvant améliorer les performances. Vous allez ensuite tester un agent formé à l’aide de ces techniques.

Les chercheurs de DeepMind ont mis au point le premier agent polyvalent capable d’adapter en permanence son comportement sans aucune intervention humaine. Ils ont également formé leur agent à la lecture de divers jeux Atari. Le document DQN (Deep Q-Learning) original de DeepMind reconnaissait deux questions importantes:

  1. * États corrélés *: prenons l’état de notre jeu au temps 0, que nous appellerons * s0 *. Disons que nous mettons à jour * Q (s0) *, selon les règles que nous avons dérivées précédemment. Maintenant, prenons l’état au temps 1, que nous appelons * s1 *, et mettons à jour * Q (s1) * selon les mêmes règles. Notez que l’état du jeu à l’heure 0 est très similaire à son état à l’heure 1. Dans Space Invaders, par exemple, les extraterrestres peuvent s’être déplacés d’un pixel chacun. Dit plus succinctement, * s0 * et * s1 * sont très similaires. De même, nous nous attendons également à ce que * Q (s0) * et * Q (s1) * soient très similaires, de sorte que la mise à jour de l’un affecte l’autre. Cela conduit à des valeurs Q fluctuantes, car une mise à jour de * Q (s0) * peut en fait contrer la mise à jour à * Q (s1) *. Plus formellement, * s0 * et * s1 * sont correlated. Puisque la fonction Q est déterministe, * Q (s1) * est en corrélation avec * Q (s0) *.

  2. * Instabilité de la fonction Q *: Rappelez-vous que la fonction * Q * est à la fois le modèle que nous formons et la source de nos étiquettes. Disons que nos étiquettes sont des valeurs sélectionnées de manière aléatoire qui représentent vraiment une distribution, * L *. Chaque fois que nous mettons à jour * Q *, nous changeons * L *, ce qui signifie que notre modèle tente d’apprendre une cible en mouvement. C’est un problème, car les modèles que nous utilisons supposent une distribution fixe.

Pour lutter contre les états corrélés et une fonction Q instable:

  1. On pourrait garder une liste d’états appelée replay buffer. A chaque pas de temps, vous ajoutez l’état de jeu que vous observez à ce tampon de réexécution. Vous échantillonnez également de manière aléatoire un sous-ensemble d’états de cette liste et vous vous entraînez sur ces états.

  2. L’équipe de DeepMind a dupliqué * Q (s, a) *. L’un s’appelle * Q_current (s, a) *, qui est la fonction Q que vous mettez à jour. Vous avez besoin d’une autre fonction Q pour les états successeurs, * Q_target (s ’, a’) *, que vous n’allez pas mettre à jour. Rappelez * Q_target (s ’, a’) * est utilisé pour générer vos étiquettes. En séparant * Q_current * de * Q_target * et en corrigeant ce dernier, vous corrigez la distribution à partir de laquelle les étiquettes sont échantillonnées. Ensuite, votre modèle d’apprentissage en profondeur peut prendre une courte période d’apprentissage de cette distribution. Après une période de temps, vous dupliquez à nouveau * Q_current * pour un nouveau * Q_target *.

Vous ne les implémenterez pas vous-même, mais vous allez charger des modèles pré-entraînés formés à ces solutions. Pour ce faire, créez un nouveau répertoire dans lequel vous allez stocker les paramètres de ces modèles:

mkdir models

Ensuite, utilisez + wget + pour télécharger les paramètres d’un modèle Space Invaders pré-entraîné:

wget http://models.tensorpack.com/OpenAIGym/SpaceInvaders-v0.tfmodel -P models

Ensuite, téléchargez un script Python qui spécifie le modèle associé aux paramètres que vous venez de télécharger. Notez que ce modèle de pré-entraînement a deux contraintes sur l’entrée qu’il est nécessaire de garder à l’esprit:

  • Les états doivent être sous-échantillonnés ou réduits en taille à 84 x 84.

  • L’entrée comprend quatre états, empilés.

Nous aborderons ces contraintes plus en détail ultérieurement. Pour l’instant, téléchargez le script en tapant:

wget https://github.com/alvinwan/bots-for-atari-games/raw/master/src/bot_6_a3c.py

Vous allez maintenant exécuter cet agent Space Invaders pré-entraîné pour voir comment il se comporte. Contrairement aux derniers robots que nous avons utilisés, vous écrirez ce script à partir de zéro.

Créez un nouveau fichier de script:

nano bot_6_dqn.py

Commencez ce script en ajoutant un commentaire d’en-tête, en important les utilitaires nécessaires et en commençant la boucle de jeu principale:

/AtariBot/bot_6_dqn.py

Immédiatement après vos importations, définissez des graines aléatoires pour rendre vos résultats reproductibles. Définissez également un hyperparamètre + num_episodes + qui indiquera au script le nombre d’épisodes sur lesquels l’agent doit être exécuté:

/AtariBot/bot_6_dqn.py

. . .
import tensorflow as tf
from bot_6_a3c import a3c_model





def main():
 . . .

Deux lignes après avoir déclaré + num_episodes +, définissez une fonction + sous-échantillon + qui sous-échantillonne toutes les images à une taille de 84 x 84. Vous sous-échantillonnerez toutes les images avant de les transmettre au réseau de neurones pré-entraîné, car le modèle pré-formé a été entraîné sur 84 images: 84 x 84:

/AtariBot/bot_6_dqn.py

. . .
num_episodes = 10




def main():
 . . .

Créez l’environnement de jeu au début de votre fonction + main + et générez-le de manière à ce que les résultats soient reproductibles:

/AtariBot/bot_6_dqn.py

. . .
def main():


   . . .

Directement après la valeur de départ de l’environnement, initialisez une liste vide pour contenir les récompenses:

/AtariBot/bot_6_dqn.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible

   . . .

Initialisez le modèle pré-entraîné avec les paramètres de modèle pré-entraînés que vous avez téléchargés au début de cette étape:

/AtariBot/bot_6_dqn.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   . . .

Ensuite, ajoutez quelques lignes indiquant au script de parcourir les temps "+ num_episodes " pour calculer les performances moyennes et initialiser la récompense de chaque épisode sur 0. En outre, ajoutez une ligne pour réinitialiser l’environnement (` env.reset () `), en collectant le nouvel état initial dans le processus, sous-échantillonnez cet état initial avec ` downsample () +, et démarrez la boucle de jeu à l’aide d’un Boucle `tandis que:

/AtariBot/bot_6_dqn.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []
   model = a3c_model(load='models/SpaceInvaders-v0.tfmodel')




       . . .

Au lieu d’accepter un état à la fois, le nouveau réseau de neurones accepte quatre états à la fois. En conséquence, vous devez attendre que la liste de + states + contienne au moins quatre états avant d’appliquer le modèle pré-entraîné. Ajoutez les lignes suivantes en dessous de la ligne lisant + while True: +. Celles-ci indiquent à l’agent de prendre une action aléatoire s’il y a moins de quatre états ou de concaténer les états et de le passer au modèle pré-entraîné s’il y en a au moins quatre:

/AtariBot/bot_6_dqn.py

       . . .
       while True:





               . . .

Ensuite, prenez une mesure et mettez à jour les données pertinentes. Ajoutez une version sous-échantillonnée de l’état observé et mettez à jour la récompense pour cet épisode:

/AtariBot/bot_6_dqn.py

       . . .
       while True:
           ...
               action = np.argmax(model([frames]))



           . . .

Ensuite, ajoutez les lignes suivantes qui vérifient si l’épisode est + done + et, le cas échéant, impriment la récompense totale de l’épisode, modifient la liste de tous les résultats et cassent la boucle` + while + `plus tôt:

/AtariBot/bot_6_dqn.py

       . . .
       while True:
           ...
           episode_reward += reward




               . . .

En dehors des boucles + while et` + for`, affiche la récompense moyenne. Placez ceci à la fin de votre fonction + main +:

/AtariBot/bot_6_dqn.py

def main():
   ...
               break

Vérifiez que votre fichier correspond aux éléments suivants:

/AtariBot/bot_6_dqn.py

"""
Bot 6 - Fully featured deep q-learning network.
"""

import cv2
import gym
import numpy as np
import random
import tensorflow as tf
from bot_6_a3c import a3c_model
random.seed(0)  # make results reproducible
tf.set_random_seed(0)

num_episodes = 10


def downsample(state):
   return cv2.resize(state, (84, 84), interpolation=cv2.INTER_LINEAR)[None]

def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   model = a3c_model(load='models/SpaceInvaders-v0.tfmodel')
   for _ in range(num_episodes):
       episode_reward = 0
       states = [downsample(env.reset())]
       while True:
           if len(states) < 4:
               action = env.action_space.sample()
           else:
               frames = np.concatenate(states[-4:], axis=3)
               action = np.argmax(model([frames]))
           state, reward, done, _ = env.step(action)
           states.append(downsample(state))
           episode_reward += reward
           if done:
               print('Reward: %d' % episode_reward)
               rewards.append(episode_reward)
               break
   print('Average reward: %.2f' % (sum(rewards) / len(rewards)))


if __name__ == '__main__':
   main()

Enregistrez le fichier et quittez votre éditeur. Ensuite, lancez le script:

python bot_6_dqn.py

Votre sortie se terminera par ce qui suit:

Output. . .
Reward: 1230
Reward: 4510
Reward: 1860
Reward: 2555
Reward: 515
Reward: 1830
Reward: 4100
Reward: 4350
Reward: 1705
Reward: 4905
Average reward: 2756.00

Comparez cela au résultat du premier script, dans lequel vous avez exécuté un agent aléatoire pour Space Invaders. La récompense moyenne dans ce cas n’était que d’environ 150, ce qui signifie que ce résultat est plus de vingt fois meilleur. Cependant, vous n’avez exécuté votre code que pour trois épisodes, car il est assez lent et la moyenne de trois épisodes n’est pas une métrique fiable. Sur 10 épisodes, la moyenne est de 2756; plus de 100 épisodes, la moyenne est d’environ 2500. Ce n’est qu’avec ces moyennes que vous pouvez facilement conclure que votre agent exécute effectivement mieux un ordre de grandeur et que vous avez maintenant un agent qui joue relativement bien avec Space Invaders.

Cependant, rappelez-vous la question soulevée dans la section précédente concernant la complexité de l’échantillon. En fin de compte, cet agent Space Invaders nécessite des millions d’échantillons à entraîner. En fait, cet agent avait besoin de 24 heures sur quatre GPU Titan X pour s’entraîner au niveau actuel; En d’autres termes, il a fallu une quantité importante de calcul pour l’entraîner correctement. Pouvez-vous former un agent hautement performant similaire avec beaucoup moins d’échantillons? Les étapes précédentes devraient vous donner suffisamment de connaissances pour commencer à explorer cette question. En utilisant des modèles beaucoup plus simples et des compromis par biais-variance, cela peut être possible.

Conclusion

Dans ce tutoriel, vous avez construit plusieurs robots pour les jeux et exploré un concept fondamental de l’apprentissage automatique appelé biais-variance. Une question naturelle à se poser est la suivante: pouvez-vous créer des bots pour des jeux plus complexes, tels que StarCraft 2? En fin de compte, il s’agit d’une question de recherche en suspens, complétée par des outils open source de collaborateurs de Google, DeepMind et Blizzard. Si ce sont des problèmes qui vous intéressent, voir open appelle à la recherche chez OpenAI, pour les problèmes actuels.

La principale conclusion de ce tutoriel est le compromis biais-variance. Il appartient au praticien d’apprentissage automatique de prendre en compte les effets de la complexité du modèle. S’il est possible d’utiliser des modèles très complexes et de superposer des quantités excessives de calculs, d’échantillons et de temps, une complexité réduite des modèles pourrait réduire considérablement les ressources nécessaires.

Related