Machine Learning für Anfänger mit TensorFlow.js: Lineare Regression

Machine Learning für Anfänger mit TensorFlow.js: Lineare Regression

Nach meinem letzten Video über Machine Learning, wollte ich es etwas genauer wissen und der Technik unter die Haube schauen. Dabei bin ich auf die Google JS Library TensorFlow.js gestoßen, welche Machine Learning auf Basis von JavaScript möglich macht. Kurz gefasst, ermöglicht Tensorflow Machine Learning im Browser! Daraus ergibt sich eine solide Grundlage für kleine Experimente.

Eines dieser Experimente habe umgesetzt und möchte in diesem Beitrag anhand eines praktischen Beispiels Schritt für Schritt erklären wie Machine Learning (AI) mit TenserFlow.js funktioniert.


Problemstellung

Basierend auf den historischen Daten, die in einem Restaurant erhoben wurden, soll eine Vorhersage darüber getroffen werden, wie viel Trinkgeld (y) bei einem Rechnungsbetrag (x) zu erwarten ist.

Historische Daten

Gast (i)Rechnungsbetrag (x) in EURTrinkgeld (y) in EUR
110.902.00
230.603.00
350.707.00
425.102.00
57.802.50
642.506.00
735.205.00
840.404.00
925.306.00
1012.301.00
1160.207.00
1247.805.50
1345.707.00
1427.504.50
1515.101.50
1620.104.00
1747.509.00
1832.503.00
1937.506.50
2020.002.50

Nachdem die Daten erhoben wurden, müssen Sie im nächsten Schritt in ein etwas anschaulicheres Modells übertragen werden. An dieser Stelle liegt es Nahe ein Koordinatensystem dafür zu wählen, da wir hier lediglich mit zwei Dimensionen arbeiten.

Die Grafik verrät bereits auf den ersten Blick, dass es eine annähernd lineare Korrelation zwischen x und y gibt. Das bedeutet, dass die Höhe des Trinkgeldes, in einem bestimmten Verhältnis zur Höhe des Rechnungsbetrags steht.

Diese erste Erkenntnis ist wichtig. Denn Sie ist der Wegweiser für das mathematische Modell, welches für den ML Algorithmus angewandt wird.

In unserem konkreten Beispiel, bedienen wir uns des Modells der Linearen Regression. Aber zunächst wollen wir die ersten Zeilen Code  für unser Machine Learning Algorithmus in Tensorflow.js schreiben. Dazu initialisieren wir unsere gesammelten Daten, die wir später als Trainingsdaten nutzen werden.

const trainX = [10.90, 30.60, 50.70, 25.10, 7.80, 42.50, 35.20, 40.40, 25.30, 12.30, 60.20, 47.80, 45.70, 27.50, 15.10, 20.10, 47.50, 32.50, 37.50, 20.00];

const trainY = [2.00, 3.00, 7.00, 2.00, 2.50, 6.00, 5.00, 4.00, 6.00, 1.00, 7.00, 5.50, 7.00, 4.50, 1.50, 4.00, 9.00, 3.00, 6.50, 2.50];

Lineare Regression

Die LR wird wie folgt definiert:

Die Durchführung einer Regression (lat. regredi = zurückgehen) hat das Ziel, anhand von mindestens einer unabhängigen Variablen x (auch erklärende Variable genannt) die Eigenschaften einer anderen abhängigen Variablen y zu prognostizieren. Wenn die abhängige Variable nur von einer unabhängigen Variablen beschrieben wird, so spricht man von einer einfachen linearen Regression.

https://www.inwt-statistics.de/blog-artikel-lesen/Einfache_lineare_Regression.html

Im Prinzip beschreibt die Definition genau unsere Problemstellung. Wir möchten eine Vorhersage (Prognose) anhand von einer Variable x (Rechnungsbetrag) für die Variable y (Trinkgeld) treffen.

Als Voraussetzung brauchen wir etwas Grundwissen aus der Statistik und Algebra. Dann kann es auch schon losgehen.

Das Vorgehen lässt sich in drei Schritte gliedern.

Schritt 1: Zufällige Gerade

Im ersten Schritt wird eine Gerade mit der Formel y = m  x + b durch das Koordinatensystem gezogen. Dabei sind die Werte m und b willkürlich. Die x-Werte haben wir aus unserem Datenbestand übernommen (trainX). Die Konstante m steht dabei für die Steigung der Geraden und b für die Verschiebung entlang der y-Achse.

See the Pen Linear Regression: no training by Kermin (@kermin) on CodePen.

Wir sehen ein ziemlich schlechtes Ergebnis. Die Gerade beschreibt unsere Verteilung nicht wirklich gut. Aber welche Gerade wäre optimal? Immerhin gibt es offensichtlich keine Möglichkeit alle Punkte mit einer Gerade zu beschreiben.

Die Lösung ist eine Annäherung. Eine Gerade mit möglichst geringen Abständen zu den Punkten aus dem Datenbestand. Um das zu erreichen, wird der Fehler (error) gemessen. Also der Abstand zwischen dem Punkt der sich aus den Daten ergibt und dem Punkt in vertikaler Richtung auf der Geraden. Das Ziel bei diesem Vorgehen ist es, die Gerade so zu wählen, dass der Fehler das Minimum erreicht. Bevor wir uns die Fehlerrechnung im Detail anschauen, zunächst etwas Code für unseren Algorithmus.

Dazu initialisieren die Konstanten m und b aus unserer Geraden-Gleichung y = m x + b, deren Werte wir mit Math.random() zufällig zwischen 0 und 1 bestimmten lassen.


const m = tf.variable(tf.scalar(Math.random()));
const b = tf.variable(tf.scalar(Math.random()));

Damit ist unsere Geraden-Gleichung einsatzbereit. Möchten wir nun y bestimmen, so können wir einen beliebigen x-Wert in die Funktion einsetzen. Das Ergebnis ist selbstverständlich wenig aussagekräftig, da wir für m und b Zufallswerte nutzen. Trotzdem möchten wir später diese Gleichung verwenden. Dafür definieren eine Funktion predict().


function predict(x) {
  return tf.tidy(function() {
    return m.mul(x).add(b);
  });
}

Die Funktion erwartet einen Parameter x und gibt uns den entsprechenden y-Wert zurück.

Schritt 2: Fehlerrechnung

Da wir in Schritt 1 eine zufällige Werte für m und b verwendet haben, stellt sich die Frage, wie wir die Funktion der Geraden so optimieren können, dass sie unser Datenset optimal beschreibt.

In nachfolgenden Schaubild habe ich händisch eine neue Gerade durch die Datenwolke gezogen. Diesmal läuft sie durch die Datenpunkte und kommt einer guten Lösung schon sehr nahe.

Soweit sind wir mit unserem Code noch nicht. Bevor wir optimieren können, muss zunächst ein Weg gefunden werden, um mit Zahlen die „Qualität“ der Zufallsgeraden beschreiben zu können. Das funktioniert über die Fehlerrechnung.

Dazu werden die Abstände von jedem Punkt entlang der y-Achse zur Geraden gemessen.

Lineare Regression: Fehlerrechnung
Mean Squared Error Function

Im unserem konkreten Beispiel, summieren wir die Abstände e1 bis e20 auf und quadrieren sie. Abschließend dividieren wir durch die Anzahl der Datenpunkte (in unserem Fall gilt N=20). Als Ergebnis erhalten wir den Mean Squared Error (Mittlerer Quadratischer Fehler).

Beispielrechnung:

Beispielrechnung für einen MSE

Dabei gilt, je kleiner der MSE ist, desto genauer beschreibt unsere Gerade das Datenset und umso genauer können wir eine Vorhersage für einen neuen Rechnungsbetrag (x) treffen.

Um dies mit Tensorflow.js umzusetzen, brauchen wir eine Funktion die den Fehler berechnet. Beim Machine-Learning spricht man auch von einer Loss-Function.

Die Funktion erwartet zwei Parameter. 

  • prediction: Die y-Werte der (Zufalls-) Geraden die wir untersuchen
  • actualValues: Die y-Werte aus dem Datenbestand (trainY)

Damit haben wir wir also alle y– und y‘-Werte um die MSE Formel anwenden zu können.


function loss(prediction, actualValues) { 
   const error = prediction.sub(actualValues).square().mean(); 
   return error; 
}

Das schöne an Tensorflow ist, dass es uns die Operatoren für die Berechnung schon bereitstellt. Damit können wir die y‘-Werte (actualValues) von den y-Werte (prediction) subtrahieren (sub()), quadrieren (square()) und dividieren (mean()). Über return wird der Mittlere Quadratische Fehler zurückgegeben.

Bevor es mit Schritt 3 weitergeht, fassen wir die bisherigen Codeschnipsel einmal zusammen.


// Trainingsdaten (historischer Datenbestand)

// Rechnungen
const trainX = [10.90, 30.60, 50.70, 25.10, 7.80, 42.50, 35.20, 40.40, 25.30, 12.30, 60.20, 47.80, 45.70, 27.50, 15.10, 20.10, 47.50, 32.50, 37.50, 20.00];

// Trinkgelder
const trainY = [2.00, 3.00, 7.00, 2.00, 2.50, 6.00, 5.00, 4.00, 6.00, 1.00, 7.00, 5.50, 7.00, 4.50, 1.50, 4.00, 9.00, 3.00, 6.50, 2.50];

// m und b als Zufallswerte für die Geradengleichung
const m = tf.variable(tf.scalar(Math.random()));
const b = tf.variable(tf.scalar(Math.random()));

// Geradengleichung
function predict(x) {
  return tf.tidy(function() {
    return m.mul(x).add(b);
  });
}

// Loss-Function zur Ermittlung des Mittlern Quadratischen Fehlers
function loss(prediction, actualValues) { 
   const error = prediction.sub(actualValues).square().mean(); 
   return error; 
}

Um den Code besser zu verstehen, loggen wir die Konstanten m und b. Außerdem wollen wir den den Fehler ausgeben.


const prediction = predict(tf.tensor1d(trainX));
loss(prediction, tf.tensor1d(trainY)).print();

Dazu übergeben wir das Array trainX als Eindimensionaler Tensor and die Funktion prediction(). Diese gibt uns für jeden x-Wert den zu erwartenden y-Wert. Im Anschluss können wir die Werte über die Loss-Function auswerten. Das Ergebnis kann über die print() ausgegeben werden.

Loss-Function in Aktion

Das wiederholte Ausführen des Codes zeigt, wie m und b im Zusammenhang mit dem Fehler stehen. Über JavaScript haben wir m und b als Konstanten initialisiert.


const m = tf.variable(tf.scalar(Math.random()));
const b = tf.variable(tf.scalar(Math.random()));

Allerdings ist das nur die halbe Wahrheit. Denn mit der Function tf.variable() werden die Konstanten als Variablen an Tenserflow übergeben. Damit sagen wir TF, dass diese Werte sehr wohl variabel sein dürfen. Warum das so wichtig ist, wird im nächsten Schritt klarer.

Schritt 3: Training

In diesem Step geht es darum unser Programm selbständig lernen zu lassen. Dazu wollen wir den Fehler systematisch minimieren. Bevor wir uns anschauen wie das mit TF umgesetzt wird, zunächst wieder etwas Theorie.

Die Methode zur Optimierung die hier eingesetzt wird nenn sich Gradient Decent. Diese basiert auf einem iterativen Ansatz.

Decent Gardient

Dabei bewegen wir uns mit jedem Schleifendurchgang in Richtung des Optimums, indem die Werte für m und b so angepasst werden, dass sich der Fehlerwert verringert.

Dazu brauchen wir eine Methode die uns die Werte aus dem vorherigen Druchlauf zwischenspeichert, sie vergleicht und anschließend m und b entsprechend nach oben oder nach unten korrigiert. Außerdem müssen wir definieren wie „groß“ die Schritte sein dürfen um den nächsten Punkt zu erreichen. Diese Schritte nennen sich Learning Rate oder Lernrate.

Bevor wir auf die Lernrate noch etwas genauer eingehen, wollen wir unser Modell mit Tensorflow endlich etwas lernen lassen. Dazu müssen zunächst zwei Konstanten initialisiert werden.


const learningRate = 0.0003;
const optimizer = tf.train.sgd(learningRate);

Neu an dieser Stelle ist lediglich der optimizer. Er besteht aus

  • train: Tenserflow Traingsfunktion
  • sgd: Steht für Stochastic Gardient Decent (Funktion für das statistische Verfahren)

Der Gardient Decent Funktion erwartet die Lerarning-Rate als Paramter.

Jetzt haben wir das Rüstzeug um die wichtigste Funktion überhaupt zu bauen – die Trainings-Funktion.


function train() { 
  optimizer.minimize(function() { 
    const predsYs = predict(tf.tensor1d(trainX)); 
    stepLoss = loss(predsYs, tf.tensor1d(trainY))   
    stepLoss.print();
    return stepLoss; 
  }); 
}

Sie besteht aus hauptsächlich aus dem Aufruf der minimize-Function über den optimizer, welcher wiederum eine Funktion als Parameter übergeben wird. In dieser führen wir unsere Loss-Funktion aus und erhalten den Mean Squared Error, welchen wir mit return zurückgeben.

Train-Function

Führen wir train() mehrfach in der Konsole aus, dann passiert etwas interessantes – der Fehler wird mit jedem Durchlauf kleiner! Tensorflow hat sich also jeweils die Werte aus dem vorherigen Durchlauf gemerkt, und diese dann als Grundlage für eine neue Annäherung verwendet.

Wie bereits Eingangs erwähnt, sollten wir uns die Lernrate abschließend noch etwas genauer ansehen. Ich habe den Wert von 0.0003 bereits vorgegeben. Ich hätte aber jeden anderen beliebigen Wert einsetzen können. Die Learning Rate solle jedoch mit Bedacht gewählt werden, denn ein zu großer Wert führt dazu, dass wir mit zu großen Schritten über das Ziel (Minimum bzw. Optimum) hinaus schießen.

Beispiel für zu hohen Wert für die Learning Rate

Ein zu kleiner Wert wiederum, kann unseren Algorithmus unendlich lang laufen lassen, weil die Schritte zu klein sind um das Minimum jemals zu erreichen. 

Vorhersage treffen

Zu guter letzt wollen wir mit unseren optimierten Modell auf unsere Problemstellung vom Anfang eingehen. Wir erinnern uns, wir wollten über einen Betrag x eine Vorhersage darüber treffen, wie hoch das zu erwartende Trinkgeld ist. 

Dazu nehmen wir einen Rechnungsbetrag von 23,50 EUR an. Nachdem wir unser Modell trainiert haben, können wir die Funktion predict() aufrufen und den Betrag übergeben und loggen mit print() das Ergebnis in die Konsole.


predict(23.50).print();
Prediction

Großartig! Wir haben ein Ergebnis und erwarten ein Trinkgeld von rund 3,29 EUR.


Fazit

Geschafft! Damit haben wir unser ersten selbstlernenden Machine Learning Algorithmus mit Tensorflow.js realisiert.

Wir haben anhand einer Problemstellung ein Modell entwickelt, welches wir an den vorliegenden Daten abgeleitet haben. Im Anschluss wurde das Modell mit Tensorflow umgesetzt und wir waren damit in der Lage unsere KI lernen zu lassen. Nach dem Lernprozess konnten wir schließlich eine Vorhersage für unser Beispiel aus dem Restaurant, für das zu erwartende Trinkgeld, treffen.

Abschließend an dieser Stelle möchte ich noch einige Punkte anmerken. Ich bin auf einige Dinge bewusst nicht eingegangen. Dazu gehören unter anderem die spezifische Funktionsweise von Tensorflow.js. Außerdem habe ich nicht alle Aspekte der Linearen Regression in diesem Beitrag diskutiert. Dieses Beispiel soll nur der Veranschaulichung dienen und ist keinen Falls als Benchmark zu verstehen. Aus diesem Grund möchte ich an dieser Stelle einige Quellen empfehlen, auf die dieser Artikel aufbaut und die viel detaillierter dokumentiert sind. 

Credits

Titelbild: Pixelbay – TensorFlow, the TensorFlow logo and any related marks are trademarks of Google Inc.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Nächster Beitrag:

DMEXCO 2018: Künstliche Intelligenz – Verwirrung statt Nutzen

DMEXCO 2018: Künstliche Intelligenz – Verwirrung statt Nutzen