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_tp1 [2018/11/30 10:35] – [Politique guidée par la valeur] edauce | public:rl_tp1 [2019/12/13 10:38] (Version actuelle) – edauce | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| + | ===== Apprentissage par renforcement : premiers pas ===== | ||
| + | |||
| + | Dans ce TP nous allons décrire le comportement d'un agent se déplaçant aléatoirement dans un environnement très simple. | ||
| + | |||
| + | Pour établir développer des algorithmes d’apprentissage par renforcement, | ||
| + | * Un **agent** : l' | ||
| + | * de percevoir son environnement (à l'aide d' | ||
| + | * d'agir sur son environnement à l'aide d' | ||
| + | * $-->$ la politique décide des choix d' | ||
| + | * Un **environnement** : | ||
| + | * Il s'agit d'un simulateur du monde extérieur à l' | ||
| + | * L' | ||
| + | * les observations | ||
| + | * les récompenses (gratification des actions) | ||
| + | |||
| + | Nous allons regarder comment programmer ces deux entités pour pouvoir réaliser des simulations simples. | ||
| + | |||
| + | ==== Structure du code ==== | ||
| + | |||
| + | Dans un projet '' | ||
| + | |||
| + | === Classe Environnement === | ||
| + | |||
| + | On considère ici que les simulations se déroulent sur un nombre limité de pas de temps (ici 10). | ||
| + | |||
| + | Voici le constructeur : | ||
| + | <code python> | ||
| + | class Environment: | ||
| + | def __init__(self): | ||
| + | self.state = 0 | ||
| + | self.steps_left = 10 | ||
| + | </ | ||
| + | Dans le cas général, l' | ||
| + | |||
| + | L' | ||
| + | <code python> | ||
| + | def get_observation(self): | ||
| + | return self.state | ||
| + | </ | ||
| + | |||
| + | La méthode '' | ||
| + | <code python> | ||
| + | def get_actions(self): | ||
| + | return [0, 1] | ||
| + | </ | ||
| + | |||
| + | La méthode '' | ||
| + | <code python> | ||
| + | def is_done(self): | ||
| + | return self.steps_left == 0 | ||
| + | </ | ||
| + | |||
| + | Enfin, la méthode '' | ||
| + | <note tip> | ||
| + | Dans cet exemple simple, | ||
| + | * l' | ||
| + | * la récompense est tirée au hasard. | ||
| + | </ | ||
| + | |||
| + | <code python> | ||
| + | def action(self, | ||
| + | if self.is_done(): | ||
| + | raise Exception(" | ||
| + | self.steps_left -= 1 | ||
| + | return random.random() | ||
| + | </ | ||
| + | |||
| + | === La classe Agent === | ||
| + | |||
| + | La classe Agent est plus simple et ne contient que deux méthodes : | ||
| + | * le constructeur | ||
| + | * la méthode de mise à jour qui effectue une itération de l' | ||
| + | |||
| + | Voici le constructeur. Il contient un compteur qui conserve le nombre total e récompenses accumulées. | ||
| + | <code python> | ||
| + | class Agent: | ||
| + | def __init__(self): | ||
| + | self.total_reward = 0.0 | ||
| + | </ | ||
| + | |||
| + | La méthode de mise à jour prend comme argument une instance d l' | ||
| + | * observer l' | ||
| + | * lire le répertoire d' | ||
| + | * choisir une action et la transmettre à l' | ||
| + | * lire la récompense et mettre à jour le compteur | ||
| + | <code python> | ||
| + | def step(self, env): | ||
| + | current_obs = env.get_observation() | ||
| + | actions = env.get_actions() | ||
| + | reward = env.action(random.choice(actions)) | ||
| + | self.total_reward += reward | ||
| + | </ | ||
| + | |||
| + | <note tip> | ||
| + | Ici bien sûr, l' | ||
| + | </ | ||
| + | |||
| + | === Programme principal === | ||
| + | |||
| + | Créez maintenant un programme principal qui crée les deux classes et exécute un épisode : | ||
| + | |||
| + | <code python> | ||
| + | env = Environment() | ||
| + | agent = Agent() | ||
| + | |||
| + | while not env.is_done(): | ||
| + | agent.step(env) | ||
| + | |||
| + | print(" | ||
| + | </ | ||
| + | |||
| + | Faites tourner cet environnement plusieurs fois et vérifiez que la somme des récompenses obtenues varie à chaque fois. | ||
| + | |||
| + | |||
| + | ==== Simulation d'un Monde-grille ==== | ||
| + | |||
| + | Les mondes-grilles (//Grid worlds//) permettent de simuler de petits labyrinthes dans lesquels les actions de l' | ||
| + | |||
| + | Nous considérons ici un labyrinthe extrêmement simple à 8 cases : | ||
| + | < | ||
| + | {{: | ||
| + | </ | ||
| + | |||
| + | * L' | ||
| + | $$\mathcal{S} = \{1, 2, 3, 4, 5, 6, 7, 8\}$$ | ||
| + | <note tip> | ||
| + | Sur le dessin ci-dessus, l' | ||
| + | </ | ||
| + | * Les actions correspondent aux mouvements de l' | ||
| + | $$\mathcal{A} = \{N, S, E, W\}$$ | ||
| + | <note important> | ||
| + | Attention cependant, toutes les actions ne sont pas autorisées dans toutes les positions. Ainsi, à la position '' | ||
| + | </ | ||
| + | * Les cases 1, 2, 3, 4, et 5 n' | ||
| + | |||
| + | Nous allons modifier pas à pas le squelette d' | ||
| + | |||
| + | === L' | ||
| + | |||
| + | L' | ||
| + | |||
| + | * 1. Le changement d' | ||
| + | |||
| + | <note tip> | ||
| + | Plutôt que d' | ||
| + | <code python> | ||
| + | next = { | ||
| + | 1 : {" | ||
| + | 2 : {" | ||
| + | 3 : {" | ||
| + | 4 : {" | ||
| + | 5 : {" | ||
| + | 6 : {" | ||
| + | 7 : {" | ||
| + | 8 : {" | ||
| + | } | ||
| + | </ | ||
| + | ainsi '' | ||
| + | </ | ||
| + | |||
| + | <note tip> | ||
| + | La récompense est la valeur qui doit être retournée par la méthode '' | ||
| + | <code python> | ||
| + | reward = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: -10, 7: 10, 8: -10} | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | * 2. Pour l' | ||
| + | |||
| + | <code python> | ||
| + | def __init__(self): | ||
| + | self.state = random.randint(1, | ||
| + | self.steps_left = 10 | ||
| + | </ | ||
| + | |||
| + | * 3. La méthode '' | ||
| + | |||
| + | === L' | ||
| + | Pour l' | ||
| + | |||
| + | === Simulation === | ||
| + | Dans le programme principal, effectuer un total de 100 simulations et calculer la moyenne et l' | ||
| + | |||
| + | <note tip> | ||
| + | Pour bien comprendre le comportement de l' | ||
| + | </ | ||
| + | |||
| + | ==== Calcul d'une fonction de valeur ==== | ||
| + | |||
| + | Afin de rendre notre agent un peu plus " | ||
| + | |||
| + | Si $s_t$ est l' | ||
| + | $$V(s_t) = E(\sum_{t' | ||
| + | avec $T$ l' | ||
| + | |||
| + | Il est nécessaire pour cela d' | ||
| + | |||
| + | Ainsi, le constructeur de l' | ||
| + | <code python> | ||
| + | class Agent: | ||
| + | def __init__(self): | ||
| + | self.total_reward = 0.0 | ||
| + | self.trace = [] | ||
| + | </ | ||
| + | |||
| + | Cette trace doit être mise à jour à chaque itération pour obtenir en fin d' | ||
| + | |||
| + | Pour calculer (de façon simplifiée) la fonction de valeur, nous utiliserons l' | ||
| + | |||
| + | < | ||
| + | V = {1 :0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} | ||
| + | nb_visites = {1 :0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} | ||
| + | total_reward_sum = {1 :0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} | ||
| + | pour i de 1 à N: | ||
| + | exécuter un épisode | ||
| + | pour tout s dans agent.trace : | ||
| + | nb_visites[s] += 1 | ||
| + | total_reward_sum[s] += agent.total_reward | ||
| + | pour tout s dans V: | ||
| + | V[s] = total_reward_sum[s] / nb_visites[s] | ||
| + | </ | ||
| + | |||
| + | Pour calculer la fonction de valeur, on utilisera N = 100 | ||
| + | |||
| + | ==== Politique guidée par la valeur ==== | ||
| + | |||
| + | Pour améliorer sa récompense moyenne, l' | ||
| + | $$ a_t = \underset{a \in \mathcal{A}}{\text{argmax }} V(\text{next}(s_t, | ||
| + | |||
| + | * Écrire une méthode '' | ||
| + | <code python> | ||
| + | def step_valeur(self, | ||
| + | ... | ||
| + | </ | ||
| + | * Lancer des simulations avec cette nouvelle méthode de mise à jour 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 = [] | ||
| + | </ | ||
| + | |||
| + | et la mise à jour se fait en ajoutant les récompenses en fin de liste : | ||
| + | <code python> | ||
| + | def step(self, env): | ||
| + | ... | ||
| + | self.rewards += [reward] | ||
| + | </ | ||
| + | |||
| + | En fin de simulation, la valeur cumulée est calculée séparément pour chaque état rencontré: | ||
| + | * pour chaque état $s_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. | ||
| + | |||
| + | |||