Sonntag, 29. April 2007

Problem Fixed, Performance Optimierungen

Problem
Mithilfe einer Analyse vom Remote Debugger der PhysX-Engine kam ich dem Fehler auf die Schliche:
Man sieht, dass der Roboter ins Nirvana transformiert wurde, d.h. die Position wurde auf eine ungültige Zahl gesetzt. Daraus folgte ein Absturz in der Physik-Engine.

Warum aber verhielt sich dies so? Nach wenigen Tests stellte sich heraus, dass setDriveOrientation dies verursacht, wenn man hier ein ungültiges Argument (NaN, Not a Number) übergibt. Dieses Argument ist direkt der Output vom neuronalen Netz, daraus folgt, dass wohl der Input des neuronalen Netzes NaN's enthielt. Nachdem ich diesen untersuchte, zeigt sich, dass ein Arkussinus als Input verwendet wird - dies ist immer gefährlich, denn dieser kann NaN's produzieren (wenn das Argument des Arkussinus im Betrag grösser als 1 ist). Nun habe ich also eine Prüfung eingebaut, dass das Argument beim Arkussinus nicht grösser als 1 ist. Der Fehler entstand wahrscheinlich durch Rechenungenauigkeiten bei Float-Variabeln, denn eigentlich sollte das Argument beim asin gar nicht grösser als 1 sein.

Performance
Beim neuronalem Netz werden jetzt C-Arrays anstatt stl-Container verwendet. Dies optimiert das Programm vor allem im Debug-Mode. Zusätzlich habe ich an der Anzahl Roboter geschraubt, die gleichzeitig simuliert werden. Es zeigte sich, dass je weniger Roboter vorhanden waren, die Physik proportional zur Anzahl Roboter schneller rechnen kann. Eine einfache Erklärung für dieses Problem finde ich nicht, denn die Roboter agieren vollständig unabhängig voneinander. Indem ich nun meine Generation von 200 Robotern in Häppchen von immer 10 unterteile (anstatt 100), wurde die Simulation ca. 30% schneller.

Als Abschluss des heutigen Arbeitstages habe ich noch ein Video hochgeladen, dass den fittesten Roboter zeigt: Er verhält sich verräterisch natürlich.

Video vom fittesten Roboter

Das Diagramm, welches die Entwicklung der Fitness zeigt, darf natürlich auch nicht fehlen.

Bemerkenswert dabei ist, dass der Roboter noch nicht klar das Maximum erreicht hat, die Kurve beginnt nur leicht zu stagnieren.

Samstag, 28. April 2007

First Post

Einführung
Ich habe mich entschieden, einen Blog einzurichten, um meine Gedanken festhalten zu können. Der Vorteil davon ist, dass ich so im Internet - von überall aus verfügbar - die Fortschritte zu Papier bringen kann, damit auch andere Beteiligte, wie z.B. mein Betreuer, sehen kann, wie sich meine Simulation weiterentwickelt.

Fortschritt bis Einführung zum Blog
Als erstes ging es darum, eine Umgebung für meine Roboter zu schaffen. Ich entschied mich für die irrlicht Engine, da sie sehr einfach zu gebrauchen ist und für meine Zwecke völlig ausreicht. Um die Physik zu simulieren nutze ich die PhysX-Engine, die auch in vielen professionellen Spielen Verwendung findet. Das Einrichten der Umgebung ging relativ schnell voran, da ich mit beiden Engines früher schon Erfahrung gesammelt hatte. Nun kam mein erstes Experiment: Der Hopper. Bei diesem Roboter ging es darum zu schauen, ob sich meine Idee bewährte und ob ich die Maturarbeit, wie ich sie mir vorstellte, durchführen konnte. Es handelte sich um einen Roboter, der ein automatisches Sprunggelenk (algorithmisch gesteuert, nicht neuronal) besitzt und somit kontinuierlich springt. Als Steuerung setzte ich im Prinzip ein neuronales Netz ohne Hidden-Layers ein (also in der Form: output = weight1*input1 + weight2*input2 + ... + weightn*inputn). Der Roboter war in der Lage, auf den Körper direkt ein Drehmoment auszuwirken, was physikalisch eigentlich nicht korrekt ist (normalerweise können Roboter nur ihre Gelenke steuern, nicht aber direkt Kräfte auf sich einwirken lassen). Dieser Drehmoment wurde mit dem simplen neuronalen Netz berechnet, also Inputs dienten verschiedene Informationen über die aktuelle Lage des Roboters wie Höhe über Boden, Winkel, Touch-Sensor und noch ein paar andere. Die Weights des neuronalen Netzes wurden dann evolutionär optimiert (dazu komme ich zu einem anderen Zeitpunkt noch). Es zeigte sich, dass relativ rasch (innerhalb weniger Generationen) Gewichte gefunden werden konnten, die den Roboter sehr effizient fortbewegen liessen, das Entwicklungspotenzial (d.h. wie effizient der Roboter noch werden kann) war aber auf Grund der relativ einfachen Aufgabestellung (sich möglichst schnell fortbewegen) und wegen der physikalischen Inkorrektheit sehr rasch erschöpft:


Diese Abbildung zeigt, wie sich die Fitness der Roboter (d.h. wie schnell sie sich fortbewegten) entwickelte. Die blaue Linie repräsentiert jeweils das beste Individuum in der Population, die Pinke den Durchschnitt.

Ich habe einen Film hochgeladen, der zeigt, wie sich der Roboter fortbewegt - bemerkenswert ist dabei, dass er sich physikalisch inkorrekt fortbewegt.

http://www.4shared.com/file/14912865/f15a14ba/firstexperiment.html

Mit diesem Experiment zeigte sich aber, dass es bei sich bei meinem Konzept um ein realistisches und realisierbares handelt, da ich mit relativ wenigem Zeitaufwand erste Resultate erzielen konnte.

Als nächstes implementierte ich dann ein richtiges neuronales Netz mit hidden Layers, da mir die bisherige Steuerung zu einfach und zu beschränkt war. Danach suchte ich einen neuen Phänotypen für den Roboter, da der Hopper wohl noch zu komplex für mich war (wenn er physikalisch korrekt simuliert werden sollte) und ich zuerst Erfahrungen bei einfacheren Robotern sammeln wollte.
Das Resultat war dann ein 2-dimensionaler Wurm mit 5 Teilen:

Hier ist mit 2-dimensional gemeint, dass er sich entlang einer Geraden und in die Höhe bewegen kann, nicht aber auf einer Ebene.

Der Wurm hat die Möglichkeit, jedes seiner 4 Gelenke über ein neuronales Netz zu steuern. Ich wählte 2 Hidden Layers mit je 12 Neuronen. Als Inputs dienten die 4 Winkel zwischen den jeweiligen Wurm-Teilen und die des letzten Frames, dies damit das neuronale Netz ein sich repetitiver Bewegungsablauf generieren konnte. 4 Output-Neuronen steuern dann die gewünschten Winkel zwischen den Wurm-Teilen. Die Physik Engine versucht dann mit entsprechenden Drehmomenten, diese zu erreichen.

Auch dieses Resultat lässt sich sehen: Im folgenden Video sieht man die Entwicklungen mit den Generationen: Jeweils der beste Roboter jeder Generation wird simuliert, zu hinterst die 1., zu vorderst die letze Generation.

http://www.4shared.com/file/14913483/371c0200/2dworm.html

Was ich heute gemacht habe
Mein Ziel war es, den Wurm so weiterzuentwickeln, dass er bei jedem Gelenk 2 Freiheitsgrade steuern kann. Der Vorteil ist, dass ich so von der 2D-Umgebung wegkomme. Die Folgen davon sind, dass die Bewegungsabläufe komplexer werden. Mein Wurm hat inzwischen ca. 400 Gene - die zu optimieren, ist keine einfache Aufgabe für den Computer. Ich könnte mir vorstellen, dass es viele Generationen braucht, damit sich der Wurm immer schneller fortbewegen kann.
Bei diesem Task war vor allem schwierig, das Rotations Target für das Gelenk zu berechnen. In der PhysX-Dokumentation wurde ich nicht schlüssig, wie ich die Rotation in 2 Teilrotationen aufsplitten kann, damit das neuronale Netz es einfacher hat, den Roboter zu steuern (eigentlich wäre ein Quaternion gefragt, welcher 4 Attributen besitzt, allerdings handelt es sich um ein Universal-Gelenk, das 2 Winkel als Target verlangt (siehe Grafik links)).

Die Lösung des Problems waren dann Euler-Winkel. Diese sind eigentlich genau die richtige Möglichkeit für mich, Rotationen zu beschreiben. Das Objekt wird zuerst entlang der einen Achse rotiert, danach wird es um die nächste Achse rotiert, wobei die Achse, um die rotiert wird, von der 1. Rotation bereits rotiert wurde. Das Gleiche gilt für die dritte Achse. Man kann es auch mit einem Flugzeug vergleichen:
Dieses Prinzip kann man auch für Target-Rotationseinstellungen (wie die Rotation von den beiden Bodies, die am Gelenk angeschlossen sind, im Verhältnis sein soll) von Gelenken anwenden, dort wird ein Quaternion verlangt, denn man aber aufgrund von Euler-Winkel berechnet.
http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles

Nun hatte ich dieses Problem also gelöst. Als Folge davon war das neuronale Netz mit der Zeit in der Lage, unseren 5-teiligen Wurm so zu steuern, dass er nach wenigen Generationen sich mehr oder weniger Effizienz fortbewegte. Im Release-Modus allerdings kam es immer wieder zu einem Crash. Das Problem ist, dass dieser Fehler nicht debug-bar ist, da er nur im Release-Modus und zudem in der DLL der Physikengine auftritt. Ich werde sehen, wie ich diesen Fehler wegkriege.