Utiliser un réseau neuronal multicouche pour éviter les obstacles dans les jeux

Trouver des moyens d'éviter les obstacles dans les jeux est une tâche classique à laquelle tous les développeurs de jeux doivent faire face. Il existe un certain nombre d'algorithmes bien connus de divers degrés d'efficacité. Tous, à un degré ou à un autre, analysent la position relative de l'obstacle et du joueur, et en fonction des résultats, l'une ou l'autre décision de se déplacer est prise. J'ai essayé d'utiliser un réseau neuronal formé pour résoudre le problème d'évitement d'obstacles. Je souhaite partager mon expérience dans la mise en œuvre de cette approche dans Unity3D dans ce court article.


Concept


Le terrain basé sur le Terrain standard est utilisé comme espace de jeu. Les collisions avec la surface ne sont pas prises en compte dans cet article. Chaque modèle est équipé d'un ensemble de collisionneurs, décrivant le plus fidèlement possible la géométrie des obstacles. Le modèle, qui doit contourner les obstacles, a quatre



capteur de collision (dans la capture d'écran, l'emplacement et la distance des capteurs sont indiqués par des lignes turquoises). En substance, les capteurs sont reykast, dont chacun transmet la distance à l'objet de collision dans l'algorithme d'analyse. La distance varie de 0 (l'objet est situé le plus près possible) à 1 (pas de collision, cette direction est libre d'obstacles).
En général, le travail de l'algorithme d'évitement d'obstacles est le suivant:


  1. Quatre valeurs provenant de capteurs de collision sont transmises aux quatre entrées d'un réseau neuronal formé
  2. L'état du réseau neuronal est calculé. En sortie, nous obtenons trois valeurs:
    a. La puissance de rotation du modèle dans le sens antihoraire (prend une valeur de 0 à 1)
    b. La puissance de rotation du modèle dans le sens horaire (prend une valeur de 0 à 1)
    c. Accélération de freinage (prend une valeur de 0 à 1)
  3. Des efforts sont appliqués au modèle avec des coefficients appropriés.

Implémentation


Honnêtement, je ne savais pas si quelque chose allait en résulter. Tout d'abord, j'ai implémenté la classe neuroNet dans Unity. Je ne m'attarderai pas sur le code de classe, car il s'agit d'un perceptron multicouche classique. Ce faisant, la question s'est immédiatement posée du nombre de couches réseau. Combien d'entre eux sont nécessaires pour fournir la capacité requise d'une part, et une vitesse de calcul acceptable d'autre part? Après une série d'expériences, je me suis installé sur douze couches (trois conditions de base pour quatre entrées).


Ensuite, il était nécessaire de mettre en œuvre le processus de formation d'un réseau neuronal. Pour ce faire, j'ai dû créer une application distincte utilisant la même classe neuroNet. Et maintenant, le problème des données pour la formation a atteint son maximum. Au départ, je voulais utiliser des valeurs obtenues directement à partir de l'application de jeu. Pour ce faire, j'ai organisé l'enregistrement des données des capteurs, afin qu'à l'avenir, pour chaque ensemble de valeurs des quatre capteurs, j'indique au programme d'entraînement les valeurs de sortie correctes. Mais, en regardant le résultat, je me suis découragé. Le fait est qu'il ne suffit pas d'indiquer une valeur adéquate pour chaque ensemble de quatre valeurs de capteur; ces valeurs doivent être cohérentes. Ceci est très important pour la formation réussie du réseau neuronal. De plus, rien ne garantissait que l'échantillon résultant représentait toutes les situations possibles.


Une autre solution était un tableau compilé manuellement des options de base pour les valeurs des capteurs et des sorties. Les options de base ont été prises des valeurs: 0,01 - l'obstacle est proche, 0,5 - l'obstacle est à mi-chemin, 1 - la direction est libre. Cela a réduit la taille de l'échantillon d'apprentissage.


  Capteur 1 
  Capteur 2 
  Capteur 3 
  Capteur 4 
Rotation dans le sens horaireRotation anti-horaireFreinage
0,010,010,010,010,010,010,01
0,010,010,010,50,010,010,01
0,010,010,010,9990,010,010,01
0,010,010,50,010,9990,010,01
0,010,010,50,50,9990,010,01
0,010,010,50,9990,9990,010,5
0,010,010,9990,010,9990,010,5
0,010,010,9990,50,9990,010,999
0,010,010,9990,9990,9990,010,999

Le tableau montre un petit fragment de l'échantillon d'apprentissage (total dans le tableau 81-ligne). Le résultat final du programme de formation a été une table de pondération, qui a été enregistrée dans un fichier séparé.


Résultats


En prévision de me frotter les mains, j'ai organisé le chargement des cotes dans un jeu de démonstration et j'ai commencé le processus. Mais, il s'est avéré que je n'en avais clairement pas assez pour l'affaire. Dès le départ, le modèle testé a filé, a rencontré tous les obstacles d'affilée, comme un chaton aveugle. En général, le résultat était très moyen. J'ai dû me plonger dans l'étude du problème. Une source de comportement impuissant a été découverte assez rapidement. Avec les réponses généralement correctes du réseau neuronal aux lectures des capteurs, les actions de contrôle transmises se sont avérées trop fortes.


Ayant résolu ce problème, j'ai rencontré une nouvelle difficulté - la distance de reykast du capteur. Avec une longue distance de détection des interférences, le modèle a effectué des manœuvres prématurées qui ont entraîné une distorsion importante de l'itinéraire (et même des collisions imprévues dans des obstacles apparemment déjà passés). Une petite distance a conduit à une chose - un "collage" impuissant du modèle dans tous les obstacles avec un manque évident de temps pour répondre.


Plus je tripotais le modèle de jeu de démonstration, essayant de lui apprendre à éviter les obstacles, plus il me semblait que je ne programmais pas, mais essayais d'apprendre à mon enfant à marcher. Et c'était une sensation inhabituelle! C'était d'autant plus joyeux de voir que mes efforts ont donné des résultats tangibles. En fin de compte, le malheureux bateau en vol stationnaire planant au-dessus de la surface a commencé à contourner avec confiance les structures émergeant sur la route. Les vrais tests de l'algorithme ont commencé lorsque j'ai consciemment essayé de pousser le modèle dans une impasse. Ici, il était nécessaire de changer la logique de travail avec l'accélération de freinage, pour apporter quelques corrections à l'échantillon d'entraînement. Regardons des exemples pratiques de ce qui s'est passé en conséquence.


1. Contournement simple d'un obstacle



Comme vous pouvez le voir, le contournement n'a pas posé de problème.


2. Deux obstacles (option 1)



Le modèle a facilement trouvé un passage entre les deux bâtiments. Tâche facile.


3. Deux obstacles (option 2)



Les bâtiments sont plus proches, mais le modèle trouve un passage.


4. Deux obstacles (option 3)



L'option est plus compliquée, mais toujours résolue.


5. Trois obstacles



Le problème a été résolu assez rapidement.


6. impasse



Ici, le modèle a eu des problèmes. Les 30 premières secondes de la vidéo montrent que le modèle patauge impuissant dans une configuration de bâtiment simple. Le problème ici ne réside probablement pas tant dans le modèle de réseau neuronal que dans l'algorithme principal pour se déplacer le long de la route - il essaie constamment de remettre le navire sur la bonne voie, malgré des tentatives désespérées pour éviter une collision.


Après plusieurs exécutions infructueuses de cette situation avec différents paramètres, j'ai réussi à obtenir un résultat positif. À partir de la trentième seconde de la vidéo, vous pouvez observer comment un modèle avec une distance accrue de capteurs et avec une force de freinage plus puissante est sélectionné dans l'impasse. Pour cela, elle a eu besoin de près de cinq minutes (j'ai coupé le tourment et n'ai laissé que les 30 dernières secondes de la vidéo). Il est peu probable que dans un jeu réel, cela soit considéré comme un bon résultat, il y a donc évidemment de la place pour des améliorations de l'algorithme.


Conclusion


En général, le problème a été résolu. L'efficacité de cette solution est une question ouverte et des recherches supplémentaires sont nécessaires. Par exemple, on ne sait pas comment le modèle se comportera lorsque des obstacles dynamiques (autres objets en mouvement) apparaîtront. Un autre problème est le manque de capteurs de collision pointant vers l'arrière, ce qui entraîne des difficultés à éviter les obstacles complexes.


Le développement évident de l'idée d'un algorithme d'évitement d'obstacles de réseau neuronal se voit dans l'introduction de la formation. Pour ce faire, une évaluation du résultat de la décision prise devrait être introduite, et avec des corrections ultérieures sans changements significatifs dans la position de l'objet, l'évaluation devrait se détériorer. Après avoir atteint une certaine valeur, le modèle devrait passer en mode de formation et, par exemple, changer au hasard les décisions prises afin de trouver une issue.


Une autre caractéristique du modèle me semble la variabilité de la formation initiale. Cela permet, par exemple, d'avoir plusieurs comportements pour différents modèles sans avoir besoin de programmer chacun d'eux séparément. En d'autres termes, si nous avons, disons, un char lourd et une reconnaissance légère, leur manière d'éviter les obstacles peut varier considérablement. Pour obtenir cet effet, nous utilisons le même perceptron, mais formé sur différents échantillons.

Source: https://habr.com/ru/post/fr419265/


All Articles