Intelligence artificielle et programmation Python publié le 12/04/2022  - mis à jour le 16/06/2022

TraAM 2021 - 2022

Pages : 1234

Narration de l’expérimentation

Intentions

Après avoir mis en évidence le phénomène d’apprentissage à l’aide de l’arbre de jeu élagué avec le tableur, la suite de l’expérimentation a consisté en une mise en œuvre algorithmique et une tentative de programmation de l’IA papier de Martin Gardner.

Les objectifs de la séance étaient les suivants :

  • présenter la modélisation du jeu Hexapawn en langage Python (représenté par une liste de listes ici) ;
  • leur faire compléter le cas d’une partie entre deux joueurs aléatoires, simuler celle-ci un grand nombre de fois afin de retrouver la fréquence de victoire de chaque couleur établie de manière empirique lors de la découverte du jeu, et de manière théorique avec l’étude de l’arbre de jeu
  • leur faire manipuler la fonction de jeu d’intelligence artificielle pour réaliser plusieurs affichages graphiques afin de mesurer la qualité de l’apprentissage en fonction du retour informatif choisi (punition ou récompense)
  • leur faire trouver, à partir de la liste des parties gagnantes pour l’IA, une stratégie gagnante d’Hexapawn qui est un jeu résolu pour les noirs.

La complexité de la modélisation en langage Python (plusieurs dizaines de lignes de code), les contraintes de temps et de programme (les listes Python ne sont pas au programme de seconde) ont fortement limité la part de programmation dévolue aux élèves.

Il a donc fallu tenir compte de leur niveau en programmation et leur donner suffisamment de tâches à réaliser pour que la séance demeure intéressante et atteigne ses objectifs.

Cette séance s’est déroulée sur une heure de cours en demi-classe, en salle informatique, sur un support de type notebook dans l’environnement Capytale (code d’accès : e4e6-513443).

Version pdf du notebook élève (PDF de 918.4 ko)

Notebook Capytale converti au format pdf

Modélisation du jeu

Le plateau de jeu a été modélisé sous la forme d’une liste de listes, le plateau initial étant défini par
plateau = [["N","N","N"],[" "," "," "],["B","B","B"]].
Après quelques explications sur le repérage des cases, je leur ai présenté les fonctions informatiques intégrant les règles du jeu et permettant la réalisation d’une partie complète. Quelques exemples, insérés dans des cellules à exécuter, étaient répartis dans le notebook afin d’illustrer mes propos et favoriser la représentation du jeu dans la machine.

Cette partie "magistrale" a permis de rappeler le fonctionnement du jeu et a mis en évidence la complexité que peut représenter la conversion d’un jeu d’apparence simple en programme informatique.

Partie entre deux joueurs aléatoires

La fonction de simulation d’une partie partie_alea_vs_alea() a été présentée et les élèves devaient compléter la portion de code relative aux tours des noirs, en faisant l’analogie avec la même portion de code relative aux blancs :

Bloc de code informatique : voir l'article sur le site.
  
  1. import random
  2. def partie_alea_vs_alea():
  3.     """renvoie une partie aléatoire entre deux joueurs qui jouent au hasard"""
  4.     tour = 0
  5.     plateau = [["N","N","N"],[" "," "," "],["B","B","B"]]
  6.     vainqueur = ""
  7.     partie_en_cours = True
  8.     while partie_en_cours == True:
  9.         if tour % 2 == 0: # au tour des blancs de jouer
  10.             coup_choisi = random.choice(coups_possibles(plateau,"B")) # choix d'un coup au hasard
  11.             plateau = plateau_joue(plateau, 'B', coup_choisi) # coup joué
  12.             if fin_de_partie(plateau, "B") == True: # vérification d'une éventuelle victoire des blancs
  13.                 partie_en_cours = False # arrêt de la partie
  14.                 vainqueur = "B" # affectation du nom du vainqueur dans la variable vainqueur
  15.         if tour % 2 == 1: # au tour des noirs de jouer
  16.             ...
  17.             ...
  18.             ...
  19.             ...
  20.             ...
  21.         tour = tour + 1 # on passe au tour suivant
  22.     pass #return vainqueur # remplacer pass par l'instruction mise en commentaire

Cette tâche n’a pas posé de problème, les élèves n’ayant qu’à faire un copier/coller de code et modifier le nom de la couleur, mais des erreurs d’indentation ont tout de même été observées, notamment dans la place du return qui a souvent été aligné avec les instructions de la boucle.

Il était ensuite demandé aux élèves de construire une fonction de simulation prenant en paramètre le nombre de parties et renvoyant la fréquence de victoires de chaque couleur.

La fonction était en partie construite et les élèves devaient compléter, avec une plus grande autonomie que précédemment, le corps de la fonction qui contenait deux structures conditionnelles symétriques, avec incrémentation d’un compteur.

Bloc de code informatique : voir l'article sur le site.
  
  1. def echantillon(nb_parties):
  2.     """renvoie la fréquence de victoire de chaque couleur après simulation de nb_parties parties"""
  3.     victoires_blancs = 0
  4.     victoires_noirs = 0
  5.     for _ in range(nb_parties):
  6.         ...
  7.         ...
  8.         ...
  9.         ...
  10.     pass # remplacer l'instruction pass par une instruction return ....

Après lecture attentive des spécifications de l’algorithme, la plupart des élèves a réussi à compléter la fonction.
Les écueils se sont situés à plusieurs niveaux et on retrouve les erreurs classiques inhérentes à l’apprentissage de Python :

  • erreurs dans l’orthographe des variables : oubli de "s" dans les variables victoires_blancs et victoires_noirs ;
  • confusion entre opérateur d’affectation = et opérateur de comparaison == ;
  • indentation des blocs de structure conditionnelle non respectée ;
  • oubli des deux points terminant l’en-tête des blocs de structure conditionnelle ;
  • difficulté à traduire l’incrémentation des compteurs en victoires_blancs = victoires_blancs + 1

L’appel de cette fonction a permis de mettre en évidence la notion de fluctuation d’échantillonnage, les élèves m’interpelant en me disant qu’ils n’avaient pas la même fréquence que moi au tableau ou que leur voisin(e).

Après leur avoir fait estimer oralement la dispersion des valeurs, je leur ai demandé d’augmenter progressivement la taille de l’échantillon, jusqu’à 15 000 tirages et ils ont pu constater, en comparant leurs valeurs, la stabilisation des fréquences, et percevoir, sous une forme expérimentale, la loi des grands nombres.

J’en ai alors profité pour leur rappeler que le modèle, c’est-à-dire l’arbre de jeu que nous avions étudié dans les séances précédentes donnait les probabilités de $\dfrac{259}{432}$ et $\dfrac{173}{432}$ et que les variations mesurées étaient de l’ordre de quelques millièmes pour les échantillons de taille 15 000. Nous avons ainsi validé le principe d’estimation d’une probabilité par une fréquence observée sur un échantillon.

Afin de mieux visualiser l’évolution des victoires de chaque couleur, un diagramme à ligne brisée a été proposé et a permis de mieux saisir la tendance pour le cumul des victoires. Celui-ci était construit selon le principe suivant :

  • pour une victoire des blancs, on se décale d’une unité vers la droite et d’une unité vers le bas ;
  • pour une victoire des noirs, on se décale d’une unité vers la droite et d’une unité vers le haut ;

Le cumul progressif des victoires blanches était alors caractérisé par une tendance baissière de la courbe :

Évolution des victoires dans le cas de deux joueurs aléatoires

Graphique illustrant l’évolution des victoires dans le cas des joueurs aléatoires (blancs vers le bas, noirs vers le haut)

 Page suivante : "Narration de l’expérimentation : Joueur aléatoire contre une IA"

Document joint

Cette archive contient l’intégralité des fichiers sources des documents (.tex, .py, .ipynb, .ods) utilisés pour la mise en œuvre de l’expérimentation