Machine Learning und einfache Neuronale Netze

Künstliche neuronale Netze: Ein Machine-Learning-Beispiel mit Neuroph

Valentin Steinhauer

© Shutterstock.com / maxuser

Ein guter Start für den Einstieg in Maschinelles Lernen und Neuronale Netze ist das Java Framework Neuroph. Der Duke’s Choice Awards Gewinner 2013 enthält eine quelloffene Java Library für die Umsetzung grundlegender NN-Konzepte. Das Beispiel eines Order-Management-Systems zeigt, was schon in wenigen Schritten mit Maschinellem Lernen möglich ist.

Machine Learning und einfache Neuronale Netze

Entsprechend dem jüngstem Industrie-4.0-Programm des Bundesministeriums für Wissenschaft und Forchung (BMFW) werden uns mehr und mehr Aufgaben aus dem Bereich des sogenannten Machine Learning bevorstehen. Was ist damit gemeint?

In der Wikipedia kann man folgende Beschreibung finden: “…Maschinelles Lernen ist ein Oberbegriff für die „künstliche“ Generierung von Wissen aus Erfahrung: Ein künstliches System lernt aus Beispielen und kann diese nach Beendigung der Lernphase verallgemeinern.“ Beim maschinellen Lernen werden also nicht einfach  Beispiele auswendig gelernt. Stattdessen erkennt ein System Muster und Gesetzmäßigkeiten in den Lerndaten, sodass auch unbekannte Daten beurteilt werden können.

Im Bereich des Maschinellen Lernen wurden schon mehrere Lösungen erarbeitet, wie in der UCI Liste [1] zu sehen ist. Mehrere Einträge in der Liste basieren auf Neuronalen Netzen. In meiner Arbeitszeit bin ich bereits mehrmals mit Situationen konfrontiert worden, wo Funktionalitäten benötigt werden, die mit Neuronalen Netzen simuliert werden können [2]. Als Framework für ein Neuronales Netz habe ich bei solchen Aufgaben Neuroph eingesetzt, das aktuell mit der letzten Version 2.92 verfügbar ist [3].

 

Künstliche neuronale Netze mit Neuroph

Neuroph ist nicht nur eine gute Wahl für diejenigen, die den Umgang mit Neuronalen Netzen erlernen oder damit experimentieren möchten. Es ist auch für eine schnelle Integration in das eigene Projekt geeignet. Es ist klein, gut dokumentiert, leicht zu benutzen und sehr flexibel. Außerdem ist es genug performant, um mit seiner Hilfe immer wiederkehrende einfache Aufgaben zu erledigen.

Neuroph stellt im Paket ein GUI zum Experimentieren mit Lernparametern zur Verfügung. Dies ist wichtig, da die Parameter oft individuell an das zu lösende Problem angepasst werden müssen. Außerdem wird Ihnen durch das GUI erlaubt, verschiedene Netzarchitekturen auszuprobieren und kennenzulernen. Das Neuroph-Framework entwickelt sich momentan in die Richtung des sogenannten Deep Learning bzw. der adaptiven mehrschichtigen Neuronalen Netzen.

Wer den Einstieg in die Welt der neuronalen Netze sucht, sieht sich schnell einer großen Vielfalt an Themen konfrontiert. Das erste praktische Beispiel wird anhand des einfachsten Netzes gezeigt – dem sogenannten Perzeptron. Bei Neugier auf weitere Beispiele sei auf [4] und [5] verwiesen.

Steuerung des Systems mit einem Perzeptron

Die Aufgabenstellung unseres Beispiels: Im produktiven Betrieb eines Order-Management-Systems wird ständig analysiert, ob Störungen bei der Bearbeitung der Transaktionen entstehen und wie sie schnell beseitigt werden können. Die Bearbeitung der einzelnen Transaktionen in solchen Systemen besteht normalerweise aus mehreren Schritten. Nehmen wir als Beispiel folgende Bearbeitungsschritte (States) an:

  • State 0: Die Aufträge befinden sich noch im Input-Filestore
  • State 1 (NEW-REQUEST): Annahme der Aufträge ins System, Ablegen der Daten  in der Transaktionsdatenbank und Erzeugen der entsprechenden Transaktionen
  • State 2 (CONVERT-REQUEST): Konvertierung der Aufträge von ankommenden Formaten (z. B. EDIFACT, XML) in das einheitliche interne Format für die weitere Bearbeitung im System
  • State 3 (MAP-REQUEST): Mapping der Daten des ankommenden Datenmodells auf das Datenmodell des Backend-System für die Bearbeitung dort
  • State 4 (SEND-REQUEST): Versenden der gemappten Daten als Auftrag an das Backend-System.
  • State 5 (WAIT-RESPONSE): Empfangen der Resultate vom Backend-System und Aufwecken der Transaktionen
  • State 6 (MAP-RESPONSE): Mapping der Daten des Resultat-Datenmodells auf das Datenmodell des Ursprungsauftrags
  • State 7 (CONVERT-RESULT): Konvertierung des einheitlichen internen Formats in das entsprechende Ausgangsformat (z. B. EDIFACT, XML) des Ursprungsauftrags
  • State 8 (SEND-RESULT): Versenden der Resultate

Jede Transaktion, die für die Bearbeitung eines Auftrags im System erzeugt wird, läuft sequenziell diese Schritte (States) durch. Zusätzlich ist die Bearbeitung der Transaktionen von bestimmten Systemparametern abhängig, wie dem verfügbaren Plattenplatz (Files-PP), dem freien Tablespace in der Datenbank (DB-TS) oder der CPU-Auslastung (CPU-A). Nehmen wir an, dass die Bearbeitung der Transaktionen an jedem oben genannten Schritt mit einem eigenen Modul (z. B. einem Adapter) durchgeführt wird. Als Lösung einer kritischen Situation können folgende Aktionen dienen, die später als Output-Werte für neuronale Netze genutzt werden:

  • Input-Filestore schließen (Rechte für das Einstellen der Aufträge entziehen)
  • E-Mail an die Systemverantwortlichen schicken
  • Alle Adapter herunterfahren
  • Nachstarten einzelner Adapter
  • Nachstarten aller Adapter

Diese Liste umfasst natürlich nicht alle möglichen Aktionen, zeigt aber, wie die automatische Prozesssteuerung benutzt bzw. wie sich diese Aufgabe mit Neuronalen Netze lösen lässt.

Wir haben an dieser Stelle nur einen Input-Vektor (boolean) und einen Output-Vektor (boolean). Zu dieser Aufgabe passt hervorragend das Perzeptron. Wir nehmen als Input-Vektor 12 Neuronen (9 States und die drei oben genannten Systemparameter mit den Zuständen Erreicht/Nicht erreicht) und als Output-Vektor 5 Neuronen als Aktionen aus der Entscheidungsliste.

Lesen Sie auch: Maschinelles Lernen mit Java: Eine Einführung

Die Input-Neuronen werden nur die Werte 0 oder 1 annehmen. Bei den States bedeutet der Wert 0, dass kein Stau von Transaktionen vor dem Adapter zur Bearbeitung gebildet worden ist. Ein Stau von Transaktionen wird dann festgestellt und der Wert auf 1 gesetzt, wenn die Anzahl der zur Bearbeitung anstehenden Transaktionen einen konfigurierten Wert erreicht hat. Bei den Systemparametern sind die Werte 0 und 1 auch entsprechend zu interpretieren: 1, wenn ein konfigurierter Schwellenwert erreicht ist und 0, wenn er noch nicht erreicht wurde. Jetzt lässt sich dieses Modell als ein neuronales Netz darstellen, nämlich als ein einfaches Perzeptron (Abb. 1).

Steinhauer_Maschinelles_Lernen_Perceptron_Abb1

Abb 1: Das Modell zum Order-Management-System als Perceptron

Die möglichen Zustände der Transaktionsbearbeitung im System und die entsprechenden Entscheidungen sind in Vektorform präsentiert. Wie sind diese Daten zu interpretieren? Nehmen wir folgende Input-/Output-Vektoren:

(0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0)  → (0, 1, 0, 0, 0)

Plattenplatz steht ausreichend zur Verfügung. Die Größe des verfügbaren Tablespaces in der Datenbank ist auch ausreichend. Die CPU-Auslastung der Maschine ist jedoch kritisch, weil der konfigurierte Schwellenwert erreicht ist. Im State 3 bei der Transaktionsbearbeitung (MAP-REQUEST) befinden sich zu viele Transaktionen. Also hat sich ein Stau von Transaktionen vor dem Mapping der Aufträge gebildet. Alle anderen Bearbeitungsschritte sind von dem Problem nicht betroffen. Da ein Nachstart des entsprechenden Adapters/Moduls wegen hoher CPU-Auslastung nicht möglich ist, wird als Entscheidung des Systems oder als aus der Situation resultierende Aktion eine E-Mail mit entsprechenden Informationen an die Systemverantwortlichen geschickt.

Implementierung und Prognose

Erzeugen wir zunächst das Projekt und binden entsprechende Libraries ein:

  • Nach dem Download des neuroph-2.92.zip öffnen Sie ein Projekt und legen ins lib-Verzeichnis folgende Pakete ab: neuroph-core-2.92.jar, slf4j-api-1.7.5.jar und slf4j-nop-1.7.6.jar
  • Danach kopieren Sie folgende Methoden ins Projekt: main, testNeuralNetwork und unknownCaseNeuralNetwork (loadedPerceptron)
  • Lassen Sie das Projekt laufen

Die main-Methode enthält alle benötigten Daten, den Aufruf machine learning, den Aufruf machine testing (testNeuralNetwork(loadedPerceptron, trainingSet)) und  den Aufruf machine predicted  state (unknownCaseNeuralNetwork(loadedPerceptron)).

public static void main(String[] args) {

DataSet trainingSet = new DataSet(12, 5);

trainingSet.addRow(new DataSetRow(new double[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, new double[]{1, 1, 0, 0, 0}));//1

trainingSet.addRow(new DataSetRow(new double[]{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, new double[]{1, 1, 1, 0, 0}));//2

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, new double[]{0, 0, 0, 1, 0}));//3

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, new double[]{1, 1, 0, 0, 0}));//4

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, new double[]{0, 1, 0, 0, 0}));//5

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, new double[]{0, 0, 0, 1, 0}));//6

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0}, new double[]{0, 1, 0, 0, 0}));//7

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, new double[]{0, 0, 0, 1, 0}));//8

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0}, new double[]{0, 1, 0, 0, 0}));//9

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, new double[]{0, 1, 0, 0, 0}));//10

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0}, new double[]{0, 1, 0, 0, 0}));//11

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, new double[]{0, 0, 0, 1, 0}));//12

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0}, new double[]{0, 1, 0, 0, 0}));//13

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, new double[]{0, 0, 0, 1, 0}));//14

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0}, new double[]{0, 1, 0, 0, 0}));//15

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, new double[]{0, 0, 0, 1, 0}));//16

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0}, new double[]{0, 1, 0, 0, 0}));//17

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, new double[]{0, 1, 0, 0, 0}));//18

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, new double[]{0, 1, 0, 0, 0}));//19

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1}, new double[]{0, 0, 0, 0, 1}));//20

trainingSet.addRow(new DataSetRow(new double[]{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, new double[]{1, 1, 0, 0, 0}));//21

// create perceptron neural network

NeuralNetwork myPerceptron = new Perceptron(12, 5);

// learn the training set

myPerceptron.learn(trainingSet);

// save trained perceptron

myPerceptron.save("mySamplePerceptron.nnet");

// load saved neural network

NeuralNetwork loadedPerceptron = NeuralNetwork.load("mySamplePerceptron.nnet");

// test loaded neural network

System.out.println("Testing loaded perceptron");

testNeuralNetwork(loadedPerceptron, trainingSet);

unknownCaseNeuralNetwork(loadedPerceptron);

}

public static void testNeuralNetwork(NeuralNetwork neuralNet, DataSet testSet) {

for (DataSetRow trainingElement : testSet.getRows()) {

neuralNet.setInput(trainingElement.getInput());

neuralNet.calculate();

double[] networkOutput = neuralNet.getOutput();

System.out.print("Input: " + Arrays.toString(trainingElement.getInput()));

System.out.println(" Output: " + Arrays.toString(networkOutput));

}

}
public static void unknownCaseNeuralNetwork(NeuralNetwork neuralNet) {

DataSetRow unknownElement = new DataSetRow();//to print only

unknownElement.setInput(new double[]{1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0});//to print only

neuralNet.setInput(new double[]{1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0});

neuralNet.calculate();

double[] networkOutput = neuralNet.getOutput();

System.out.print("unknown case, input: " + Arrays.toString(unknownElement.getInput()));

System.out.println(" output: " + Arrays.toString(networkOutput));

}

Die oben beschriebenen Situationen sind aus der Praxis entnommen und decken daher nicht alle Fälle ab. Sie sind dennoch hilfreich, um den Einsatz eines Perzeptrons für die Prozesssteuerung zu verdeutlichen. Mit den oben gezeigten Trainingsdaten wird das Prozesssteuerungsperzeptron trainiert. Bei Bedarf können diese Daten erweitert werden, ohne dass die Software angepasst werden muss.

Diese einfache Perzeptron-Anwendung kann operative Prozesssteuerung in der Produktion nicht nur effektiv, sondern auch in angemessener Zeit gestalten. Was passiert, wenn eine unerwartete Situation eintritt? Zum Beispiel, wenn etwas blockiert ist und damit einige Werte 1 sind?

(1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)

Das Perzeptron gibt aus: 1 1 0 0 0:

(1.0,1.0,0.0,0.0,0.0)

Das bedeutet, es müssen die Input-Filestores geschlossen und eine E-Mail an die Systemverantwortlichen geschickt werden. Man kann sagen, dass hier eine künstliche Generierung vom Wissen stattgefunden hat.

Wie wir gesehen haben, ist das Perzeptron geeignet, diese einfache logische Aufgabe zu lösen. In einigen anderen Fällen ist das Problem aber keine einfache logische Aufgabe. Zur Lösung solcher Probleme kann das sogenannte Multi Layer Perzeptron (MLP) eingesetzt werden.

Alarm-Zeit-Abschätzung

Wir haben im vorherigen Kapitel die Systemparameter in den Input-Vektor aufgenommen und mit boolschen Werten belegt. Überschreitet der Wert dabei eine Grenze, wird er auf 1 gesetzt. Es kann die Frage aufkommen, wie schnell der kritische Zustand erreicht werden kann. An dieser Stelle ist ein MLP die richtige Wahl; mit einer oder mehreren Schichten zwischen den Input- und Output-Schichten. Wir werden nicht tief in die Theorie gehen, sondern befassen uns nur praktisch mit folgender Faustregel: Eine Schicht zwischen Input und Output mit 2n+1 Knoten, wobei n die Anzahl der Eingangsparameter ist.

Steinhauer_Maschinelles_Lernen_mlperceptron_Abb2

Abb. 2: Das Multi Layer Perceptron der Zeitabschätzung

Bauen wir ein MLP, um abzuschätzen, wie lange das System nicht in den kritischen Zustand kommt. Nehmen wir die drei Eingangsparameter wie oben: der Plattenplatz (der noch freie Speicher prozentual zum verfügbaren), der noch freie Tablespace-Platz in der Datenbank (prozentual zum verfügbaren) und die noch mögliche CPU-Auslastung (prozentual zur verfügbaren). Das heißt, dass für die Zwischenschicht 3×2+1= 7 gilt. Als Input-/Output-Parameter dient die Zeit bis zum Alarm, normiert z. B. auf 24 Stunden.

DataSet trainingSet = new DataSet(3, 1);

trainingSet.addRow(new DataSetRow(new double[]{0.1, 0.1, 0.1}, new double[]{0.1}));

trainingSet.addRow(new DataSetRow(new double[]{0.2, 0.2, 0.2}, new double[]{0.2}));

trainingSet.addRow(new DataSetRow(new double[]{0.3, 0.3, 0.3}, new double[]{0.3}));

trainingSet.addRow(new DataSetRow(new double[]{0.4, 0.4, 0.4}, new double[]{0.4}));

trainingSet.addRow(new DataSetRow(new double[]{0.5, 0.5, 0.5}, new double[]{0.5}));

trainingSet.addRow(new DataSetRow(new double[]{0.99, 0.99, 0.99}, new double[]{0.99}));

// create perceptron neural network

MultiLayerPerceptron myMlPerceptron = new MultiLayerPerceptron(TransferFunctionType.GAUSSIAN, 3, 7, 1);

// learn the training set

myMlPerceptron.learn(trainingSet);

// save trained perceptron

myMlPerceptron.save("mySampleMlPerceptron.nnet");

// load saved neural network

NeuralNetwork loadedPerceptron = NeuralNetwork.load("mySampleMlPerceptron.nnet");

// test loaded neural network

System.out.println("Testing loaded perceptron");

testNeuralNetwork(loadedPerceptron, trainingSet);

unknownCaseNeuralNetwork(loadedPerceptron);

Anmerkung: Die Zwischenschicht wird mit einer Übertragungsfunktion der TransferFunctionType als Gaussian verknüpft. Ob es in anderen Fällen die gleiche Übertragungsfunktion sein sollte, hängt von den Trainingsdaten ab. Diese müssen untersucht werden, bevor man über diese Funktion entscheiden kann. Eine Empfehlung ist, mehrere im Paket angebotene Funktionstypen zu testen, um bessere Übereinstimmung zwischen den Testdaten und den Vorhersagen zu erreichen. Beim oben genannten Funktionstyp für Gaussian bekommen wir Folgendes (die testNeuralNetwork-Methode wurde nicht geändert):

Input: [0.1, 0.1, 0.1] Output: [0.181]

Input: [0.2, 0.2, 0.2] Output: [0.271]

Input: [0.3, 0.3, 0.3] Output: [0.373]

Input: [0.4, 0.4, 0.4] Output: [0.4723]

Input: [0.5, 0.5, 0.5] Output: [0.563]

Input: [0.99, 0.99, 0.99] Output: [0.919]

Die erste Auffälligkeit ist, dass die Genauigkeit nicht besonders hoch ist. Dafür gibt es natürlich Gründe, die sowohl beim Neuroralen Netz liegen als auch an den Messungen. Wie man die Genauigkeit erhöhen kann, ist ein spezielles Thema und kann später in einem anderen Artikel diskutiert werden.

Berechnen wir jetzt die Prognose:

public static void unknownCaseNeuralNetwork(NeuralNetwork neuralNet) {

DataSetRow unknownElement = new DataSetRow();//to print only

unknownElement.setInput(new double[]{0.15, 0.2, 0.3});//to print only

neuralNet.setInput(new double[]{0.15, 0.2, 0.3});

neuralNet.calculate();

double[] networkOutput = neuralNet.getOutput();

System.out.print("unknown case, input: " + Arrays.toString(unknownElement.getInput()));

System.out.println(" output: " + Arrays.toString(networkOutput));

}

Und das Resultat ist:

unknown case, input: [0.15, 0.2, 0.3] output: [0.309]

Das können wir wie folgt interpretieren: Der noch freie Plattenplatz liegt bei 15 Prozent, der noch freie Platz im Tablespace beträgt 20 Prozent und die CPU-Auslastung kann noch um 30 Prozent steigen. Falls die Daten prozentual noch weiter steigen, kann das in rund acht Stunden zum Systemcrash führen (24/3,09).  So hat auch hier eine künstliche Generierung vom Wissen stattgefunden.

So kann eine E-Mail den Systembetreuer benachrichtigen:

Achtung!

In ca. 8 Stunden ist ein System-Crash zu erwarten: Sie müssen die Input-Filestores schließen.

10.07.2016 12:31

Ihr gelehrtes System

Fazit

Die Umsetzung von Machine-Learning-Anwendungen mit dem Neuroph-Framework ist gut möglich, effektiv und empfehlenswert. Viele Probleme bleiben für einen Entwickler mit dem Neuroph-Paket im Hintergrund gelöst. Wir freuen uns über Rückmeldungen: valentin.steinhauer@t-online.de.

Verwandte Themen:

Geschrieben von
Valentin Steinhauer
Valentin Steinhauer
Dr. Valentin Steinhauer ist bei Corisecio in Darmstadt tätig. Er verfügt über mehrjährige Erfahrung aus Softwareprojekten als Coach, Trainer, Architekt, Teamleiter und Entwickler.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: