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:35] – [Fonction de perte] 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 Na 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=' | ||
+ | </ |