Explication du fonctionnement de Neural Bird
Posté le 14/06/2018 22:39
Dans
Neural Bird, Je cherche à faire évoluer une intelligence artificielle pour jouer à Flappy Bird. Je découperai donc cette explication en 3 parties :
1.Jouer à Flappy Bird
2.Intelligence Artificielle
3.Evoluer
1.Jouer à Flappy Bird
Nous devons d'abord définir ce que signifie "jouer à Flappy Bird".
Dans la version du jeu que j'ai programmé, il y a un oiseau(le joueur) qui peux décider de "sauter" ou pas à chaque tour de boucle, cet oiseau avance vers deux tuyaux verticaux espacés par un trou.
Le but est de sauter au bon moment pour passer dans le trou et ne pas toucher les tuyaux.
Le programme NRLBIRD1 fait jouer une partie de flappy bird, le voici ci dessous:
ViewWindow 1, 127, 0, 1, 63, 0
1 -> A
32 -> B
0 -> C
30 -> D
RanInt#(0, 63 - 7 -> E
0 -> F
0 -> Z
Cls
Text 24, 61, "0 "
StoPict 1
BG-Pict 1
Do
C > -10 => C - 1 -> C
B + C -> B
D - 1 -> D
If D = 1 :Then
30 -> D
RanInt#(0, 63 - 7 -> E
IfEnd
Getkey => 5 -> C
Cls
If D = 10 :Then
If (B < E Or B > E + 7) :Then
0 -> A
Else
F + 500 -> F
Z + 1 -> Z
Text 24, 61, Z
StoPict 1
BG-Pict 1
IfEnd
IfEnd
PlotOn 10, B
F-Line D, 1, D, E
F-Line D, E + 7, D, 63
'Text 1,1,F
LpWhile A
J'ai ici enlevé le bout de code qui décidait de si il fallait sauter ou non et je l'ai remplacé par Getkey=>-5->G (ce qui signifie : si une touche est pressée, alors on saute)
Ce qu'il faut faire maintenant est définir l'information dont on a besoin pour savoir quand on doit sauter.
J'ai choisit comme information :
-La distance verticale entre l'oiseau et le milieu du trou
-La distance horizontale entre l'oiseau et les tuyaux
-La vitesse verticale de l'oiseau
"Jouer à Flappy Bird", c'est donc ici équivalent à "Doit on sauter ou non en fonction de ces trois valeurs?"
Le problème ici c'est : que faire de ces trois valeurs?
Parce que là on a :
1. récupérer les valeurs
2.???
3.sauter ou non
4.profit
On passe donc à la deuxième partie pour répondre à cette question.
2.Intelligence Artificielle
Pour savoir si on doit sauter ou non, je vais utiliser un réseau de neurones artificiels.
mais qu'est-ce que c'est qu'un réseau de neurones artificiels?
Là, je vais pas vraiment rentrer dans les détails de quesquecé et tout par peur de dire des bêtises parce que je suis pas un pro, mais en gros, c'est un modèle mathématique inspiré des neurones biologiques permettant de résoudre des problèmes(je sais, c'est très large comme définition, mais il y a plusieurs sortes de réseaux de neurones artificiels, je veux pas faire de la désinformation en généralisant, donc on va surtout s'intéresser ici à celui que j'ai utilisé dans le programme, qui est un
perceptron multicouche).
Voici à quoi ressemble le réseau de neurones utilisé ici :
Je vais d'abord expliquer comment un seul neurone fonctionne :
Un neurone a une ou plusieurs entrées et une sortie. Il associe à chaque entrée un poids et a un poids supplémentaire associé à une entrée toujours égale à 1.
Pour calculer la sortie, le neurone va faire la somme de chaque entrée multipliée par son poids respectif, puis va passer le résultat dans une fonction d'activation. J'utilise dans ce programme la fonction sigmoide qui renvoie un nombre entre 0 et 1.
Prenons l'exemple du neurone D sur le schéma, sa sortie aura pour valeur: sigmoide(A*poids1 + B*poids2 + C*poids3 + 1*poids4)
En faisant ça pour chaque neurone, voici le pseudocode pour avoir la sortie du réseau de neurones :
/// neurone D
A*poids1 + B*poids2 + C*poids3 + poids4 -> D /// somme pondérée des entrées
(1/(1 + (e^( - D)))) -> D // calcul de sigmoide(D)
/// neurone E
A*poids5 + B*poids6 + C*poids7 + poids8 -> E
(1/(1 + (e^( - E)))) -> E
/// neurone F
D*poids9 + E*poids10 + poids11-> F
(1/(1 + (e^( - F)))) -> F
voilà maintenant le code qui permet de choisir si il faut sauter ou pas :
(D - 10) / 20 -> G
(E + 3.5 - B) / 32 -> H
C / 5 -> I
List 1[1] * G + List 1[2] * H + List 1[3] * I + List 1[4] -> J
(1/(1 + (e^( - J)))) -> J
List 1[5] * G + List 1[6] * H + List 1[7] * I + List 1[8] -> K
(1/(1 + (e^( - K)))) -> K
List 1[9] * J + List 1[10] * K + List 1[11] -> L
(1/(1 + (e^( - L)))) -> L
L > 0.5 => 5 -> C
On remarque que j'ai stocké les 11 poids du réseau de neurones dans la liste 1 et que les variables ne sont pas les mêmes que dans le pseudocode.
Les trois premières lignes effectuent le calcul des trois entrées du réseau de neurones, on peut aussi remarquer que je divise chaque entrée par la valeur maximale qu'elle peut prendre pour la "normaliser", c'est à dire restreindre sa valeur entre 0 et 1 pour empêcher qu'une entrée influence plus le résultat du réseau de neurones qu'une autre parce qu'elle est d'un autre ordre de grandeur.
Et la dernière ligne fait sauter l'oiseau si la sortie du réseau de neurones est supérieure à 0,5.
Voilà notre "Intelligence artificielle"!
Ainsi, si vous prenez le programme NRLBIRD1 et rajoutez "{-0.7502818134,0.9997988678,0.4780217439,0.596196316,1.048353149,-1.109820854,0.2241587688,-0.7859120504,0.2589741613,-0.7438888932,0.06033758228 -> List 1" au début du programme pour initialiser les poids du réseau de neurones. En lançant le programme, vous devriez voir l'oiseau sauter de lui même quand il lui semble être le bon moment.
hepepep pas si vite, d'où tu les sors ces poids?
Vous avez tout à fait raison de poser cette question car avec ces poids, le réseau de neurones joue parfaitement au jeu, mais avec d'autres, il pourrait être très mauvais.
On peut dire que les poids définissent "l'intelligence" du réseau de neurones.
Ces poids on été obtenus grâce à l'algorithme que je vais présenter dans la 3ème partie, et grâce à Totoyo, que je remercie pour avoir fait tourner le programme pendant 4 jours!
3.Evoluer
Pour trouver les meilleurs poids, j'utilise un
algorithme génétique, voilà comment il fonctionne :
1.générer aléatoirement 10 ensembles de 11 poids
2.répéter 3 à 6 tant que le programme est ouvert
3.calculer le score de chaque ensemble de poids en les mettant dans la liste 1 et les faisant jouer une partie
4.Prendre le meilleur ensemble de poids et le mélanger avec un autre tiré aléatoirement ce qui donne deux nouvaux ensembles de poids
5.remplacer les deux moins bons ensembles de poids par les deux nouveaux
6. modifier au hasard certains poids
chaque tour de la boucle de 3 à 6 s'appelle une "génération".
Cet algorithme est dans le programme NRLBIRD que je vais décomposer étape par étape
1.
11 -> Dim List 11
{1 -> List 13
110 -> Dim List 12
Locate 1, 1, "Initialisation"
Locate 3, 2, "%"
For 1 -> θ To 110
Locate 1, 2, Int (100 * θ / 110)
Ran# * 2 - 1 -> List 12[θ
Next
Cette partie initialise les 10*11 = 110 poids dans la liste 12 avec des valeurs aléatoires entre -1 et 1
La liste 11 est aussi initialisée pour qu'elle puisse stocker les 10 scores de chaque ensemble de poids et le meilleur score atteint
La liste 13 stocke le nombre de générations et la moyenne des scores à chaque génération.
2.
3.
C'est cette partie du programme NRLBIRD qui calcule les scores de chaque ensemble de poids
For 0 -> θ To 9
Cls
Text 4, 80, List 13[1
Text 14, 68, " "
Text 14, 68, θ + 1
StoPict 1
BG-Pict 1
11 -> Dim List 1
For 1 -> r To 11
List 12[r + 11 * θ] -> List 1[r]
Next
Prog "NRLBIRD1"
F -> List 11[θ + 1
If F > List 11[11 :Then
F -> List 11[11
11 -> Dim List 10
For 1 -> r To 11
List 12[r + 11 * θ] -> List 10[r]
Next
IfEnd
Next
C'est une boucle qui à chaque tour copie les 11 valeurs d'un des 10 ensemble de poids contenus dans la liste 12 dans la liste 1, puis fait jouer une partie avec le programme NRLBIRD1 et récupère le score qui est stocké dans la variable F et le met dans la liste 11.
Enfin, si le score est supérieur au record, alors tous les poids qui viennent d'être "testés" sont transférés dans la liste 10 et le nouveau record est sauvegardé.
Une question importante à se poser est "comment définir le score?" puisque si la réponse était juste "le nombre de tuyaux passés", un oiseau sautant tout le temps et mourrant très haut loin du trou serait considéré comme aussi bon qu'un oiseau visant le trou en sautant au bon moment mais se loupant d'un pixel à la fin.
C'est pourquoi le score est défini par cette ligne de code :
F + 60 - Abs (E + 3.5 - B) -> F
à chaque frame de jeu, le score est incrémenté de 60 - la distance verticale entre l'oiseau et le trou, ainsi, plus l'oiseau est proche du trou, meilleur son score sera.
4. et 5.
Je récupère d'abord les deux moins bons ensembles de poids avec ce code :
10000000000 -> N
0 -> O
For 1 -> M To 10
If List 11[M] < N :Then
List 11[M] -> N
M -> O
IfEnd
Next
12345678912345 -> List 11[O
10000000000 -> N
0 -> P
For 1 -> M To 10
If List 11[M] < N :Then
List 11[M] -> N
M -> P
IfEnd
Next
O et P contiennent les "numéros" des deux moins bons ensembles de poids.
Puis je récupère le meilleur et un numéro au hasard :
- 10000000000 -> N
0 -> Q
For 1 -> M To 10
If List 11[M] > N And List 11[M] != 12345678912345 :Then
List 11[M] -> N
M -> Q
IfEnd
Next
RanInt#(1, 10) -> R
Q et R contiennent les "numéros" du meilleur ensemble de poids et d'un autre au hasard.
Ensuite je remplace les ensembles O et P par un mélange de Q et R :
For 1 -> S To 11
If Ran# > 0.5 :Then
List 12[S + (Q - 1) * 11] -> List 12[(O - 1) * 11 + S]
List 12[S + (R - 1) * 11] -> List 12[(P - 1) * 11 + S]
Else
List 12[S + (Q - 1) * 11] -> List 12[(P - 1) * 11 + S]
List 12[S + (R - 1) * 11] -> List 12[(O - 1) * 11 + S]
IfEnd
Next
Pour chaque poids, je met au hasard celui de Q dans O ou dans P et celui de R dans l'autre.
6.
Enfin, pour chaque valeur de la liste 12, il y a une faible chance que celle-ci soit un peu modifiée, ou soit une nouvelle valeur entre -1 et 1 :
For 1 -> S To 110
Ran# <= 0.05 => List 12[S] + Ran# * 0.2 - 0.1 -> List 12[S
Ran# <= 0.02 => Ran# * 2 - 1 -> List 12[S
Next
Voilà c'est tout!
Si je n'ai pas été clair ou si vous voulez que j'approfondisse sur certains passages, n'hésitez pas à poser des questions.
Citer : Posté le 17/06/2018 11:51 | #
Et cette histoire de fonction sigmoide, elle permet juste de renvoyer un nombre en 0 et 1 pour les poids ?
Citer : Posté le 17/06/2018 13:08 | #
Oui mais par exemple si on souhaite que le réseau puisse "résoudre" ce jeu et le même jeu à 90°, avec les mêmes neurones, c'est possible ? Et si on souhaite qu'il puisse donner les mêmes sorties avec une permutation des entrées (interversion de deux capteurs par exemple) ?
Oui c'est possible, si tu pivotes ta calculatrice ça fonctionnera toujours
Plus sérieusement, non ça ne fonctionnera pas. Le réseau à besoin de réapprendre. Après je pense qu'a partir d'une certaine complexité, le réseau est capable de comprendre le principe de passer entre les tuyaux.
Pour la permutation des capteurs, à mon avis ça fait tout foirer.
Et cette histoire de fonction sigmoide, elle permet juste de renvoyer un nombre en 0 et 1 pour les poids ?
Ce n'est pas pour les poids, mais pour la somme des poids multipliés par la valeur des neurones d'entrée. Si on reprend le shéma de Alexot
Un poids c'est un trait en fait. En oubliant le neurone gris à 1.
D = A*poids_AD + B*poids_BD + C*poids_CD
Sachant que A, B, et C sont les valeurs des "capteurs" entre 0 et 1, et les poids entre 0 et 1.
La somme peut excéder 1, donc il faut la caster en 0 et 1, la fonction simoid va de ]-infini;+infini[ à [0;1], et est casi linéraire pour les valeurs entre 0 et 2. C'est très pratique
Après il existe d'autres fonctions que la sigmoid. Apparamment certainent permettent de converger plus vite vers la solution optimale.
Parce qu'en général, on a plus de capteurs que d'actionneurs. C'est plus difficile de comprendre le monde que d'agir dessus.
Si ça vous intéresse, cette vidéo est parfaite :
Citer : Posté le 17/06/2018 13:45 | #
Ce que je trouve paradoxal, c'est qu'on parle d'intelligence artificielle
Oui, c'est peut être pas vraiment une "intelligence artificielle" mais j'utilise ce terme dans le sens "un système qui joue automatiquement au jeu" un peu comme quand on parle d'IA de morpion ou de x jeu. Après, la façon dont il joue est plutôt "intuitive" et c'est pour ça qu'il n'a pas besoin de connaître la physique du jeu ou comprendre le gameplay, c'est un peu comme le vélo, il faut de l'entraînement pour savoir en faire, mais une fois appris c'est intuitif, tu n'as pas besoin de réfléchir à comment te pencher en fonction de la pesanteur pour ne pas tomber, ou de réfléchir à quels muscles activer dans quel ordre... C'est un peu une activité "bête" mais il faut quand même avoir un cerveau ou une sorte d' "intelligence" pour la réaliser et je pense qu'on peux dire la même chose pour flappy bird.
Citer : Posté le 17/06/2018 15:04 | #
Je dirais même plus, toute la playlist de 3blue1brown est excellente : https://www.youtube.com/watch?v=aircAruvnKk&list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi