iX 9/2017
S. 52
Titel
Maschinelles Lernen
Aufmacherbild

Python-Tutorial, Teil 2: Neuronale Netze und Deep Learning

Zeichenerkennung

Handgeschriebene Ziffern oder Gesichter zu erkennen, ist für Menschen eine leichte Sache. Computer hingegen taten sich damit bisher schwer. Verbesserte neuronale Netze (Deep Learning) erzielen verblüffende Ergebnisse, die man mit Python und der Bibliothek TensorFlow nachvollziehen kann.

Professor Yann LeCun und seine Kollegen aus dem Bereich der künstlichen Intelligenz der New York University standen vor dem Problem, dass es jede Menge Verfahren zur Erkennung von Schriften gab, aber keinerlei objektive Vergleichsmöglichkeiten. Welcher von zwei Algorithmen ist der bessere? Die Lösung, die Professor LeCun entwickelte, ist eine freie Bilddatenbank, die mehr als 70 000 Bilder von handgeschriebenen Ziffern enthält.

Die freie Bilddatenbank MNIST enthält 60 000 Graustufenbilder handgeschriebener Ziffern von unterschiedlichen Personen (Abb. 1).

Diese als MNIST (Modified National Institute of Standards and Technology Database) bekannte Bilderbasis entstand aus 60 000 Ziffern, die von Schriftproben der Mitarbeiter des American Census Bureau stammen, und weiteren 10 000 von US-High-School-Studenten. An dieser Sammlung kann sich jeder neue Algorithmus versuchen. Einige schaffen es in die Bestenliste, die zusammen mit den Daten auf der Webseite zu finden ist (siehe „Onlinequellen“, [a]). Häufig verwenden die Verfahren des maschinellen Lernens die handgeschriebenen Ziffern der Mitarbeiter zum Trainieren und die der Studenten, um später die Qualität zu überprüfen.

Integrierter Zugriff auf die Datenbasis

Bequemerweise enthält die im ersten Tutorialteil vorgestellte Bibliothek TensorFlow [1, b] bereits eine einfache Möglichkeit, an die MNIST-Bilder zu kommen:

from tensorflow.examples.tutorials.mnist ⤦
 import input_data
mnist = input_data.read_data_sets ⤦
 ("MNIST_data/", one_hot=True)

Dabei holt die Methode read_data_sets die handgeschriebenen Ziffern aus dem Internet und legt sie im als Parameter übergebenen Verzeichnis MNIST_data ab. Anschließend stehen alle Daten in der Variablen mnist zur Verfügung. Diese enthält drei Datasets: mnist.train mit den Trainingsbildern (circa 55 000), mnist.test zum Testen (circa 10 000) sowie mnist.validation zum Überprüfen der Ergebnisse (etwa 5000). Jedes Dataset besteht aus den eigentlichen Bildern, die sich in einem Array, beispielsweise mit dem Namen mnist.train.images, befinden, und der dazugehörigen Auflösung, um welche Zahlen es sich handelt (mnist.train.labels).

Wie in Abbildung 1 zu sehen, bestehen die Bilder aus 28 × 28 Bildpunkten. Da es sich um Graustufenbilder handelt, lässt sich jedes Pixel durch eine Zahl zwischen 0 für Weiß und 1 für Schwarz darstellen; 0,75 entspricht demnach einem dunklen und 0,25 einem hellen Grau. Zur Vereinfachung der späteren Berechnungen sind die 784 Bildpunkte eines Bildes in einem eindimensionalen Array gespeichert.

So liefert der Ausdruck mnist.train.images[100][6] dann vom hundertsten Bild den sechsten Bildpunkt. Zum Ausgeben des Werts für die tatsächlich dargestellte Zahl reicht die Zeile

mnist.train.label[100]
>[ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]

Wie man an der Ausgabe sieht, bekommt man, wenn es sich etwa um eine 4 handelt, nicht einfach den jeweiligen Integer-Wert zurück, sondern ein Array, bei dem das fünfte Element (Python zählt Arrays ab 0), also target[4], auf 1 gesetzt ist:

0 = 1000000000
1 = 0100000000
2 = 0010000000
et cetera

Diese Darstellung der Zahlen, manchmal One-Hot-Verschlüsselung genannt, vereinfacht die spätere Verarbeitung mit TensorFlow etwas.

Neuronale Netze zur Schrifterkennung einsetzen

Seit den 1950er-Jahren schwirrt der Begriff „neuronale Netze“ durch die Welt der Programmierung. Damals hatten Forscher entdeckt, dass die Neuronen im Gehirn unterschiedlich gewichtete Eingabeimpulse bekommen und daraus einen Ausgabeimpuls erzeugen, der wieder anderen Neuronen als Eingabe dient. Computerwissenschaftler haben versucht, dies mit der damaligen Technik durch Matrixberechnungen nachzuprogrammieren.

Was heutzutage bei der aktuellen Technik neuronaler Netze übrig geblieben ist, sind auf jeden Fall die Matrixberechnungen. Aktuelle neuronale Netze haben eigentlich nichts mehr mit der Hirnforschung zu tun. Der Vergleich zwischen menschlichen Gehirnen und Elektronenrechnern ist zwar gutes Marketing, kann aber bei dem Blick auf die technischen Hintergründe eher verwirren.

Aus Informatiksicht sind neuronale Netze nur eine Verkettung von Funktionen, deren Parameter sich über Lernbeispiele anpassen lassen. Die Funktionen rechnen mit Matrizen oder mehrdimensionalen Feldern, die die Python-Bibliothek TensorFlow als Tensoren bezeichnet.

Eine nichttriviale Aufgabenstellung: Schrifterkennung

Komplizierte Aufgaben – wie das Erkennen handgeschriebener Ziffern – lassen sich mit einem einfachen EVA (Eingabe-Verarbeitung-Ausgabe)-Schema aus der klassischen Programmierung darstellen (siehe Abbildung 2).

Aus den Bildpunkten als Eingabe ermittelt das neuronale Netz als Ausgabe die Wahrscheinlichkeiten für eine bestimmte Ziffer (Abb. 2).

Bei der Schrifterkennung ist die Eingabe die Farbe der einzelnen Bildpunkte. In diesem Fall sind es 28 × 28, also 784 Eingabevariablen. In der Mathematik werden diese klassischerweise mit dem Buchstaben x bezeichnet, in diesem Fall also mit x1 bis x784. Oder man speichert sie gleich in einem Array X, bestehend aus 784 Elementen.

Die Ausgabe Y ist die Wahrscheinlichkeit, dass es sich um eine bestimmte Ziffer handelt. Beispielsweise steht das Symbol y0 für die Wahrscheinlichkeit, dass es sich um eine 0 handelt, y1 dafür, dass es eine 1 ist, et cetera. Das Ergebnis Y ist wieder ein Array, und zwar aus zehn Elementen (y0, …, y9).

Logistische Regression

Man könnte auch sagen, es gibt die Klassen 0 bis 9. Die Aufgabe besteht jetzt darin, zu ermitteln, zu welcher Klasse eine geschriebene Ziffer gehört. Daher stuft man dies im maschinellen Lernen als Klassifizierungsproblem ein. Ein Grund, warum die Ausgabe mit Wahrscheinlichkeiten arbeitet und nicht nur einfach das beste Ergebnis als Zahl ausgibt, ist die Option, nicht nur das bestmögliche Ergebnis nachsehen zu können, sondern genauso, welches das zweit- oder drittbeste gewesen wäre.

Der Ansatz, dass jeder Bildpunkt xi irgendwie die Ausgabe Y beeinflusst, ist zwar nicht sehr präzise, aber bestimmt nicht ganz verkehrt. Um es etwas genauer zu formulieren: Jede Eingabe X beeinflusst jede Ausgabe Y mit einem bestimmten Gewicht (Weight), einem bestimmten Faktor. Eine Formel für die Wahrscheinlichkeit, dass es sich um die Ziffer 0 handelt, könnte mit dem Bias b dann so aussehen:

y0 = W11 * x1 + W12 * x2 + W13 * x3 + … + b1
Wie die Eingaben x die Ausgaben y mit bestimmten Gewichten beeinflussen, lässt sich als Flussdiagramm, Formel oder in Matrix-Schreibweise darstellen (Abb. 3).

Die zehn Formeln – für jede Ziffer von 0 bis 9 eine – lassen sich in eine einzige Formel mit Matrizen und Vektoren zusammenfassen (siehe Abbildung 3). Das hat den Vorteil, dass TensorFlow genau darauf ausgelegt ist. Eine Einführung zu diesem Modul enthält der erste Artikel dieser Reihe ab Seite 42 [1].

import tensorflow as tf
x = tf.placeholder(tf.float32, [None, 784])

Die Eingabe x enthält die Bildpunkte als Gleitkommazahlen (float32). In diesem Fall ist x als zweidimensionales Array definiert, mit dem sich gleichzeitig mehrere Bilder übergeben lassen. Die erste Dimension steht für das Bild, die zweite für die Bildpunkte, so ist x[4][6] der sechste Bildpunkt des vierten Bilds. Das Array x ist in TensorFlow mit [None, 784] definiert. None steht dabei für eine unbekannte Größe, hier eine vorab nicht bekannte Zahl von Bildern.

Da x nur zur Übergabe von Daten aus Python an TensorFlow dient, reicht ein tf.placeholder vollkommen aus. Die Gewichtung W und das Bias b sind dagegen Variablen, die sich während der Berechnung ändern und daher zu definieren sind: