Module 10 — Projet : agent entraîné sur un environnement Gymnasium
Neuf modules ont posé les briques ; ce module les assemble sur un problème complet. L'objectif n'est pas seulement d'obtenir une politique qui « marche », mais d'apprendre les gestes qui distinguent un projet de renforcement crédible d'une démonstration jetable.
L'énoncé
LunarLander-v2 : poser un module lunaire entre deux drapeaux, moteurs latéraux et vertical. État de dimension 8, quatre actions discrètes (rien, gauche, principal, droite). Récompense +100 à +140 pour un atterrissage réussi, -100 pour un crash, pénalité continue pour la consommation de carburant et l'angle. C'est un environnement modeste — un ordinateur portable suffit — mais assez riche pour révéler tous les pièges du domaine.
Le livrable attendu : un agent PPO qui atteint 200 points de moyenne sur 100 épisodes consécutifs (le seuil officiel de « résolution »), avec un carnet reproductible et une vidéo.
Squelette du projet
projet-lunarlander/
entrainement.py # entraine et sauvegarde le modele + les journaux
evaluation.py # rejoue N episodes sur un modele sauvegarde
video.py # enregistre une video d'une politique
graphiques.py # trace les courbes de recompense avec intervalles
requirements.txt # gymnasium, stable-baselines3, matplotlib
README.md # comment reproduire, avec graines exactes
modeles/ # .zip PPO
journaux/ # tensorboard + numpy des courbes
videos/ # mp4
Un point crucial : tout ce qui n'est pas dans le code doit être dans le README, y compris la commande exacte, les graines, la version de gymnasium et de stable-baselines3. Sans cela, la reproduction est impossible.
Entraînement en cinq graines
Une seule exécution ne prouve rien en renforcement — les variations entre graines dépassent facilement les différences d'algorithme. Toujours entraîner sur au moins 3 à 5 graines et rapporter la moyenne avec un intervalle.
import gymnasium as gym
import numpy as np
from stable_baselines3 import PPO
from stable_baselines3.common.env_util import make_vec_env
from stable_baselines3.common.callbacks import BaseCallback
class JournalRecompense(BaseCallback):
def __init__(self):
super().__init__()
self.recompenses_episodes = []
def _on_step(self):
for info in self.locals["infos"]:
if "episode" in info:
self.recompenses_episodes.append(info["episode"]["r"])
return True
for graine in [0, 1, 2, 3, 4]:
env = make_vec_env("LunarLander-v2", n_envs=8, seed=graine)
modele = PPO("MlpPolicy", env, learning_rate=3e-4, n_steps=2048,
batch_size=64, n_epochs=10, gamma=0.99, gae_lambda=0.95,
clip_range=0.2, ent_coef=0.01, seed=graine, verbose=0)
journal = JournalRecompense()
modele.learn(total_timesteps=1_000_000, callback=journal)
modele.save(f"modeles/ppo_graine_{graine}")
np.save(f"journaux/recompenses_graine_{graine}.npy",
np.array(journal.recompenses_episodes))
Lire une courbe d'apprentissage honnête
Cinq courbes brutes, superposées, sont illisibles. La bonne représentation :
- Moyenner les cinq courbes sur des fenêtres de 100 épisodes (moyenne mobile).
- Aligner les axes des x en nombre de pas d'environnement, pas en épisodes (les épisodes n'ont pas la même longueur).
- Tracer la médiane et remplir la zone entre le premier et le troisième quartile — plus robuste qu'une moyenne avec écart-type.
Une courbe qui monte de 0 à 250 en un million de pas, avec un intervalle étroit à la fin, est un vrai signal. La même courbe avec un intervalle qui va de -100 à 250 signale un algorithme instable ; le pilote de la graine 3 s'écrase peut-être encore alors que celui de la graine 0 est déjà expert.
Évaluer proprement
L'évaluation se fait sur des épisodes déterministes, séparés de l'entraînement, sur des graines différentes de celles vues pendant l'apprentissage.
from stable_baselines3.common.evaluation import evaluate_policy
modele = PPO.load("modeles/ppo_graine_0")
env_eval = gym.make("LunarLander-v2")
env_eval.reset(seed=1000) # graine non vue pendant l'entrainement
moyenne, ecart = evaluate_policy(modele, env_eval, n_eval_episodes=100,
deterministic=True)
print(f"recompense sur 100 episodes : {moyenne:.1f} +/- {ecart:.1f}")
Publier le score déterministe et le score stochastique (deterministic=False) : le premier reflète la politique gloutonne, le second la politique jouée pendant l'entraînement. Un grand écart entre les deux signale une politique très pointue qui échoue quand l'exploration résiduelle la déstabilise.
Enregistrer une vidéo
Une vidéo de 30 secondes vaut mille mots dans un rapport de projet.
from gymnasium.wrappers import RecordVideo
env = RecordVideo(gym.make("LunarLander-v2", render_mode="rgb_array"),
video_folder="videos", episode_trigger=lambda i: True)
obs, _ = env.reset(seed=42)
while True:
action, _ = modele.predict(obs, deterministic=True)
obs, r, termine, tronque, _ = env.step(action)
if termine or tronque:
break
env.close()
Les leçons qui ne se transfèrent pas au monde réel
Un mot final sur les limites, sans lequel un cours de renforcement induit en erreur.
Un pilote entraîné en simulation ne vole pas sur un vrai lunar lander. La physique de Box2D est approchée, sans frottement de gaz réel, sans variation de gravité locale, sans capteurs bruités. Le gouffre entre simulation et réel (sim-to-real) est le principal obstacle pratique du renforcement en robotique. Les méthodes pour le combler — randomisation de domaine, ajout de bruit sensoriel, adaptation en ligne — occupent une littérature à part entière.
De même, une politique optimale sur un scénario particulier est parfois catastrophique quand le scénario change légèrement. Le renforcement optimise exactement l'environnement d'entraînement, sans garantie de généralisation. Un modèle supervisé se dégrade souvent gracieusement hors distribution ; un agent de renforcement peut basculer d'expert à pire qu'aléatoire pour un changement mineur.
Enfin, la reproductibilité reste limitée même avec des graines fixées : les opérations non déterministes sur GPU, l'ordre d'ordonnancement des environnements parallèles, la version de bibliothèque font varier légèrement les courbes. Documenter tout ce qui peut l'être, accepter le reste.
Un rapport qui montre une seule courbe d'entraînement sans intervalle, sans indication de graine, sans version de bibliothèque, et qui rapporte le meilleur épisode plutôt qu'une moyenne d'évaluation, ne prouve rien. Le lecteur ne peut ni reproduire, ni juger si l'écart avec une baseline est significatif ou du bruit. Ce genre de résultat n'est pas seulement peu convaincant : il est activement trompeur.
En résumé
- Cinq graines minimum, courbes en médiane avec intervalles interquartiles, axes en pas d'environnement — pas d'exception à cette règle.
- Évaluer sur des graines non vues, en mode déterministe et stochastique, et publier les deux scores.
- Une vidéo courte accompagne toujours un rapport ; elle révèle ce qu'une courbe cache (comportements dégénérés, hésitations).
- Le gouffre sim-to-real est le principal obstacle du domaine ; une politique optimale en simulation n'est pas transférable telle quelle.
Module suivant : récapitulation complète du cours, carte des algorithmes, et annonce de l'examen final de 40 questions.