Ein Leitfaden für Deeplearning4j

1. Einleitung

In diesem Artikel erstellen wir ein einfaches neuronales Netzwerk mit der Bibliothek deeplearning4j (dl4j) - einem modernen und leistungsstarken Tool für maschinelles Lernen.

Bevor wir anfangen, erfordert dieser Leitfaden keine fundierten Kenntnisse in linearer Algebra, Statistik, Theorie des maschinellen Lernens und vielen anderen Themen, die für einen fundierten ML-Ingenieur erforderlich sind.

2. Was ist Deep Learning?

Neuronale Netze sind Rechenmodelle, die aus miteinander verbundenen Schichten von Knoten bestehen.

Knoten sind neuronähnliche Prozessoren numerischer Daten. Sie nehmen Daten von ihren Eingaben, wenden einige Gewichte und Funktionen auf diese Daten an und senden die Ergebnisse an Ausgaben. Ein solches Netzwerk kann mit einigen Beispielen der Quelldaten trainiert werden.

Das Training speichert im Wesentlichen einen numerischen Zustand (Gewichte) in den Knoten, was sich später auf die Berechnung auswirkt. Trainingsbeispiele können Datenelemente mit Merkmalen und bestimmte bekannte Klassen dieser Elemente enthalten (z. B. „Dieser Satz von 16 × 16 Pixeln enthält einen handgeschriebenen Buchstaben„ a “).

Nach Abschluss des Trainings kann ein neuronales Netzwerk Informationen aus neuen Daten ableiten, auch wenn diese bestimmten Datenelemente zuvor noch nicht gesehen wurden . Ein gut modelliertes und gut ausgebildetes Netzwerk kann Bilder, handgeschriebene Briefe, Sprache erkennen, statistische Daten verarbeiten, um Ergebnisse für Business Intelligence zu erzielen, und vieles mehr.

Tiefe neuronale Netze wurden in den letzten Jahren mit dem Fortschritt des Hochleistungs- und Parallel-Computing möglich. Solche Netzwerke unterscheiden sich von einfachen neuronalen Netzwerken dadurch, dass sie aus mehreren Zwischenschichten (oder versteckten Schichten) bestehen . Diese Struktur ermöglicht es Netzwerken, Daten viel komplizierter zu verarbeiten (rekursiv, wiederkehrend, faltungsmäßig usw.) und viel mehr Informationen daraus zu extrahieren.

3. Einrichten des Projekts

Um die Bibliothek nutzen zu können, benötigen wir mindestens Java 7. Aufgrund einiger nativer Komponenten funktioniert sie auch nur mit der 64-Bit-JVM-Version.

Bevor wir mit dem Handbuch beginnen, überprüfen wir, ob die Anforderungen erfüllt sind:

$ java -version java version "1.8.0_131" Java(TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

Fügen wir zunächst die erforderlichen Bibliotheken zu unserer Maven- Datei pom.xml hinzu . Wir extrahieren die Version der Bibliothek in einen Eigenschaftseintrag (die neueste Version der Bibliotheken finden Sie im Maven Central-Repository):

 0.9.1    org.nd4j nd4j-native-platform ${dl4j.version}   org.deeplearning4j deeplearning4j-core ${dl4j.version}  

Beachten Sie, dass die Abhängigkeit von nd4j-native-platform eine der mehreren verfügbaren Implementierungen ist.

Es basiert auf nativen Bibliotheken, die für viele verschiedene Plattformen (macOS, Windows, Linux, Android usw.) verfügbar sind. Wir könnten das Backend auch auf die nd4j-cuda-8.0-Plattform umstellen , wenn wir Berechnungen auf einer Grafikkarte ausführen möchten , die das CUDA-Programmiermodell unterstützt.

4. Daten vorbereiten

4.1. Vorbereiten der DataSet-Datei

Wir werden die „Hallo Welt“ des maschinellen Lernens schreiben - Klassifizierung des Irisblumendatensatzes. Dies ist ein Datensatz, der von Blüten verschiedener Arten ( Iris setosa , Iris versicolor und Iris virginica ) gesammelt wurde .

Diese Arten unterscheiden sich in Länge und Breite der Blütenblätter und Kelchblätter. Es wäre schwierig, einen genauen Algorithmus zu schreiben, der ein Eingabedatenelement klassifiziert (dh bestimmt, zu welcher Art eine bestimmte Blume gehört). Ein gut ausgebildetes neuronales Netzwerk kann es jedoch schnell und fehlerfrei klassifizieren.

Wir werden eine CSV-Version dieser Daten verwenden, wobei die Spalten 0..3 die verschiedenen Merkmale der Art enthalten und die Spalte 4 die Klasse des Datensatzes oder die Art enthält, die mit einem Wert von 0, 1 oder 2 codiert ist:

5.1,3.5,1.4,0.2,0 4.9,3.0,1.4,0.2,0 4.7,3.2,1.3,0.2,0 … 7.0,3.2,4.7,1.4,1 6.4,3.2,4.5,1.5,1 6.9,3.1,4.9,1.5,1 …

4.2. Vektorisieren und Lesen der Daten

Wir codieren die Klasse mit einer Zahl, weil neuronale Netze mit Zahlen arbeiten. Das Transformieren realer Datenelemente in Zahlenreihen (Vektoren) wird als Vektorisierung bezeichnet. Deeplearning4j verwendet dazu die datavec-Bibliothek.

Verwenden wir zunächst diese Bibliothek, um die Datei mit den vektorisierten Daten einzugeben. Beim Erstellen des CSVRecordReader können wir die Anzahl der zu überspringenden Zeilen (z. B. wenn die Datei eine Kopfzeile enthält) und das Trennzeichen (in unserem Fall ein Komma) angeben:

try (RecordReader recordReader = new CSVRecordReader(0, ',')) { recordReader.initialize(new FileSplit( new ClassPathResource("iris.txt").getFile())); // … }

Um die Datensätze zu durchlaufen , können Sie eine der mehreren Implementierungen der DataSetIterator- Schnittstelle verwenden. Die Datensätze können sehr umfangreich sein, und die Möglichkeit, die Werte zu paginieren oder zwischenzuspeichern, kann nützlich sein.

Unser kleiner Datensatz enthält jedoch nur 150 Datensätze. Lesen Sie daher alle Daten mit einem Aufruf von iterator.next () auf einmal in den Speicher .

Wir geben auch den Index der Klassenspalte an, der in unserem Fall der Anzahl der Features (4) und der Gesamtzahl der Klassen (3) entspricht.

Beachten Sie außerdem, dass wir das Dataset mischen müssen, um die Klassenreihenfolge in der Originaldatei zu entfernen.

Wir geben einen konstanten zufälligen Startwert (42) anstelle des Standardaufrufs System.currentTimeMillis () an , damit die Ergebnisse des Mischens immer gleich sind. Dies ermöglicht es uns, jedes Mal stabile Ergebnisse zu erzielen, wenn wir das Programm ausführen:

DataSetIterator iterator = new RecordReaderDataSetIterator( recordReader, 150, FEATURES_COUNT, CLASSES_COUNT); DataSet allData = iterator.next(); allData.shuffle(42);

4.3. Normalisieren und Teilen

Eine andere Sache, die wir mit den Daten vor dem Training machen sollten, ist, sie zu normalisieren. Die Normalisierung ist ein zweiphasiger Prozess:

  • Sammeln einiger Statistiken über die Daten (fit)
  • Ändern (transformieren) Sie die Daten auf irgendeine Weise, um sie einheitlich zu machen

Die Normalisierung kann für verschiedene Datentypen unterschiedlich sein.

Wenn wir beispielsweise Bilder in verschiedenen Größen verarbeiten möchten, sollten wir zuerst die Größenstatistik erfassen und dann die Bilder auf eine einheitliche Größe skalieren.

Normalisierung bedeutet für Zahlen normalerweise, sie in eine sogenannte Normalverteilung umzuwandeln. Die NormalizerStandardize- Klasse kann uns dabei helfen:

DataNormalization normalizer = new NormalizerStandardize(); normalizer.fit(allData); normalizer.transform(allData);

Nachdem die Daten vorbereitet sind, müssen wir den Satz in zwei Teile aufteilen.

Der erste Teil wird in einer Schulungssitzung verwendet. Wir werden den zweiten Teil der Daten (den das Netzwerk überhaupt nicht sehen würde) verwenden, um das trainierte Netzwerk zu testen.

This would allow us to verify that the classification works correctly. We will take 65% of the data (0.65) for the training and leave the rest 35% for the testing:

SplitTestAndTrain testAndTrain = allData.splitTestAndTrain(0.65); DataSet trainingData = testAndTrain.getTrain(); DataSet testData = testAndTrain.getTest();

5. Preparing the Network Configuration

5.1. Fluent Configuration Builder

Now we can build a configuration of our network with a fancy fluent builder:

MultiLayerConfiguration configuration = new NeuralNetConfiguration.Builder() .iterations(1000) .activation(Activation.TANH) .weightInit(WeightInit.XAVIER) .learningRate(0.1) .regularization(true).l2(0.0001) .list() .layer(0, new DenseLayer.Builder().nIn(FEATURES_COUNT).nOut(3).build()) .layer(1, new DenseLayer.Builder().nIn(3).nOut(3).build()) .layer(2, new OutputLayer.Builder( LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) .activation(Activation.SOFTMAX) .nIn(3).nOut(CLASSES_COUNT).build()) .backprop(true).pretrain(false) .build();

Even with this simplified fluent way of building a network model, there’s a lot to digest and a lot of parameters to tweak. Let’s break this model down.

5.2. Setting Network Parameters

The iterations() builder method specifies the number of optimization iterations.

The iterative optimization means performing multiple passes on the training set until the network converges to a good result.

Usually, when training on real and large datasets, we use multiple epochs (complete passes of data through the network) and one iteration for each epoch. But since our initial dataset is minimal, we'll use one epoch and multiple iterations.

The activation() is a function that runs inside a node to determine its output.

The simplest activation function would be linear f(x) = x. But it turns out that only non-linear functions allow networks to solve complex tasks by using a few nodes.

There are lots of different activation functions available which we can look up in the org.nd4j.linalg.activations.Activation enum. We could also write our activation function if needed. But we'll use the provided hyperbolic tangent (tanh) function.

The weightInit() method specifies one of the many ways to set up the initial weights for the network. Correct initial weights can profoundly affect the results of the training. Without going too much into the math, let’s set it to a form of Gaussian distribution (WeightInit.XAVIER), as this is usually a good choice for a start.

All other weight initialization methods can be looked up in the org.deeplearning4j.nn.weights.WeightInit enum.

Learning rate is a crucial parameter that profoundly affects the ability of the network to learn.

We could spend a lot of time tweaking this parameter in a more complex case. But for our simple task, we'll use a pretty significant value of 0.1 and set it up with the learningRate() builder method.

One of the problems with training neural networks is a case of overfitting when a network “memorizes” the training data.

This happens when the network sets excessively high weights for the training data and produces bad results on any other data.

To solve this issue, we’re going to set up l2 regularization with the line .regularization(true).l2(0.0001). Regularization “penalizes” the network for too large weights and prevents overfitting.

5.3. Building Network Layers

Next, we create a network of dense (also known as fully connect) layers.

The first layer should contain the same amount of nodes as the columns in the training data (4).

The second dense layer will contain three nodes. This is the value we can variate, but the number of outputs in the previous layer has to be the same.

The final output layer should contain the number of nodes matching the number of classes (3). The structure of the network is shown in the picture:

After successful training, we'll have a network that receives four values via its inputs and sends a signal to one of its three outputs. This is a simple classifier.

Finally, to finish building the network, we set up back propagation (one of the most effective training methods) and disable pre-training with the line .backprop(true).pretrain(false).

6. Creating and Training a Network

Jetzt erstellen wir aus der Konfiguration ein neuronales Netzwerk, initialisieren es und führen es aus:

MultiLayerNetwork model = new MultiLayerNetwork(configuration); model.init(); model.fit(trainingData);

Jetzt können wir das trainierte Modell anhand des restlichen Datensatzes testen und die Ergebnisse mit Bewertungsmetriken für drei Klassen überprüfen:

INDArray output = model.output(testData.getFeatureMatrix()); Evaluation eval = new Evaluation(3); eval.eval(testData.getLabels(), output);

Wenn wir jetzt eval.stats () ausdrucken , werden wir feststellen , dass unser Netzwerk ziemlich gut darin ist , Irisblumen zu klassifizieren, obwohl es Klasse 1 dreimal mit Klasse 2 verwechselt hat.

Examples labeled as 0 classified by model as 0: 19 times Examples labeled as 1 classified by model as 1: 16 times Examples labeled as 1 classified by model as 2: 3 times Examples labeled as 2 classified by model as 2: 15 times ==========================Scores======================================== # of classes: 3 Accuracy: 0.9434 Precision: 0.9444 Recall: 0.9474 F1 Score: 0.9411 Precision, recall & F1: macro-averaged (equally weighted avg. of 3 classes) ========================================================================

Mit dem fließenden Konfigurations-Builder können wir schnell Schichten des Netzwerks hinzufügen oder ändern oder einige andere Parameter anpassen, um festzustellen, ob unser Modell verbessert werden kann.

7. Fazit

In diesem Artikel haben wir mithilfe der deeplearning4j-Bibliothek ein einfaches, aber leistungsstarkes neuronales Netzwerk aufgebaut.

Wie immer ist der Quellcode für den Artikel auf GitHub verfügbar.