Jonathan Suru

Entraîner un agent RL sur une grille 10×10 : environnement, récompenses et curriculum


Dans cet article, je vous présente le projet sur lequel j'ai travaillé ces dernières semaines : un agent d'apprentissage par renforcement évoluant sur une grille de 10×10 cases. Sa mission est simple en apparence rejoindre une noix tout en évitant des murs et un prédateur mais la mise en œuvre d'un apprentissage efficace m'a confronté à plusieurs défis d'ingénierie.

Plutôt que de me concentrer uniquement sur l'algorithme, j'ai exploré trois axes qui se sont révélés tout aussi déterminants : la génération d'environnements garantissant l'existence d'une solution , la conception d'une fonction de récompense qui guide l'agent sans l'enfermer dans des comportements parasites et enfin la mise en place d'un curriculum d'apprentissage pour structurer la progression par étapes.

Le résultat est un agent qui atteint un taux de réussite moyen de 98,2 % sur sept niveaux de difficulté, dont 92 % sur le plus exigeant (prédateur rapide).

L'environnement : poser les bases

Pour commencer, posons les bases de l'environnement dans lequel l'agent va évoluer.

La grille et les règles

L'environnement est une grille carrée de 10×10 cases. J'ai choisi cette taille parce qu'elle offre suffisamment d'espace pour des chemins complexes tout en restant accessible à un agent qui débute. Une grille plus petite (8×8) limite les possibilités de détour, tandis qu'une plus grande (15×15) allonge inutilement l'exploration.

L'agent dispose de quatre actions : haut, bas, gauche, droite. S'il tente de sortir de la grille ou de pénétrer dans un mur, il reste sur place. La cible "une noix" est placée à une distance de Manhattan \( d \) comprise entre 1 et 7. Cette distance est le principal indicateur de difficulté : plus elle est grande, plus j'ajoute de murs sur le chemin.

À partir du niveau 5, un prédateur entre en jeu. Sa règle de déplacement est simple : à chaque pas, il se déplace d'une case vers l'agent avec une probabilité qui définit sa vitesse. Il est immobile au niveau 5, lent au niveau 6, et rapide au niveau 7. L'épisode se termine sur un succès (la noix est atteinte), une capture (le prédateur rejoint l'agent), ou un timeout après 400 pas.

L'observation

Au-delà des règles de déplacement, la manière dont l'agent perçoit le monde est tout aussi cruciale. Pour qu'il puisse interpréter son environnement, je lui fournis une carte à quatre canaux de dimensions (10, 10, 4) :

Ce canal séparé pour le prédateur est un détail qui m'a coûté plusieurs heures de débogage. Je l'avais initialement fusionné avec les murs, et l'agent ne parvenait pas à distinguer un obstacle fixe d'une menace mobile. Il fonçait alors droit sur le prédateur, sans comprendre pourquoi il se faisait capturer.

Générer des niveaux garantis résolubles

Maintenant que l'observation est définie, il faut s'assurer que les cartes générées ne soient pas des pièges sans issue. Lorsqu'on génère des murs aléatoirement, une fraction non négligeable des niveaux est impossible à résoudre : l'agent peut se retrouver enfermé, ou la noix inaccessible. Confronter l'agent à l'insoluble est un problème d'équité, car même une politique parfaite échouerait.

Pour garantir la résolubilité, j'ai adopté une approche constructive. À chaque génération, je trace d'abord un chemin en L entre le départ et la noix. Ce chemin consiste à avancer horizontalement jusqu'à la colonne de la noix, puis verticalement jusqu'à sa ligne.

J'interdis ensuite aux murs de s'installer sur ce couloir. Je protège également les cases adjacentes au départ et à la noix, pour éviter que l'agent ne soit bloqué dès son premier mouvement ou que la cible soit scellée.

Le prédateur est placé hors de ce chemin, sur une case libre à au moins trois cases du départ et de la noix. Si aucune cellule ne satisfait cette contrainte, je le positionne sur le chemin en L lui-même. Cette construction garantit qu'une politique triviale "suivre le couloir" mène toujours au succès.

La fonction de récompense : un équilibre itératif

Une fois les niveaux garantis résolubles, reste à définir le principal moteur de l'apprentissage : la récompense. La conception de la récompense a été l'étape la plus longue. Chaque version révélait un nouveau comportement parasite, que je corrigeais en ajoutant un signal spécifique.

Éviter l'immobilité

Commençons par la première difficulté rencontrée. Ma première idée fut un façonnage symétrique :

\[ r = \alpha \cdot (d_{\text{old}} - d_{\text{new}}) \]

L'idée était de récompenser le rapprochement et de pénaliser l'éloignement. Pourtant, l'agent est resté immobile. Pourquoi ? Pour contourner un mur, il faut parfois s'éloigner temporairement de la noix. L'agent calculait que ce détour coûtait une pénalité, et préférait donc l'inaction.

Le façonnage unilatéral

Ce constat m'a conduit à une seconde itération. J'ai alors rendu le façonnage unilatéral :

\[ r_{\text{progress}} = \alpha \cdot \max(0, d_{\text{old}} - d_{\text{new}}) \]

L'agent s'est remis à bouger, mais il passait son temps à heurter les murs, car ces collisions ne généraient aucun signal négatif distinct.

Des signaux pour chaque comportement

Toutefois, ce correctif en a fait émerger un autre. J'ai donc enrichi la récompense avec des pénalités spécifiques :

\[ r = -\beta_{\text{temps}} - \beta_{\text{mur}} \cdot \mathbb{1}_{\text{wall}} + \alpha \cdot \max(0, d_{\text{old}} - d_{\text{new}}) - \beta_{\text{backtrack}} \cdot \mathbb{1}_{\text{backtrack}} + r_{\text{predator}} \]

Avec :

Pour le prédateur, j'ai ajouté deux mécanismes. Le premier est un signal d'évitement actif :

\[ r_{\text{avoid}} = 0.25 \cdot (d_{\text{pred}}^{\text{new}} - d_{\text{pred}}^{\text{old}}) \]

L'agent est récompensé lorsqu'il s'éloigne du danger. Le second est une zone d'alerte : lorsque la distance au prédateur devient inférieure ou égale à 3, une pénalité progressive est appliquée :

\[ r_{\text{proximity}} = -(4 - d_{\text{pred}}) \cdot 0.25 \]

Cette pénalité précoce est essentielle : elle apprend à l'agent à anticiper la capture plutôt qu'à la subir.

Comment choisir ces coefficients ?

Mais comment choisir ces valeurs ? Les coefficients 0.2, 0.3, 0.25 résultent d'un arbitrage empirique. La pénalité de mur doit être plus forte que le bonus d'un pas de progression (sinon l'agent préfère traverser les murs). La récompense de succès (+10) doit écraser toutes les pénalités pour encourager l'exploration. Le coût temporel est juste assez fort pour éviter les longues errances. C'est un équilibre que je n'ai trouvé qu'en observant les courbes d'apprentissage.

L'algorithme PPO : acteur, critique et stabilité

Passons maintenant à l'algorithme qui fait tourner tout cela. J'ai choisi PPO pour sa stabilité réputée. Mais pour comprendre son fonctionnement, il faut d'abord saisir l'architecture qui le sous-tend.

Architecture acteur-critique

Faisons d'abord un point sur l'architecture. PPO appartient à la famille des méthodes acteur-critique. Ces deux rôles sont distincts.

L'acteur est le réseau qui prend les décisions. Il reçoit l'observation et produit une distribution de probabilités sur les actions. Le critique est un évaluateur : il estime la valeur de l'état, c'est-à-dire le total des récompenses futures espérées :

\[ V(s) = \mathbb{E} \left[ \sum_{t=0}^{\infty} \gamma^t r_t \mid s_0 = s \right] \]

Le critique permet à l'acteur de savoir si une action était meilleure ou moins bonne que prévu. Concrètement, les deux réseaux partagent un tronc commun (trois couches de convolution (64, 128, 128 filtres) suivies d'une couche dense de 256 neurones) puis se divisent en deux têtes.

Le clipping : pourquoi PPO est stable

Pourquoi PPO est-il particulièrement stable ? La réponse tient en un mot : le clipping. Le ratio de vraisemblance mesure le changement de politique :

\[ r_t(\theta) = \frac{\pi_\theta(a_t | s_t)}{\pi_{\text{old}}(a_t | s_t)} \]

L'objectif s'écrit alors :

\[ L^{\text{CLIP}}(\theta) = \mathbb{E}_t \left[ \min\left( r_t(\theta) \hat{A}_t,\; \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right] \]

Avec \( \epsilon = 0.2 \). Si le ratio s'éloigne trop de 1, il est rogné entre 0,8 et 1,2. L'agent peut s'améliorer, mais jamais brutalement. C'est ce frein qui évite les effondrements de performance.

L'estimation de l'avantage (GAE)

En complément du clipping, il faut une estimation fine de la qualité des actions. L'avantage généralisé permet de regarder au-delà de la récompense immédiate. On définit d'abord l'erreur temporelle :

\[ \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) \]

L'avantage est une somme pondérée de ces erreurs :

\[ \hat{A}_t = \sum_{l=0}^{T-t-1} (\gamma \lambda)^l \delta_{t+l} \]

Avec \( \gamma = 0.99 \) et \( \lambda = 0.95 \). Le GAE permet à l'agent de comprendre qu'un détour, bien que coûteux sur le moment, est un investissement payant à long terme.

La perte totale

Ces différents termes sont finalement rassemblés dans une perte unique. La perte totale combine trois composantes :

\[ L_{\text{total}} = L^{\text{CLIP}} + c_1 \cdot L_{\text{critic}} - c_2 \cdot H(\pi_\theta) \]

Où \( L_{\text{critic}} \) est l'erreur du critique (loss de Huber), \( H(\pi_\theta) \) l'entropie de la politique (qui encourage l'exploration), et \( c_1 = 0.5 \), \( c_2 = 0.01 \).

Le curriculum : structurer la progression

L'algorithme étant posé, voyons maintenant comment structurer l'apprentissage dans le temps. Confronter un agent à un prédateur rapide dès le départ est une erreur : il n'a pas encore acquis les bases de la navigation. J'ai donc organisé l'apprentissage en sept niveaux de difficulté croissante.

Les niveaux

Pour cela, j'ai défini une progression en plusieurs étapes :

NiveauMursVitesse du prédateur
D1-D4Densité croissanteAucun (0)
D5ÉlevéeImmobile (0)
D6ÉlevéeLent (0.5)
D7ÉlevéeRapide (1.0)

Chaque niveau possède ses propres seuils de validation. Les niveaux D1 à D4 permettent d'acquérir la navigation de base. Le niveau D5 introduit le prédateur mais sans mouvement, pour que l'agent apprenne à le contourner. Les niveaux D6 et D7 ajoutent progressivement la dimension temporelle.

L'évaluation greedy

Mais comment savoir si l'agent est prêt à passer à l'étape suivante ? La décision de passer au niveau supérieur ne s'appuie jamais sur le taux de réussite pendant l'exploration. Un succès obtenu par hasard (actions aléatoires) ne prouve rien. J'évalue systématiquement l'agent sur sa politique déterministe (greedy) : il choisit toujours l'action qu'il estime être la meilleure, sans aucune exploration.

Un niveau est validé si :

  1. Le taux de succès greedy dépasse le seuil propre au niveau (STAGE_THRESHOLD, par exemple 0.8 pour D7) ;
  2. Cette performance est maintenue pendant deux évaluations consécutives (pour éviter les coups de chance) ;
  3. L'agent a effectué un nombre minimal de mises à jour (STAGE_MIN_UPDATES) pour laisser le temps à la politique de converger.

L'exigence de deux réussites consécutives est une précaution que j'ai ajoutée après avoir observé des progressions suivies d'effondrements. Elle filtre les épisodes de performance aléatoire.

La régression

Cependant, il faut aussi prévoir le cas où l'agent est en difficulté. Si le taux de succès greedy tombe en dessous de 15 % (regress_threshold = 0.15) après le nombre minimal de mises à jour, l'agent redescend d'un niveau. J'ai mis en place ce mécanisme après avoir constaté des stagnations prolongées : l'agent restait bloqué sur un niveau trop difficile, sans progresser. La régression agit comme un filet de sécurité qui lui permet de reprendre confiance sur un terrain plus familier.

Cette valeur de 15 % n'est pas arbitraire. En dessous de ce seuil, l'agent n'a manifestement pas compris la mécanique du niveau, et le maintenir plus longtemps ne fait que renforcer des comportements erronés. Un recul temporaire est plus efficace qu'un entêtement.

L'anti-oubli

Au-delà de la montée et de la descente, un autre problème se pose : l'oubli. Même lorsque l'agent atteint le niveau 7, 25 % des environnements en parallèle rejouent un niveau inférieur, tiré aléatoirement entre D1 et le niveau courant.

Ce mécanisme d'anti-oubli est inspiré des techniques de replay en RL. En se concentrant exclusivement sur le prédateur rapide, l'agent risque d'oublier les compétences de base (naviguer dans des couloirs simples). En rejouant occasionnellement les anciens niveaux, il consolide ses acquis. C'est une forme de régularisation qui stabilise l'apprentissage sur le long terme.

Paramètres du curriculum

Enfin, pour que ce système fonctionne, certains paramètres doivent être calibrés. Les seuils et les durées minimales sont définis par niveau dans une configuration dédiée.

Ces valeurs ont été ajustées empiriquement. Les niveaux inférieurs ont des seuils bas car l'objectif est de progresser rapidement. Les niveaux supérieurs exigent une maîtrise plus solide avant de passer à la suite. Le nombre minimal de mises à jour augmente avec la difficulté, car les comportements complexes (comme l'évitement actif) nécessitent plus de temps pour converger.

Résultats et performance

Toute cette mécanique repose sur un outil qui change la donne : JAX. J'ai écrit tout le code en JAX pour bénéficier de la vectorisation. Grâce à jax.vmap, une fonction conçue pour un seul environnement s'applique à un lot. L'état de chaque environnement est stocké dans un objet dont chaque champ a une première dimension N.

Sur un GPU AMD MI300X, je fais tourner 128 environnements en parallèle. Chaque pas est calculé simultanément. Le gain est spectaculaire : sans vectorisation (CPU, boucle Python), chaque entraînement prendrait plusieurs heures. Avec le GPU, la même charge ne demande que quelques secondes. Ce gain n'est pas un luxe : c'est une condition nécessaire à l'expérimentation. Sans cette rapidité, ajuster les coefficients de récompense ou les seuils du curriculum aurait été prohibitif.

Résultats quantitatifs

Après 550 mises à jour PPO, voici les taux de succès mesurés sur la politique greedy pour chaque niveau :

Taux de succès de la politique greedy par niveau au cours de l'entraînement.

Taux de succès de la politique greedy par niveau au cours de l'entraînement.

Analyse des résultats

L'agent est parfait sur les niveaux sans prédateur (D1 à D4) dès la 300e mise à jour. Sur les niveaux D5 et D6, il atteint respectivement 97 % et 98 % en fin d'entraînement. Le niveau le plus difficile, D7 (prédateur rapide), plafonne à 92 %.

Le moment le plus marquant se situe entre les mises à jour 125 et 250. Sur le niveau D3 (premiers murs significatifs), le taux de succès passe brutalement de 52 % à 99 %. Cette transition coïncide exactement avec le moment où l'agent intègre la pénalité de collision : il cesse de taper dans les murs.

En observant les trajectoires, l'agent rejoint la noix en 6 à 12 pas, en maintenant une distance de sécurité d'environ 2 à 3 cases face au prédateur. Il n'hésite pas à faire des détours pour l'éviter, ce qui montre que le signal de zone d'alerte (pénalité à 3 cases) a bien été intégré.

Enseignements et perspectives

Ce projet m'a confirmé trois principes :

  1. Un environnement doit toujours être résoluble. Construire un chemin garanti est un investissement nécessaire. Si le niveau est impossible, l'agent n'a aucune chance, et l'apprentissage est condamné.
  2. Les récompenses se découvrent en observant l'agent. Chaque pénalité ajoutée est la réponse à un comportement parasite que j'ai constaté. C'est un dialogue permanent entre le concepteur et l'agent, où les courbes d'apprentissage servent de retour.
  3. La progression par étapes, la régression, l'anti-oubli et les seuils par niveau sont des garde-fous indispensables. La vectorisation GPU est ce qui rend l'expérimentation possible.

Conclusion

Ce projet m'a permis de vérifier qu'un algorithme classique comme PPO, associé à un environnement bien conçu, des récompenses discriminantes et un curriculum progressif, suffit à résoudre une tâche qui semblait d'abord difficile. L'agent atteint 98,2 % de réussite en moyenne, et 92 % sur le niveau le plus exigeant.

Trois enseignements se dégagent : la résolubilité des niveaux est un prérequis, les récompenses se découvrent en observant les comportements parasites de l'agent, et le curriculum (progression, régression, anti-oubli) n'est pas une option. La vectorisation GPU, quant à elle, est ce qui rend l'expérimentation itérative possible.

Pour les lecteurs qui souhaitent approfondir les aspects techniques (équations complètes du GAE, détails d'implémentation JAX, hyperparamètres précis et figures d'apprentissage) le rapport complet est disponible sur mon dépôt GitHub.

Liens Utiles

Pour approfondir vos connaissances et explorer des outils avancés, voici quelques ressources :

Ma recommandation musicale du jour : à écouter sans modération !

Écouter sur YouTube