Algorithmen
Algorithmen sind die Abfolge von Befehlen, die einen Computer dazu bringen etwas zu tun, was man gern hätte.
Algorithmen sind die Abfolge von Befehlen, die einen Computer dazu bringen etwas zu tun, was man gern hätte.
Viele interessante Artikel befassen sich mit der Rotationsmatrix und wie man diese zur Transformation zwischen Koordinatensystemen verwenden kann. Für meinen konkreten Anwendungsfall – der Auswertung von fahrdynamischen Messdaten im Fahrzeugbetrieb – konnte ich jedoch kein “Kochrezept” finden welches mir bei der Umrechnung von gemessenen Beschleunigungswerten aus dem gedrehten, sensorfesten Koordinatensystem in das fahrzeugfeste Koordinantensystem weiterhelfen konnte.
Auf meinem Weg der Informationsbeschaffung bin ich unter anderen auch immer wieder auf den Motorblog-Beitrag von Paul Balzer und die Verlesungsvideos von Prof. Dr.-Ing. Jörg J. Buchholz zurückgekommen und habe im direkten Austausch wertvolle Tipps erhalten. Dabei ist auch das Angebot dieses Gastbeitrags entstanden, welches ich hiermit gerne wahrnehme um zu erläutern mit welcher Verfahrensweise ich einen Lösungsweg gefunden habe.
Für ein Vorstellungsgespräch bei meinem jetzigen Arbeitgeber habe ich unter anderem nach den gängigsten Filtern zur Beschreibung von Bewegungen im Raum gesucht. Dabei bin ich auf dieses 4D Kalman Filter gestoßen. Dort werden die mathematischen Zusammenhänge und die Implementierung des Filters in Matlab beleuchtet. Da ich bei meiner aktuellen Arbeitstelle allerdings mit C-Code arbeite, habe ich mir zum Ziel gesetzt, den Algorithmus in C umzusetzen.
Mein Vorschlag ist daher keine Kritik oder Neuentwicklung des besprochenen Filters an sich, sondern ist vielmehr als Ergänzung für Entwickler gedacht, die lieber in C arbeiten.
Der komplette Source-Code befindet sich hier. Abhängigkeiten zu externen (nicht-Standard-) Bibliotheken wurden gezielt vermieden, allerdings wurde an zwei Stellen externen Code mit in das Projekt eingebaut und entsprechend erwähnt. Dieser Filter kann durch einfache Änderungen als Basis für einen echten eingebetteten Filter hergenommen werden.
Ich werde hier Schritt für Schritt durch die wichtigsten Stellen des Codes gehen und an geeigneten Stellen auf den originalen Algorithmus verweisen.
Sollten programmiertechnische Fehler auffallen oder gar Copyright-Probleme entdeckt werden, bitte ich sehr darum, sich bei mir zu melden oder selbst über Github aktiv zu werden. Der Code wurde auf meiner Maschine mit meinem Compiler getestet, ein plattformübergreifendes Funktionieren kann nicht garantiert werden. Ich bin aber gerne für konstruktive Kritik offen!
Benötigte Includes, hauptsächlich für Ein- und Ausgabe und zu selbstgeschriebener KalmanFilter.h-Datei.
Erzeugen von Zufallsvariablen. Benutzt wird die Box Muller Transformation zur Erzeugung Gauß-verteilter Zufallszahlen (entnommen von hier). Dies entfällt bei einem echten System.
Matrizen werden entsprechend dem Beispiel mit Anfangsdaten befüllt.
In diesen Matrizen werden Zwischenergebnisse gespeichert. Diese werden hier mit Nullen initialisiert.
Es wird eine Ergebnis-Datei geöffnet und der Gnuplot initialisiert.
Hauptschleife, die den Algorithmus implementiert. Kann im Falle einer echten Anwendung durch eine while-Schleife ersetzt werden, in der zyklisch Werte abgefragt und weiterverarbeitet werden. Das Abfragen der Werte kann in
geschehen.
Vor jedem Rechenschritt steht der Teil des originalen Algorithmus, der darunter berechnet wird. In
werden Zwischenschritte der Berechnung ausgegeben. Hier kann man zu Debug-Zwecken auch andere Werte verwenden.
Housekeeping
Der Rest des C-Files implementiert die nötigen Algorithmen. Bei den meisten handelt es sich um einfache Matrix-Multiplikationen oder Ausgabefunktionen. Die einzig kompliziertere Rechnung ist die der Matrix-Inversen (entnommen von hier). Durch die Ausgabe im gnuplot und als Textdatei, kann der Entwickler schnell und einfach feststellen, dass er richtig entwickelt hat. Im Falle dieses Algorithmus habe ich so recht einfach meinen Output mit dem des Originaloutputs vergleichen können.
Enthält
hauptsächlich Funktionsdeklarationen der im vorhergehenden Teil beschriebenen Funktionen.
werden nützliche Abkürzungen definiert, die den Hauptcode etwas aufgeräumter wirken lassen sollen. Der Schlüssel #define weist den Compiler an, den nachfolgenden String VOR dem Compilieren durch den darauffolgenden String zu ersetzen.
Beispiel:
#define WRITEFLOATARRAYTOFILE(f, arr) int i; for ( i= 0; i < SIZEOFARRAY(arr); i++) fprintf(f, "%f\n", arr[i]);
Schreibt die Werte eines Float-Arrays (arr) in eine Textdatei (f), mit jeweils einem Zeilenvorschub (\n) unter Zuhilfenahme eines weiteren hilfreichen Shortcuts SIZEOFARRAY (da die Größe eines Arrays in C nicht ohne Umwege zu erhalten ist). Im Code ist diese Funktionalität durch die Abkürzung WRITEFLOATARRAYTOFILE(f, arr) zu erreichen.
Dieses Skript entfernt zuvor gebaute Objekte und Executables und baut den Source Code neu. Außerdem wird er nach dem Bauen ausgeführt und das eventuell schon vorhandene gnuplot Fenster vor dem Ausführen geschlossen. So kann nach Änderungen am Code im gleichen Fenster neu gecleanet, gebaut und ausgeführt werden; alles mit einem Tastendruck.
Wie das funktioniert, wird im folgenden Video dargestellt.
Eigentliche Anweisungen an den Compiler. Muss bei Hinzufügen, Ändern oder Löschen von Source-Code angepasst werden.
Leicht angepasster Original-Algorithmus von hier.
Das Programm muss mit einem geeigneten C-Compiler compiliert werden. Ich selbst benutze MinGW auf Windows 10. Falls gnuplot zur Anzeige der Daten genutzt werden soll, ist die Installation von gnuplot erforderlich.
Wie besprochen, handelt es sich im hier vorgeschlagenen Beitrag um eine Code-Portierung von Matlab auf C. Die folgenden Bilder zeigen, dass beide Algorithmen sich mit gleich parametriertem Filter in etwa gleich verhalten. Da es sich in beiden Fällen um “echte” Zufallszahlen handelt, kann nie der exakt gleiche Output erwartet werden.
Folgende Abbildung zeigt den Output des originalen Matlab-Algorithmus.
Der vorgeschlagene Ansatz in C liefert folgenden Output:
— Dies ist Teil2 der Tutorials zu Neuronalen Netzen – hier ist Teil1: Neuronale Netze einfach erklärt —
Nach der Einführung in die Thematik der Neuronalen Netze nun ein einfaches Deep Learning Beispiel mit der wichtigsten Bibliothek, welche für das Thema Machine Learning mit Neuronalen Netzen derzeit genutzt wird: Tensorflow!
Deep Learning ist hauptsächlich (bis auf Forschungsthemen) Supervised Learning, d.h. man zeigt dem Netzwerk also für bestimmte Eingangswerte die richtige Lösung (z.B. Klassifikation von Bildern oder Gruppen), das Netzwerk lernt Schritt für Schritt besser zu werden. Für viele Ingenieurs-Problemstellungen ist die zu schätzende Ausgangsgröße keine einzelne Klasse oder Kategorie (z.B. Auto, Haus, Boot) sondern ein kontinuierlicher Wert, z.B. ein Preis oder eine Kraft oder Fläche o.ä.
Neuronale Netze sind ein universeller Approximator: Wenn es einen Zusammenhang zwischen Ein- und Ausgangsgröße gibt, ist es sehr wahrscheinlich, dass ein (handwerklich korrekt aufgesetztes) Neuronales Netz diesen finden wird. Achtung: Es findet Korrelationen! Ob diese Korrelationen auch einen kausalen Zusammenhang haben oder nicht muss der Experte entscheiden.
Für dieses Tutorial knüpfen wir an Michael Nielsons Tutorial an und versuchen die folgende Funktion \(f(x)\) von einem Netzwerk schätzen zu lassen.
\[f(x)=0.2+0.4x^2+0.3x \cdot \sin(15x)+0.05 \cdot \cos(50x)\]
Ein typisches Regressionsproblem, daher nun ein Tensorflow Tutorial zum DNNRegressor, welches sich prinzipiell auch für höherdimensionale bzw. kompliziertere Fragestellungen nutzen lässt.
Wie schon viele von euch habe ich mich im Rahmen meines eigenen Projektes mit dem Kalmanfilter beschäftigt und dabei viel von den Blogeinträgen KF 1/KF 2/EKF gelernt. Da ich auch aufgrund des Standortvorteils direkt mit Paul über die Gestaltung fachsimpeln konnte, ist zwischen uns die Idee enstanden euch meine Umsetzung nicht vorzuenhalten. Deswegen möchte ich im Folgenden genauer auf die Bedatung der verschiedenen Matrizen, aber weniger auf die Berechnungen eingehen. Dafür sind ja schon die genannten Einträgen da.
Irgendwann musste es passieren: Ein im automatisierten Fahrbetrieb befindliches Fahrzeug ist in einen Unfall verwickelt und kann die Situation nicht retten. In diesem Fall ist ein Mensch um’s Leben gekommen. Sehr tragisch. Was ist passiert?
Ein Truck biegt an einer Kreuzung links ab und nimmt einem geradeaus fahrenden Fahrzeug die Vorfahrt. Soweit erst einmal ein statistisch gesehen relativ häufiger Vorfall. In diesem Fall war das Fahrzeug ein Tesla Model S im ‘Autopilot’ Modus. Das bedeutet, dass das Fahrzeug selbst die Längs- und Querführung übernommen hat (Lenken & Gas/Bremse). Die Software wird von Tesla unter Realbedingungen ‘beim Kunden’ getestet, sie ist ausdrücklich als Beta Version verfügbar, das bedeutet der Kunde darf sich nicht auf die Fehlerfreiheit verlassen, denn er hat eingewilligt sie als Testfahrer zu nutzen. Mutig von Tesla, deutsche Hersteller haben sich dagegen entschieden, wenngleich sie technisch auch so weit wären.
Es ist ein Unglück, welches durch die Verkettung menschlichen Versagens hervorgerufen wird aber in der Verfehlung der Technik mündet:
Die mediale Debatte entfaltet sich natürlich in Richtung Tesla und Fehler der “Autopilot” Funktion. Wieso hat der Autopilot das nicht erkannt? Nungut, steigen wir mit ein.
Es fehlt eindeutig an deutschsprachigen Beginner-Tutorials zum Thema Neuronale Netze. Es gibt ausgesprochen gute – ich meine wirklich herausragend gut! – Tutorials zum Thema, welche man diesem hier unbedingt vorziehen sollte. Bitteschön:
Doch was soll ich sagen, diese Tutorials sind umfangreich und gleich die große Kanone. Wenn man nur Spatzen haben möchte, dann hilft es vielleicht, ein einfaches Tutorial zum Einstieg zu nutzen, in dem großzügig auf Ausnahmen, Feinheiten und Randbedingungen verzichtet wird. Das versuche ich mit dem folgenden Beitrag.
Neuronale Netze ([engl.] Neural Networks) sind schon ziemlich alt und in der Wissenschaft schon lange Thema. Seit einigen Jahren erleben sie in praktischen (medienwirksamen und leicht verständlichen) Anwendungsfällen allerdings eine unglaubliche Renissance. Ein wichtiger Grundstein war sicherlich, dass NVIDIA Ende der 2000er Jahre mit ihren CUDA Grafikkarten eine unglaubliche Rechenpower zur Verfügung stellte. Warum? Neuronale Netzwerke lernen im Grunde mit simplen Multiplikationen bzw. Faltungen, welche sich prima parallel abarbeiten lassen. Hat man mehr Prozessorkerne, hat man in überschaubarer Zeit bessere Lernkurven. Eine Quad-Core CPU stellt dabei keine relevante Anzahl Kerne dar. Das wäre, wie mit einem 50ccm Roller zu einem DTM Rennen zu kommen.
Keine Chance also, auf dem heimischen Laptop ein auch nur ansatzweise konkurrenzfähiges Neuronales Netzwerk in überschaubarer Zeit (heißt: Wochen/Monate) zu trainieren!
Am beeindruckendsten finde ich, sind derzeit folgende Neuronalen Netze (welche noch kombiniert mit so genannten Reinforcement Learning Algorithmen funktionieren und die Q-Values lernen):
Not bad, right? Steigen wir ein.
Alle Begrifflichkeiten zu erläutern würde zu weit führen, aber die Wichtigsten im Überblick:
Spricht man über ein Neuronales Netz, so ist natürlich die Frage, was ein Neuron ist. Angelehnt an die Biologie ist ein Neuron ein ‘Ding’, welches auf einen Reiz mehr oder weniger reagiert, d.h. diesen Reiz ‘durch lässt’. Je nachdem wie stark es aktiviert ist, lässt es mehr oder weniger Reiz durch.
Dabei werden Eingangswerte \(x_0, x_1, x_2, …\) mit Gewichten \(w_0, w_1, w_2, …\) multipliziert, alle Produkte summiert, noch ein Offset \(b\) hinzu addiert und alles geht als Eingang in die Aktivierungsfunktion \(f\). Resultat ist der Ausgang des Neurons, die Aktivierung. Diese kann auch wieder Mehrdimensional sein, denn wenn danach noch ein Layer folgt, so benötigt man ja für die nächste Ebene wieder Eingänge.
Spricht man nun davon, dass ein Neuron ‘aktiviert’ wird (d.h. das Netzwerk lernt), so muss man eine mathematische Funktion \(f\) hinterlegen, die diese Aktivierung modelliert. Typisch und oft genutzt ist die Sigmoid Funktion oder auch der Tangens Hyperbolicus. Großer Beliebtheit erfreut sich die ReLu (Rectifier Linear Unit) Funktion, welche eine simple \(f(x)=max(0, x)\) Funktion ist. Alle 3 sind nachfolgend dargestellt. Alle 3 sind nichtlinear, was deren Sinn ist.
Sie geben für einen Input (in der Abbildung beispielhaft -6…+6) je nach Aktivierung einen Output. Der funktionelle Zusammenhang ist nichtlinear. Je nachdem wie stark das Neuron aktiviert ist, gibt es ein Signal unterschiedlich stark weiter. Zu Beginn des Lernprozesses, muss jedes Neuron initialisiert werden.
Wie man an den Aktivierungsfunktionen sehen kann, ist es von elementarer Bedeutung, ob ein Neuron mit 0 oder -1 oder +1 aktiviert ist. Je nach Aktivierungsfunktion lässt es ein Signal gar nicht oder teilweise oder stark durch. Zu Beginn weiß das Neuronale Netz noch gar nichts, man muss es aber initialisieren. Wenn man ein Netzwerk mit 100 Neuronen hat, sollte man 100 verschiedene Aktivierungen initialisieren.
Die Initialisierung kann im einfachsten Fall gleichverteilt erfolgen. Aber auch normalverteilt oder mit etwas ausgeklügelterer Logik. Auf keinen Fall jedoch alle mit exakt der gleichen Zahl (z.B. 0,0). Das würde dazu führen, dass jedes Neuron exakt gleich ist und beim Lernen ergäbe sich durch die Mathematik hinter dem Lernprozess, dass Fehler bzw. Korrekturen auf jedes Neuron gleich angewendet werden. Das Neuronale Netzwerk würde so nicht schlauer werden, denn es hätte ja dann nur 100 identisch dumme Neuronen.
Das Wichtigste am Lernen ist, dass man weiß, wann etwas richtig oder falsch war. Möchte man z.B. etwas vom Neuronalen Netz klassifziert haben (Ziffern, Buchstaben, Tiere, …), so gibt es nur ein richtig oder falsch. Eine Zielfunktion für ein Klassifizierungsproblem würde im einfachsten Fall nur die korrekt erkannten Dinge zählen. Umso mehr korrekt erkannt wurde, umso besser gelernt. Mit etwas mehr drüber nachdenken kommt man auf die Idee Softmax als Zielfunktion zu nehmen.
Es gibt aber auch Anwendungsfälle, da soll das Neuronale Netzwerk einen Wert schätzen und keine Klasse. Diese Anwendungsfälle nennt man Regressionsprobleme. Dafür ist eine andere Zielfunktion notwendig. Im einfachsten Fall berechnet man, wieviel das Netzwerk und die korrekte Funktion auseinander liegen. Diese Zielfunktionen werden L1 oder L2 Norm genannt, wobei bei letzterer der Fehler quadriert wird. In einigen Anwendungsfällen empfiehlt sich auch die Cross-Entropy als Loss Function.
Eine Zielfunktion kann aber z.B. auch ein Punktestand in einem Spiel (umso mehr, umso besser), die Anzahl an geschlagenen Figuren (umso mehr, umso besser), die erreichte Distanz (umso weiter, umso besser) oder die Entfernung zu einem Hindernis (umso dichter/weiter, umso besser). Je nach Anwendungsfall, welches das Netzwerk lösen soll.
Kommen wir nun zum elementaren und kompliziertesten Teil: Das Lernen! Nicht nur bei Kindern ist das kompliziert, auch bei Neuronalen Netzen muss man da clever sein, um ihnen etwas beizubringen. Die Neuronen sind im ersten Schritt ja initialisiert mit zufälligen Aktivierungen. Das Netzwerk berechnet die Ausgabegröße und wird von der Zielfunktion (Loss) gnadenlos bestraft. Der Fehler den es gemacht hat, wird über so genannte Backpropagation auf die jeweiligen Neuronen zurück verteilt, die ihn verursacht haben.
Nun könnte man auf die Idee kommen, dass man die verschiedenen Neuronen einfach nach und nach einzeln durch geht und mit verschiedenen Aktivierungen (z.B. -6…6) probiert (Bruteforce), bis das Netzwerk die beste Zielfunktion ausgibt. Glückwunsch, so viel Rechenpower hat selbst Google nicht, das für halbwegs reale Anwendungsfälle durch zu spielen.
Eine etwas bessere Idee ist, zu schauen, ob der Fehler größer oder kleiner wird, wenn man die Aktivierung erhöht. Mathematisch gesehen wird die Steigung der Fehlerfunktion bestimmt. Idealer Weise gibt es eine Richtung in die man optimieren kann, sodass die Fehlerfunktion minimiert wird.
Diese Suche nach der idealen Aktivierung bedeutet mathematisch das Finden von Minimalwerten im Fehlerraum. Gibt es nur zwei Parameter, so kann man eine Fehlerfunktion wunderschön visualisieren. Dabei können verschiedene mathematische Suchen implementiert werden. Der Klassiker ist sicherlich die Stochastic Gradient Descent (SGD) Methode, welche numerisch einen Anstieg der Funktion bestimmt und in die abfallende Richtung optimiert. Es gibt andere Verfahren wie Nesterov Momentum, Adagrad und Adadelta oder Rmsprop. Alle haben Vor- und Nachteile sowie Parameter, welche einzustellen sind.
Der Fehler \(e\), den das Netzwerk bei einer Schätzung des Ausgangswertes macht, wird zurück an das Netzwerk gegeben. Dabei wird dieser anteilig auf das Neuron verteilt, welches maßgeblich am Fehler \(e\) beteiligt war. Das mathematische Verfahren ist die Backpropagation. Man kann es sich bildlich so vorstellen:
Das funktioniert auch, wenn man mehr als einen Layer hat.
Ein Neuronales Netzwerk besteht aus mehreren Neuronen. Dabei gibt es verschiedene Architekturen. Es gibt aber immer einen Input- und ein Output-Layer (im GIF oberhalb orange und grün). Bei der Bezeichnung des Netzwerkes wird der Input Layer nicht mit gezählt.
Ein 3-Layer Neuronales Netzwerk hat
usw. Die Layer sind nacheinander (Sequentiell) angeordnet und verbunden. Ab einer gewissen Anzahl an Layern (10…20) spricht man von Deep Neural Networks.
Wenn jedes Neuron mit jedem Neuron des nächsten Layers verbunden ist, dann spricht man von ‘Fully Connected Layer’ (auch ‘Dense-Layer’). Es gibt dann so viele Gewichte \(w\), wie es Verbindungen gibt.
Soll das Netzwerk irgendwelche Informationen aus Bildern (oder Videospielen, YouTube Videos, Kamera, …) lernen, werden zur Extraktion von Features Layer eingesetzt, welche eine Faltungsoperation ausführen. Faltung (oder Englisch: Convolution, die Netzwerke heißen dann Convolutional Neuronal Network oder kurz ConvNets), ist eine relativ simple Funktion, welche in einem gleitenden Fenster das Produkt von zwei Funktionen berechnet. Es zeigt sich aber, dass dieses Vorgehen so mächtig ist, dass ein Layer damit relevante Erkennungsmerkmale für viele reale Gegenstände aus dem Bild heraus arbeitet..
Nun kommen wir zum eigentlichen Teil. Ein Gewicht \(w\) ist eigentlich das Elementare in einem neuronalen Netzwerk, denn es sagt, wie stark ein Neuron mit einem anderen verbunden ist. Der Ausgangswert eines Neurons, multipliziert mit dem Gewicht \(w\) und addiertem Offset \(b\), ergibt den Eingangswert für die Aktivierungsfunktion des Neurons! Mathematisch beschrieben, sieht das so aus:
\[out=f\left(\sum_iw_ix_i + b\right)\]
Wobei \(w_i\)=Gewicht, \(x_i\)=Eingangswert (bzw. Ausgangswert des Neurons des vorhergehenden Layers), \(b\)=Offset des Layers und \(f\)=Aktivierungsfunktion (z.B. Sigmoid). Der Index \(i\) bezieht sich darauf, wieviel Verbindungen auf das Neuron kommen. Besteht der Layer aus 10 Neuronen, wäre \(i=10\). Der Offset \(b\) ist ohne Index, dieser ist pro Layer für jedes Neuron konstant.
Im Code liegt die Wahrheit, hier ist also welcher, Quelle: CS231n
# forward-pass of a 3-layer neural network: f = lambda x: 1.0/(1.0 + np.exp(-x)) # activation function (use sigmoid) x = np.random.randn(3, 1) # random input vector of three numbers (3x1) h1 = f(np.dot(W1, x) + b1) # calculate first hidden layer activations (4x1) h2 = f(np.dot(W2, h1) + b2) # calculate second hidden layer activations (4x1) out = np.dot(W3, h2) + b3 # output neuron (1x1)
Im Code oberhalb sind die Variablen \(W1, W2, W3, b1, b2, b3\) das, was das Neuronale Netzwerk ‘lernen’ muss (wobei die Dimension von \(W_i\) und \(b_i\) von der Dimension der Eingangsdaten abhängt). D.h. die Backpropagation muss den Fehler so auf die \(W_i\) und \(b\) verteilen, dass der Fehler zwischen \(out\) und dem korrekten Wert minimal wird.
Die `numpy dot` Funktion bedeutet elementweise Multiplikation von Matrix-Elementen, denn was in der Formel als Summe dargestellt ist, kann natürlich effizient als elementweise Matrixmultiplikation in einem Rutsch ausgeführt werden. Es ist keine zeitaufwendige Schleife für die Summe notwendig.
Ein Neural Network ist im Prinzip eine wiederholte Matrixmultiplikation mit eingearbeiteter Aktivierungsfunktion.
Nun haben wir alles beisammen, was man benötigt, um ein Neuronales Netzwerk aufzusetzen.
Wie im ersten Teil erwähnt, bringt es nichts auf dem heimischen Laptop ein Netzwerk aufzusetzen, was Super Mario spielen lernt. Es ist prinzipiell möglich, doch die Lernzeit ist so lang, dass bis zum Erreichen eines respektablen Erfolges viele Sommer vergehen werden.
Wir nehmen ein sehr einfaches Beispiel.
Ein Neuronales Netz ist per Definition ein universeller Approximator. Das bedeutet, dass es bereits mit einem Hidden Layer prinzipiell (und mit gewissem Fehler) jede Funktion nachahmen kann. Voraussetzung ist, dass es genügend Neuronen in dem Layer gibt und diese eine nichtlineare Aktivierungsfunktion haben.
Wir könnten den Code prinzipiell selbst in Python aufsetzen [siehe z.B. hier], doch das ist uneffizient und nicht sehr clever.
Besser ist es, eine Bibliothek zu verwenden, die das perfekt macht. Da kann man endlos durch probieren: What is the best deep learning library at the current stage for working on large data?
Das Feld der Neuronalen Netze ist so populär, dass ca. täglich neue Bibliotheken und Wrapper für C++, Lua, Python oder Java auf Github erscheinen, welche mehr oder weniger Beta sind und mehr oder weniger Dokumentation besitzen. Auch die Big Player wie facebook oder Google veröffentlichen ihre Bibliotheken. Wer die Wahl hat, hat die Qual.
Als Anfänger sollte man darauf achten, dass man nicht erst 2 Wochen mit dem Setup für GPU und AWS Cloud zubringt, bevor man überhaupt das 1. mal anfängt rumzuspielen. Daher empfehle ich: Python > Theano > Keras!
Man kann natürlich gleich mit realen Fragestellungen beginnen, doch da lässt sich schwerlich verstehen, worum es im Kern geht. Daher als Beispiel eine gaaaaaanz einfache Sache: Es gibt eine Funktion \(f(x)\), welche dem Netzwerk nicht bekannt ist. Es soll ein neuronales Netzwerk \(g(x)\) aufgesetzt und angelernt werden, was diese Funktion approximiert.
Michael Nielsen hat dazu eine schöne Spielwiese programmiert: A visual proof that neural nets can compute any function.
Wie bekommen wir das nun mit Python, Theano und Keras aufgesetzt?
# Import Keras and Numpy Stuff we need from keras.models import Sequential from keras.layers.core import Dense, Activation from keras.optimizers import Adadelta import numpy as np f = lambda x: 0.2+0.4*x**2+0.3*x*np.sin(15*x)+0.05*np.cos(50*x) x = np.linspace(0, 1, 101) y = f(x)
Der Eingangsvektor \(x\) (kann auch eine Matrix sein, z.B. wenn mit mehreren Trainingsdaten gleichzeitig gelernt werden soll) des Neuronalen Netzwerkes besteht nun einfach aus 100 Zahlen zwischen 0…1. Der Vektor hat die Dimensionen [1, 100]:
\[x=\left[0, 0.01, 0.02, …, 1.0\right]\]
Damit die schöne Matrixstruktur und einfachen Multiplikationen funktionieren, wird der Vektor auf die Dimension [100, 1] umgestellt (also 100 lang, nicht breit). Pro Spalte in \(x\) sollte ein Trainingsdatensatz, pro Zeile ein Wert des Trainingsdatensatzes sein. Da wir nur einen Trainingsdatensatz mit 100 Werten haben, hat unser \(x\) die Form [100, 1].
X = x.reshape((-1, 1)) print(np.shape(X)) # (100, 1) print(np.shape(y)) # (100, )
Jetzt bauen wir das Neuronale Netzwerk mit Keras auf.
model = Sequential() # Sequentielles Netz, d.h. Layer nach Layer... model.add(Dense(input_dim=1, output_dim=300, init="uniform")) model.add(Activation("relu")) model.add(Dense(output_dim=1, init="uniform"))
Was haben wir jetzt aufgesetzt? Ein Dense Layer ist ein Fully Connected Layer, d.h. jedes Neuron soll mit jedem des nächsten verbunden werden. Input hat die Dimension 1. Unser Inputvektor \(x\) hat die Form [100, 1]. Wie bereits beschrieben, bedeutet die Länge 100, dass wir 100 Werte haben, welche aber die Dimension 1 haben. Die Output Dimension wurde mit 300 initialisiert, das bedeutet, dass das Netzwerk 300 Neuronen bekommt im 1. Layer. Die Initialisierung soll ‘uniform’, d.h. gleichverteilt erfolgen.
Als Aktivierungsfunktion wird die ReLu Funktion hinzugefügt.
Anschließend, als Output-Layer wieder ein Fully-Connected Layer mit einer Output Dimension, d.h. das Netzwerk soll keine Klassifizierung o.ä. machen, sondern eine reale Zahl ausgeben. Diese müssen wir nun irgendwie mit dem Sollwert, d.h. der Funktion \(f(x)\) vergleichen und dem Netzwerk eine Loss-Funktion mitteilen.
model.compile(loss='mean_squared_error', optimizer=Adadelta())
In Keras wird das aufgesetzte Modell mit Loss Funktion (in dem Fall der mittlere quadratische Fehler oder auch L2 Norm genannt) und Optimizer (in dem Fall Adadelta) gleich kompiliert, d.h. wenn möglich als optimierter GPU Code im Hintergrund erzeugt.
Nun ist alles aufgesetzt und in Code gegossen. Nun kann man es lernen lassen.
model.fit(X, y, batch_size=15, nb_epoch=10000)
Das Lernen erfolgt mit jeweils 15 der 100 Werte aus dem Eingangsvektor \(x\). Die Fehlerfunktion wird 10.000x die gemachten mittleren quadratischen Fehler via Backpropagation auf die Gewichte \(w\) und \(b\) zurück verteilen. Je nach Initialisierung und Loss Funktion und Aktivierungsfunktion und gewählter Hyperparameter, lernt das Netzwerk schneller oder langsamer.
Hier kann man für einfachere Funktionen beim Lernen zusehen.
Die Loss Funktion sieht beispielsweise so aus (logarithmische y-Achse).
Nach 10.000 Lernvorgängen (auf einem MBP 2.9GHz, Intel Core i5 dauert das knapp 1 Minute), hat das Netzwerk die Funktion \(g(x)\) gefunden, welche \(f(x)\) approximiert.
g=model.predict(X)
Gar nicht schlecht für 1 Minute lernen mit einem Layer mit 300 Neuronen, oder? Allerdings nicht perfekt. Vor allem der erste Teil der Funktion ist relativ schlecht approximiert. Da könnte man nun anfangen zu optimieren. Ist die Schrittweite zu groß, die Loss Funktion die richtige, die Lerndauer zu kurz, …
Bei einer Lerndauer von nur 1 Minute kann man viel rum probieren und mal schnell wieder neu anlernen. Bei einem sehr komplexen Netzwerk, mit 10 Layern und mehreren Millionen Neuronen, dauert das Lernen, selbst in Rechenzentren, einige Tage. Da ist ein gewisses KnowHow notwendig, um in die richtige Richtung zu optimieren.
Ach übrigens: Für kommerzielle Anfragen gibt es auch die Möglichkeit mich zum Thema zu konsultieren: Hire me!
— Im Teil 2 des Deep Learning Tutorials wird dieses Beispiel mit Tensorflow noch mal aufgegriffen! —
Artificial Neural Networks sind aus der modernen Machine Learning Welt nicht mehr weg zu denken. Von der Sortierung der facebook Timeline, Siris Spracherkennung oder auch Fahrspur- und Kollisionserkennung in kamerabasierten Fahrerassistenzsystemen – überall helfen sie uns im Alltag.
Sofern man eine Zielfunktion aufstellen kann, ist das Lernen parallel und relativ schnell (weil der Rechner schnell prüfen kann ob es stimmt und wie groß der Fehler war) möglich. In eng eingegrenzten Szenarien (z.B. Go spielen, Atari spiele spielen, …) können sie menschliche Fähigkeiten durchaus nachahmen oder sogar besser werden als der beste Mensch in der Disziplin.
Doch ein Neuronales Netz, was Atari spielt, kann keine Sprache verstehen. Es sind keine universellen Profis. Allerdings sind die derzeit bestehenden Netzwerke auch noch wesentlich kleiner als das menschliche Gehirn. Die Biochemie im menschlichen Gehirn ist auch wesentlich komplexer als eine einfache ReLu oder Sigmoid Funktion mit ein paar Gewichten. Dennoch: Es gibt nicht wenige Gelehrte dieser Tage, die die Gefahr kommen sehen.
Ein schöner Beitrag dazu: The AI Revolution.
Haben wir damals in Science Fiction Filmen (Minority Report) noch über die Vorhersage von Verbrechen geschmunzelt, so ist es heute Realität.
Haben wir damals in Science Fiction Filmen (Terminator) noch über Skynet gelacht, einem Neuronalen Netz, was alle Informationen sammelt, so haben Firmen wie Google, facebook oder Geheimdienste wohl im Ansatz vergleichbare Architekturen aufgebaut.
Wann wird also der Moment gekommen sein, in dem die SciFi Vision beginnt und eine allumfassendes KI auf die Idee kommt, sich selbst zu trainieren und eigene Zielfunktionen aufzustellen? Eventuell sogar mit einem End-to-End trainierten Neuronalen Netz, welches auch gleich die Stellsignale für Elektromotoren mit lernt und physisch in Erscheinung tritt?
Hope we’re not just the biological boot loader for digital superintelligence. Unfortunately, that is increasingly probable
— Elon Musk (@elonmusk) 3. August 2014
Ein interessantes Interview zum Thema: Jürgen Schmidhuber – Intelligente Roboter werden vom Leben fasziniert sein.
Bald werden eben die klügsten Bestandteile der Zivilisation nicht mehr die Menschen sein. – Jürgen Schmidhuber
Mit Abstand am häufigsten gelesen hier im Blog, sind die Beiträge zum Kalman Filter (Teil 1 & Teil 2 & EKF). In den Kommentaren und Emails, die ich dazu bekomme, zeigt sich allerdings, dass da mit Kanonen auf Spatzen geschossen wird. Einige Probleme, die der Algorithmus lösen soll, sind gar nicht so kompliziert, dass man einen Kalman Filter oder Extended Kalman Filter aufsetzen muss.
Ich möchte in diesem Beitrag den kleinen Bruder vom Kalman Filter, den Alpha-Beta-Filter, vorstellen. Dieser ist zwar im Grunde ein Kalman-Filter, allerdings mit einigen Vereinfachungen, sodass er leichter zu implementieren ist.
Prof. Amnon Shashua ist Co-founder & CTO von Mobileye und er sprach auf der Deutsche Bank Global Auto Industry Conference.
Er spricht über die historische Entwicklung der Computer Vision für Fahrerassistenzsysteme und auch die zukünftige. So wird beispielsweise dieses Jahr ein automatischer Notbremsassistent bei Audi eingeführt, welcher nur mit Computer Vision als Sensorik funktioniert. Bisher ein klassisches Betätigungsfeld für Radarsensorik oder Lidar.
Er spricht außerdem über False-Positive (d.h. es gibt kein Hindernis, das System hat aber etwas erkannt und ausgelöst) und False-Negative (d.h. es gab ein Hindernis, das Computer Vision System hat es aber nicht erkannt) Auswirkungen für die Detektion von Ereignissen.
Außerdem wird auf die Fortschritte der Straßenverlaufs- und Hinderniserkennung bei Nacht eingegangen.
Ebenfalls wird auf die Erkennung von Straßeneigenschaften, d.h. Schlaglöcher oder Schwellen, eingegangen. Diese können relativ genau erkannt und das Fahrwerk bzw. die Geschwindigkeit darauf angepasst werden. Dies wird spätestens bei autonomer Fahrt interessant.
Um das Fahrzeug in der Umgebung sicher bewegen zu können, gibt es verschiedene Ansätze. Google beispielsweise fährt umher und scannt einfach die gesamte Stadt mit 360° Laserscannern in riesigen Punktwolken ab, welche dann zu 3D Modellen zusammen gesetzt werden. Daraus können Straßen, Hindernisse, Ampeln, Bordsteine, Einfahrten etc. extrahiert werden.
Genau auf der anderen Seite der Möglichkeiten steht, gar keine Daten zu sammeln, sondern nur Sensoren immer schauen zu lassen, wie es denn gerade aussieht.
Doch all diese Step-by-Step Verbesserungen reichen nicht, um autonome Fahrt realisieren zu können. Dafür muss ein großer, riesiger Schritt getan werden.
Prof. Shashua spricht über Deep Learning und das MobileEye seit 2 Jahren an der Nutzung von Deep Learning Algorithmen (genauer: Deep Convolutional Networks) für die Umfeldsensorik arbeitet. Dabei war vor allem das Problem der vielen (kleinen) Operationen (mathematischen Faltungen = Convolutions) die Herausforderung, was aber mit dem neuen EyeQ3 Chip wohl gelöst sei.
Mit angelernten neuronalen Netzen war es möglich Fahrspuren zu erkennen, selbst wenn keine Fahrbahnmarkierungen zu ‘sehen’ waren. Außerdem erkennt das Netz freie Fahrbahn, Fahrzeuge und Straßenbegrenzungen. Siehe auch ‘Es beginnt selbst zu lernen…‘
Dies ist keine Zukunftsmusik, so Shashua, es wird dieses Jahr einen Fahrzeughersteller geben, der dies in Serie heraus bringen wird. Und er sprach viel über Tesla in diesem Vortrag. :)
Sein Fazit ist, dass die Computer Vision die Sensorik für Fahrerassistenzsysteme werden wird, Radar und Laserscanner nur zur Redundanz bzw. Absicherung eingesetzt werden, falls der Fahrzeughersteller diese Kosten auf sich nimmt. Denn Computer Vision Sensorik ist die mit Abstand günstigste.
Nungut, bei Nebel sieht ein Computer Vision System auch nichts, da muss das Radar die Abstandsinformation liefern.
Lange Rede, kurzer Sinn, hier der Vortrag:
Errata: In der ursprünglichen Fassung des Artikels war die Beschreibung für False-Positive und False-Negative vertauscht.
Wer sich immer gefragt hat, weshalb man sich mit inversen Pendeln, Laplace Transformation, Bode-Diagramm und PID Reglern herum schlagen soll?
Deshalb:
Passend dazu vielleicht auch ein Anwendungsbeispiel: Das lineare Besäufnis.
Die Rotation eines Körpers im Raum ist ein Thema, welches einen Ingenieur in vielen Einsatzbereichen tangiert. Es gibt auch schon unzählige Webseiten dazu und auch die Wikipedia lässt sich zum Thema Drehmatrix oder Eulersche-Winkel ausführlich aus.
Doch so richtig gepasst hat bisher keine Beschreibung. Deshalb an dieser Stelle noch einmal eine ausführliche und einfache Beschreibung der 3D Rotation eines Körpers/Vektors mit Euler-Winkeln nach ZYX-Konvention im DIN70000 Koordinatensystem des Fahrzeugs.
Continue Reading
Dieser Beitrag wurde aus dem IPython Notebook konvertiert, ich bitte um etwas Nachsicht bezüglich der Formatierung.
OK, jeder Ingenieur hat nach der Ankündigung “Timo Boll gegen KUKA Roboter” und dem imposanten Teaser Video wahrscheinlich ein lächeln im Gesicht gehabt. KUKA, das deutsche mittelständische Unternehmen einen YouTube Hit? Richtig gutes Marketing? Wow, das kann was werden.
Doch dann die Ernüchterung: Das angekündigte Match war doch nur ein “Werbespot“. Doch wieso eigentlich?
Das Problem am Tischtennis spielen gegen einen Roboter ist eigentlich nicht die Roboterbewegung sondern etwas ganz anderes.
Da die Beiträge zum Kalman Filter (Teil1 und Teil2) sowie der Beitrag zum Extended Kalman Filter die am Häufigsten gelesenen des Motorblogs sind, habe ich zum Extended Kalman Filter noch mal einen etwas detaillierteren Screencast aufgezeichnet, welcher recht ausführlich erläutert, wie dieser aufgesetzt wird und arbeitet.
Extended Kalman Filter with CTRV Vehicle Model in Python from Paul Balzer on Vimeo.
Die Grundlage von enorm vielen Bildverarbeitungsalgorithmen ist die Kantenerkennung. Das bedeutet, dass in einem Bild die Umrisse eines Objekts herauszufinden sind oder markante Linien gefunden werden.
Doch wie funktioniert solch ein Algorithmus im Detail?
Erfasst man einen realen Vorgang mit einem Sensor, so hat man intuitiv das Gefühl, dass dieser Sensor den realen Wert ‘schon korrekt messen wird’. Das Problem ist, dass kein Sensor perfekt ist. Am Beispiel der Umfeldsensorik für PKW ist ein sehr populärer Sensor der Radar, welcher z.B. für die adaptive Geschwindigkeitsregelung ACC, Ein-/Ausparkassistenten oder auch Totwinkelassistenten eingesetzt wird.
Schaut man sich die Rohmesswerte eines solchen Sensors an, wird klar, was mit ‘nicht perfekt’ gemeint ist:
Die Herausforderung ist nun, aus diesen Rohmessdaten einen klaren Mehrwert zu generieren. Es ist innerhalb eines Bruchteils einer Sekunde die Entscheidung zu treffen:Ist dort etwas?
Kommt dort etwas?
Wie schnell ist es?
Was ist es?
Nachdem wir im Teil 1 den Kern des Kalman Filters geklärt haben, widmen wir uns nun dem komplizierteren Teil. Die im Teil 1 genannte Vorgehensweise mit dem multiplizieren bzw. addieren der Mittelwerte und Varianzen funktioniert so nur im eindimensionalen Fall. \(\)
Das heißt, wenn der Zustand, den man messen möchte, mit nur einer Variablen vollständig beschrieben werden kann. Das Beispiel, welches eingangs genannt wurde, die Position eines Fahrzeugs im Tunnel zu bestimmen, kann aber nicht mehr mit einer Variablen vollständig beschrieben werden. Zwar interessiert nur die Position, aber diese ist genau genommen ja schon 2-Dimensional in der Ebene (\(x\) & \(y\)). Außerdem kann nur die Geschwindigkeit (\(\dot x\) & \(\dot y\)) gemessen werden, nicht die Position direkt. Dies führt zu einem 4D-Kalman-Filter, mit folgenden Zustandsvariablen:
$$x=\begin{bmatrix}
x \\
y \\
\dot x \\
\dot y
\end{bmatrix}=\begin{matrix}\text{Position X} \\ \text{Position Y} \\ \text{Geschwindigkeit in X} \\ \text{Geschwindigkeit in Y}
\end{matrix}$$
Herr Kalman hatte sich nun überlegt, wie man es schafft, trotz verrauschter Messung einzelner Sensoren, eine optimale Schätzung aller Zustände zu berechnen.