Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Réalisation d'une IA jouant au jeu Mountain Car avec Q-Learning

Présentation du jeu Mountain Car

MountainCar

Le jeu Mountain Car est un environnement de type classic control disponible dans Gymnasium. Le but est simple :

  • Une voiture doit atteindre le drapeau en haut de la colline droite (en prenant d'abord de l'élan sur la colline gauche).
  • La voiture possède 3 choix de déplacement: accélérer vers la gauche, la droite ou ne pas accélérer.
  • L’agent reçoit une récompense de -1 à chaque timestep (il n'y a aucune récompense positive).

Principe du Q-Learning

Le Q-Learning est un algorithme d’apprentissage par renforcement qui permet à un agent d’apprendre une politique optimale en interagissant avec son environnement. L’idée est d’apprendre une table de valeurs Q(s,a), où :

  • Q(s,a) représente la valeur d’une action a dans un état s.
  • L’agent met à jour cette table en utilisant la récompense immédiate et une estimation de la valeur future des états.

La formule de mise à jour de Q est la suivante :

  • : taux d’apprentissage (learning rate)
  • : facteur d’escompte (discount factor)
  • r : récompense reçue après l’action
  • s′ : nouvel état après l’action

Étapes pour réaliser l’IA

Initialisation de l’environnement et des paramètres

On commence par importer les bibliothèques nécessaires et initialiser l'environnement :

from collections import defaultdict
import gymnasium as gym
import numpy as np
import matplotlib.pyplot as plt

# Initialisation de l'environnement
env = gym.make("MountainCar-v0")

learning_rate = 0.1 # Taux d'apprentissage (alpha)
n_episodes = 10000 # Nombre d'épisodes
start_epsilon = 1.0 # Probabilité d'exploration (100% au début)
epsilon_decay = start_epsilon / (n_episodes / 2) # Décroissance de epsilon à chaque épisode
final_epsilon = 0.1 # Valeur minimale d'epsilon
discount_factor = 0.99 # Facteur d'escompte (gamma)

Discrétisation

Le problème de Mountain Car est que ses états sont continus (position et vitesse réelles). Mais le Q-Learning repose sur une table Q discrète : il faut donc convertir les valeurs continues en indices entiers.

Pour cela, on divise chaque dimension de l’espace des états en un certain nombre de bacs (bins).

Par exemple :

Si n_bins = (20, 16), cela signifie :

  • La position est découpée en 20 intervalles
  • La vitesse est découpée en 16 intervalles
  • Cela donne une table Q de taille 20 * 16 * 3 (car 3 actions possibles)

Plus les intervalles sont nombreux :

  • plus l’approximation de l’espace est fine, et donc on aura une meilleure précision,
  • mais la table Q devient plus grande donc l'apprentissage prend plus de temps
def discretize_state(self, state):
        ratios = (state - self.obs_low) / self.bin_width
        new_state = np.clip((ratios).astype(int), 0, np.array(self.n_bins) - 1)
        return tuple(new_state)

Boucle d’entraînement

Pour chaque épisode, l’agent interagit avec l’environnement en choisissant des actions selon une stratégie -gloutonne. À chaque étape, la table Q est mise à jour selon la formule du Q-Learning.

rewards_per_episode = [] # tableau pour stocker le reward de chaque épsiode (uniquement visuel)

for episode in range(n_episodes):
    state, info = env.reset()
    state = agent.discretize_state(state) # Discrétisation de l'état initial
    done = False
    total_reward = 0

    while not done:
        action = agent.get_action(state) # Stratégie epsilon-gloutonne
        next_state, reward, terminated, truncated, info = env.step(action) # Exécution de l'action
        next_state = agent.discretize_state(next_state) # Discrétisation

        agent.update(state, action, reward, next_state, terminated) # Mise à jour de la table Q
        state = next_state # Passage à l'état suivant
        total_reward += reward
        done = terminated or truncated


    agent.decay_epsilon() # Décroissance de epsilon
    rewards_per_episode.append(total_reward) # Ajout du reward de cet épisode

env.close()

# Visualisation de l'apprentissage sous forme de graphe:
window = 100
moving_avg = np.convolve(rewards_per_episode, np.ones(window) / window, mode="valid")

plt.plot(moving_avg)
plt.title("MountainCar - Moyenne des récompenses")
plt.xlabel("Épisodes")
plt.ylabel("Récompense moyenne")
plt.show()

Méthodes de l'agent

class MountainCarAgent:
    def __init__(self, env: gym.Env,
                 learning_rate: float,
                 initial_epsilon: float,
                 epsilon_decay: float,
                 final_epsilon: float,
                 discount_factor: float,
                 n_bins=(20, 16)):  # nombre d'intervalles de discrétisation
        self.env = env

        # Paramètres du Q-learning
        self.lr = learning_rate
        self.discount_factor = discount_factor
        self.epsilon = initial_epsilon
        self.epsilon_decay = epsilon_decay
        self.final_epsilon = final_epsilon
        self.training_error = []

        # Discrétisation
        self.n_bins = n_bins
        self.obs_low = env.observation_space.low # vitesse et position minimale
        self.obs_high = env.observation_space.high # vitesse et position maximale
        self.bin_width = (self.obs_high - self.obs_low) / np.array(self.n_bins)

        # Q-table stockée sous forme de dictionnaire
        self.q_values = defaultdict(lambda: np.zeros(env.action_space.n))

    def get_action(self, state) -> int: # Stratégie epsilon-gloutonne
        if np.random.random() < self.epsilon:
            return self.env.action_space.sample() # Exploration : action aléatoire
        else:
            return int(np.argmax(self.q_values[state])) # Exploitation : action avec la meilleure valeur Q
        
    def update(self, state, action, reward, next_state, terminated): # Mise à jour de la table Q
        future_q = (not terminated) * np.max(self.q_values[next_state])
        target = reward + self.discount_factor * future_q
        td_error = target - self.q_values[state][action]
        self.q_values[state][action] += self.lr * td_error
        self.training_error.append(td_error)
    
    def decay_epsilon(self): # Décroissance de epsilon
        self.epsilon = max(self.final_epsilon, self.epsilon - self.epsilon_decay)

Points à retenir

  • Discrétisation : on découpe l’espace continu des états pour l’adapter au Q-Learning, le paramètre n_bins : définit la résolution de cette discrétisation.
  • Exploration/Exploitation : au début, l’agent explore beaucoup ( grand), puis il exploite ce qu’il a appris.
  • Récompenses : elles sont toujours négatives ce qui fait que l’agent apprend à atteindre le drapeau le plus vite possible.