Neuro-evolution – Apprentissage supervisé dans Godot Engine
En IA, au-delà de l’apprentissage supervisé classique il existe la neuro-evolution. Voilà un article que j’aurais aimé publier il y a longtemps. Pour tout vous dire, l’ébauche trainait dans mes brouillons depuis 2020. Mais bon… stuff happened 🏳️⚧️
Petit disclaimer avant de plonger plus dans le sujet. Bien que la base soit similaire, matrice et réseau de neurones, ce projet n’a rien à voir avec les IA génératives que l’on connait aujourd’hui. J’aime bien expérimenter et créer des petits « toy projects » afin de comprendre comment fonctionnent certaines technologies. Mais je reste profondément contre la manière dont l’IA générative est utilisée aujourd’hui, pour plein de raisons évidentes que je ne vais pas détailler ici.
Dans mes précédents articles, je vous avais expliqué le fonctionnement du perceptron et des réseaux de neurones, notamment comment créer son réseau à partir d’un jeu de données. Mais dans certaines situations, il est aussi possible d’entrainer un modèle sans données au préalable. Pour cela, on va utiliser des algorithmes évolutionnistes, dont une des techniques est la neuro-evolution.
La méthode est beaucoup plus simple qu’il n’y parait. L’idée est de faire émerger des modèles à partir d’une sorte de sélection naturelle. On démarre avec des modèles aléatoires qu’on met en compétition dans une simulation. À chaque round, nous classerons les meilleures IA pour créer de nouvelles variations enfants, soit par mutation, soit par reproduction. Et nous répétons la simulation ainsi de suite.
Ce sera peut-être plus parlant avec un exemple concret. J’aimerais apprendre à des voitures à conduire automatiquement sur un circuit en 2D. À chaque manche, elles seront en compétition, et celles qui seront allées le plus loin ou qui auront fait le tour le plus rapidement seront sélectionnées pour les manches suivantes.

Pour commencer, nous allons devoir créer notre voiture et son « pilote ». Elle aura un certain nombre de capteurs (ici des raycasts) qui lui permettront de détecter la distance à laquelle elle se trouve d’un mur. Ce seront les entrées que nous passerons à notre réseau de neurones. Je réutilise la class Neural Network que j’avais créée pour mes précédents articles.

Les valeurs en sortie du réseau simuleront les inputs, comme si un joueur appuyait sur les boutons d’accélération et de direction de sa manette.
var inputs = nn.predict([
get_distance($Raycasts/Left),
get_distance($Raycasts/UpLeft),
get_distance($Raycasts/UpLeft2),
get_distance($Raycasts/Up),
get_distance($Raycasts/UpRight),
get_distance($Raycasts/UpRight2),
get_distance($Raycasts/Right),
velocity.length() / max_speed
])
var left_pressed: bool = inputs[0] > 0.5
var up_pressed: bool = inputs[1] > 0.5
var right_pressed: bool = inputs[2] > 0.5
Il nous faut maintenant un moyen d’évaluer la performance de nos bolides. Pour cela, chaque voiture se voit attribuer un score selon un certain nombre de critères de réussite.
- Les voitures gagnent des points à chaque checkpoint passé.
- Les checkpoints doivent être passés dans l’ordre.
- Les voitures ont un bonus de points si elles font le tour complet du circuit.
- Si une voiture touche un mur, elle est éliminée.
- Si une voiture est trop lente à passer le prochain checkpoint, elle est éliminée.
- Les premiers véhicules à passer un checkpoint ou la ligne d’arrivée gagnent un bonus de points.
Première étape: on fait spawn une série de voitures avec un réseau aléatoire. Elle concourront en simultané sur le circuit, mais pour simplifier elles n’auront pas de collision entre elles. À la fin de chaque round on les classe selon leurs scores. Les meilleures sont gardées et on crée des enfants à partir d’elles. Concrètement cela s’inspire de la manière dont on hérite des gènes de nos parents. On prend des valeurs aléatoires du réseau de chaque parent pour créer un réseau enfant. On va également créer quelques enfants avec de légères mutations dans leur réseau.
var child = NeuralNetwork.reproduce(parent1, parent2)
var child2 = NeuralNetwork.mutate(car, Callable(self, "mutate"))
À partir de quelques rounds, il y aura une voiture qui va comprendre comment tourner à droite, puis à gauche, une qui accélérera plus fort et ira plus vite, etc.
La manière dont on va évaluer les performances est cruciale pour l’entrainement de nos IA. Si nos critères sont trop vagues ou mal pensés, les modèles pourraient juste ne pas évoluer ou même apprendre à tricher pour gagner plus de points. Par exemple imaginez qu’on ait défini le bonus de points si la voiture passe la ligne d’arrivée en oubliant de la contraindre à passer tous les checkpoints avant. L’IA pourrait juste faire une petite marche arrière et passer l’arrivée.
Maintenant qu’on a cette simulation, on pourrait aller beaucoup plus loin et imaginer plein d’autres situations. Par exemple leur faire apprendre le code de la route et les faire rouler dans le trafic. Mais je pense que pour ce projet on va en rester là^^
Je vous partage encore le projet de SethBling qui m’avait inspiré celui-ci. MarI/O est un programme qui permet à Mario d’apprendre automatiquement à passer les niveaux. C’est un peu plus complexe que mon exemple, car contrairement à mon approche où la structure du réseau est fixe, son programme permet de faire évoluer non seulement les poids mais aussi la topologie du réseau grâce aux réseaux NEAT (Evolving Neural Networks through
Augmenting Topologies). Si cela vous intéresse je vous invite à lire ce papier du MIT.
Comme d’habitude vous trouverez les sources de mon projet sur GitHub.
N’hésitez pas à lancer la simulation et à jouer avec le projet, je trouve toujours fascinant de voir ces petites voitures apprendre toutes seules.
Merci de m’avoir lu 🩷