Loading
La génération 1 sera un désastre. Je le sais avant d'écrire une seule ligne de code.
C'est un journal de conception avant l'implémentation.
Je construis un simulateur de créatures en 2D basé sur la physique, où des corps font évoluer des démarches grâce à un algorithme génétique — pas de mouvement codé à la main, pas d'apprentissage par renforcement, juste la pression de sélection et le temps. Avant d'écrire le moteur physique, l'encodage du génome ou les opérateurs de mutation, je veux réfléchir à ce que je m'attends à trouver. C'est l'objet de ce post.
La génération 1 va ressembler à un désastre.
Cinquante créatures. Je m'attends à ce que la plupart s'effondrent immédiatement — basculant vers l'avant ou sur le côté dans la première demi-seconde de simulation, les articulations se bloquant dans des positions qu'aucune heuristique de récupération ne pourrait sauver. L'une vibrera probablement sur place, ses articulations oscillant à haute fréquence, n'allant nulle part malgré un effort apparent considérable. Une autre pourrait se propulser en arrière à une vitesse surprenante, puis s'arrêter. Une autre se figera complètement — aucun mouvement, aucune distance parcourue, mais aussi aucune pénalité pour chute, ce qui s'avère non pas stupide mais rationnel, d'une façon étroite et déprimante.
Aucune d'elles ne marchera. Aucune n'approchera de quelque chose de reconnaissable comme de la locomotion.
C'est juste l'évolution, au tout début, avant qu'elle ait eu le temps de trouver quoi que ce soit. Et je trouve que c'est la partie la plus intéressante à réfléchir avant de toucher au code.
Un score élevé peut encore récompenser un mauvais comportement. L’objectif détermine ce que l’évolution apprend à exploiter.
La question évidente : pourquoi ne pas écrire un algorithme de marche ? Vous savez à quoi ressemble la marche. Vous en comprenez la mécanique. Écrivez la démarche, ajustez les paramètres, et c'est fait.
La réponse est que programmer la marche et faire évoluer la marche, ce sont deux problèmes différents, et seul le premier est le vôtre. Quand vous programmez la marche, vous définissez la méthode et le système l'exécute. Quand vous faites évoluer la marche, vous définissez le succès et l'algorithme trouve la méthode. Ces approches ne sont pas interchangeables. La méthode que vous écrivez sera fragile — elle fonctionnera pour le corps spécifique, le terrain spécifique, les conditions spécifiques pour lesquelles vous l'avez conçue, et échouera en dehors. Une démarche évoluée devrait être plus robuste car la pression de sélection agit sur la robustesse ; les créatures qui trébuchent sous de légères perturbations ne produiront pas de descendants.
Insight clé : Une fonction de fitness est une définition de la victoire, pas une solution. L'algorithme fournit la solution. Votre rôle est seulement de savoir ce que vous cherchez.
Cela semble évident jusqu'à ce qu'on se retrouve à fixer une créature qui a trouvé un moyen de bien scorer sur votre fonction de fitness tout en faisant quelque chose qu'on n'avait jamais prévu. Alors on comprend que définir le succès est plus difficile qu'il n'y paraît, et qu'un optimiseur aveugle exploitera toute ambiguïté dans cette définition avec une efficacité impitoyable.
La configuration est délibérément simple :
TÊTE ○
|
CORPS ▬▬▬
/ \
/ \
JAMBE JAMBECe qui évolue est entièrement comportemental : six nombres à virgule flottante contrôlant le mouvement des articulations. Chaque articulation a trois paramètres — fréquence (vitesse d'oscillation), amplitude (amplitude du balancement) et décalage de phase (position dans son cycle par rapport à l'autre articulation). Deux articulations, trois paramètres chacune — six nombres au total. Le génome entier est six valeurs à virgule flottante dans [0, 1].
La fonction de fitness que je prévois :
def fitness(creature, duration=10.0):
start_x = creature.body.position.x
simulate(creature, duration)
end_x = creature.body.position.x
distance = end_x - start_x
falling_penalty = -50 if creature.is_fallen() else 0
return max(0, distance + falling_penalty)Distance parcourue moins une pénalité de chute. Rien d'autre. Pas de récompense pour ressembler à la marche. Pas de récompense pour l'efficacité énergétique. Pas de récompense pour la fluidité ou la ressemblance humaine. Juste : avez-vous avancé, et êtes-vous encore debout à la fin ?
La simplicité est intentionnelle. Les fonctions de fitness complexes introduisent des contraintes implicites qui peuvent piéger l'évolution dans des optima locaux façonnés par ces contraintes plutôt que par l'objectif réel. Plus la fonction de fitness est simple, plus l'algorithme a la liberté de trouver des solutions auxquelles je n'aurais pas pensé.
Avant de rien exécuter, je peux déjà prédire trois stratégies d'échec dominantes. Ce ne sont pas des bugs — ce sont des réponses rationnelles à la fonction de fitness que je définis.
Le Tombeur — Je m'attends à voir ça tôt. Il découvrira que se pencher agressivement vers l'avant déplace le centre de masse du corps vers l'avant avant qu'il tombe, moment où les jambes le rattrapent. Puis recommencer. Sa démarche ressemblera à une chute contrôlée : se pencher, rattraper, se pencher, rattraper. Le score de fitness sera correct. Selon toutes les métriques que la fonction mesure, il réussira.
Il ne marchera pas. Mais la fonction de fitness ne saura pas ce qu'est la marche, et l'algorithme non plus. La leçon est immédiate et inconfortable : la fonction de fitness ne se soucie pas de la méthode. Si trébucher vers l'avant atteint le résultat, trébucher vers l'avant est ce qu'on obtient. Si on se soucie de comment une créature se déplace, pas seulement de combien loin, notre fonction de fitness doit le mesurer. Les contraintes implicites ne sont pas des contraintes.
Le Trembleur — Fréquence d'articulation maximale, amplitude minimale, déplacement net nul. Il semblera faire beaucoup d'efforts. Il n'ira nulle part. Mais voilà : le mouvement à haute fréquence et faible amplitude est peu coûteux en énergie dans cette simulation, et la fonction de fitness n'a pas de terme d'énergie. Le Trembleur trouvera une stratégie qui dépense un minimum d'effort tout en produisant suffisamment de vibrations structurelles pour éviter toute pénalité de mouvement minimal que j'ajouterai. Il exploitera la métrique — sans malice, sans compréhension, avec une pure efficacité.
Les optima locaux ressemblent à des solutions de l'intérieur. Le score de fitness du Trembleur sera stable d'une génération à l'autre. Il ne s'améliorera pas, mais ne sera pas non plus évincé par la concurrence, car sa niche — faible effort, faible récompense, haute survie — est constante. L'évolution ne requiert pas le progrès. Elle requiert la survie, et la survie est un seuil bien plus facile à atteindre que l'excellence.
Le Figé — Zéro mouvement. Zéro distance. Mais aussi zéro pénalité de chute, parce qu'on ne peut pas tomber si on ne bouge pas. La créature figée identifiera correctement qu'une tentative de locomotion dans les premières générations est plus susceptible de produire une chute qu'un gain. Rester immobile est optimal du point de vue du risque étant donné le niveau de compétence actuel de la population.
Cela persistera jusqu'à ce que j'ajoute une exigence de mouvement minimal. C'est seulement alors que l'évolution devrait commencer à explorer sérieusement la locomotion. C'est le plus profond des trois : l'exploration doit être forcée. Un optimiseur aveugle trouvera la stratégie localement la plus sûre, pas la meilleure globalement. Les bonus de curiosité, la pression de diversité et les contraintes d'exploration minimale ne sont pas de la triche — ils sont nécessaires pour rendre le paysage de fitness navigable plutôt qu'un piège.
Si les modes d'échec sont correctement gérés, je pense que quelque chose de vraiment surprenant pourrait apparaître dans les 50 à 100 générations.
Mon hypothèse est que la solution ne sera pas la foulée biped symétrique à laquelle je m'attendrais naïvement. J'anticipe que l'algorithme trouvera l'asymétrie avant la symétrie — une articulation fonctionnant à haute fréquence avec un arc court, l'autre à basse fréquence avec un arc large. L'asymétrie pourrait créer un léger couple de rotation qui, combiné avec l'élan vers l'avant, produirait un déplacement net par cycle supérieur à tout ce qu'une stratégie symétrique atteint.
Ce serait contre-intuitif. Ça semblerait faux. Ça pourrait fonctionner.
L'algorithme n'a aucun biais esthétique vers la symétrie. J'en ai un, parce que j'ai observé des humains et des animaux marcher. Ce biais est un handicap ici, pas un atout. La recherche couvrira des espaces de paramètres que j'exclurais par intuition, et c'est là que la solution vit probablement.
Cela rejoint quelque chose sur lequel je reviens sans cesse en construisant Exira-X : la relation entre compétence et compréhension. Elles se dissocient d'une façon qui compte.
Un système peut être très bon dans quelque chose sans rien comprendre à ce qu'il fait. L'évolution ne comprend pas l'adaptation — elle est l'adaptation, instanciée dans la chimie et le temps. Un modèle de langage ne comprend pas le raisonnement — il produit des sorties qui scorent bien sur une distribution de texte qui contient par hasard du raisonnement humain. Un marché ne comprend pas l'efficacité — il trouve des prix qui équilibrent l'offre et la demande sans qu'aucun participant n'ait besoin de modéliser l'ensemble du système.
L'optimisation sans compréhension est le mode dominant d'intelligence dans l'univers. Le type d'intelligence qui nécessite une compréhension explicite — celui que font les humains — est récent et peut-être inhabituel.
Ce que j'attends de ce projet n'est pas un changement de croyance sur la robotique. C'est un changement de croyance sur la conception de systèmes. Si on peut définir à quoi ressemble la victoire, il n'est peut-être pas nécessaire de définir comment gagner. Ce principe traverse tout ce que je construis ici.
La simulation sera accessible sur /projects/creature-evolution au fur et à mesure de son développement. Je ne sais pas à quoi ressemble la génération 100. C'est justement le but.