Wege zu finden, um Hindernissen in Spielen auszuweichen, ist eine klassische Aufgabe, mit der sich alle Spieleentwickler befassen müssen. Es gibt eine Reihe bekannter Algorithmen mit unterschiedlichem Wirkungsgrad. Alle analysieren bis zu dem einen oder anderen Grad die relative Position des Hindernisses und des Spielers, und basierend auf den Ergebnissen wird die eine oder andere Entscheidung getroffen, sich zu bewegen. Ich habe versucht, ein trainiertes neuronales Netzwerk zu verwenden, um das Problem der Hindernisvermeidung zu lösen. In diesem kurzen Artikel möchte ich meine Erfahrungen bei der Implementierung dieses Ansatzes in Unity3D teilen.
Konzept
Das auf dem Standardgelände basierende Terrain wird als Spielraum verwendet. Kollisionen mit der Oberfläche werden in diesem Artikel nicht berücksichtigt. Jedes Modell ist mit einer Reihe von Kollidern ausgestattet, die die Geometrie von Hindernissen so genau wie möglich beschreiben. Das Modell, das Hindernisse umgehen muss, hat vier
Kollisionssensor (im Screenshot werden Position und Entfernung der Sensoren durch türkisfarbene Linien angezeigt). Im Wesentlichen handelt es sich bei Sensoren um Reykast, von denen jeder die Entfernung zum Kollisionsobjekt im Analysealgorithmus durchläuft. Der Abstand variiert von 0 (das Objekt befindet sich so nah wie möglich) bis 1 (keine Kollision, diese Richtung ist frei von Hindernissen).
Im Allgemeinen funktioniert der Algorithmus zur Vermeidung von Hindernissen wie folgt:
- Vier Werte von Kollisionssensoren werden den vier Eingängen eines trainierten neuronalen Netzwerks zugeführt
- Der Zustand des neuronalen Netzes wird berechnet. Am Ausgang erhalten wir drei Werte:
a. Die Drehkraft des Modells gegen den Uhrzeigersinn (nimmt einen Wert von 0 bis 1 an)
b. Die Drehkraft des Modells im Uhrzeigersinn (nimmt einen Wert von 0 bis 1 an)
c. Bremsbeschleunigung (nimmt einen Wert von 0 bis 1 an) - Das Modell wird mit geeigneten Koeffizienten angestrebt.
Implementierung
Ehrlich gesagt hatte ich keine Ahnung, ob aus diesem Unternehmen etwas werden würde. Zunächst habe ich die neuroNet-Klasse in Unity implementiert. Ich werde nicht auf den Klassencode eingehen, da es sich um ein klassisches mehrschichtiges Perzeptron handelt. Dabei stellte sich sofort die Frage nach der Anzahl der Netzwerkschichten. Wie viele davon sind erforderlich, um einerseits die erforderliche Kapazität und andererseits eine akzeptable Berechnungsgeschwindigkeit bereitzustellen? Nach einer Reihe von Experimenten habe ich mich auf zwölf Schichten festgelegt (drei Grundbedingungen für vier Eingaben).
Als nächstes war es notwendig, den Prozess des Trainings eines neuronalen Netzwerks zu implementieren. Dazu musste ich eine separate Anwendung erstellen, die dieselbe neuroNet-Klasse verwendete. Und jetzt ist das Problem der Daten für das Training auf seine volle Höhe gestiegen. Anfangs wollte ich Werte verwenden, die direkt von der Spieleanwendung erhalten wurden. Zu diesem Zweck habe ich die Datenerfassung von den Sensoren so organisiert, dass in Zukunft für jeden Wertesatz der vier Sensoren dem Trainingsprogramm die richtigen Ausgabewerte angezeigt werden. Aber als ich das Ergebnis betrachtete, wurde ich entmutigt. Tatsache ist, dass es nicht ausreicht, für jeden Satz von vier Sensorwerten einen angemessenen Wert anzugeben, diese Werte müssen konsistent sein. Dies ist sehr wichtig für das erfolgreiche Training des neuronalen Netzwerks. Darüber hinaus gab es keine Garantie dafür, dass die resultierende Stichprobe alle möglichen Situationen widerspiegelte.
Eine alternative Lösung war eine manuell zusammengestellte Tabelle mit grundlegenden Optionen für die Werte der Sensoren und Ausgänge. Die grundlegenden Optionen wurden Werte genommen: 0,01 - das Hindernis ist nah, 0,5 - das Hindernis ist auf halbem Weg, 1 - die Richtung ist frei. Dies hat die Größe der Trainingsstichprobe verringert.
Sensor 1 | Sensor 2 | Sensor 3 | Sensor 4 | Drehung im Uhrzeigersinn | Drehung gegen den Uhrzeigersinn | Bremsen |
---|
0,01 | 0,01 | 0,01 | 0,01 | 0,01 | 0,01 | 0,01 |
0,01 | 0,01 | 0,01 | 0,5 | 0,01 | 0,01 | 0,01 |
0,01 | 0,01 | 0,01 | 0,999 | 0,01 | 0,01 | 0,01 |
0,01 | 0,01 | 0,5 | 0,01 | 0,999 | 0,01 | 0,01 |
0,01 | 0,01 | 0,5 | 0,5 | 0,999 | 0,01 | 0,01 |
0,01 | 0,01 | 0,5 | 0,999 | 0,999 | 0,01 | 0,5 |
0,01 | 0,01 | 0,999 | 0,01 | 0,999 | 0,01 | 0,5 |
0,01 | 0,01 | 0,999 | 0,5 | 0,999 | 0,01 | 0,999 |
0,01 | 0,01 | 0,999 | 0,999 | 0,999 | 0,01 | 0,999 |
Die Tabelle zeigt ein kleines Fragment der Trainingsstichprobe (insgesamt in Tabelle 81 - Zeile). Das Endergebnis des Trainingsprogramms war eine Gewichtungstabelle, die in einer separaten Datei gespeichert wurde.
Ergebnisse
In Erwartung, meine Hände zu reiben, organisierte ich das Laden der Gewinnchancen in ein Demospiel und startete den Prozess. Aber wie sich herausstellte, habe ich eindeutig nicht genug für den Fall getan. Von Anfang an stieß das getestete Modell wie ein blindes Kätzchen auf alle Hindernisse in einer Reihe. Im Allgemeinen war das Ergebnis sehr mittelmäßig. Ich musste mich mit dem Problem befassen. Eine Quelle hilflosen Verhaltens wurde ziemlich schnell entdeckt. Mit den im Allgemeinen korrekten Antworten des neuronalen Netzwerks auf Sensorablesungen erwiesen sich die übertragenen Steueraktionen als zu stark.
Nachdem ich dieses Problem gelöst hatte, stieß ich auf eine neue Schwierigkeit - die Sensor-Reykast-Entfernung. Mit einer großen Entfernung zur Erkennung von Interferenzen führte das Modell vorzeitige Manöver durch, die zu einer erheblichen Verzerrung der Route führten (und sogar zu unvorhergesehenen Kollisionen bei scheinbar bereits passierten Hindernissen). Eine kleine Entfernung führte zu einer Sache - einem hilflosen "Einstecken" des Modells in alle Hindernisse mit einem deutlichen Mangel an Zeit für eine Reaktion.
Je mehr ich mich mit dem Demo-Spielmodell beschäftigte und versuchte, es zu lehren, um Hindernissen auszuweichen, desto mehr schien es mir, dass ich nicht programmierte, sondern mein Kind das Laufen beibrachte. Und es war eine ungewöhnliche Sensation! Umso erfreulicher war es zu sehen, dass meine Bemühungen greifbare Ergebnisse brachten. Am Ende begann das unglückliche Schwebeboot, das über der Oberfläche schwebte, souverän um die auf der Route entstehenden Strukturen herumzugehen. Die eigentlichen Tests für den Algorithmus begannen, als ich bewusst versuchte, das Modell in eine Sackgasse zu treiben. Hier musste die Arbeitslogik mit Bremsbeschleunigung geändert werden, um einige Korrekturen am Trainingsmuster vorzunehmen. Schauen wir uns praktische Beispiele an, was als Ergebnis passiert ist.
1. Einfache Umgehung eines Hindernisses
Wie Sie sehen, hat der Bypass keine Schwierigkeiten verursacht.
2. Zwei Hindernisse (Option 1)
Das Modell fand leicht einen Durchgang zwischen den beiden Gebäuden. Einfache Aufgabe.
3. Zwei Hindernisse (Option 2)
Gebäude sind näher, aber das Modell findet eine Passage.
4. Zwei Hindernisse (Option 3)
Die Option ist komplizierter, aber immer noch gelöst.
5. Drei Hindernisse
Das Problem wurde ziemlich schnell gelöst.
6. Sackgasse
Hier hatte das Modell Probleme. Die ersten 30 Sekunden des Videos zeigen, dass das Modell in einer einfachen Gebäudekonfiguration hilflos zappelt. Das Problem liegt hier höchstwahrscheinlich weniger im neuronalen Netzwerkmodell als im Hauptalgorithmus für die Bewegung entlang der Route - er versucht beharrlich, das Schiff wieder auf Kurs zu bringen, trotz verzweifelter Versuche, eine Kollision zu vermeiden.
Nach mehreren erfolglosen Durchläufen dieser Situation mit unterschiedlichen Parametern gelang es mir, ein positives Ergebnis zu erzielen. Ab der dreißigsten Sekunde des Videos können Sie beobachten, wie ein Modell mit einem größeren Abstand der Sensoren und einer stärkeren Bremskraft aus der Sackgasse ausgewählt wird. Dafür brauchte sie fast fünf Minuten Zeit (ich schnitt die Qual aus und ließ nur die letzten 30 Sekunden des Videos). Es ist unwahrscheinlich, dass dies in einem echten Spiel als gutes Ergebnis angesehen wird, daher gibt es offensichtlich Raum für Verbesserungen des Algorithmus.
Fazit
Im Allgemeinen wurde das Problem gelöst. Wie effektiv diese Lösung ist, ist eine offene Frage, und es sind weitere Untersuchungen erforderlich. Beispielsweise ist nicht bekannt, wie sich das Modell verhält, wenn dynamische Hindernisse (andere sich bewegende Objekte) auftreten. Ein weiteres Problem ist das Fehlen von nach hinten zeigenden Kollisionssensoren, was zu Schwierigkeiten bei der Vermeidung komplexer Hindernisse führt.
Die offensichtliche Weiterentwicklung der Idee eines Algorithmus zur Vermeidung von Hindernissen für neuronale Netze zeigt sich in der Einführung des Trainings. Zu diesem Zweck sollte eine Bewertung des Ergebnisses der getroffenen Entscheidung eingeführt werden, und bei nachfolgenden Korrekturen ohne wesentliche Änderungen der Position des Objekts sollte sich die Bewertung verschlechtern. Bei Erreichen eines bestimmten Wertes sollte das Modell in den Trainingsmodus wechseln und beispielsweise die getroffenen Entscheidungen zufällig ändern, um einen Ausweg zu finden.
Ein weiteres Merkmal des Modells scheint mir die Variabilität des ersten Trainings zu sein. Dies ermöglicht es beispielsweise, mehrere Verhaltensweisen für verschiedene Modelle zu haben, ohne dass jedes einzeln programmiert werden muss. Mit anderen Worten, wenn wir beispielsweise einen schweren Panzer und eine leichte Aufklärung haben, kann ihre Art, Hindernissen auszuweichen, erheblich variieren. Um diesen Effekt zu erzielen, verwenden wir dasselbe Perzeptron, trainieren jedoch an verschiedenen Proben.