Aller au contenu
coden

Neuroévolution : faire évoluer les poids et la forme des réseaux de neurones, et l’algorithme NEAT

Le neurone et le réseau en bref, l’évolution d’un contrôleur pour le pendule inversé sur un chariot, pourquoi l’évolution plutôt que le gradient, XOR et les neurones cachés, NEAT avec ses marquages historiques et sa spéciation, ce qui se passe sans spéciation, et les successeurs de HyperNEAT à AmoebaNet.

15 minutes Matěj Žalmánek 2026-10-15

Série Bases de l’IA Partie 31 sur 52
  1. Carte de l’intelligence artificielle : qu’est-ce qui est quoi et comment tout s’emboîte
  2. Brève histoire de l’intelligence artificielle : de Turing aux modèles de langage
  3. Données, modèle, entraînement, inférence : les notions de base sur un seul exemple
  4. Lire la notation mathématique : symboles, indices, sommes et quantificateurs
  5. Ensembles et relations : les briques dont sont faites les définitions en IA
  6. Fonctions et applications : pourquoi tout modèle d’IA est une fonction
  7. Logique et démonstrations : les règles selon lesquelles on raisonne en IA
  8. Graphes et arbres : la structure sur laquelle reposent la recherche et les réseaux de neurones
  9. Algèbre linéaire pour l’apprentissage automatique : vecteurs, matrices et pourquoi l’IA multiplie surtout
  10. Dérivées, gradient et règle de dérivation en chaîne : comment un modèle sait dans quelle direction apprendre
  11. Descente de gradient : comment un modèle apprend
  12. Qu’est-ce que la probabilité : du lancer de dé aux axiomes de Kolmogorov
  13. Probabilité conditionnelle et théorème de Bayes : comment réviser une estimation quand on apprend du nouveau
  14. Variable aléatoire et fonction de répartition : une seule fonction qui dit tout de la loi
  15. Fonction de densité et fonction de masse : pourquoi une variable continue a une probabilité nulle en chaque point
  16. Espérance, variance et covariance : où se situe une loi, quelle est sa largeur et ce qui varie avec quoi
  17. Panorama des lois de probabilité : Bernoulli, binomiale, Poisson, uniforme, normale et exponentielle
  18. Loi des grands nombres et théorème central limite : pourquoi les moyennes sont si prévisibles
  19. Statistique et estimation de paramètres : maximum de vraisemblance, MAP et intervalles de confiance
  20. Théorie de l’information : entropie, entropie croisée et divergence KL — d’où viennent les fonctions de perte
  21. Recherche dans l’espace d’états : BFS, DFS, UCS et A* en animations
  22. Jeux et adversaires : minimax, élagage alpha-bêta et Monte Carlo tree search
  23. Satisfaction de contraintes : backtracking, propagation de contraintes et AC-3 sur un sudoku et une carte des régions tchèques
  24. Logique et systèmes à base de connaissances : chaînage avant et arrière, résolution et systèmes experts
  25. Planification : STRIPS, monde des blocs, heuristiques par relaxation et planification dans les agents IA
  26. Recherche locale et recuit simulé : un voyageur de commerce à travers les chefs-lieux de région tchèques
  27. Algorithmes génétiques : population, croisement, mutation et sélection sur un sac à dos de randonnée
  28. Stratégies d’évolution et programmation génétique : de la règle du cinquième à la loi de Kepler
  29. Optimisation par colonies de fourmis : comment une piste de phéromone trouve un chemin court
  30. Optimisation par essaim de particules : inertie, expérience propre et expérience de l’essaim
  31. Neuroévolution : faire évoluer les poids et la forme des réseaux de neurones, et l’algorithme NEAT
  32. Ensembles flous et fonctions d’appartenance : comment écrire « un peu chaud »
  33. Logique floue et probabilité : pourquoi « un peu chaud » ne veut pas dire « probablement chaud »
  34. Logique floue : t-normes, implications et règles SI–ALORS
  35. Commande floue : Mamdani et Sugeno, du ventilateur au pendule inversé sur un chariot
  36. Clustering flou : l’algorithme fuzzy c-means et les points entre les clusters
  37. Systèmes neuro-flous (ANFIS) : des règles floues qui apprennent à partir des données
  38. Chaînes de Markov : matrice de transition, distribution stationnaire et PageRank
  39. Modèles de Markov cachés : l’algorithme de Viterbi et le casino malhonnête
  40. Réseaux bayésiens : pourquoi l’herbe est mouillée et ce que cela implique pour l’arroseur
  41. Le filtre de Kalman : comment estimer, à partir de mesures imprécises, où se trouve une voiture et à quelle vitesse elle roule
  42. Types d’apprentissage automatique : supervisé, non supervisé, semi-supervisé et plus
  43. Prompts, contexte et RAG : comment donner au modèle les bonnes informations
  44. Autoencodeurs variationnels (VAE) : de nouvelles images à partir d’un espace latent continu
  45. Réseaux antagonistes génératifs (GAN) : le faussaire contre le détective
  46. Modèles de diffusion : comment une image naît du bruit pur
  47. Processus de décision markoviens : états, actions, récompenses et équations de Bellman
  48. Q-learning et DQN : apprendre la valeur des actions, du tableau au réseau de neurones
  49. Policy gradient et PPO : apprendre directement la politique
  50. Comment fonctionne un agent IA : un modèle de langage en boucle avec des outils
  51. Programmer avec des agents IA : consigne, tests et vérification
  52. Évaluation et sécurité des systèmes d’IA : comment mesurer et comment surveiller

Programme complet de la série 52 sur 83 sujets publiés

Ce que vous retiendrez
  • Comment l’évolution trouve les poids d’un réseau quand on n’apprend le succès qu’à la fin.
  • Pourquoi ajuster les poids ne suffit pas pour XOR et comment NEAT fait croître un réseau à partir du minimum.
  • Pourquoi NEAT ne fonctionne presque pas sans spéciation et où mène la neuroévolution aujourd’hui.

Utile à connaître avant : Algorithmes génétiques : population, croisement, mutation et sélection sur un sac à dos de randonnée, Stratégies d’évolution et programmation génétique : de la règle du cinquième à la loi de Kepler

Aujourd’hui, les réseaux de neurones apprennent presque exclusivement par descente de gradient : le réseau commet une erreur, on calcule dans quel sens tourner chaque poids, et on le tourne. Mais que faire si l’erreur n’est pas dérivable — si l’on n’apprend le succès qu’à la fin de la partie, ou si l’on ne sait même pas à quoi le réseau doit ressembler ? C’est là qu’intervient l’évolution. La neuroévolution laisse des algorithmes évolutionnaires faire pousser les poids des réseaux et, dans sa forme la plus intéressante, aussi leur forme. Cette partie clôt le module sur les algorithmes inspirés de la nature et ouvre en même temps la porte aux réseaux de neurones.

Elle fait suite aux parties Algorithmes génétiques et Stratégies d’évolution et programmation génétique.

1. Le réseau de neurones en bref

Définition — neurone artificiel et réseau

Un neurone avec les entrées x1, …, xn, les poids w1, …, wn et le biais (seuil) b calcule

y = σ(w1x1 + … + wnxn + b),

où σ est la fonction d’activation — par exemple la sigmoïde σ(z) = 1 / (1 + e−z), ou un simple échelon « positif → 1, sinon 0 ».

Un réseau de neurones est un graphe orienté de neurones : les entrées du réseau mènent aux neurones, les sorties des neurones à d’autres neurones. Dans un réseau à propagation avant, le graphe n’a pas de cycles. Les neurones qui ne sont ni des entrées ni des sorties sont cachés.

En termes simples

Le neurone additionne ses entrées, chacune multipliée par son poids, et selon la somme il « s’allume » ou non. Les poids déterminent ce qui compte pour lui. Un réseau, ce sont de nombreux neurones branchés les uns après les autres. Tout le comportement du réseau tient dans les valeurs des poids et dans qui est relié à qui — et les deux peuvent être cultivés par l’évolution. Le programme consacre tout un module distinct aux réseaux de neurones et à leur apprentissage par gradient.

y = σ( ∑i=1n wi xi + b )

xi
entrées — ce que le neurone « voit » (pour le pendule, la position, la vitesse, l’angle) ; le neurone ne les modifie pas, il se contente de calculer avec
wi
poids — la seule chose qui s’apprend ou évolue ; le signe dit si l’entrée soutient le neurone ou le freine, la grandeur dit à quel point elle compte
b
biais — déplace la frontière de décision ; sans lui, des entrées nulles donneraient toujours le même σ(0) et la frontière devrait passer par l’origine
σ
fonction d’activation — la non-linéarité ; sans elle, même un réseau à plusieurs couches ne calculerait qu’une fonction linéaire, car la composée de fonctions linéaires est encore linéaire
La somme ∑ wixi + b est un vote pondéré des entrées ; σ en fait la sortie. La frontière entre « s’allume » et « ne s’allume pas » est l’ensemble où la somme vaut zéro — une droite (dans le plan), en général un hyperplan.

2. Évolution des poids : le pendule inversé sur un chariot

Un problème de commande classique : sur un chariot se dresse une tige montée sur une articulation, et le chariot doit la maintenir à la verticale en se déplaçant à gauche et à droite — comme lorsqu’on tient un balai en équilibre sur la paume de la main.

Définition — problème du pendule inversé sur un chariot

L’état est un quadruplet (x, v, θ, ω) : la position x et la vitesse v du chariot, l’angle θ de la tige par rapport à la verticale et sa vitesse angulaire ω. Toutes les 0,02 s, le contrôleur choisit une force de +10 N ou de −10 N, et l’état évolue selon les équations du mouvement (chariot de 1 kg, tige de 0,1 kg et de 1 m de long). L’essai s’arrête quand |θ| > 12° ou |x| > 2,4 m. Le but est de tenir 1 000 pas (20 s).

Le contrôleur sera un neurone unique à activation en échelon : la force vaut +10 N quand w1x + w2v + w3θ + w4ω + b > 0, sinon −10 N. La fitness est le nombre moyen de pas sur trois départs différents.

En termes simples

Cinq nombres décident quand donner un coup au chariot vers la gauche et quand vers la droite. Aucun professeur ne dit quelle force était la bonne à quel moment — on apprend seulement combien de temps la tige a tenu. C’est exactement le genre de situation que le gradient résout mal et l’évolution facilement.

Définition — équations du mouvement du pendule inversé sur un chariot

L’angle θ est mesuré depuis la verticale, une force positive pousse le chariot dans le sens des x positifs. Notons M = mv + mt = 1 + 0,1 = 1,1 kg la masse totale du chariot et de la tige, l = 0,5 m la moitié de la longueur de la tige (distance du centre de gravité à l’articulation) et g = 9,8 m/s². Sans frottement (équations sous la forme donnée par Barto, Sutton et Anderson, 1983, et utilisée aujourd’hui encore par la plupart des implémentations), l’accélération angulaire de la tige ε et l’accélération du chariot a valent :

aF = (F + mt l ω² sin θ) / M

ε = (g sin θ − cos θ · aF) / (l (4/3 − mt cos² θ / M))

a = aF − mt l ε cos θ / M

La simulation avance par la méthode d’Euler avec un pas Δt = 0,02 s : la nouvelle position est l’ancienne plus Δt fois l’ancienne vitesse, la nouvelle vitesse est l’ancienne plus Δt fois l’accélération — x ← x + Δt v, v ← v + Δt a, θ ← θ + Δt ω, ω ← ω + Δt ε.

ε = ( g sin θ − cos θ · aF ) / l (4/3 − mt cos² θ / M)

g sin θ
pesanteur — la raison pour laquelle la tige tombe : plus elle est inclinée, plus la gravité la fait basculer ; à la verticale ce terme est nul, la verticale est donc un équilibre, mais instable
aF
accélération du chariot due à la force du contrôleur (plus une petite force centrifuge de la tige) — le seul moyen par lequel le contrôleur agit sur la tige : le chariot passe sous la tige et l’articulation la « repousse » en arrière
cos θ
quelle part de la poussée horizontale fait tourner la tige — presque tout pour une tige verticale, rien pour une tige couchée ; c’est pourquoi une tige inclinée de plus de quelques degrés se rattrape difficilement
l (4/3 − …)
inertie de la tige — le 4/3 provient du moment d’inertie d’une tige homogène autour de son extrémité, ⅓ m(2l)² = ⁴⁄₃ ml² ; le terme soustrait corrige le fait que, lorsque la tige tourne, le chariot en dessous bouge lui aussi
L’accélération angulaire de la tige est un duel entre deux termes : la pesanteur fait basculer la tige, l’accélération du chariot la ramène.
Exemple — un pas de simulation

Départ (0, 0, 0,05, 0), soit une tige inclinée de 0,05 rad ≈ 2,9°. Dénominateur : 0,5 · (4/3 − 0,1 · 0,9988² / 1,1) ≈ 0,621.

sans force (F = 0) : ε = 9,8 · 0,0500 / 0,621 ≈ +0,79 rad/s² — la tige commence à tomber

force de +10 N : aF = 10 / 1,1 ≈ 9,09 m/s²,   ε = (0,490 − 0,999 · 9,09) / 0,621 ≈ −13,8 rad/s²

après le pas : ω = 0 + 0,02 · (−13,8) ≈ −0,28 rad/s,   v ≈ 0 + 0,02 · 9,72 ≈ 0,19 m/s

Une force de ±10 N est énorme face à la pesanteur — un seul coup inverse le sens de la chute. La difficulté n’est donc pas dans la force, mais dans le moment choisi : quand cesser de pousser pour que la tige ne bascule pas de l’autre côté.

Que donne le hasard ? Sur mille contrôleurs aux poids aléatoires, un seul a tenu les 20 secondes ; la médiane est de 9 pas — la tige tombe en moins d’un cinquième de seconde. Un contrôleur naïf, qui se contente d’aller du côté où la tige penche, tient 39 pas : il rattrape bien la tige, mais la fait osciller de plus en plus. La stratégie d’évolution (5, 20) de la partie sur les stratégies d’évolution a trouvé un contrôleur qui tient les 20 secondes entières depuis les trois départs, à la 15ᵉ génération — après environ trois cents contrôleurs essayés :

seulement d’après l’angle 0,8 s évolution des poids 20,0 s+
Pendule inversé sur un chariot : les 8 premières secondes de la simulation

Le contrôleur obtenu donne le plus grand poids à la vitesse angulaire — il ne rattrape pas la tige seulement quand elle tombe, mais déjà quand elle commence à partir. L’angle et la vitesse du chariot ont eux aussi des poids non négligeables, si bien que le chariot n’oscille pas et ne sort pas du rail. Personne n’a écrit une telle « règle » ; elle est sortie de l’évolution.

Définition — fitness du contrôleur et évolution des poids

Pour les poids w = (w1, …, w4, b), Tk(w) ∈ {1, …, 1 000} est le nombre de pas avant que, depuis le départ k, la tige tombe ou le chariot sorte (au plus 1 000). La fitness vaut

Z(w) = ⅓ (T1(w) + T2(w) + T3(w)),

avec les départs (0, 0, 0,05, 0), (0, 0, −0,08, 0) et (0,5, 0, 0,02, 0,1). Trois départs au lieu d’un empêchent l’évolution de trouver un contrôleur qui ne sait gérer qu’une seule position initiale. Z est constante par morceaux — une petite variation des poids ne change le plus souvent pas du tout le nombre de pas, si bien que le gradient est nul presque partout. D’où l’évolution.

Stratégie d’évolution (5, 20) : cinq parents aux poids tirés au hasard dans [−1, 1] ; à chaque génération, vingt descendants, chacun obtenu à partir d’un parent pris au hasard en ajoutant 0,5 · N(0, 1) à chaque poids ; les parents suivants sont les cinq meilleurs descendants.

import random

def regulator(w, s):                 # s = (x, v, θ, ω)
    u = w[0]*s[0] + w[1]*s[1] + w[2]*s[2] + w[3]*s[3] + w[4]
    return 10 if u > 0 else -10       # activation en échelon : force ±10 N

def zdatnost(w):                     # simuluj() : pas avant la chute, au plus 1000
    return sum(simuluj(regulator, w, start) for start in STARTY) / 3

rodice = [[random.uniform(-1, 1) for _ in range(5)] for _ in range(5)]
for generace in range(30):
    deti = [[wi + 0.5 * random.gauss(0, 1) for wi in random.choice(rodice)]
            for _ in range(20)]
    deti.sort(key=zdatnost, reverse=True)
    rodice = deti[:5]                # (5, 20) : seuls les descendants survivent
Exemple — comment décide le contrôleur obtenu

Poids trouvés par l’évolution (arrondis) : w = (0,05 ; 1,01 ; 1,37 ; 4,23), b = 0,09 pour (x, v, θ, ω).

tige inclinée, immobile : (0, 0, 0,05, 0) → u = 1,37 · 0,05 + 0,09 ≈ +0,16 → +10 N, le chariot passe sous la tige

tige inclinée, mais qui revient déjà : (0, 0, 0,05, −0,1) → u = 0,07 − 0,42 + 0,09 ≈ −0,26 → −10 N, le chariot freine

Dans le second cas, la tige est inclinée de la même façon, mais le contrôleur pousse dans l’autre sens : la vitesse angulaire −0,1 rad/s, avec le poids 4,23, pèse plus que l’angle 0,05 rad avec le poids 1,37. La tige revient d’elle-même, et si le chariot continuait à pousser, elle basculerait de l’autre côté. Le contrôleur naïf « seulement d’après l’angle » pousserait de la même façon dans les deux états — c’est pourquoi il fait osciller la tige.

3. Pourquoi l’évolution plutôt que le gradient

  • Récompense rare. Pour le pendule, un jeu ou un robot, la qualité ne se voit qu’au bout d’un épisode entier. À l’évolution, un seul nombre à la fin suffit.
  • Parties non dérivables. Activation en échelon, simulateur physique, règles du jeu — l’évolution n’a pas besoin de les dériver.
  • Parallélisme. Chaque individu peut être évalué indépendamment sur un autre ordinateur.
  • Structure. Le gradient sait ajuster des poids, mais pas ajouter un neurone. L’évolution peut changer aussi la forme du réseau.

L’inconvénient est la voracité : au lieu d’un seul gradient qui indique la direction pour tous les poids à la fois, l’évolution essaie des changements aléatoires. Pour des réseaux à des millions de poids, le gradient est en général bien plus économe, pour peu qu’on puisse le calculer.

4. Quand les poids ne suffisent pas : XOR

La fonction XOR renvoie 1 quand exactement une des deux entrées vaut 1. Comme l’a montré la partie Logique et démonstrations, un neurone unique n’en est pas capable : sa sortie partage le plan des entrées par une droite, et aucune droite ne sépare les points (0, 1), (1, 0) de (0, 0), (1, 1). Pour XOR, il faut au moins un neurone caché. Et cela ouvre une question que l’évolution des poids seule ne résout pas : combien de neurones, et comment les relier ?

Théorème — XOR n’est pas linéairement séparable

Il n’existe pas de nombres w1, w2, b tels que w1x1 + w2x2 + b > 0 soit vrai exactement pour les points où XOR vaut 1.

Démonstration. Si de tels nombres existaient, on aurait

(0, 0) : b ≤ 0    (1, 1) : w1 + w2 + b ≤ 0

(1, 0) : w1 + b > 0    (0, 1) : w2 + b > 0

En additionnant la première ligne, on obtient w1 + w2 + 2b ≤ 0, en additionnant la seconde, w1 + w2 + 2b > 0. Contradiction. ∎

La démonstration n’utilise pas la forme de la fonction d’activation — elle vaut pour tout neurone unique dont la sortie ne dépend que du signe de la somme pondérée (échelon comme sigmoïde avec un seuil à 0,5).

On peut répondre à la main — concevoir une architecture et ne régler que les poids par évolution. Mais pour des problèmes plus complexes, on ignore quelle architecture est la bonne. Pourquoi ne pas la faire évoluer elle aussi ?

5. NEAT : l’évolution de réseaux qui grandissent

Les tentatives de faire évoluer la structure des réseaux se heurtaient à trois problèmes. Comment croiser deux réseaux de formes différentes ? Comment donner sa chance à une nouvelle structure qui est d’abord moins bonne, parce que ses nouveaux poids ne sont pas encore réglés ? Et comment éviter que les réseaux ne gonflent inutilement ? En 2002, Kenneth Stanley et Risto Miikkulainen ont répondu par l’algorithme NEAT (NeuroEvolution of Augmenting Topologies), fondé sur trois idées.

Définition — génome NEAT et marquages historiques

Le génome est une liste de gènes de connexion ; chaque gène comporte un neurone source et un neurone cible, un poids, un indicateur activé/désactivé et un numéro d’innovation. Chaque fois qu’une mutation crée pour la première fois dans la population une nouvelle connexion, celle-ci reçoit le prochain numéro d’innovation libre ; la même modification structurelle chez un autre individu reçoit le même numéro.

Mutations : modification des poids ; ajout d’une connexion entre deux neurones encore non reliés ; ajout d’un neurone — une connexion existante a → b de poids w est désactivée et remplacée par la paire a → h de poids 1 et h → b de poids w.

Le croisement aligne les gènes des parents d’après leurs numéros d’innovation : les gènes communs sont hérités au hasard de l’un des parents, les autres du parent le plus apte.

En termes simples

Le numéro d’innovation est l’acte de naissance d’une connexion. Quand deux réseaux ont une connexion numéro 7, il s’agit de « la même » connexion, même si chacun y est arrivé par un autre chemin — et lors du croisement, on peut les apparier comme des gènes identiques chez deux parents. Un nouveau neurone est inséré de sorte que le réseau calcule d’abord à peu près comme avant ; rien d’essentiel ne se casse, seule une possibilité s’ajoute.

Exemple — ajout d’un neurone sur une connexion de poids 2

Comme l’article original, notre NEAT utilise une sigmoïde plus raide σ(z) = 1 / (1 + e−4,9z). La connexion x1 → y de poids 2 apportait 2x1 à la sortie. Après la scission, on a x1 → h de poids 1 et h → y de poids 2 :

x1 = 1 :   2 · σ(1) = 2 · 0,993 ≈ 1,985   (auparavant 2)

x1 = 0 :   2 · σ(0) = 2 · 0,5 = 1   (auparavant 0)

« À peu près » ne vaut donc ici qu’en partie : pour l’entrée 1, le changement est négligeable, pour l’entrée 0, la somme gagne un 1, car la sigmoïde renvoie la moitié en zéro. Le réseau s’en trouve un peu dégradé — et c’est justement pourquoi une nouvelle structure a besoin de la protection dont il est question plus loin.

Définition — spéciation et partage de fitness

La distance de compatibilité de deux génomes est

δ = c1 E / N + c2 D / N + c3 W̄,

où E est le nombre de gènes en excès (aux numéros au-delà de la fin du génome le plus court), D le nombre de gènes disjoints (manquants au milieu), W̄ la différence moyenne de poids des gènes communs et N la taille du plus grand génome (1 pour les petits génomes). Les génomes dont δ est sous le seuil appartiennent à la même espèce. La fitness d’un individu est divisée par la taille de son espèce, et les descendants sont répartis entre les espèces selon leur fitness moyenne.

En termes simples

Les réseaux de structure semblable sont surtout en compétition entre eux, pas avec toute la population. Un nouveau neurone dispose ainsi de quelques générations pour que ses poids se règlent, au lieu d’être aussitôt balayé par les réseaux établis. Et comme la fitness est partagée au sein d’une espèce, aucune espèce ne peut envahir toute la population.

δ = c1 E / N + c2 D / N + c3 W̄

c1 E
gènes en excès — structure qu’un génome n’a acquise qu’après la dernière « invention » commune ; signe d’une branche d’évolution plus jeune et différente
c2 D
gènes disjoints — inventions datant d’une époque où les deux existaient déjà, mais que seul l’un des deux possède ; une autre mesure de la différence de structure
N
taille du plus grand génome — pour que deux grands génomes qui diffèrent de quelques gènes sur beaucoup ne paraissent pas aussi éloignés que deux petits qui diffèrent presque en tout ; pour les génomes de moins de 20 gènes, on prend N = 1
c3 W̄
différence moyenne de poids des gènes communs — des réseaux câblés de la même façon mais aux poids très différents calculent des fonctions différentes ; le coefficient c3 est plus petit, car la structure change plus rarement et compte davantage
Les constantes c1 = c2 = 1, c3 = 0,4 et le seuil δt = 3 sont les valeurs utilisées pour XOR par Stanley et Miikkulainen (2002) ; notre programme utilise les mêmes.
Exemple — distance après l’ajout d’un neurone

Le génome A possède les gènes 1–5. Le génome B est issu d’un ancêtre semblable : il possède lui aussi les gènes 1–5 (le gène 3 désactivé, car un nouveau neurone l’a scindé) et en plus les gènes 6 et 7 issus de l’ajout du neurone. Les poids des gènes communs diffèrent de 0,2 ; 0,6 ; 0 ; 0,9 et 0,8.

communs : 1–5 → W̄ = (0,2 + 0,6 + 0 + 0,9 + 0,8) / 5 = 0,5

en excès : 6, 7 (au-delà de la fin du génome le plus court, qui s’arrête au gène 5) → E = 2 ;   disjoints : aucun → D = 0

δ = 1 · 2 / 1 + 1 · 0 / 1 + 0,4 · 0,5 = 2,2 < 3 → même espèce

Si B gagnait encore une connexion (gène 8), on obtiendrait E = 3 et δ = 3,2 ≥ 3 — B fonderait une nouvelle espèce. Un seul nouveau neurone ne fait donc pas sortir un réseau de son espèce, mais un nouveau neurone plus une nouvelle connexion, si.

nk = P · F̄k / ∑j F̄j,   F̄k = ∑i ∈ Sk fi / |Sk|

nk
nombre de descendants de l’espèce k à la génération suivante — c’est ainsi que NEAT récompense le succès, et non par la sélection d’individus dans toute la population
P
taille de la population (150) — les descendants se partagent comme un gâteau, la population ne grandit ni ne diminue
|Sk|
nombre de membres de l’espèce — partage de fitness : la fitness de chaque membre est divisée par la taille de son espèce, f′i = fi / |Sk| ; une grande espèce ne peut donc pas obtenir plus du seul fait qu’elle est grande
F̄k
somme des fitness partagées de l’espèce, c’est-à-dire sa fitness moyenne — c’est la qualité qui décide de la part du gâteau, pas le nombre
∑j F̄j
somme sur toutes les espèces — la division donne une part, et les parts totalisent 1, de sorte que l’on répartit exactement P descendants (après l’arrondi à l’entier inférieur, le reste va à la meilleure espèce)
Exemple — pourquoi le partage protège une nouvelle espèce

Population de 150 : l’espèce établie A compte 120 réseaux de fitness moyenne 9,0, la nouvelle espèce B, avec un neurone caché, 30 réseaux de fitness moyenne 7,5.

avec partage : nA = 150 · 9,0 / 16,5 ≈ 81,8 → 81 (+1 reste) = 82,   nB = 150 · 7,5 / 16,5 ≈ 68,2 → 68

sans partage (d’après les sommes des fitness, 1 080 et 225) : A ≈ 150 · 1 080 / 1 305 ≈ 124,   B ≈ 26

Sans partage, la nouvelle espèce s’éteindrait en quelques générations, parce qu’elle est moins bonne et plus petite. Avec le partage, elle obtient plus de descendants qu’elle n’a de membres, et elle a le temps de régler ses poids.

La troisième idée est la plus simple : partir du minimum. Au départ, aucun réseau n’a de neurone caché — les entrées mènent directement à la sortie. La structure ne s’ajoute que lorsqu’elle est payante. Les réseaux obtenus sont donc en général petits.

Tout cela réuni — une génération de notre NEAT simplifié (les probabilités sont celles avec lesquelles tourne le programme pour XOR) :

population ← 150 réseaux minimaux (x₁, x₂ et le biais directement vers y,
    poids aléatoires dans [−2, 2])
pour la génération g = 0, 1, …, 100 :
    évaluer la fitness f de chaque réseau
    si un réseau résout le problème : fin
    répartir les réseaux en espèces : un réseau rejoint la première espèce dont le
                                      représentant est à δ < 3,
                                      sinon il en fonde une nouvelle
    éliminer les espèces sans amélioration depuis 15 générations (sauf l’espèce du
        meilleur réseau)
    attribuer à l’espèce k n_k = 150 · F̄_k / ∑ F̄_j descendants
    dans chaque espèce :
        parents ← les 20 % meilleurs membres
        si l’espèce compte plus de 5 membres, son meilleur réseau passe tel quel
        chaque descendant suivant :
            avec la probabilité 0,75 croisement de deux parents, sinon copie d’un seul
            avec la probabilité 0,8 modifier les poids
                (90 % : w ← w + N(0, 1), 10 % : nouveau poids aléatoire)
            avec la probabilité 0,1 ajouter une connexion
            avec la probabilité 0,03 ajouter un neurone (scinder une connexion active
                prise au hasard)

6. NEAT sur XOR

Notre NEAT simplifié travaille avec une population de 150 réseaux et au plus 100 générations. Lors de l’exécution de la figure, il a résolu XOR à la génération 27 avec un réseau à un seul neurone caché et sept connexions — soit la plus petite solution possible :

f = ( 4 − ∑ |y − t| )2

∑ |y − t|
erreur — somme des écarts entre la sortie y et la valeur correcte t sur les quatre entrées ; en valeur absolue, pour que les erreurs vers le haut et vers le bas ne se compensent pas
4
plus grande erreur possible (quatre entrées, chaque écart au plus 1) — la soustraction transforme l’erreur, que l’on veut réduire, en une fitness, que l’évolution augmente, et qui est toujours positive ou nulle
2
mise au carré — amplifie les différences près de la solution : un réseau d’erreur 0,5 a une fitness de 12,25, un réseau d’erreur 1 seulement 9, si bien que la sélection récompense davantage les derniers pour cent
Fitness pour XOR selon l’article original de NEAT (Stanley et Miikkulainen, 2002) ; au plus 16. Un réseau qui répond 0,5 à tout a une fitness de (4 − 2)² = 4. Un réseau compte comme solution quand, après arrondi de la sortie, il répond correctement aux quatre entrées.
−1,4 −3,0 3,3 −3,4 −5,7 −3,7 0,9 x₁ x₂ 1 y h x₁x₂y 00 0,00 01 0,86 10 1,00 11 0,01
Réseau pour XOR obtenu par NEAT ; à droite, ses sorties

Le réseau se laisse lire. Le neurone caché h ne s’allume que lorsque les deux entrées valent 0 (toutes deux ont un fort poids négatif, le biais est positif). Les connexions directes vers la sortie calculeraient à elles seules « pas les deux à la fois » — la sortie 1 pour tout sauf (1, 1) —, mais le poids fortement négatif venant de h l’éteint pour (0, 0). Les sorties 0,000 ; 0,855 ; 1,000 et 0,005 donnent exactement XOR après arrondi. Le nombre d’espèces est entre-temps passé d’une seule à une vingtaine environ.

Exemple — calcul du réseau pour les quatre entrées

Poids (arrondis) : vers h depuis x1, x2 et le biais −3,37 ; −3,67 ; 0,95 ; vers y depuis x1, x2, le biais et h −1,42 ; −2,96 ; 3,32 ; −5,69. L’activation est σ(z) = 1 / (1 + e−4,9z). On calcule d’abord h, puis y (le tableau utilise les poids non arrondis, un recalcul à la main peut donc différer sur le dernier chiffre) :

(x1, x2)zhhzyy|y − t|
(0, 0)0,950,9903,32 − 5,69 · 0,990 = −2,320,0000,000
(0, 1)−2,720,000−2,96 + 3,32 = 0,360,8550,145
(1, 0)−2,430,000−1,42 + 3,32 = 1,891,0000,000
(1, 1)−6,090,000−1,42 − 2,96 + 3,32 = −1,060,0050,005

L’erreur vaut 0,150 et la fitness (4 − 0,150)² ≈ 14,82 sur 16. C’est pour (0, 1) que le réseau répond le moins bien : la sortie 0,855 est bien au-dessus de la moitié, mais pas proche de 1 — cela suffit pour une solution, et un réglage supplémentaire des poids augmenterait encore la fitness.

Sur cinquante exécutions avec des graines différentes, NEAT a résolu XOR dans 47 cas, en moyenne en 31 générations, avec 2,6 neurones cachés réellement utiles. L’article original indique 32 générations et 2,35 neurones cachés — notre NEAT simplifié en est donc proche.

7. Ce sur quoi repose NEAT

Les mêmes 50 exécutions, avec une partie de l’algorithme désactivée :

VarianteXOR résoluGénération moyenne
NEAT47 sur 5031,1
sans spéciation (une seule espèce)1 sur 50—
sans croisement50 sur 5047,3

Sans spéciation, NEAT ne fonctionne presque pas. Un réseau doté d’un neurone caché est, à sa naissance, moins bon que les réseaux établis qui n’en ont pas, car ses nouveaux poids sont aléatoires — et dans une compétition commune, la sélection l’élimine aussitôt. La spéciation le protège jusqu’à ce qu’il mûrisse. Sans croisement, XOR est résolu lui aussi, seulement plus lentement et avec des réseaux plus grands (3,1 neurones cachés actifs) : le croisement aide à assembler les découvertes partielles de différents individus.

8. Les successeurs de NEAT

  • HyperNEAT (Stanley et al., 2009) fait évoluer un petit réseau qui ne fait que générer les poids d’un grand réseau selon la géométrie du problème — comme un génome qui ne décrit pas chaque cellule, mais les règles de croissance.
  • La recherche de nouveauté (Lehman et Stanley, 2011) ne récompense pas la fitness, mais un comportement nouveau. Dans un labyrinthe aux impasses trompeuses, elle a trouvé la sortie plus souvent qu’une recherche guidée par la fitness.
  • Neuroévolution profonde. En 2017, l’équipe d’Uber AI a montré qu’un simple algorithme génétique peut entraîner des réseaux à des millions de poids sur des jeux Atari avec des résultats comparables à l’apprentissage par renforcement.
  • Recherche évolutionnaire d’architectures neuronales. Le réseau AmoebaNet (Google, 2019) est né de l’évolution de blocs de construction et figurait à l’époque parmi les meilleurs en reconnaissance d’images sur ImageNet. Aujourd’hui, les architectures sont plus souvent conçues par des humains ou par d’autres méthodes, mais l’évolution reste une des possibilités.

9. Résumé

  • La neuroévolution cultive des réseaux de neurones à l’aide d’algorithmes évolutionnaires — elle est utile là où le gradient ne peut pas être calculé ou là où la récompense n’arrive qu’à la fin.
  • L’évolution des poids d’un seul neurone maintient le pendule inversé sur un chariot pendant 20 secondes après environ trois cents essais ; des poids aléatoires donnent une médiane de 9 pas.
  • Pour XOR, un seul neurone ne suffit pas — il faut changer la structure du réseau.
  • NEAT fait évoluer les poids comme la structure : les marquages historiques permettent le croisement, la spéciation protège les nouvelles structures et les réseaux grandissent à partir du minimum.
  • Sans spéciation, NEAT a résolu XOR dans 1 exécution sur 50, avec spéciation dans 47 — et la meilleure solution n’avait qu’un seul neurone caché.

Envie de mettre la main à la pâte ? Demandez à un agent d’IA d’écrire un pendule inversé sur un chariot avec l’évolution du contrôleur, puis essayez deux tiges de longueurs différentes sur un même chariot — la célèbre version plus difficile, sur laquelle NEAT excellait autrefois.

Vous voyez les choses autrement ? Écrivez à support@coden-app.com.

Un Agent sur votre ordinateur, piloté depuis votre téléphone