Kategorien
Deep Learning Künstliche Intelligenz

Einführung in Deep Learning mit TensorFlow, Keras und Python

TensorFlow ist ein mächtiges Framework für maschinelles Lernen. Es wird von Google entwickelt und unter einer Open-Source Lizenz bereitgestellt.

Keras ist eine Framework, das eine einheitliche Schnittstelle zu verschiedenen machine learning Backends, wie TensorFlow, Theano oder dem Cognitive Toolkit von Microsoft bietet. Das bedeutet, dass Code, der einmal in Keras geschrieben wurde, ohne weitere Änderung auf diesen drei Frameworks läuft. Mit Keras kann man neuronale Netze in wenigen Zeilen Code entwickeln und trainieren. Ein Kears Programm kann entweder auf der CPU oder der GPU ausgeführt werden.

Vorraussetzung

Wenn man die Beispiele aus dieser Anleitung selbst ausprobieren möchte, müssen auf dem Rechner Python 3 und die Packete NumPy, TensorFlow und Keras installiert sein. Am besten installiert man sich die Anaconda Plattform.

Das Laden der Daten

Wir verwenden einen Datensatz mit Informationen über Diabeteserkrankungen der Pima Indianer. Dieser Datensatz stammt ursprünglich vom National Institute of Diabetes and Digestive and Kidney Diseases. Das Ziel des Datensatzes ist es, anhand bestimmter diagnostischer Messungen, die im Datensatz enthalten sind, diagnostisch vorherzusagen, ob ein Patient an Diabetes leidet oder nicht.

Details zum Datensatz findet man hier: https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.names
Den Datensatz selbst heraunterladen kann man hier: https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv

Die Daten beschreiben, ob ein Proband in den letzten fünf Jahren an Diabetes erkrant war oder nicht. Das macht unsere Aufgabe zu einem binären Klassifizierungsproblem:

0: Der Proband war nicht erkrankt.
1: Der Proband war erkrankt.

Da alle Daten in der CSV-Datei numerisch gespeichert sind, können wir sie leicht mit Numpy und Keras verarbeiten.

# Importieren der Klassen und Funktionen

from numpy import loadtxt
from keras.models import Sequential
from keras.layers import Dense

# Den Datensatz aus der CSV-Datei einlesen und in eine Numpy-Variable speichern

dataset = loadtxt('pima-indians-diabetes.csv', delimiter=',')

# Wir speichern in X die Input-Daten und in Y die Output-Daten

X = dataset[:,0:8]
y = dataset[:,8]

Wenn wir die Datei pima-indians-diabetes.csv mit einem Texteditor öffnen, sehen wir etwas in der Art:

6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1

Die ersten acht Zahlen sind die Input-Daten und stehen für folgendes:

  1. Anzahl der Schwangerschaften
  2. Plasmaglukosekonzentration a 2 Stunden in einem oralen Glukosetoleranztest
  3. Diastolischer Blutdruck (mm Hg)
  4. Dicke der Trizeps-Hautfalte (mm)
  5. 2-Stunden-Serum-Insulin (mu U/ml)
  6. Body-Mass-Index (Gewicht in kg/(Größe in m)^2)
  7. Diabetes-Ahnentafel-Funktion
  8. Alter (Jahre)

Die neunte Zahl einer Zeile ist der Output. Sie gibt an, ob dieser Proband an Diabetes erkrankt war oder nicht.

Definition des Neuronalen Netzes in Keras

Neuronale Netze in Keras sind als eine Folge von Schichten definiert.

Wir erstellen ein Sequentielles Modell und fügen Schichten einzeln hinzu, bis wir mit unserer Netzwerkarchitektur zufrieden sind.

Als erstes muss sichergestellt werden, dass die Eingabeschicht die richtige Anzahl von Eingabemerkmalen hat. Dies kann beim Erstellen der ersten Schicht mit dem input_dim-Argument und dem Setzen auf 8 für die 8 Input-Variablen spezifiziert werden.

Wie wissen wir die Anzahl der Schichten und deren Typen?

Dies ist eine sehr schwierige Frage und Gegenstand aktueller Forschung. Es gibt Heuristiken, die wir verwenden können. Oft wird die beste Netzwerkstruktur jedoch durch einen Prozess des Ausprobierens und Experimentierens gefunden. Im Allgemeinen braucht man ein Netzwerk, das groß genug ist, um die Struktur des Problems zu erfassen.

Wir verwenden hier ein vollständig zusammenhängendes Netz mit drei Schichten (fully connected layers).

Vollständig verbundene Schichten werden mit der Klasse Dense definiert. Wir können die Anzahl der Neuronen (oder Knoten) in der Schicht als erstes Argument angeben und die Aktivierungsfunktion mit dem Aktivierungsargument spezifizieren.

Wir verwenden die ReLU Aktivierungsfunktion auf den ersten beiden Schichten und die Sigmoid-Funktion in der Ausgabeschicht.

Früher wurden die Aktivierungsfunktionen Sigmoid und Tanh für alle Schichten bevorzugt. Heutzutage wird eine bessere Performance mit der Aktivierungsfunktion ReLU erreicht. Wir verwenden Sigmoid auf dem Output-Layer, um sicherzustellen, dass unsere Netzwerkausgabe zwischen 0 und 1 liegt. Die Ausgabe wird dann der Klasse 1 zugeordnet, wenn der Schwellenwert 0,5 überschritten wird und der Klasse 0, wenn der Wert unter 0,5 liegt.

Zusammengefasst bedeutet das:

  • Das neuronale Netz verarbeitet einen Input mit 8 Variablen.
  • Die erste verborgene Schicht (hidden layer) hat 16 Neuronen (Knoten).
  • Die zweite verborgene Schicht hat 8 Neuronen.
  • Die dritte verborgene Schicht hat 4 Neuronen.
  • Die letzte Schicht (Output) hat 1 Knoten.
model = Sequential()
model.add(Dense(16, input_dim=8, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(4, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

Hier ist darauf zu achten, dass in der ersten verborgenen Schicht die Dimension des Inputs angegeben wird.

Das neuronale Netz kompilieren

Zum Kompilieren wählt das Backend (TensorFlow in unserem Fall) eine gute Repräsentation des Modells und definiert die Hardware, auf der das Training effizient läuft (CPU oder GPU).

Beim Kompilieren müssen wir einige zusätzliche Eigenschaften angeben, die beim Training des Netzwerks benötigt werden. Trainieren eines Netzwerks bedeutet, den besten Satz von Gewichten zu finden, um die Eingänge auf die Ausgänge in unserem Datensatz abzubilden.

Wir müssen die Verlustfunktion angeben, die zur Auswertung eines Satzes von Gewichten verwendet werden soll. Der Optimierer wird verwendet, um verschiedene Gewichte für das Netzwerk und alle optionalen Metriken zu durchsuchen, die wir während des Trainings sammeln und berichten möchten.

Wir verwenden „cross entropy“ als Verlustfunktion für unsere binäre Klassifizierung. Als Optimierer nehmen wir „Adam„, einen effizienten stochastischen gradient descent Algorithmus, der sich selbst optimiert.

Eine Auswertung über die Genauigkeit unseres Netzes sammeln wir mit dem „metrics“ Parameter.

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

Das neuronale Netz trainieren

Wir haben unser Modell definiert und für eine effiziente Berechnung zusammengestellt.

Nun ist es an der Zeit, das Modell an einigen Daten auszuführen.

Wir können unser Modell auf unseren geladenen Daten trainieren oder anpassen, indem wir die fit() Funktion auf dem Modell aufrufen.

Das Training erfolgt über Epochen und jede Epoche ist in Batches aufgeteilt.

  • Epoche: Man durchläuft alle Zeilen des Trainingsdatensatzes.
  • Batch: Eine oder mehrere Samples, die vom Modell innerhalb einer Epoche berücksichtigt werden, bevor die Gewichte aktualisiert werden.

Eine Epoche besteht aus einem oder mehreren Batches, basierend auf der gewählten Batchgröße und das Modell ist für viele Epochen geeignet.

Der Trainingsprozess läuft für eine feste Anzahl von Iterationen durch den Datensatz namens Epochen, die wir mit dem Argument Epochen angeben müssen. Wir müssen auch die Anzahl der Datensatzzeilen festlegen, die berücksichtigt werden, bevor die Modellgewichte innerhalb jeder Epoche aktualisiert werden, was als Batchgröße bezeichnet und mit dem Argument batch_size festgelegt wird.

Für dieses Problem werden wir für eine kleine Anzahl von Epochen (150) laufen und eine relativ kleine Batchgröße von 10 verwenden.

Diese Konfigurationen können experimentell durch Ausprobieren ausgewählt werden. Wir wollen das Modell so weit trainieren, dass es eine gute (oder ausreichend gute) Abbildung von Zeilen der Eingangsdaten auf die Ausgangsklassifikation lernt. Das Modell wird immer einen gewissen Fehler aufweisen, aber die Fehlermenge wird sich nach einiger Zeit für eine gegebene Modellkonfiguration nivellieren. Dies wird als Modellkonvergenz bezeichnet.

model.fit(X, y, epochs=150, batch_size=10)

Auswertung

Wir haben unser neuronales Netz auf den gesamten Datensatz trainiert und können die Leistung des Netzes auf dem gleichen Datensatz bewerten.

Dies gibt uns nur eine Vorstellung davon, wie gut wir den Datensatz modelliert haben, aber keine Vorstellung davon, wie gut der Algorithmus auf neuen Daten funktionieren könnte. Wir haben dies der Einfachheit halber getan, aber idealerweise könnten Sie Ihre Daten in Trainings- und Testdatensätze trennen, um Ihr Modell zu trainieren und auszuwerten.

Wir können unser Modell auf Ihrem Trainingsdatensatz mit der evaluate()-Funktion auf Ihrem Modell auswerten und ihm den gleichen Input und Output übergeben, der zum Training des Modells verwendet wurde.

Dadurch wird eine Vorhersage für jedes Input- und Output-Paar generiert und es werden Werte gesammelt, einschließlich des durchschnittlichen Verlusts und aller Metriken, die wir konfiguriert haben, wie z.B. die Genauigkeit.

Die Funktion evaluate() gibt eine Liste mit zwei Werten zurück. Der erste Wert ist der Verlust des Modells im Datensatz und der zweite Wert ist die Genauigkeit des Modells im Datensatz. Wir sind nur daran interessiert, die Genauigkeit zu berichten, daher werden wir den Verlustwert ignorieren.

_, accuracy = model.evaluate(X, y)
print('Accuracy: %.2f' % (accuracy*100))

Vorhersagen machen

Nun haben wir ein tolles neuronales Netz trainiert. Dieses wollen wir selbstvertänglich verwenden, um Vorhersagen auf neuen Daten zu machen. Das geht ganz einfach mit der predict() Funktion.

predictions = model.predict(X)
rounded = [round(x[0]) for x in predictions]

Da wir die Sigmoid-Funktion verwenden, ist der Output eine Wahrscheinlichkeit zwischen 0 und 1. Wir können uns auch direkt die vorhergesagte Klasse ausgeben lassen:

predictions = model.predict_classes(X)