Jeu de grille 10×10 en Python/pygame servant de terrain d'expérimentation pour l'apprentissage par renforcement (Reinforcement Learning). Un personnage doit rejoindre une sortie en évitant les obstacles. L'objectif est d'entraîner un agent IA capable de trouver seul le chemin optimal — y compris sur des terrains qu'il n'a jamais vus.
| Librairie | Version | Rôle |
|---|---|---|
| pygame | 2.6.1 | Affichage de la grille, sprites PNG, gestion des événements clavier/souris |
| torch | 2.12.0+cpu | Calcul tensoriel, réseaux de neurones DQN (MLP, FiLM, CNN) |
| stable-baselines3 | ≥ 2.3.0 | Implémentation de l'algorithme PPO, gestion des environnements vectorisés |
| gymnasium | ≥ 0.29.0 | Interface standard reset() / step() pour les environnements RL |
| numpy | ≥ 1.24.0 | Encodage des observations, calculs vectoriels |
| pytest | 8.3.5 | Tests unitaires (362 tests, 0 échec) |
| pytest-cov | 6.1.0 | Mesure de la couverture de code |
| pyinstaller | 6.13.0 | Compilation en exécutable Windows autonome (dungeon.exe) |
DQN est un algorithme off-policy : l'agent mémorise ses expériences dans un replay buffer et apprend en rejouant des transitions passées. À chaque mise à jour, les poids du réseau peuvent être fortement modifiés.
Problème rencontré : quand plusieurs terrains différents sont utilisés, deux seeds peuvent exiger des actions opposées dans des états visuellement similaires. DQN écrase les poids appris pour un seed en apprenant un autre → catastrophic forgetting systématique au-delà de 3 seeds.
Résultat : excellent sur seed unique (score 100 en <2000 épisodes), inutilisable en généralisation.
PPO est un algorithme on-policy avec une contrainte de stabilité : le paramètre clip_range limite l'écart entre l'ancienne et la nouvelle politique à chaque mise à jour. L'agent ne peut pas "désapprendre" brusquement.
Pourquoi PPO résout le catastrophic forgetting : les mises à jour bornées préservent les politiques déjà acquises tout en permettant l'amélioration progressive. Sur un pool de 10 seeds, PPO atteint 89% de win rate là où DQN s'effondre à 0%.
Réseau dense entièrement connecté. La grille 10×10 est aplatie en un vecteur de 300 valeurs (one-hot par case × 3 types), auxquelles s'ajoutent 4 positions normalisées → 304 entrées.
Entrée (304) → Dense(256) → ReLU → Dense(128) → ReLU → Dense(64) → ReLU → Sortie (4 Q-values)
Limite : chaque position de la grille utilise des poids différents. Apprendre "obstacle en (2,3) → tourner" ne transfère pas à "obstacle en (7,8) → tourner". Nécessite des milliers de seeds différents pour couvrir l'espace des configurations.
- Task-conditioning : le seed est encodé en one-hot (10 bits) et concaténé à l'observation → 314 entrées. Le réseau "triche" en mémorisant une politique par seed plutôt qu'en apprenant à naviguer.
- FiLM (Feature-wise Linear Modulation) : le seed module chaque couche cachée via des paramètres
gammaetbetaappris. Plus stable que le task-conditioning mais même limitation fondamentale.
Ces deux approches améliorent les performances sur seeds connus mais ne généralisent pas aux seeds inconnus.
La grille est encodée en tenseur spatial 10×10×5 (5 canaux : herbe, roche, eau, personnage, sortie). Des filtres convolutionnels glissent sur toute la grille avec les mêmes poids partout.
Entrée (10×10×5)
→ Conv2D(5→16 filtres, noyau 3×3) → ReLU [sortie : 8×8×16]
→ Conv2D(16→32 filtres, noyau 3×3) → ReLU [sortie : 6×6×32]
→ Flatten (1 152 valeurs)
→ Dense(128) → ReLU
→ Dense(64) → ReLU
→ Sortie (4 Q-values)
Avantage : un filtre "obstacle à droite" apprend à reconnaître un obstacle en n'importe quelle position de la grille. Vu une fois en (2,3), généralisé automatiquement en (7,8).
Résultat CNN + PPO + full-random (état actuel) :
| Mode évaluation | Seeds entraînement | Seeds inconnus |
|---|---|---|
| Déterministe | ~8% | 73.0% |
| Stochastique ×3 | — | 85.8% |
Le modèle généralise sur des terrains jamais vus, avec un score moyen de 95/100 sur les victoires en déterministe (chemin quasi-optimal).
| Approche | Résultat | Statut |
|---|---|---|
| Seed unique | Convergence rapide (<2000 ep) | ✅ fonctionne |
| Pool fixe de seeds + curriculum | 59% max, forgetting au-delà de pool3 | ✅ testé, limité |
| Pool fixe + PPO | 76% training, 3% inconnus | ✅ testé |
| Full random (seed=None) + PPO + CNN | 73.0% inconnus déterministe | ✅ retenu |
La clé de la généralisation n'était ni l'architecture ni l'algorithme seuls, mais la diversité maximale des terrains combinée au CNN. En voyant un terrain différent à chaque épisode, l'agent ne peut pas mémoriser — il doit apprendre une vraie stratégie de navigation.
Tous les scripts s'exécutent depuis la racine du projet avec
.venv\Scripts\python.exe. Python requis : 3.12 (testé et validé sur Python 3.12.x).
.venv\Scripts\python.exe src\main.pyEntraînement via Stable-Baselines3. Produit des checkpoints .zip et des logs .jsonl. Recommandé pour la généralisation multi-seeds.
.venv\Scripts\python.exe src\train_ppo.py [options]| Paramètre | Type | Défaut | Description |
|---|---|---|---|
--timesteps N |
int | 500 000 | Nombre total de pas d'environnement |
--seed N |
int | None | Seed fixe — même terrain à chaque épisode |
--seed-pool 0-99 |
str | None | Pool de seeds. Accepte 0,1,2 ou plage 0-99. Sans ce paramètre ni --seed : full random (seed=None, terrain différent à chaque épisode) |
--lr 1e-4 |
float | 3e-4 | Learning rate. |
--n-envs N |
int | 1 | Nombre d'environnements parallèles (accélère l'entraînement, recommandé : 8) |
--pretrained path |
Path | None | Checkpoint .zip SB3 de départ (transfer learning) |
--architecture |
str | mlp |
Architecture réseau : mlp (304 entrées) ou cnn (tenseur 10×10×5, recommandé) |
La configuration qui a donné les meilleurs résultats de généralisation est PPO + CNN + seeds aléatoires :
# Premier run — from scratch
.venv\Scripts\python.exe src\train_ppo.py ^
--timesteps 5000000 ^
--architecture cnn ^
--n-envs 8 ^
--lr 0.0001
# Runs suivants — continuation depuis le dernier checkpoint
.venv\Scripts\python.exe src\train_ppo.py ^
--timesteps 5000000 ^
--architecture cnn ^
--pretrained "models\{ts}_run\{run}\final.zip" ^
--n-envs 8 ^
--lr 0.0001Pourquoi cette configuration ?
- Pas de
--seedni--seed-pool(full random) : un terrain complètement nouveau est généré à chaque épisode. L'agent ne peut donc pas mémoriser les solutions — il est forcé d'apprendre une vraie stratégie de navigation générale. Avec un pool fixe, il "apprend par cœur" et ne généralise pas aux terrains inconnus. --architecture cnn: le CNN partage ses filtres sur toute la grille, ce qui lui permet de reconnaître les obstacles et les chemins quelle que soit leur position.--n-envs 8: 8 environnements tournent en parallèle, ce qui multiplie par ~8 la vitesse de collecte d'expériences.--lr 0.0001: un learning rate faible (3× plus petit que le défaut) assure des mises à jour stables et évite d'écraser ce qui a déjà été appris lors des runs précédents.
Il faut enchaîner plusieurs runs : chaque run de 5M timesteps prend environ 40–60 minutes et améliore les performances de ~10 pts. L'entraînement converge progressivement par accumulation :
| Run | Timesteps cumulés | Win rate déterministe (seeds inconnus) |
|---|---|---|
| Run 1 (from scratch) | 5M | 10.5% |
| Run 2 | 10M | 56.0% |
| Run 3 | 15M | 66.5% |
| Run 4 | 20M | 73.0% |
Entraînement headless par Q-learning. Produit des checkpoints .pt et des logs .jsonl.
.venv\Scripts\python.exe src\train.py [options]| Paramètre | Type | Défaut | Description |
|---|---|---|---|
--episodes N |
int | 3000 | Nombre d'épisodes d'entraînement |
--seed N |
int | None | Seed fixe — même terrain à chaque épisode |
--seed-pool 0,1,2 |
str | None | Pool de seeds tirés aléatoirement. Accepte 0,1,2 ou plage 0-9 |
--lr 3e-4 |
float | 1e-3 | Learning rate de l'optimiseur Adam |
--pretrained path |
Path | None | Checkpoint .pt de départ (transfer learning) |
--architecture |
str | film |
Architecture réseau : obs (304 entrées), taskcond (314 entrées), film (FiLM conditioning) |
Exemple :
.venv\Scripts\python.exe src\train.py --episodes 3000 --seed 42 --architecture filmSorties :
logs/{ts}_run/{ts}_{label}_ep{N}.jsonl— log JSON (une ligne par épisode + ligne meta)models/{ts}_run/{ts}_{label}_ep{N}/ep<N>.pt— checkpoints tous les 500 épisodesmodels/{ts}_run/{ts}_{label}_ep{N}/final.pt— poids finaux
Élargit progressivement le pool de seeds par étapes. Passe à l'étape suivante quand le win rate cible est atteint.
.venv\Scripts\python.exe src\curriculum.py [options]| Paramètre | Type | Défaut | Description |
|---|---|---|---|
--pool 0,1,...,9 |
str | obligatoire | Ensemble de seeds disponibles |
--stages 1,3,6,10 |
str | 1,3,6,10 |
Nombre de seeds par étape (progression) |
--max-episodes-per-stage N |
int | 2000 | Plafond d'épisodes par étape si le seuil n'est pas atteint |
--win-rate-threshold 0.8 |
float | 0.8 | Taux de victoire cible (sur 100 derniers épisodes) pour avancer |
--lr 3e-4,1e-4 |
str | 1e-3 |
Learning rate par étape (liste séparée par virgules, dernière valeur répétée) |
--pretrained path |
str | None | Checkpoint .pt de départ |
--architecture |
str | film |
Architecture réseau : obs, taskcond, film |
Exemple :
.venv\Scripts\python.exe src\curriculum.py ^
--pool 0,1,2,3,4,5,6,7,8,9 ^
--stages 1,3,6,10 ^
--max-episodes-per-stage 2000 ^
--win-rate-threshold 0.8 ^
--lr 3e-4,1e-4 ^
--architecture filmlogs/
└── {ts}_run/
└── {ts}_{label}_ts{N}[_from_{ts}].jsonl ← log JSON, une ligne par épisode + ligne meta
models/
└── {ts}_run/
└── {ts}_{label}_ts{N}[_from_{ts}]/
├── ppo_50000_steps.zip ← checkpoint PPO toutes les 50k steps
├── ppo_100000_steps.zip
├── ...
└── final.zip ← poids finaux
Le suffixe _from_{ts} est automatiquement ajouté quand --pretrained est utilisé, permettant de tracer la chaîne de transfer learning.
Mesure les performances d'un checkpoint sur un ensemble de seeds (connus ou inconnus).
.venv\Scripts\python.exe analyze\evaluate.py [options]| Paramètre | Type | Obligatoire | Description |
|---|---|---|---|
--checkpoint path |
Path | ✅ | Chemin vers le checkpoint .pt (DQN) ou .zip (PPO) |
--seeds 100-299 |
str | ✅ | Seeds à évaluer. Accepte plage 100-299 ou liste 0,1,2 |
--n-episodes N |
int | Non (1) | Nombre d'épisodes par seed. Utile en mode stochastique pour réduire la variance |
--stochastic |
flag | Non | Mode stochastique PPO (deterministic=False). Ignoré pour DQN |
--verbose |
flag | Non | Affiche le détail par seed (win/loss + score) |
Mode déterministe vs stochastique :
- Déterministe : l'agent choisit toujours l'action de probabilité maximale. Mesure ce que le modèle sait faire.
- Stochastique : l'agent tire aléatoirement selon ses probabilités. Peut débloquer des situations difficiles mais les scores sont plus bas.
Exemples :
# Évaluation déterministe sur 200 seeds inconnus
.venv\Scripts\python.exe analyze\evaluate.py ^
--checkpoint "models\{ts}_run\{run}\final.zip" ^
--seeds 100-299
# Évaluation stochastique, 3 épisodes par seed
.venv\Scripts\python.exe analyze\evaluate.py ^
--checkpoint "models\{ts}_run\{run}\final.zip" ^
--seeds 100-299 ^
--n-episodes 3 ^
--stochastic
# Évaluation sur seeds d'entraînement avec détail par seed
.venv\Scripts\python.exe analyze\evaluate.py ^
--checkpoint "models\{ts}_run\{run}\final.zip" ^
--seeds 0-99 ^
--verboseCalcule le win rate max et final (fenêtre glissante 100 épisodes) pour chaque run DQN dans logs/. Permet de comparer les architectures et stratégies.
.venv\Scripts\python.exe analyze\analyze_runs.pyAucun paramètre. Affiche un tableau comparatif de tous les runs détectés.
Scanne les seeds 0–2999 et calcule pour chacun le coût Dijkstra, le détour rochers, les cases eau traversées. Utile pour sélectionner des seeds pédagogiquement intéressants pour un curriculum.
.venv\Scripts\python.exe analyze\search_seeds.pyAucun paramètre. Affiche 20 seeds candidats répartis en 5 groupes de difficulté (facile → difficile).
.venv\Scripts\python.exe -m pytest tests\ -v362 tests couvrant : Grid, GameState, PathFinder, DungeonEnv, DQNAgent, ReplayBuffer, curriculum, exploit, evaluate, train_ppo (MLP et CNN).
.venv\Scripts\python.exe -m PyInstaller src\main.py --onefile --noconsole --name dungeonProduit dist\dungeon.exe — autonome, ne nécessite pas Python installé.
Lancer avec .venv\Scripts\python.exe src\main.py.
┌─────────────────────────────────────────────────────┐ ← HUD (108px)
│ Déplacements : 0 Note : 0 Information : │ Ligne 1 — stats
│ [Génération terrain] Seed: [____] Trail 0/0 ... │ Ligne 2 — terrain + seed + stats IA
│ [IA simple model] [IA multi model] [IA restart] │ Ligne 3 — boutons IA
│ ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ Ligne 4 — barre de chargement
├─────────────────────────────────────────────────────┤
│ │
│ Grille 10×10 (800×800 px) │ ← Terrain de jeu
│ │
└─────────────────────────────────────────────────────┘
| Élément | Apparence | Description |
|---|---|---|
| Herbe | Tuile verte (grass.png) | Case franchissable, coût 1 déplacement |
| Roche | Tuile grise (rock.png) | Obstacle infranchissable. Tenter d'y entrer coûte 1 déplacement sans bouger |
| Eau | Tuile bleue (water.png) | Case franchissable, coût 2 déplacements |
| Personnage | Guerrier pixel art (player.png) | Position courante du joueur |
| Sortie | Porte (castle.png) | Objectif à atteindre |
| Trail jaune | Trait centré | Chemin parcouru par le joueur (affiché en continu) |
| Trail rouge | Trait décalé à gauche (−5px) | Chemin optimal Dijkstra (affiché uniquement en fin de partie) |
| Trail orange | Trait décalé à droite (+5px) | Chemin(s) joué(s) par l'IA |
| Champ | Description |
|---|---|
| Déplacements | Nombre de déplacements accumulés (herbe=1, eau=2, choc mur=1) |
| Note | Score final 0–100. round(100 × coût_optimal / coût_joueur). 100 = chemin optimal |
| Information | Vide pendant la partie, affiche GAGNE à l'arrivée |
| Touches (gris) | Rappel des contrôles clavier |
| Élément | Interaction | Description |
|---|---|---|
| [Génération terrain] | Clic | Génère un nouveau terrain aléatoire solvable (Dijkstra vérifie qu'un chemin existe) |
| Seed : [champ] | Clic + saisie numérique + Entrée | Génère le terrain correspondant au seed saisi (reproductible) |
| Trail X/N | Lecture seule | Progression de l'animation IA (mode multi uniquement) |
| Victoires : Y/N | Lecture seule | Nombre d'épisodes IA gagnés / total chargés |
| Note moy : ZZ | Lecture seule | Score moyen sur tous les épisodes IA (échecs = 0 inclus) |
| Bouton | Description |
|---|---|
| [IA simple model] | Ouvre un sélecteur de fichier .pt (DQN) ou .zip (PPO). Joue un épisode complet en mode déterministe. Affiche le trail orange + chemin optimal rouge en fin de partie |
| [IA multi model] | Ouvre un sélecteur de dossier *_run/. Charge tous les checkpoints du run en thread de fond, puis anime les trails un par un (200ms/trail). Barre de chargement en ligne 4 |
| [IA restart] | Efface les trails orange et rouge, puis rejoue tous les modèles chargés sur le terrain courant depuis le cache (sans relire le disque). Affiche "Calcul..." pendant le recalcul |
Visible uniquement pendant le chargement multi-model. Indique la progression du chargement des checkpoints.
| Touche | Action |
|---|---|
| ← ↑ → ↓ | Déplace le personnage d'une case dans la direction indiquée |
| R | Génère un nouveau terrain aléatoire (équivalent au bouton [Génération terrain]) |