Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
| public:rl_tp2 [2019/01/07 15:09] – [Discrétiser l'environnement] edauce | public:rl_tp2 [2019/01/07 16:07] (Version actuelle) – [Calcul d'une fonction de valeur sur les transitions] edauce | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| + | ===== Utilisation de la librairie OpenAI Gym ===== | ||
| + | |||
| + | Comme nous l' | ||
| + | * L' | ||
| + | * L' | ||
| + | |||
| + | On considère de plus que le temps s' | ||
| + | |||
| + | Dans le cas parfaitement observable, l' | ||
| + | |||
| + | La librairie '' | ||
| + | |||
| + | === Classe Space === | ||
| + | La classe '' | ||
| + | |||
| + | {{: | ||
| + | |||
| + | |||
| + | |||
| + | Un espace peut être de type discret, continu, ou encore être défini comme une combinaison d' | ||
| + | Tous les espaces implémentent les méthodes : | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | * Un espace de type '' | ||
| + | * Un espace de type '' | ||
| + | * l' | ||
| + | * l' | ||
| + | * l' | ||
| + | |||
| + | === Classe Env === | ||
| + | La classe centrale est la classe '' | ||
| + | |||
| + | La classe '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Elle propose les méthodes: | ||
| + | * la méthode '' | ||
| + | * l' | ||
| + | * la récompense | ||
| + | * une indication pour savoir si l' | ||
| + | * la méthode '' | ||
| + | * la méthode '' | ||
| + | |||
| + | **remarque :** la méthode '' | ||
| + | |||
| + | ==== Le pendule inversé ==== | ||
| + | |||
| + | Dans ce TP, nous nous intéressons au problème du pendule inversé (voir {{https:// | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Une barre rigide est attachée par une charnière à un chariot, qui bouge le long d'un rail sans friction. | ||
| + | |||
| + | Le système est contrôlé en appliquant une force de +1 ou -1 au chariot. | ||
| + | |||
| + | Le pendule démarre en position verticale instable, et le but est de le maintenir ainsi le plus longtemps possible, en appliquant des forces de +1 ou -1 alternativement. | ||
| + | |||
| + | Une récompense de +1 est donnée tant que le pendule est maintenu en position verticale. | ||
| + | |||
| + | L' | ||
| + | * le pendule est à plus de 15 degrés de la verticale | ||
| + | * le chariot est à plus de 2.4 unités du centre | ||
| + | |||
| + | Le vecteur d' | ||
| + | |||
| + | <note tip> | ||
| + | NB : si on connaît les caractéristiques physiques du chariot, il est possible de trouver une loi de contrôle qui maintient le pendule en position verticale. Le problème d' | ||
| + | </ | ||
| + | |||
| + | ==== Premières simulations ==== | ||
| + | <note tip> | ||
| + | Pour installer gym dans Pycharm, il faut utiliser un environnement virtuel (qui donne le droit d' | ||
| + | * Pour configurer un environnement, | ||
| + | * une fois l' | ||
| + | * l' | ||
| + | </ | ||
| + | |||
| + | Chaque environnement proposé par Gym possède un nom unique. | ||
| + | |||
| + | Pour voir la liste des environnements proposés, suivre {{https:// | ||
| + | |||
| + | L' | ||
| + | |||
| + | Pour créer l' | ||
| + | <code python> | ||
| + | import gym | ||
| + | env = gym.make(' | ||
| + | </ | ||
| + | L' | ||
| + | La commande : | ||
| + | <code python> | ||
| + | obs = env.reset() | ||
| + | </ | ||
| + | initialise l' | ||
| + | |||
| + | L' | ||
| + | <code python> | ||
| + | env.render() | ||
| + | c = input(' | ||
| + | </ | ||
| + | remarque : l' | ||
| + | |||
| + | Il est possible de modifier la position du pendule à l'aide d'une action. Pour choisir une action au hasard: | ||
| + | <code python> | ||
| + | action = 0 | ||
| + | </ | ||
| + | <note tip> | ||
| + | NB : L' | ||
| + | * l' | ||
| + | * l' | ||
| + | </ | ||
| + | pour appliquer l' | ||
| + | <code python> | ||
| + | observation, | ||
| + | env.render() | ||
| + | c = input(' | ||
| + | </ | ||
| + | On peut remarquer sur la fenêtre graphique que le pendule a bougé de manière infinitésimale suite à l' | ||
| + | <note tip> | ||
| + | NB : la fonction step retourne plusieurs valeurs : | ||
| + | * une observation | ||
| + | * un reward (recompense) | ||
| + | * un indicateur '' | ||
| + | </ | ||
| + | |||
| + | Pour le faire bouger de manière plus franche, il faut appliquer plusieurs fois l' | ||
| + | <code python> | ||
| + | obs = env.reset() | ||
| + | for i in range(100): | ||
| + | env.render() | ||
| + | action = 0 | ||
| + | obs, reward, done, _ = env.step(action) | ||
| + | c = input(" | ||
| + | </ | ||
| + | Le chariot disparaît très vite à gauche de l' | ||
| + | |||
| + | Pour contrôler le pendule, il faut donc alterner les actions 0 et 1. | ||
| + | Essayons : | ||
| + | <code python> | ||
| + | obs = env.reset() | ||
| + | for i in range(1000): | ||
| + | env.render() | ||
| + | if i % 2 == 0 : | ||
| + | action = 0 | ||
| + | else: | ||
| + | action = 1 | ||
| + | obs, reward, done, _ = env.step(action) | ||
| + | c = input(" | ||
| + | </ | ||
| + | C'est un peu mieux, mais la barre ne reste pas en position verticale (le système est instable) | ||
| + | |||
| + | Essayons de prendre en compte l' | ||
| + | <code python> | ||
| + | obs = env.reset() | ||
| + | for i in range(1000): | ||
| + | env.render() | ||
| + | if obs[2] < 0: | ||
| + | action = 0 | ||
| + | else: | ||
| + | action = 1 | ||
| + | obs, reward, done, _ = env.step(action) | ||
| + | c = input(" | ||
| + | </ | ||
| + | C'est encore un peu mieux mais le pendule finit quand même par se déstabiliser. | ||
| + | |||
| + | Si nous prenons en compte l' | ||
| + | < | ||
| + | obs = env.reset() | ||
| + | total_steps = 0 | ||
| + | while True: | ||
| + | env.render() | ||
| + | if obs[2] < 0: | ||
| + | action = 0 | ||
| + | else: | ||
| + | action = 1 | ||
| + | obs, reward, done, _ = env.step(action) | ||
| + | total_steps += 1 | ||
| + | if done: | ||
| + | break | ||
| + | print(" | ||
| + | c = input(" | ||
| + | </ | ||
| + | |||
| + | Il est également possible de contrôler le pendule de manière aléatoire à l'aide le la méthode '' | ||
| + | |||
| + | <code python> | ||
| + | obs = env.reset() | ||
| + | total_steps = 0 | ||
| + | while True: | ||
| + | env.render() | ||
| + | action = env.action_space.sample() | ||
| + | obs, reward, done, _ = env.step(action) | ||
| + | total_steps += 1 | ||
| + | if done: | ||
| + | break | ||
| + | print(" | ||
| + | c = input(" | ||
| + | </ | ||
| + | |||
| + | **A faire** | ||
| + | * Modifiez le code pour que ce soit l' | ||
| + | * Définissez une loi de contrôle qui prenne en compte à la fois la position angulaire '' | ||
| + | |||
| + | ==== Discrétiser l' | ||
| + | Les algorithmes d' | ||
| + | |||
| + | Définir un critère simple : '' | ||
| + | * l' | ||
| + | * l' | ||
| + | |||
| + | On peut généraliser ce principe et définir 16 états différents à partir des 4 observables : | ||
| + | * état 0 : '' | ||
| + | * état 1 : '' | ||
| + | * ... | ||
| + | * état 15 : '' | ||
| + | |||
| + | **A faire ** : écrire une fonction qui calcule un état discret à partir des valeurs d'une observation. | ||
| + | |||
| + | |||
| + | |||
| + | ==== Calcul d'une fonction de valeur sur les transitions ==== | ||
| + | |||
| + | L' | ||
| + | |||
| + | Rappel : Si $s_t$ est l' | ||
| + | $$V(s_t) = E(\sum_{t' | ||
| + | est la fonction de valeur de l' | ||
| + | |||
| + | La " | ||
| + | |||
| + | Contrairement au TD1, nous ne possédons pas de modèle de transitions d' | ||
| + | $$Q(s_t, a_t) = E(\sum_{t' | ||
| + | la fonction de valeur sur les transitions d' | ||
| + | |||
| + | NB : l' | ||
| + | |||
| + | Il est nécessaire pour cela d' | ||
| + | |||
| + | Nous reprenons ici le modèle d' | ||
| + | <code python> | ||
| + | class Agent: | ||
| + | def __init__(self): | ||
| + | self.total_reward = 0.0 | ||
| + | self.trace = [] | ||
| + | </ | ||
| + | (pas besoin de méthode '' | ||
| + | |||
| + | Cette trace doit être mise à jour à chaque itération pour obtenir en fin d' | ||
| + | |||
| + | On suppose ici que l' | ||
| + | <code python> | ||
| + | action = env.action_space.sample() | ||
| + | </ | ||
| + | |||
| + | Pour calculer (de façon simplifiée) la fonction de valeur, nous utiliserons l' | ||
| + | |||
| + | < | ||
| + | Q = {} | ||
| + | nb_visites = {} | ||
| + | total_rewards_sum | ||
| + | pour tout couple (s,a) possible: | ||
| + | | ||
| + | | ||
| + | | ||
| + | pour i de 1 à N: | ||
| + | exécuter un épisode | ||
| + | pour tout (s,a) dans agent.trace : | ||
| + | nb_visites[(s, | ||
| + | total_rewards_sum[(s, | ||
| + | pour tout (s,a) dans Q: | ||
| + | si nb_visites[(s, | ||
| + | Q[(s,a)] = total_rewards_sum[(s, | ||
| + | </ | ||
| + | |||
| + | Pour calculer la fonction de valeur, on utilisera N = 1000 | ||
| + | |||
| + | Modifier l'init : | ||
| + | <code python> | ||
| + | class Agent: | ||
| + | def __init__(self): | ||
| + | self.total_reward = 0.0 | ||
| + | self.trace = [] | ||
| + | self.Q = {} | ||
| + | self.nb_visites = {} | ||
| + | self.total_rewards_sum | ||
| + | ... | ||
| + | </ | ||
| + | <note important> | ||
| + | L' | ||
| + | pour tout couple (s,a) possible: | ||
| + | Q[(s,a)] = 0 | ||
| + | nb_visites[(s, | ||
| + | total_rewards_sum[(s, | ||
| + | </ | ||
| + | Définir les méthodes suivantes : | ||
| + | * une méthode qui exécute un épisode complet en utilisant une politique aléatoire: | ||
| + | <code python> | ||
| + | def run_episode_random(self, | ||
| + | ... | ||
| + | </ | ||
| + | <note important> | ||
| + | Attention, la '' | ||
| + | </ | ||
| + | * une méthode qui met à jour les dictionnaires '' | ||
| + | <code python> | ||
| + | def update_dicos(self): | ||
| + | ... | ||
| + | </ | ||
| + | <note important> | ||
| + | Attention penser à remettre zéro la '' | ||
| + | </ | ||
| + | * une méthode qui estime la valeur de Q après un grand nombre d' | ||
| + | <code python> | ||
| + | def update_Q(self): | ||
| + | ... | ||
| + | </ | ||
| + | |||
| + | Implémentez ensuite l' | ||
| + | |||
| + | ==== Politique guidée par la valeur ==== | ||
| + | |||
| + | Pour améliorer sa récompense moyenne, l' | ||
| + | $$ a_t = \underset{a \in \mathcal{A}}{\text{argmax }} Q(s_t, a)$$ | ||
| + | |||
| + | * Écrire une méthode '' | ||
| + | <code python> | ||
| + | def choix_action(self, | ||
| + | ... | ||
| + | </ | ||
| + | * Lancer des simulations avec cette nouvelle méthode : | ||
| + | <code python> | ||
| + | action = agent.choix_action(s) | ||
| + | </ | ||
| + | * et calculer la récompense moyenne obtenue sur 100 épisodes et comparer au résultat précédent. Conclusion? | ||
| + | |||
| + | ==== Améliorations ==== | ||
| + | |||
| + | Le calcul exact de la fonction de valeur nécessite de mémoriser l' | ||
| + | |||
| + | <code python> | ||
| + | class Agent: | ||
| + | def __init__(self): | ||
| + | self.rewards = [] | ||
| + | self.trace = [] | ||
| + | self.Q = {} | ||
| + | self.nb_visites = {} | ||
| + | self.total_rewards_sum | ||
| + | ... | ||
| + | </ | ||
| + | |||
| + | et la mise à jour se fait en ajoutant les récompenses en fin de liste : | ||
| + | self.rewards += [reward] | ||
| + | à chaque pas de temps (après chaque appel à '' | ||
| + | |||
| + | En fin de simulation, la valeur cumulée est calculée séparément pour chaque (s,a) rencontré: | ||
| + | * pour chaque couple $s_t, a_t$ de la trace : | ||
| + | * calculer la somme des récompenses **présente et futures** | ||
| + | * ajouter la valeur dans '' | ||
| + | * incrémenter le nombre de visites | ||
| + | |||
| + | Calculer à nouveau la fonction de valeur et comparer la politique obtenue à la politique précédente. | ||
| + | |||
| + | |||