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_tp4 [2019/01/18 00:33] – [Mise en oeuvre dans un environnement continu] edauce | public:rl_tp4 [2019/01/22 22:36] (Version actuelle) – edauce | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| + | ===== TP4 : Q-learning sur les espaces d' | ||
| + | L' | ||
| + | |||
| + | Les espaces d' | ||
| + | |||
| + | Néanmoins, de nombreux problèmes de contrôle ont lieu dans des environnements continus. Nous considérons ici le cas où les observations $s$ appartiennent à un espace d' | ||
| + | |||
| + | Pour approcher la valeur de transition $Q(s,a)$, il est impossible d' | ||
| + | |||
| + | ==== Environnement === | ||
| + | |||
| + | L' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | //A car is on a one-dimensional track, positioned between two " | ||
| + | |||
| + | <code python> | ||
| + | ENV_NAME = ' | ||
| + | env = gym.make(ENV_NAME) | ||
| + | </ | ||
| + | |||
| + | Dans cet environnement, | ||
| + | |||
| + | Il existe trois actions possibles : pousser à gauche, ne rien faire ou pousser à droite. | ||
| + | |||
| + | <note important> | ||
| + | La difficulté de l' | ||
| + | |||
| + | La récompense est ici de -1 à chaque pas de temps (le signe de la récompense est inversé par rapport au pendule du TP3). Ici, on cherche à obtenir les épisodes les plus courts possibles (et non les épisodes les plus longs). | ||
| + | |||
| + | Le simulateur stoppe la simulation après 200 pas de temps. Il y a donc deux conditions d' | ||
| + | * le véhicule a atteint l' | ||
| + | * le véhicule n'a pas atteint l' | ||
| + | |||
| + | Pour calculer l' | ||
| + | < | ||
| + | if done and self.env._elapsed_steps < 200: | ||
| + | err = reward - self.Q[s, a] | ||
| + | else: | ||
| + | err = reward + self.GAMMA * self.max_Q(s) - self.Q[s, a] | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | <note tip> | ||
| + | Dans un premier temps, essayez de résoudre cet environnement à l'aide du Q-learning tabulaire vu au TP3. Cela implique de modifier un peu la méthode de discrétisation (voir [[public: | ||
| + | |||
| + | Ici la position de départ étant à -0.5, le seuil de la discrétisation de la position est fixé à -0.5: | ||
| + | <code python> | ||
| + | def discretise(self, | ||
| + | if type(self.env.env) is ...: | ||
| + | ... | ||
| + | elif type(self.env.env) is gym.envs.classic_control.mountain_car.MountainCarEnv: | ||
| + | return | ||
| + | + 2 * int(obs[1] >= 0) | ||
| + | else: | ||
| + | ... | ||
| + | </ | ||
| + | |||
| + | créez dans le projet du TP3 un nouveau programme principal '' | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | ==== Mise en oeuvre dans un environnement continu ==== | ||
| + | |||
| + | La prise en compte d' | ||
| + | |||
| + | <note important> | ||
| + | Pour conserver l' | ||
| + | </ | ||
| + | |||
| + | Nous utiliserons ici la librairie {{https:// | ||
| + | |||
| + | <code python> | ||
| + | import torch | ||
| + | import torch.nn as nn | ||
| + | </ | ||
| + | |||
| + | <note tip> | ||
| + | Pensez à installer la librairie torch dans l' | ||
| + | </ | ||
| + | |||
| + | Voici la forme que prend la nouvelle classe Agent | ||
| + | <code python> | ||
| + | class Agent_nn: | ||
| + | def __init__(self, | ||
| + | self.env = env | ||
| + | self.N_obs = np.prod(env.observation_space.shape) | ||
| + | self.N_act = env.action_space.n | ||
| + | self.GAMMA = GAMMA | ||
| + | self.ALPHA = ALPHA | ||
| + | self.CHOICE = CHOICE | ||
| + | self.net = nn.Sequential( | ||
| + | nn.Linear(self.N_obs + self.N_act, N_HIDDEN), | ||
| + | nn.ReLU(), | ||
| + | nn.Linear(N_HIDDEN, | ||
| + | ) | ||
| + | self.optimizer = torch.optim.Adam(self.net.parameters(), | ||
| + | </ | ||
| + | |||
| + | L' | ||
| + | * un réseau de neurones '' | ||
| + | * un optimiseur permettant de calculer les valeurs de gradient par rétropropagation sur toutes les couches | ||
| + | * ici la méthode Adam est sélectionnée. On pourra également tester la méthode SGD. | ||
| + | * '' | ||
| + | |||
| + | <note important> | ||
| + | On pourra fixer la valeur de N_HIDDEN à 50. | ||
| + | </ | ||
| + | |||
| + | <note tip> | ||
| + | La définition d'un réseau de neurones en pytorch repose sur une structure de données spécifique (un tenseur torch) | ||
| + | * qui contient eut être stocké sur CPU ou sur GPU | ||
| + | * qui contient, en plus des paramètres, | ||
| + | |||
| + | Toutes les données manipulées par le réseau de neurone doivent être au format torch. | ||
| + | |||
| + | Pour passer d'un format numpy à un format torch : | ||
| + | x_tf = torch.FloatTensor([x]) | ||
| + | Pour passer d'un format torch à un format numpy : | ||
| + | x = x_tf.detach().numpy() | ||
| + | | ||
| + | On trouve sur le Web de nombreux {{https:// | ||
| + | |||
| + | **remarque**: | ||
| + | </ | ||
| + | |||
| + | === Définir la fonction Q === | ||
| + | |||
| + | La fonction de valeur Q devient une méthode de classe : | ||
| + | |||
| + | <code python> | ||
| + | def Q(self, obs, act, tf = False): | ||
| + | input = np.concatenate((obs, | ||
| + | input_tf = torch.FloatTensor([input]) | ||
| + | output_tf = self.net(input_tf) | ||
| + | if tf: | ||
| + | return output_tf | ||
| + | else: | ||
| + | return output_tf.data.numpy()[0] | ||
| + | </ | ||
| + | |||
| + | On utilise pour coder l' | ||
| + | <note tip> **A faire** | ||
| + | |||
| + | Ecrivez la méthode : | ||
| + | def one_hot(self, | ||
| + | ... | ||
| + | qui retourne un vecteur de taille $N_a$ contenant des zéros partout sauf dans la case d' | ||
| + | </ | ||
| + | |||
| + | **A faire** : il faut maintenant reprendre la plupart des fonctions et des politiques définies dans le TP3 : | ||
| + | * en éliminant la discrétisation | ||
| + | * en remplaçant | ||
| + | |||
| + | ==== Fonction de perte ==== | ||
| + | |||
| + | Il reste bien sûr le plus important qui est la définition de la mise à jour du réseau de neurones en fonction de l' | ||
| + | |||
| + | La fonction de perte est définie ici comme le carré de l' | ||
| + | |||
| + | <code python> | ||
| + | def Q_loss(self, | ||
| + | Q_prim = self.max_Q(obs_prim) | ||
| + | if done and self.env._elapsed_steps < 200: | ||
| + | Q_target = reward | ||
| + | else: | ||
| + | Q_target = reward + self.GAMMA * Q_prim | ||
| + | Q_target_tf = torch.FloatTensor([Q_target]) | ||
| + | return torch.sum(torch.pow(Q_target_tf - output_tf, 2), 1) | ||
| + | </ | ||
| + | |||
| + | ==== Algorithme ==== | ||
| + | |||
| + | La mise à jour de la fonction de valeur se fait à chaque pas de temps comme dans l' | ||
| + | |||
| + | <code python> | ||
| + | def run_episode_Q_learning(self, | ||
| + | obs = env.reset() | ||
| + | obs[1] *= 100 | ||
| + | while True: | ||
| + | if render: | ||
| + | env.render() | ||
| + | if self.CHOICE == ' | ||
| + | act = self.choix_action_softmax(obs) | ||
| + | else: | ||
| + | act = self.choix_action_epsgreedy(obs) | ||
| + | obs_prim, reward, done, _ = env.step(act) | ||
| + | obs_prim[1] *= 100 | ||
| + | output_tf = self.Q(obs, act, tf = True) | ||
| + | loss = self.Q_loss(act, | ||
| + | loss.backward() | ||
| + | self.optimizer.step() | ||
| + | self.optimizer.zero_grad() | ||
| + | obs = obs_prim | ||
| + | if done: | ||
| + | break | ||
| + | </ | ||
| + | <note important> | ||
| + | NB : l' | ||
| + | |||
| + | Pour rendre le code plus générique, | ||
| + | |||
| + | <code python> | ||
| + | def normalise(obs) | ||
| + | if type(self.env.env) is gym.envs.classic_control.cartpole.CartPoleEnv: | ||
| + | obs[0] *= 10 | ||
| + | obs[2] *= 10 | ||
| + | elif type(self.env.env) is gym.envs.classic_control.mountain_car.MountainCarEnv: | ||
| + | obs[1] *= 100 | ||
| + | return obs | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | ** A faire ** | ||
| + | Pour faire fonctionner l' | ||
| + | * de paramétrer finement les valeurs d' | ||
| + | * de faire tourner l' | ||
| + | <note tip> | ||
| + | On pourra commencer par tester l''' | ||
| + | </ | ||
| + | |||
| + | Afin de faciliter la résolution du problème, reprendre l' | ||
| + | |||
| + | On regardera ici : | ||
| + | * le nombre total d' | ||
| + | * la perte moyenne sur l' | ||
| + | * la valeur de l' | ||
| + | |||
| + | <code python> | ||
| + | writer = SummaryWriter(log_dir=' | ||
| + | </ | ||