Anhand von Trainingsdaten in Form von täglich im Auto erfassten Kilometerständen versucht Michael Schillis KI-Programm Muster im Fahrverhalten zu erkennen und Prognosen abzugeben.
In der Rubrik Deep Learning sprudeln die Neuerscheinungen zurzeit ja nur so aus den Verlagen heraus, “Neuronale Netzwerke” hier, “Entscheidungsbäume” da. Und mit brandaktuellen Open-Source-Tools wie Tensorflow oder Scikit kann selbst Otto Normalverbraucher seinem Heim-PC künstliche Intelligenz einhauchen. Was läge da näher, als den lerneifrigen Rechner mit den gesammelten Daten zu füttern und zu prüfen, ob er dann anhand historischer Werte und mittels neuer KI-Techniken die Zukunft vorhersagen kann.
Einfach linear
Wie in einer früheren Ausgabe dieser Reihe schon einmal besprochen, habe ich einen Adapter im Auto stecken, der über den OBDII-Port Fahrdaten sammelt und sie über das Handy-Netzwerk auf einem Webservice hinterlegt [2]. Diese Daten holen dann Skripte per REST-API vom Netz und können damit minutiös belegen, wer wann mit dem Auto wohin gefahren ist.
So ist es zum Beispiel ein Leichtes, tägliche Kilometerstände abzurufen und als CSV-Datei auszugeben (Abbildung 1) oder, wie Abbildung 2 zeigt, die Tachodaten eines ganzen Jahres grafisch über die Zeitachse aufzutragen.
Der – abgesehen von ein paar abweichenden Rucklern – lineare Verlauf der Kilometerstände deutet darauf hin, dass das Automobil fast jeden Tag eine stattliche Anzahl von Kilometern abspult, und falls jemand fragt “Was wird der Tacho wohl nächstes Jahr im Juli anzeigen?”, könnte ein mathematisch einigermaßen geschulter Mensch mittels Dreisatz relativ zügig den zukünftigen Kilometerstand errechnen.
Doch wie steht es mit den heute verfügbaren KI-Programmen, wie aufwändig wäre es, die historischen Fahrdaten einzufüttern, den Computer den Tachoverlauf lernen zu lassen, um später akkurate Zukunftsprognosen zu erstellen?
Doch noch Hexenwerk
Heute erhältliche KI-Tools sind von intuitiver Intelligenz jedoch noch weit entfernt und erfordern schon noch, dass man die Rahmenbedingungen genau absteckt, bevor der Computer überhaupt etwas erkennt. Ist aber der lineare Verlauf der Kurve bekannt, wählt der Fachmann ein KI-Tool für lineare Regression, dann ist der Erfolg programmiert.
Tensorflow, ein heißes KI-Framework aus dem Hause Google, hilft auf hoher Abstraktionsebene beim Einfüttern von Daten sowie beim Trainieren und Auswerten von Modellen. Da KI-Tools relativ viel mit linearer Algebra und Matrizen rechnen, helfen auch Mathe-Tools wie Pythons Pandas bei der Arbeit. Tensorflow für Python 3 installiert sich auf Ubuntu einfach mit dem Python-Modul-Installer »pip3 install tensorflow«, Gleiches gilt für Pandas und andere Module.
Bei meiner Installation warf die Tensorflow-Engine übrigens bei jedem Aufruf mit wüsten Warnungen um sich, die sich durch Setzen der Environment-Variablen »TF_CPP_MIN_LOG_LEVEL« auf den Wert »3« jedoch abstellen ließen.
KI-Fütterung
Tensorflow erwartet die mathematischen Gleichungen zum Betreiben eines Modells als so genannte »Nodes« in einem Graphen, den es in »Sessions« mit Parametern füllt und wieder und wieder ausführt, entweder auf einem einzigen PC oder auch gerne parallel auf ganzen Clustern von Maschinen im Rechenzentrum. Listing 1 definiert die Geradengleichung für das lineare Modell in Zeile 22 als »Y=X*W+b«.
Listing 1
linreg.py
01 #!/usr/bin/python3
02 import pandas as pd
03 import tensorflow as tf
04 import numpy
05 import os
06
07 os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
08
09 rnd = numpy.random
10 learning_rate = .01
11 training_epochs = 2000
12 chkpoint = 250
13
14 train_df = pd.read_csv("odometer.csv")
15
16 X = tf.placeholder("float")
17 Y = tf.placeholder("float")
18
19 W = tf.Variable(rnd.randn(), name="weight")
20 b = tf.Variable(rnd.randn(), name="bias")
21
22 # model: Y = X*W + b
23 pred = tf.add(tf.multiply(X, W), b)
24
25 # mean squared error
26 total = len(train_df.index)
27 cost = tf.reduce_sum(
28 tf.pow(pred-Y, 2))/(2*total)
29
30 # normalize training set
31 nn_offset=int(train_df[['date']].min())
32 nn_div=int(train_df[['date']].max() -
33 train_df[['date']].min())
34 print("norm_off=", nn_offset)
35 print("norm_mult=", nn_div)
36 train_df['date'] -= nn_offset
37 train_df['date'] /= nn_div
38
39 opt = tf.train.GradientDescentOptimizer(
40 learning_rate).minimize(cost)
41
42 init = tf.global_variables_initializer()
43
44 # tensorflow session
45 with tf.Session() as sess:
46 sess.run(init)
47 for epoch in range(training_epochs):
48 for ix, row in train_df.iterrows():
49 sess.run(opt, feed_dict={
50 X: row['date'],
51 Y: row['miles']})
52 if epoch % chkpoint == 0:
53 c=sess.run(cost, feed_dict={
54 X: train_df['date'],
55 Y: train_df['miles']})
56 print("W=", sess.run(W),
57 "b=", sess.run(b),
58 "cost=", c)
Die Variable »X« ist hier der Eingabewert für die Simulation, sie gibt also den Zeitwert vor, zu dem das Verfahren den Kilometerstand »Y« als Ausgabe errechnet. Die Parameter »W« (Weight) und »b« (Bias) zum Multiplizieren von X beziehungsweise zum Addieren eines Offsets soll das Modell im Training bestimmen, und zwar so, dass Y dann möglichst genau dem Kilometerstand im Training zum Zeitpunkt X entspricht.
Hierzu definieren die Zeilen 16 bis 17 die Variablen »X« und »Y« als »placeholder« und die Zeilen 19 bis 20 die Parameter »W« und »b« als »Variable« und initialisieren sie mit zufälligen Werten aus der Random-Bibliothek des »numpy«-Moduls. Zeile 14 liest in einem Rutsch die in zwei Spalten »date« und »miles« vorliegenden Daten der CSV-Datei in einen Pandas-Dataframe ein, einer Art Tabelle mit zwei Spalten.
Das eigentliche Training übernimmt der Optimizer in Zeile 39, der nach dem Gradient-Descent-Verfahren versucht die Geradengleichung durch Modifizieren der Parameter »W« und »b« so lange an die Einzelpunkte aus den Trainingsdaten anzupassen, bis die ab Zeile 27 definierte Kosten- (oder Fehler-)Kalkulation »cost« auf einen minimalen Wert fällt. Sie definiert dazu, wieder in Tensorflow-Semantik, die mittlere quadratische Abweichung aller Trainingspunkte von der durch »W« und »b« bestimmten Geraden.
In der ab Zeile 45 laufenden Tensorflow-Session iteriert die For-Schleife ab Zeile 47 über alle in Zeile 11 festgesetzten 2000 Trainingseinheiten und berechnet für je 250 Durchgänge den Wert für die Kostenfunktion, um den User bei Laune zu halten. Fürs Training muss Tensorflow aber letztlich nur in Zeile 49 mit »run« die Session mit den aktuellen X- und Y-Werten aufrufen, die dann über den Optimizer im Hintergrund die Formel in Zeile 23 aufruft, das Ergebnis ausrechnet und ihrerseits über die Kostenfunktion die Parameter weiter anpasst. Nach 2000 Durchgängen zeigt sich das Bild in Abbildung 3, der Wert für »W« pendelt sich auf 6491 ein, der für »b« auf 32838.
Normal bleiben
Allerdings klappt die Regression nur, falls die Trainingsdaten vorher auf einen begrenzten Wertebereich normalisiert wurden. Füttert das Skript etwa den Optimizer mit den unmodifizierten Unix-Sekunden, schaukelt sich dieser auf und produziert immer unsinnigere Werte, bis er schließlich die Grenzen der Fließkommazahlen sprengt und alle Parameter auf »nan« (Not a Number) setzt.
Deshalb normalisieren die Zeilen 31 bis 37 in Listing 1 die Trainingsdaten vorab nach der Min-Max-Methode, finden mit den Pandas-Methoden »min()« und »max()« den minimalen und den maximalen Zeitstempelwert, ziehen ersteren als Offset von allen Trainingswerten ab und dividieren sie anschließend durch die Min-Max-Differenz.
Heraus kommen dann normalerweise Trainingswerte zwischen 0 und 1 (aber Achtung falls min=max!), die der Optimizer besser verarbeitet.
Mit den gelernten Parametern lassen sich nun im Rahmen des Modells historische Werte reproduzieren oder auch die Zukunft voraussagen. Welchen Kilometerstand wird das Auto am 2. Juni 2019 aufweisen? Das Datum hat einen Epoch-Wert von 1559516400, den das Modell genau wie im Trainingsfall erst normalisieren muss. Der in Abbildung 3 gefundene Offset »norm_off« von 1486972800 wird abgezogen und das Eingabedatum auch noch durch den Skalierungsfaktor »norm_mult« von 7686000 geteilt.
Es ergibt sich ein X-Wert von 9.43, der in die Formel »Y=X*W+b« eingesetzt einen Kilometerstand von 94.115 voraussagt. Alles unter der Annahme natürlich, dass das Modell stimmt, also ein linearer Anstieg vorliegt, und dass die drei Monate Trainingsdaten für die genaue Ermittlung des Anstiegs ausreichen.
Daten vorenthalten
Um sicherzustellen, dass das Modell nicht nur die Trainingsdaten simuliert, sondern auch die Wirklichkeit vorhersagt, teilen KI-Fachkräfte die vorliegenden Daten oft in ein Training- und ein Testset auf. Sie trainieren das Modell nur mit Daten aus ersterem, denn sonst besteht die Gefahr, dass es zwar minutiös die Trainingsdaten nachahmt, aber auch temporäre Ausreißer nachbildet, die später in der Produktion nicht mehr vorkommen, und Artefakte vorhersagt, die dann nicht eintreffen.
Bleibt das Testset bis zum Abschluss des Trainings unangetastet und sagt danach das Modell auch die Testdaten richtig voraus, wird sich das KI-System nach aller Wahrscheinlichkeit auch später in der Produktion richtig verhalten.
Nun konnte mein HP-41CV-Taschenrechner vor 30 Jahren aus einer Ansammlung von x/y-Werten und der Annahme eines linearen Zusammenhangs auch schon mittels linearer Regression die Parameter W und b bestimmen. Allerdings kann Tensorflow nun weit mehr, denn neben komplexeren Regressionstechniken versteht es auch neuronale Netzwerke und Decision Trees.
Kein einfaches Muster
Wer mit Adleraugen die Kilometerstände betrachtet, stellt fest, dass der Zuwachs keineswegs genau linear mit der Zeit erfolgt. Abbildung 5 zeigt den höher aufgelösten Kilometerzuwachs pro Tag, und es ist bereits darin offensichtlich, dass der Anstieg gewaltigen Schwankungen unterliegt. So bewegt sich das Auto an den meisten Tagen zwischen 30 und 90 Kilometer, während es hin und wieder ein oder zwei Tage lang fast kaum fährt und oft sogar ganz stillsteht.
Ein Mensch schaut sich einfach den Graphen in Abbildung 5 an – und zack! ist klar, dass der Wagen am Wochenende weniger als an Werktagen herumkesselt. Damit ein KI-System die gleiche Leistung erbringt, muss es der programmierende Mensch erst mal an die Hand nehmen und in die richtige Richtung leiten.
Liegen die Datumsangaben zum Beispiel – wie in Unix üblich – als Epoch-Sekunden vor, findet das KI-System nie im Leben heraus, dass alle 7 Tage Wochenende mit weniger Fahrbetrieb ist. Eine lineare Regression würde höchstens die letzten paar Datenpunkte in die Zukunft strecken, eine Polynomregression gar im Overfitting-Rausch völlig irre Patterns produzieren.
Die Lern-Algorithmen kommen auch schlecht mit unvollständigen Daten zurecht. Fehlen Messwerte, zum Beispiel an Tagen, an denen das Auto nur in der Garage stand, füllt der gewissenhafte Maschinenlehrer sie mit sinnvollen Werten auf, etwa mit Nullen. Und er fügt das hinzu, was in der Disziplin Machine Learning als “Expert Knowledge” gilt: Da der Wochentag der Datumswerte bekannt ist und dem Algorithmus hoffentlich weiterhilft, stellt eine neue CSV-Datei »miles-per-day-wday.csv« einfach die laufende Nummer des Wochentags (Strings mögen neuronale Netzwerke nicht, nur Zahlen) dem abgelesenen Tages-Kilometerstand entgegen (Abbildung 4).
Listing 2 nutzt dann das Framework »sklearn« zum Aufbau eines neuronalen Netzwerks, das es mit Kilometerständen trainiert, um den zugehörigen Wochentag zu erraten. Es liest dazu zunächst die CSV-Datei ein und formt daraus den Dataframe »X« mit den Kilometerständen sowie »y« als Vektor mit den zugehörigen Wochentagsnummern.
Listing 2
neuro.py
01 #!/usr/bin/python3
02 import pandas as pd
03
04 from sklearn.model_selection \
05 import train_test_split
06 from sklearn.preprocessing \
07 import StandardScaler
08 from sklearn.neural_network \
09 import MLPClassifier
10
11 train_df = \
12 pd.read_csv("miles-per-day-wday.csv")
13 X = train_df.drop('weekday', axis=1)
14 y = train_df['weekday']
15
16 X_train, X_test, y_train, y_test = \
17 train_test_split(X, y)
18
19 scaler = StandardScaler()
20 scaler.fit(X_train)
21 X_train_n = scaler.transform(X_train)
22 X_test_n = scaler.transform(X_test)
23
24 mlp = MLPClassifier(
25 hidden_layer_sizes=(2,2),max_iter=1000)
26 mlp.fit(X_train_n,y_train)
27
28 print(mlp.predict(X_test_n))
Die Funktion »train_test_split()« spaltet die vorliegenden Daten in ein Trainings- und in ein Testset auf, die der Standard-Skalierer in den Zeilen 19 bis 22 normalisiert, denn neuronale Netzwerke sind extrem penibel, was den Wertebereich der Eingabewerte angeht.
Das in Zeile 24 erzeugte Multilayer-Perceptron vom Typ »MLPClassifier« spannt das neuronale Netzwerk mit zwei Layern auf und schreibt vor, dass das Training maximal 1000 Schritte dauern darf. Der Aufruf der Methode »fit()« führt dann das Training durch, bei dem der Optimierer im so genannten Supervised Learning versucht die internen Rezeptorenverstärker zur Bewertung der Eingabe so lange zu verstellen, bis sich der Fehler zwischen der aus dem Trainingsparameter errechneten Vorhersage und dem Erwartungswert in »y_train« minimiert.
Das Ergebnis war im Versuch nicht berauschend, teilweise variierten die vorhergesagten Werte stark von Aufruf zu Aufruf und die Präzision ließ zu wünschen übrig. Eine Reihe verschiedenartiger Eingabeparameter würden zu besseren Ergebnissen führen.
Insgesamt stehen mit Tensorflow und Scikit zwei ausgereifte Frameworks zum Experimentieren mit KI-Anwendungen zur Verfügung. Der Einstieg ist nicht von Pappe, da die Literatur ([3], [4]) zu den neuesten Errungenschaften noch jung und unausgereift ist und eine Reihe von Werken sich im Entwicklungsstadium befindet. Es lohnt sich aber, die Materie zu erkunden, denn diesem Aufgabenfeld steht eine rosige Zukunft bevor.
Online PLUS
Im Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/Ausgaben/2017/07/plus
Infos
- Listings zu diesem Artikel: https://www.linux-magazin.de/static/listings/magazin/2017/07/snapshot/
- Michael Schilli, “Wege zum Connected Car”: Linux-Magazin 10/16, S. 84, https://www.linux-magazin.de/Ausgaben/2016/10/Perl-Snapshot
- Sarah Guido, Andreas C. Müller, “Introduction to Machine Learning with Python”: O’Reilly Media, 2016
- Aurélien Géron, “Hands-On Machine Learning with Scikit-Learn and Tensorflow”: O’Reilly Media, 2017











