Résolution du solitaire anglais par algorithme génétique (applet java)
Transcription
Résolution du solitaire anglais par algorithme génétique (applet java)
Résolution du solitaire anglais par algorithme génétique (applet java) Nicolas Maisonneuve - 2002 Applet disponible sur : www.ifrance.com/nmaisonneuve/solitaire.html 1 Introduction Dans le cadre du module la vie artificielle, nous avons vu les techniques de programmation évolutionniste et notamment les algorithmes génétiques. Pour bien percevoir l'intérêt de ces méthodes par rapport à des approches plus classiques, il était intéressant de choisir un problème combinatoire de type NP-complet comme le solitaire anglais. De nombreuses méthodes de résolution ont déjà été appliquées à ce problème allant de la "Brute Force" bien pensée à des techniques comme le "Depth first search" [2]. Dans notre cas, nous proposerons une heuristique fondée sur un algorithme génétique inspiré de Pascal Glauser [3]. Notre but ne sera pas de trouver l'ensemble des solutions possibles mais une seule (pas toujours la même) en un temps raisonnable (quelques secondes). Nous présenterons tout d'abord le jeu puis le principe de résolution du problème, enfin nous terminerons avec une description de l'applet java ainsi que les résultats obtenus. 2 2.1 Présentation du jeu Principe du jeu Le solitaire a été inventé par un prisonnier emprisonné à la Bastille pendant la seconde moitié du XVIIe siècle. La première trace écrite du jeu date de 1710 et est due à Leibniz qui s'est intéressé de très près à ce jeu. Le plateau du jeu est constitué d'une planchette creusée de trous pouvant recevoir des billes. Le plateau est percé de 37 trous pour le solitaire Français et de 33 pour l'Anglais (figure 1), celui auquel nous allons nous intéresser. Figure 1 - Solitaire anglais ou "HI-Q": configuration de départ (à gauche) et configuration idéale d'arrivé (à droite). Les règles sont simples puisqu'il y n'en a qu'une seule. Au début du jeu, la bille au centre du plateau est enlevée. Le but du jeu est d'arriver à ce qu'il ne reste plus qu'une bille (l’excellence étant qu’elle soit au centre du plateau 1 ) en éliminant les billes de la même façon qu'aux dames: la bille par dessus laquelle une autre bille saute est enlevée du plateau de jeu. Seuls les sauts horizontaux et verticaux sont permis. A noter que d’autres configurations de départ autre que la suppression du pion central existent, cela peut être une figure comme le Cross, le Plus, le Fireplace, le Up-Arrow. 2.2 Un problème de complexité NP-complet Nous avons les règles, celles-ci sont relativement basiques, il ne nous reste plus qu’à faire calculer toutes les parties possibles à l’ordinateur pour trouver des solutions. Oui mais combien de parties différentes sont possibles? Il s'agit en fait d'un problème NP-complet. Pour cette version du solitaire, Durango Bill [1] a trouvé 577,116,156,815,309,849,672 parties différentes même si un grand nombre de celles-ci ne sont que des rotations ou des symétries d'autres parties. En imaginant que l'ordinateur calcule un million de parties par seconde, il faudrait plus de 18 millions d'années pour trouver toutes les solutions! Il ne serait donc pas raisonnable d'explorer tout l'espace de recherche, d'où la nécessité d'employer des méthodes heuristiques. Des techniques comme le « Depth first search » 2] ont déjà étaient employées avec succès: si l’algorithme est bien paramétré, une solution est trouvée en 1 seconde en ayant exploré 21000 « patterns » (distribution donnée de billes sur le plateau de jeu). Par ailleurs la solution est toujours la même puisque la recherche se fait de la même manière. Le challenge va donc être de trouver des solutions différentes en explorant si possible moins de « patterns ». 3 3.1 Principe de résolution L'algorithme génétique C’est là qu’interviennent plusieurs techniques et notamment l’algorithme génétique. L’algorithme génétique s’appuie sur le principe de sélection de Charles Darwin (en ajoutant la mutation, et les techniques de crossover). "Il ne fait que transposer ce que fait la nature à des systèmes artificiels. Il simule les processus évolutifs Darwiniens et génétiques s'appliquant aux chromosomes. Il transforme un ensemble d'objets en une population d'individus souvent représentés par des chaînes de caractères pour imiter les chaînes d'ADN. Chaque individu ayant alors une valeur d'adaptation (fitness) à son environnement. C’est cette valeur qui tend à augmenter à chaque génération. L'algorithme fait appel à quatre opérateurs de base : 1. l’évaluation du fitness. 2. la sélection : c'est le choix des individus en fonction de leur fitness 3. le croisement : c'est le mélange des bagages génétiques. 4. la mutation : le bagage génétique est modifié abruptement." Ce qui est intéressant avec les algorithmes génétiques est qu’il n’est pas nécessaire de savoir formaliser le problème. Le solitaire peut être vu comme une boite noire dont le paramètre 1 Pendant longtemps on a considéré que finir le jeu avec la bille au centre était une difficulté supplémentaire. Or A. Bialostocki [4] a démontré que la dernière bille ne peut être placée qu’à 2 emplacements possibles: au centre, emplacement n°17 sur la figure 1, et à l’extrémité, emplacements n°2 (plus les rotations possibles: 20,32,14). En fait tous ces positions proviennent du même avant-dernier coup où les 2 billes sont placées sur 5et 10, autrement écrit on ne peut que réussir à mettre la bille au centre! Par contre, dans le cas du solitaire français, il est impossible de finir avec la bille au centre. d’entrée est la liste des enchaînements de pions, et la sortie le nombre de pions qu’il reste. La résolution du solitaire est alors un problème d’optimisation: minimiser le nombre de pions restants. Comment utiliser alors la puissance des algorithmes génétiques ici ? Tout simplement en représentant un individu comme une stratégie de jeu donnant alors un enchaînement de pions qui lui est propre. Son fitness sera le nombre de pions restant à la fin de la partie. Ainsi, au fur et mesure des générations ce fitness va en moyenne diminuer pour donner finalement le résultat. Comment modéliser une stratégie de jeu ? Nous utiliserons l’idée de Pascal Glauser en introduisant des valeurs (poids) sur chaque trou. En tout 7 poids différents (représentées par des lettres sur la figure 2) sont répartis sur le plateau de jeu. La valeur des poids est comprise entre 1 et 16. Autrement écrit, le génotype d’un individu est l’ensemble des 7 gènes codés sur 4 bits (d’où 16 possibilités de valeurs) représentant les 7 poids différents (de A à G). ABA CDC ACEFECA BDFGFBD ACEFECA CDC ABA Figure 2 - distribution des poids sur le plateau. A chaque lettre correspond un poids donné. A un tour donné, pour savoir quelque coup jouer parmi l’ensemble des coups réalisables, on procède ainsi: pour chaque coup possible, on le joue, puis on calcule alors la somme des poids des trous encore occupés par les pions. Le coup dont cette somme est la plus importante sera finalement choisi. 3.2 Autres techniques Arrivé à ce stade, il ne reste plus qu’à programmer. Si l’on commence à exécuter le programme avec cette version, aucune solution ne sera trouvée en quelques minutes. En effet, malgré l’implémentation d’une sélection par roulette biaisée, d’un croisement par crossover, d’une mutation et les différents paramétrages effectués, aucun solution n’est trouvée (il reste toujours 2 à 3 billes à la fin du jeu) au bout de quelques minutes et ceci pour 20, 50 ou 100 individus. Pourquoi ? Sûrement parce que notre espace de recherche est très faible; nous avons au maximum 16^7 stratégies de jeu différentes ce qui est ridicule lorsque l’on connaît le nombre de parties possibles. Pour améliorer nos chances de réussite, nous allons utiliser plusieurs techniques. 3.2.1 Modélisation par graphe Un des problèmes est qu’il est possible de refaire une partie dont on sait qu’elle n’aboutira pas. Pour contrer cela, nous utiliserons une modélisation par graphe. L’espace de recherche sera modélisé par un graphe orienté où un nœud défini une configuration donnée de billes sur le plateau (« pattern »). Un arc correspondra alors à l’action d’un coup, autrement écrit, la différence entre 2 nœuds reliés est du au mouvement d’un pion. Cette structure est plus précisément un arbre dont la racine est la configuration initiale. La profondeur d’un nœud est le nombre de tours qu’il a fallu jouer avant d’arriver à cette configuration. Nous rechercherons alors un nœud de profondeur 31 car il faut 31 coups (32 pions dans la configuration initiale - 1 qui reste à la fin) pour gagner. 3.2.2 Le backtracking A quoi nous sert cette modélisation ? D’une part, elle va nous éviter de refaire une partie déjà jouée en utilisant des techniques de backtracking. Le graphe se construit au fur et mesure des parties jouées par la population. A la fin d'une partie (non gagnée) d'un individu, le dernier nœud de profondeur n va enlever les arcs qui le relient à ces pères. Si un père n’avait que lui pour fils alors il va lui aussi rompre les liens avec ses pères de profondeur n-2. Et ceci de manière récursive. L’intérêt est d’éliminer au fur et à mesure les coups possibles pour une configuration de jeu donnée. Un individu ne fera donc plus la même partie si nous lui redemandons de jouer. D’autre part, Le backtracking se fait sur le graphe (en éliminant des coups possibles) mais aussi sur les parties des individus. Au début de chaque partie, l’individu prend sa dernière partie faite et en commençant par la fin remonte jusqu’à trouver une configuration valide pour commencer à jouer. Ceci évite à l’individu de recommencer entièrement une partie. 3.2.3 Symétrie du problème Nous utiliserons la symétrie du plateau pour diminuer le temps de calcul et augmenter les chances. Lors de la création d’un nouveau nœud dans le graphe, une vérification est faite pour savoir si cette configuration n’est pas la symétrie 2 par rapport au centre d’un autre nœud existant. Si c’est le cas, elle ne sera pas mise dans le graphe; son père recevra comme nœud fils, le nœud trouvé. C’est comme ci le joueur, en pleine partie, tourne le plateau; cela ne change en rien la résolution du problème. Cela évite des calculs en plus et de la place mémoire en moins. 4 4.1 L’applet java Description de l’applet java Avec ces différentes techniques nous avons programmé une applet java. Elle se compose de 2 parties: La fenêtre principale (Figure 3) et un fenêtre permettant de visualiser la partie gagnante (Figure 4). Plusieurs paramètres sont disponibles. • 2 Les paramètres généraux Individus : Nombre d’individus pour la simulation Générations : Nombre de générations d’individus maximum. Parties : Nombre de parties jouées pour chaque individu entre 2 générations (pour chaque individu, le meilleur score de partie est gardé comme étant son fitness final avant la reproduction) A noter que l’on pourrait voir aussi une symétrie par rapport aux 2 axes centraux, vertical et horizontal mais nous ne l’avons pas exploité (voir [5] ). • Les paramètres génétiques Comme plusieurs versions ont, plusieurs types d’algorithme ont été implémentés pour chaque étape du processus génétique. Evaluation : Eval1 : nombre de pions enlevés Eval2 : nombre de pions enlevés et prise en compte la distance des pions restants par rapport au centre (à priori plus fin) Sélection Selec1 : prend les N/2 individus les plus adaptés Select2 : roulette biaisée de N/2 individus select3 : donne un nombre de 1 à N à chaque individu suivant son classement. Fait une roulette biaisée en prenant en compte ces nombres. Croisement Crois1 : pour un couple d’enfants, chaque gène d’un enfant hérite du gène d’un des 2 parents choisi au hasard avec une chance ½. L’autre enfant héritera alors du gène de l’autre parent. Crois2 : pour un couple d’enfants, crossover à 1 point (taux de 60%) Mutation (taux de 1%) Mut1: taux de Mutation pour un gène. Inverse un bit dans la séquence Mut2: taux de Mutation pour un gène. Met une nouvelle séquence au hasard Mut3 : taux de Mutation pour chaque bit de la séquence. Inverse le bit (forte mutation) Figure 4 - Fenêtre de visualisation d'une partie Figure 3 - Fenêtre principale Chaque individu est représenté par un plateau de jeu. La couleur d’un emplacement est la valeur de son poids. Ainsi on peut facilement voir le phénotype de l’individu. Plusieurs informations (à droite de la Figure 3) nous sont données sur l’état de la simulation: Le nombre de générations effectuées, le nombre de configurations explorées (appelées « Patterns » dans l’applet), le temps de la simulation et la réussite ou l’échec sur la découverte d’une solution. 4.2 Fonctionnement Après avoir paramétré l’application, le lancement se fait en appuyant sur le bouton « GO ». Une fois la simulation finie, il est possible de voir la dernière partie jouée pour chaque individu en cliquant sur celui-ci. A noter que les individus sont classés par ordre décroissant de fitness: celui qui a le plus grand fitness est en haut à gauche (un bug d'affichage est présent pour le premier individu, ce n'est pas le bon mais si l’on clique cela fonctionne). Si nous voulons voir la meilleure partie réalisée, il suffit de cliquer sur l’individu en haut à gauche. Vous obtiendrez alors la fenêtre de la figure 4. Il ne vous reste plus qu’à faire défiler la partie avec les boutons. 4.3 Résultats Selon le paramétrage que l’on effectue, les chances de réussite, le nombre de configurations explorés et le temps de recherche varient. C’est pourquoi plusieurs tests ont été faits pour essayer de calibrer aux mieux l’application. Chaque test comprenait 100 simulations pour avoir une moyenne valide. Un compromis doit être trouvé entre le pourcentage de réussite et le nombre de configurations explorées. Après quelques trentaines de tests, les résultats sont bons mais assez variables 3 puisque en moyenne, sur 100 simulations effectuées, on obtient : • Pour environ 99% de réussite (à chaque simulation une solution a été trouvée) une simulation explore 13000 à 15000 configurations en 500 ms (sur un Athlon 1,5GigaHz, sans graphisme) ceux-ci en prenant comme paramètres :Eval1, Sel1, Crois1, Mut3, taux de mutation=0.03, taux de crossover=0.06, parties=1 ; individus=20 ; • Pour environ 80% de réussite 1000 à 12000 configurations explorées en 400 ms. ceux-ci en prenant comme paramètres :Eval1, Sel1, Crois1, Mut3, taux de mutation=0.03, taux de crossover=0.06, parties=1 ; individus=15 ; 5 Conclusion Cette version a plus de réussite et est plus rapide que l’algorithme génétique de Pascal Glauser. Elle réussit à diminuer le nombre de patterns explorés de 14000 en moyenne contre 21000 avec le « Depth first search » ce qui montre une meilleure heuristique de recherche. A noter qu’une des qualités des algorithmes génétiques est une recherche balançant efficacement entre l’exploitation des résultats obtenus et l’exploration hasardeuse de nouveaux candidats. Or dans notre cas on sent bien que l’exploration joue un rôle majeur (Mut3, type de mutation forte, donne les meilleurs résultats) et que l’exploitation (crossover) est mineure. En effet comme il n’existe pas de méthode ou stratégie plus ou moins définies pour trouver une solution à ce jeu, la recombinaison (crossover) de deux stratégies de jeux n’a a priori aucun raison de donner un meilleur résultat. Ainsi le poids de l’exploitation des résultats déjà trouvés joue un rôle faible dans la recherche de solution. Heureusement grâce au backtracking et à des méthodes de symétries, on n’arrive à tirer partie de l’expérience des jeux effectués. 3 Plusieurs tests comprenant 100 simulations ont été lancés. Nous avons obtenu des résultats assez variables entre les différents tests, c’est pourquoi nous mettrons des intervalles de valeur pour les résultats. 6 Les algorithmes génétiques constituent actuellement un sujet de recherche important dans le cadre de l’optimisation et de la recherche opérationnelle. Parmi les principaux avantages des algorithmes génétiques, nous pouvons signaler : 1. La recherche d’un optimum s’effectue à partir d’une population et non d’un point unique. Ce parallélisme implicite permet de proposer plusieurs solutions différentes en fin d’exécution. 2. il n’est pas nécessaire de savoir formaliser le problème.Seul l’évalution du solution doit pouvoir être faite. 3. Les algorithmes génétiques utilisent des règles de transition probabilistes, et non déterministes, ce qui permet de s‘extraire des optima locaux. En contrepartie, les développements théoriques sur la convergence de ces procédures sont actuellement limités. Par ailleurs, la détermination des paramètres nécessaires à leur fonctionnement est basée généralement sur l’empirisme et le savoir-faire des utilisateurs 4 . Néanmoins, les algorithmes génétiques semblent être une technique efficace pour résoudre un grand nombre de problèmes dont la combinatoire rend difficile l’application des méthodes mathématiques classiques. 6 1. 2. 3. 4. 5. 4 Références Durango Bill , "33 Hole Peg Solitaire", http://www.durangobill.com/Peg33.html Armando B. Matos, "Depth-first search solves Peg Solitaire", http://citeseer.nj.nec.com/matos98depthfirst.html Pascal Glauser, "Java-Solitaire: Play or find solutions with a genetic algorithm!", http://homepage.sunrise.ch/homepage/pglaus/Solitaire/solitaire.htm Alexander Bogomolny, "Peg solitaire and Group Theory", http://www.cut-theknot.com/proofs/PegsAndGroups.shtml “Solitaire Brute force”, http://www.galad.com/programm/solitair/sanalyse.htm Ces réglages pourraient être optimisées par un autre algorithme génétique. 7