JEXP                       JEXP

Künstliche Intelligenz in Java mit Deeplearning4j

neural art

Der Hype um künstliche Intelligenz und maschinellem Lernens mittels neuronaler Netze sollte an niemandem vorbeigegangen sein. In den letzten Jahren hat der Zuwachs an Rechenleistung und grossen Mengen verfügbaren kategorisierten Trainingsdaten es ermöglicht, Ansätze aus den letzten 40 Jahren aus dem Kälteschlaf zu erwecken und zur Anwendung zu bringen.

Wir haben diese deutlich bei den Verbesserung von Übersetzungen, Bilderkennung, Sprachsynthese und spielerischer Meisterleistung von Computerspielen bis Go erlebt. Verstärktes Interesse und massive Finanzierung in Forschung und Anwendung haben auch zu neuen Arten von Neuronalen Netzen, wie Convolutional Neural Netzwerke (CNN) zur Bildverarbeitung, Recurrent Neuronal Netzwerke (RNN) für zeitabhängige Informationen wie Sprache, Sensordaten und Videos und Auto-Encodern zur Eigenschaftsermittlung von Eingabedaten geführt. Gegenüber handoptimierten Methoden zur Eigenschaftsermittlung und Auswertung haben Neuronale Netze den Vorteil, dass sie relativ einfach trainierbar und generisch einsatzfähig sind. Sie stellen eine gutes Verhältnis von Aufwand und Ergebnisqualität dar. In der JavaSpektrum 02/2017 gab es einige Schwerpunktartikel zum Thema maschinelles Lernen.

Als Entwickler steht man dem ganzen erst einmal etwas überfordert gegenüber, kann sich aber mit etwas Durchhaltevermögen in den Wust von Begrifflichkeiten und Ansätzen aus der linearen Algebra einarbeiten. Mir ging es ebenso und es hat einige Zeit gedauert, mit der Materie warm zu werden, meine letzen Mathematik-Semester sind auch schon mehr als 20 Jahre her. Das Thema ein sehr weites und aktives Feld, es ist kein Wunder dass Onlinekurse, Bücher und Entwickler mit den entsprechenden Qualifikationen hoch im Kurs stehen.

Ein Großteil der Aktivität ist im Python-Umfeld zu sehen, die bekannten Frameworks wie Tensorflow, Torch, Theanos, Scikit-Learn, MxNet und ML-Lib sind alle mit Python APIs benutzbar.

Einführung

Klassische vorwärtsgerichtete Netze (feed forward)

Neuronale Netze sind einem vereinfachtem Modell von biologischen Neuronen nachempfunden. Informationen werden gewichtet entgegengenommen und führen beim Erreichen eine Schwellwertes zur Aktivierung nachfolgender Neuronen. Diese sind in Schichten angeordnet von der *Eingabe*schicht mit Neuronen für jeden Eingabecharakteristik (Feature), darauf folgen mehrere versteckte (hidden) Schichten von Neuronen, deren Gewichte das Gedächtnis des Netzes darstellen.

Neuronen jeder Schicht ist zumeist mit allen Elementen der nächsten Schicht verbunden, Kanten mit einem Gewicht von Null tragen aber nicht und Kanten mit höhren Gewichten tragen entsprechend stärker zur Aktivierung der nachfolgenden Neuronen bei. Zum Ende folgt die Ausgabeschicht, die entweder aus einem Neuron bei Regression oder binärer Klassifizierung, oder aus einem Neuron pro Klasse (Auswertung mit Wahrscheinlichkeitswert) besteht.

Neuronale Netze können sowohl zur Zuordnung von Eingaben zu einer bestimmten Anzahl von Klassen (Klassifizierung) als auch zur Vorhersage von abhängigen Ausgabewerten (Regression) genutzt werden. Das Ziel ist, die Struktur und Essenz der Trainingsdaten zu lernen, um dann auf unbekannten Daten verlässliche Ergebnisse vorherzusagen. Oft werden Neuronale Netze als Graphstruktur mit Knoten und gewichteten Beziehungen veranschaulicht, die Anwendung auf (GPU/Cluster)-Berechnungsplattformen ist aber mehrheitlich als Matritzenmultiplikationen umgesetzt.

MultiLayerNeuralNetworkBigger english

Die Berechnung der Ausgabe eines Neurons und des Netzes, stellt nur die pro Schicht wiederholte Anwendung einer Aktivierungsfunktion auf die Eingabesumme (Summe der gewichteten Eingangswerte) plus einem Basislevel (Bias).

Ausgabewert = Aktivierungsfunktion ( Summe (Eingangswert * Gewicht) + Bias )

Die Aktivierungsfunktion bildet meist den Eingabebereich auf 0..1 oder -1..1 ab und sollte gut ableitbar sein. Beispiele sind Sigmoid (1/(1+e^-x)), Tanh (sinh(x)/cosh(x)), ReLU (Rectified Linear Unit,` 0 für x < 0, sonst x`). [AktivierungsFunktionen]

activation functions

Das spannende und komplizierte an diese Netzen ist dagegen der Trainings- und Lernmechanismus, mittels Rückkopplung (Back-Propagation).

Vereinfacht dargestellt wird pro Trainingsdatum (oder kleinem Set), die Abweichung von berechnetem und erwartetem Wert auf die Gewichte des Netzwerkes kaskadiert zurückverteilt. Dabei wird berücksichtigt, welche Neuronen und Gewichte wie viel zum Fehler beigetragen haben. Um eine schrittweise Annäherung an das Optimium (Minimum des Fehlers über alle Ausgaben und Trainingswerte) zu erreichen, wird der Fehleranteil noch mit einer vergleichsweise kleinen Lernrate multipliziert. Diese Gewichtsanpassung wird wiederum ausgehend von der Ausgabe Schicht für Schicht vorgenommen, wobei die zu verteilende Fehlerabweichung pro Schicht und Neuron neu berechnet wird.

Was zeichnet Deep Learning-Netzwerke im besonderem aus?

  • Höhere Anzahl an Neuronen und Schichten

  • Nicht alle Schichten müssen vollständig mit der nächsten verbunden sein

  • Automatische Extraktion von Eingabe Charakteristika

  • Teilweise rekursiver Aufbau - Teile des Netzwerkes bestehen aus kleineren dedizierten Netzen mit bestimmten Eigenschaften

Einige der Aspekte sind erst durch höhere Rechenleistungen besonders im GPU Umfeld möglich geworden, andere durch neue Erkenntnisse aus der Forschung.

Convolutional Neuronale Netzwerke (CNN)

Diese neuronalen Netzwerke nutzen Mechanismen aus der Bildverarbeitung insbesondere Kernels um Bilddaten in geeigente Eingabeinformationen für das Netzwerk umzuwandeln. Anders bisherige Ansätze sind sie sehr robust gegenüber Veränderungen im Bild wie Drehungen, Farb- und Helligkeitsänderungen usw. d.h. sie lernen wirklich die Essenz des Bildes. Das geschieht durch mehrere hintereinandergeschaltete Netze, die über mehrere Schritte ausgehend von einfachen (Kontrast-)Kanten und Punkten komplexere Gebilde höherer Ordnung wie z.B. Gesichter, Gebäude, Verkehrszeichen, Zahlen, Tiere usw. erkennen können. Aktuell werden CNN’s auch für Textverarbeitung eingesetzt, da Texte ähnlich wie Bilder in Ausschnitten betrachtet werden können.

CNNs benutzen Tensoren (mehrdimensionale Matritzen/Arrays mit mehr als 3 Dimensionen) um Bilder zu repräsentieren. Breite und Höhe des Bildes sind die ersten 2 Dimensionen, die Farbinformation die dritte und strukturelle Eigenschaften des Bildes an einem Punkt (z.b. Kanten) die vierte Dimension.

Der Name kommt vom lateinischen 'convolvere' was für "zusammenrollen". Convolution stellt die Überlappung oder Überlagerung zweier Funktionen dar, ähnlich zur Interferenz, eine einfache Variante ist es, einfach beide Funktionen zu multiplizieren. Dabei stellt die eine Funktion Attribute des Bildes dar, und die zweite einen Filter, der bestimmte Aspekte heraushebt und verstärkt, zum Beispiel ein Kantenfilter über Kontrastwerte.

In der ersten Schicht eines CNN werden viele verschiedenen Filterfunktionen auf dasselbe Eingangsbild angewandt, um unterschiedliche Aspekte hervorzuheben. Im Tensor werden dann diese Eigenschaften pro Bildpunkt und Farbe hinterlegt. Für die Weiterverarbeitung werden diese Eigenschaften auf einen 0..1 Wertebereich normalisiert.

RGB-Farbwerte und damit Kontraste, Helligkeiten usw. werden über Strukturmuster vieler 3x3 Kernel-Matrix-Filter verstärkt und selektiert, wie sie auch aus der Bildverarbeitung bekannt sind. Die Aufgabe des Netzwerkes ist es herauszufinden, welche Farben und Strukturen relevant für die Erkennungsleistung sind.

karpathy convnet labels

Durch diesen Ansatz und eine geeignete Wahl der Filterfrequenz (Schrittweite) kann die Bildinformation auf eine Anzahl kleiner, spezifischer Matritzen reduziert werden.

convnet

In weiteren Schichten werden diese Matritzen weiter reduziert, z.b. durch eine Maximumselektion und weitere, wiederholte Eigenschaftsfilter auf dann höheren Abstraktionsleveln. Wenn die Eigenschaftsmatritzen klein genug sind (z.b. 32x32), können sie dann mit regulären Feed-Forward-Netzen klassifiziert werden.

Recurrent Neuronale Netzwerke

Wenn die Eingabedaten sich über einen Zeitverlauf ändern (Features mit Zeitstempel), dann ist es nützlich wenn unser Netzwerk Zusammenhänge über die Zeit erkennen kann. Ähnlich wie bei Bildern, werden spezifische Mustern und Eigenheiten erkannt, diesmal aber in der zwei- (oder mehr-)dimensionalen Abbildung des Werteverlaufs über die Zeit.

Zum einen können Werte innerhalb eines wandernden Zeitfensters als einzelne Eingaben betrachtet werden, zum anderen erinnern sich RNNs an vergangene Muster.

Das ist mit einem Gedächtnis möglich, das den vorherigen Aktivierungszustand in die aktuelle Auswertung einfliessen lässt.

Für Videos zB. kann eine Mischung aus RNN (Long Short Term Memory (LSTM)) und CNN genutzt werden, um sowohl den Zeit-Aspekt als auch die Bildverarbeitung abzudecken.

Hello World

XOR mittels Feed Forward Netzwerk

Das erste Neuronale Netz, das Perceptron, hatte noch keine Zwischenschichten, und konnte damit nur lineare Abbildungen vornehmen. Eine nichtlineare Aufgabe wie ein XOR funktionierte damit nicht.

Wir können es aber einfach mit einem Netz mit zwei Eingaben, zwei Neuronen in der versteckten Schicht und einer Ausgabe modellieren.

ml xor

Wenn man die Eingaben auf die Paare (0,0) oder (1,1) setzt, erhält man nach der Berechnung 0, sonst 1 am Ausgang.

Mittels der sigmoid Aktivierungsfunktion erhält man für Eingabesummen deutlich kleiner als Null eine 0 und deutlich größer als Null eine 1.

Anbei die Berechnungstabelle für 2 Beispielbelegungen:

input1 input2 hidden1 hidden2 output

0

1

sigmoid(-10 + 0*20 + 1*20) = s(10) = 1

sigmoid(30 + 0*-20 + 1*-20) = s( 10) = 1

sigmoid(-30 + 1*20 + 1*20) = s( 10) = 1

1

1

sigmoid(-10 + 1*20 + 1*20) = s(30) = 1

sigmoid(30 + 1*-20 + 1*-20) = s(-10) = 0

sigmoid(-30 + 1*20 + 0*20) = s(-10) = 0

ml xor 11

Bilderkennung

Für die Bilderkennung stellt die Klassifizierung von handschriftlichen Zahlen (70000 Beispiele im MNIST Datenset) das gängige "Hello World" dar.

MNIST

Im einfachsten Fall, werden die 28x28 Pixel Bilder auf entsprechend 784 Eingabe-Neuronen für den jeweiligen Grauwert abgebildet, und mittels Feed-Forward-Netzwerk klassifiziert, und auf die 10 Zahlen (Klassen) abgebildet. Dafür werden andere Aktivierungsfunktionen (ReLU und SoftMax) benutzt aber ansonsten ist es ein klassisches Netzwerk.

Man kann die Bilder der Zahlen aber auch über ein CNN vorverarbeiten und damit relevante Eigenschaften (vor allem Kanten und Kurven) herausfiltern.

Dafür werden zuerst die Kanteninformationen des Bildes mittels eines Bildverarbeitungs-Kernels der Convolutional Schichten umgewandelt und in den Subsampling Schichten weiter reduziert. Wenn wir dann genügend wenig abstrahierte Eigenschaften zur Verfügung haben, können wir wieder unser vertrautes FeedForward Netzwerk zur Klassifikation einsetzen.

Deeplearning4j

Im Java Umfeld hat sich Deeplearning4j [DL4J] - gut erkenntbar an der vertrauten "4j" Endung hervorgetan. Es ist unter der Apache 2.0 Lizenz verfügbar und kann von Java, Kotlin, Groovy, Scala und Clojure aus benutzt werden. Deeplearning4j ist unter Berücksichtigung von aktuellen Erkenntnissen und Plattformen in C++ und Java entwickelt worden und stellt Dienste zur Modellierung, Eingabe-Vektorisierung (via DataVec Bibliothek), Auswertung und Datenintegration bereit. Die API von DL4J erlaubt die Konstruktion, Konfiguration, Tuning, Training, Auswertung und Nutzung komplexer neuronaler Netze. Die Laufzeitumgebung von DL4J basiert auf einer effizienten Implementierung für n-dimensionale Felder (ND4J) für CPU und GPU, die effiziente, wissenschaftliche (Matrix-)Berechnungen ermöglicht. Die kompakte, einfache Syntax von ND4J ähnelt der von Pythons Numpy und Matlab. Die Bibliothek kann sich bei Bedarf sowohl Grafikprozessor- als auch Hadoop- und Spark Cluster für das Training der Netze zu Nutze machen.

Das O’Reilly Buch "Deep Learning - A Practitioner’s Approach" [Dl4JBuch] der Initiatoren von DL4J, Adam Gibson und Josh Patterson ist gerade erst im August erschienen, und sehr zu empfehlen. An manchen Stellen etwas knapp gehalten führt es doch sehr gut ins Thema der Neuronalen Netze und des Deep-Learning ein, und zeigt im zweiten Teil, wie die Konzepte konkret mit der Bibliothek umgesetzt werden können.

Das wollen wir im folgenden für einige praxisrelevanten Beispiele nachvollziehen, die meisten Deeplearning4j-Bespiele sind in einem separaten GitHub Repository [DL4JBeispiele] verfügbar.

Deeplearning4j’s API erlauben die Konfiguration des Neuronalen Netzwerkes via einer Builder API. Zuerst werden globalen Eigenschaften des Netzwerkes, wie Lernrate, Optimitierungsalgorithmus, Anzahl der Trainingsiterationen, und andere gesetzt.

API Beispiel XOR

ListBuilder builder = new NeuralNetConfiguration.Builder()
  .seed(123)
  .iterations(1)
  .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
  .learningRate(0.1)
  .list()

Dann werden die Schichten des Netzwerkes - Eingabe, Verarbietung und Ausgabe definiert. Zum Schluss legen wir noch fest, dass wir kein Vortraining des Netzes vornehmen und Backpropagation benutzten wollen.

MultiLayerConfiguration conf = builder
  .layer(0, new DenseLayer.Builder().nIn(2).nOut(2)
  .weightInit(WeightInit.DISTRIBUTION).dist(new UniformDistribution(0,1))
  .activation(Activation.SIGMOID)
  .build())

  .layer(1, new OutputLayer.Builder(LossFunctions.LossFunction.MSE).nIn(2).nOut(1)
  .weightInit(WeightInit.DISTRIBUTION).dist(new UniformDistribution(0,1))
  .activation(Activation.SIGMOID).build())

  .pretrain(false).backprop(true).build();

Mit der resultierenden Konfiguration können wir jetzt ein Netzwerk initialisieren und trainieren.

MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();
model.setListeners(new ScoreIterationListener(10));

Die Trainingsdaten liegen dabei als mehrdimensionale Felder (NDArray) in DataSet vor, das jeweils Eingaben und erwartete Ausgaben enthält. Diese können mit Hilfsklassen aus Dateien oder Datenbanken geladen werden.

Über einen DataSetIterator werden die Daten dann in kleinen Gruppen (Microbatches) dem Netzwerk zum Training

DoublesDataSetIterator iterator = new DoublesDataSetIterator(
  Arrays.asList(
    makePair(new double[] {0,0},new double[] {0}),
    makePair(new double[] {0,1},new double[] {1}),
    makePair(new double[] {1,0},new double[] {1}),
    makePair(new double[] {1,1},new double[] {0})), 1);

for (int n = 0; n < 10000; n++) {
  model.fit(iterator);
}

Danach können wir unser Modell evaluieren.

Evaluation eval = model.evaluate(iterator);
eval.getPredictionErrors().forEach( p ->
  System.out.printf("Predicted: %d, Actual: %d%n", p.getPredictedClass(), p.getActualClass())
);
System.out.println(eval.stats());
Examples labeled as 0 classified by model as 0: 2 times
Examples labeled as 1 classified by model as 1: 2 times

==========================Scores========================================
 Accuracy:        1
 Precision:       1
 Recall:          1
 F1 Score:        1
========================================================================
  • Accuracy - Der Anteil der korrekt identifizierten Eingaben (true positive, false negative), generelle Qualität.

  • Precision - Anzahl der korrekten Ergebnisse Klasse 1 (true positive) durch Anzahl aller korrekten und inkorrekten Klasse 1 (true und false positive).

  • Recall - Anzahl der korrekten Klasse 1 (true positive) durch die Anzahl der korrekt identifizierten Ergebnisse (true positive, false negative).

  • F1 Score - Gewichteter Durchschnitt von Precision und Recall.

Präzision, Recall und F1 messen die Relevanz des Modells. Das ist besonders bei Modellen wichtig, deren "false negatives" kritische Auswirkungen haben.

Und natürlich auch benutzen. Hier mittels Regression, die unsere einzige Ausgabe auf 0..1 abbildet mit model.predict kann man dagegen Klassifikationen vornehmen.

INDArray data = Nd4j.zeros(2, 2);

data.putScalar(0,0,0d);
data.putScalar(0,1,1d);

data.putScalar(1,0,1d);
data.putScalar(1,1,1d);

INDArray output = model.output(data);

for (int i=0;i<data.rows();i++) {
    System.out.println(data.getRow(i) +" -> "+ output.getRow(i));
}
[0.00, 1.00] -> 0.96
[1.00, 1.00] -> 0.04

API Beispiel MNIST Convolutional Netzwerk

Um mit Deeplearning4j ein CNN zu definieren kann man die Hilfe eines speziellen InputType in Anspruch nehmen, der sich um zusätzliche Konfiguration und die korrekte Anzahl von Elementen für Convolutional und SubSampling-Schichten kümmert.

Ansonsten gibt man die Auflösung und die Farbkanäle des Bildes an. Und definiert dann die Abfolge der Schichten. Hier ist es:

  1. Convolutional

  2. SubSampling

  3. Convolutional

  4. SubSampling

  5. FeedForward

  6. Ausgabe (Klassifikation auf 10)

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
... global parameters ...

// 5x5 matrix, stride 1, 20 filter
.layer(0, new ConvolutionLayer.Builder(5, 5)
  // nIn and nOut specify depth. nOut is the number of filters to be applied
  .nIn(1).nOut(20).activation(Activation.IDENTITY)
  .stride(1, 1).build())

// 2x2 kernel, stride 2 = 1/4
.layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
  .kernelSize(2,2).stride(2,2).build())

// 5x5 matrix, stride 1, 50 filter
.layer(2, new ConvolutionLayer.Builder(5, 5)
  // Note that nIn need not be specified in later layers
  .nOut(50).activation(Activation.IDENTITY)
  .stride(1, 1).build())

// 2x2 kernel, stride 2 = 1/4
.layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
  .kernelSize(2,2).stride(2,2).build())

// feed forward layer
.layer(4, new DenseLayer.Builder().activation(Activation.RELU)
  .nOut(500).build())

.layer(5, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
        .nOut(10) // 10 classes
        .activation(Activation.SOFTMAX).build())

.setInputType(InputType.convolutionalFlat(28,28,1)) //28x28 pixel, 1 color
.backprop(true).pretrain(false).build();
DataSetIterator mnistTrain = new MnistDataSetIterator(batchSize,true,123);
DataSetIterator mnistTest  = new MnistDataSetIterator(batchSize,false,123);

MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();
model.setListeners(new ScoreIterationListener(1));

for( int i=0; i<nEpochs; i++ ) {
    model.fit(mnistTrain);

    Evaluation eval = model.evaluate(mnistTest);
    Sytem.out.println(eval.stats());
    mnistTest.reset();
}

Referenzen

Last updated 2018-10-21 23:33:58 CEST
Impressum - Twitter - GitHub - StackOverflow - LinkedIn - Medium