Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de

AWS Deep Java Library – Was ist das?

Auf dem Markt von Deep-Learning-Frameworks wird man meist im Bereich Python fündig. NumPy, Matplotlib, MXNet, PyTorch oder auch TensorFlow sind aus der Deep-Learning-Gemeinde nicht mehr wegzudenken. Start-ups, die über einen eher kleinen sowie modernen IT-Stack verfügen, haben zumeist kein Problem damit, sich schnell in Richtung Python oder andere moderne Implementierungssprachen auszurichten. Doch wie sieht es mit großen Unternehmen aus, die einen eher großen IT-Stack und über die Jahre auch sehr gut ausgebildete und erfahrene Mitarbeiter aufgebaut haben?

  • 29.01.2021
  • Lesezeit: 11 Minuten
  • 97 Views

Meist steht die Programmiersprache Java im Fokus. Genau diese Zielgruppe spricht AWS mit ihrer Deep Java Library (DJL) an. AWS möchte es Millionen von Java-Entwicklern ermöglichen, Deep-Learning-Modelle in ihrer gewohnten Programmiersprache zu entwerfen und zu trainieren.

AWS DJL verknüpft native Java-Konzepte mit bestehenden Deep-Learning-Frameworks. Dadurch sind Anwender in der Lage, mit modernsten Innovationen im Bereich Deep Learning wie auch mit der modernsten Hardware zu arbeiten. Die einfachen APIs abstrahieren die Komplexität, die mit der Entwicklung von Deep-Learning-Modellen verbunden ist. Somit sind diese leicht erlernbar und einfach anzuwenden.

Die AWS DJL eignet sich einerseits für den sofortigen Einsatz bereits vortrainierter Modelle. Diese lassen sich als Deep-Learning-Use-Cases von Tag eins an in die jeweilige Java-Applikation integrieren. Andererseits lassen sich mit DJL auch eigene Modelle je nach Bedarf trainieren. Hierfür braucht es dann allerdings geeignete Datensätze, die als „Futter“ für das KI-Training dienen. Im vorliegenden Artikel sehen wir uns zunächst ein vortrainiertes KI-Modell für die Bild-Objekt-Erkennung an, welches bereits in DJL enthalten ist. Im zweiten Implementierungsbeispiel wird dann beschrieben, wie sich ein eigenes Modell mit einem vorhandenen Datensatz trainieren lässt.

Bild-Objekt-Erkennung mit dem vortrainierten Modell von DJL

Deep Learning ist bereits heute in einer Vielzahl von Use-Cases für Unternehmen relevant. So kommt es beispielsweise im Einzelhandel für Prognosen der Kundennachfragen oder auch zur Analyse von Kundeninteraktionen mit Chatbots zum Einsatz; in der Automobilindustrie, um autonomen Fahrzeugen das Fahren beizubringen oder auch um Qualitätsmängel in der Produktion zu erkennen. In unserem Implementierungsbeispiel möchten wir uns der Objekterkennung auf einzelnen Bildaufnahmen widmen. Für diesen Anwendungsfall bietet uns DJL ein bereits trainiertes Modell, sodass wir uns für den ersten Schritt nicht mit dem Training des Modells beschäftigen müssen.

Um DJL mit einem Anwendungsprojekt zu verwenden, erstellen wir im ersten Schritt ein Projekt in Maven und fügen DJL als Dependency in die pom.xml-Datei ein, siehe Listing 1 und 2.

dependencyManagement>
<dependencies>
 <dependency>
 <groupId>ai.djl</groupId>
 <artifactId>bom</artifactId>
 <version>${djl.version}</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
</dependencies>
</dependencyManagement>
Listing 1: Dependency-Management
<dependencies>
<dependency>
 <groupId>commons-cli</groupId>
 <artifactId>commons-cli</artifactId>
 <version>1.4</version>
</dependency>
<dependency>
 <groupId>org.apache.logging.log4j</groupId>
 <artifactId>log4j-slf4j-impl</artifactId>
 <version>2.12.1</version>
</dependency>
<dependency>
 <groupId>com.google.code.gson</groupId>
 <artifactId>gson</artifactId>
 <version>2.8.5</version>
</dependency>
<dependency>
 <groupId>ai.djl</groupId>
 <artifactId>api</artifactId>
</dependency>
<dependency>
 <groupId>ai.djl</groupId>
 <artifactId>basicdataset</artifactId>
</dependency>
<dependency>
 <groupId>ai.djl</groupId>
 <artifactId>model-zoo</artifactId>
</dependency>
<dependency>
 <groupId>ai.djl.mxnet</groupId>
 <artifactId>mxnet-model-zoo</artifactId>
</dependency>
<dependency>
 <groupId>ai.djl.mxnet</groupId>
 <artifactId>mxnet-engine</artifactId>
</dependency>
<dependency>
 <groupId>ai.djl.mxnet</groupId>
 <artifactId>mxnet-native-auto</artifactId>
 <scope>runtime</scope>
</dependency>
</dependencies>
Listing 2: DJL-Dependencies

Im nächsten Schritt suchen wir uns ein Bild heraus, welches wir durch unsere Objekterkennung laufen lassen wollen. Ich habe hierfür eine Gruppe Personen im Star Wars-Outfit herausgesucht (s. Abb. 1). Für die Demoapplikation lege ich die Datei in meinem Projekt unter „main/ressources/test“ ab. In einer marktfähigen Applikation ließe sich an dieser Stelle auch eine Benutzeroberfläche für den Endkunden einrichten, wo er ein Bild seiner Wahl hochladen kann.

Abb. 1: Photo by Saksham Gangwar on Unsplash

Im nächsten Schritt implementieren wir nun die Programmierschnittstelle der DJL in unserer Applikation, siehe Listing 3. Wichtig ist hierbei, dass wir unser Source-Bild wie folgt angeben:
Path imageFile = Paths.get("src/test/resources/Starwars.jpg");

Mithilfe des „Criteria“-Objekts laden wir dann unser vortrainiertes Modell mit folgenden Parametern:

public class ObjectDetection {
private static final Logger logger =
 LoggerFactory.getLogger(ObjectDetection.class);
private ObjectDetection() {}
public static void main(String[] args)
 throws IOException, ModelException, TranslateException {
 DetectedObjects detection = ObjectDetection.predict();
 logger.info("{}", detection);
}
public static DetectedObjects predict()
 throws IOException, ModelException, TranslateException {
 Path imageFile = Paths.get("src/test/resources/Starwars.jpg");
 Image img = ImageFactory.getInstance().fromFile(imageFile);
 Criteria<Image, DetectedObjects> criteria =
 Criteria.builder()
 .optApplication(Application.CV.OBJECT_DETECTION)
 .setTypes(Image.class, DetectedObjects.class)
 .optFilter("backbone", "resnet50")
 .optProgress(new ProgressBar())
 .build();
 try (ZooModel<Image, DetectedObjects> model =
 ModelZoo.loadModel(criteria)) {
 try (Predictor<Image, DetectedObjects> predictor =
 model.newPredictor()) {
 DetectedObjects detection = predictor.predict(img);
 saveBoundingBoxImage(img, detection);
 return detection;
 }
 }
}
private static void saveBoundingBoxImage(Image img,
 DetectedObjects detection) throws IOException {
 Path outputDir = Paths.get("build/output");
 Files.createDirectories(outputDir);
 //Make image copy with alpha channel because original image was jpg
 Image newImage = img.duplicate(Image.Type.TYPE_INT_ARGB);
 newImage.drawBoundingBoxes(detection);
 Path imagePath = outputDir.resolve("Starwars.jpg");
 // OpenJDK can't save jpg with alpha channel
 newImage.save(Files.newOutputStream(imagePath), "png");
 logger.info("Detected objects image has been saved in: {}",
 imagePath);
}
}
Listing 3: Implementierung des DJL API
  • optApplication: In unserem Fall nutzen wir das Object Detection Model.
  • setTypes: Definition von Input und Output.
  • optFilter: Auswahl des Modells (in unserem Fall resnet50).

Lassen wir nun unsere main-Methode laufen, so wird uns im Ordner „build/output“ das Ergebnis unserer Objekterkennung ausgegeben (s. Abb. 2). Hierauf ist dann zu erkennen, dass die vier Objekte auf dem Bild als Personen erkannt worden sind.

Abb. 2: Klassifizierung des Star Wars-Bilds

Und schon haben wir unseren ersten Deep-Learning-Use-Case in unsere Java-Applikation integriert. Natürlich geht das nicht für alle Vorgänge so einfach. Umso komplexer der Use-Case ist, desto höher auch der Aufwand für das Training. Glücklicherweise gibt es auch hierfür ein vorgefertigtes Vorgehen bei DJL, das wir uns im nächsten Teil ansehen.

Eigene KI-Modelle mit DJL trainieren

Das Sammeln von Daten ist für Organisationen schon heute in vielen Bereichen überlebenswichtig – ob Regierungen, Weltkonzerne oder auch Mittelständler. In unserer digitalen Welt werden diese Daten beim Besuch einer Website, einer Produktbestellung oder auch durch den Aufenthalt an einem bestimmten Ort ganz automatisch gesammelt.

Solche Daten sind die Grundlage für die Anwendung von KI. Diese ist in der Lage, die unstrukturierten Daten zu analysieren und zu verwenden. Die Schlussfolgerungen, die die KI daraus zieht, helfen sowohl bei Entscheidungen als auch bei der Prozessvereinfachung und ermöglichen darüber hinaus sogar neue Geschäftsmodelle. Während bei einem bereits trainierten Modell wie dem vorherigen keine Trainingsdaten notwendig sind, müssen diese für das Training eines eigenen KI-Modells bereitgestellt werden. Umso mehr Daten für den gewünschten Use-Case vorhanden sind, desto genauer kann die KI Entscheidungen treffen.

Was bedeutet das nun für unsere Implementierung mithilfe der DJL? Möchten wir mit spezifischen Use-Cases statt mit einem vortrainierten Modell arbeiten, müssen wir diese Modelle für unsere Belange trainieren. Also ist unsere erste Aufgabe, einen Use-Case zu entwickeln, den wir in unserem Szenario umsetzen möchten.

Für diesen Artikel werden wir diesen Schritt überspringen und nutzen ein allgemeines Trainingsdataset, da hier das Training des Modells im Fokus steht und nicht die User-Journey-Suche. Die Datenerhebung bringt selbst noch einmal weitreichende Anforderungen mit sich und würde den Rahmen des vorliegenden Artikels sprengen. Für unser trainiertes Modell nutzen wir das „Footwear Classification Model“. Hierbei handelt es sich um ein Multiklassen-Klassifizierungs-Computer-Vision-Modell (CV-Modell), das mithilfe von überwachtem Lernen trainiert wurde und Schuhe in eine von vier Klassen einordnet: Stiefel, Sandalen, Schuhe oder Hausschuhe. Der Trainingsdatensatz stammt von der University of Texas in Austin. Der Schuhdatensatz besteht aus 50.025 beschrifteten Katalogbildern, die von Zappos.com gesammelt wurden.

Als ersten Arbeitsschritt müssen wir uns den Datensatz herunterladen. Hierzu gehen wir auf [UTexas], laden uns die Datei utzap50k-images.zip herunter und speichern sie in unserem Projekt. Wird der Trainingsdatensatz entpackt, so lässt sich schnell erkennen, dass es dort vier Ordner gibt, die unseren Klassen entsprechen. Innerhalb der einzelnen Klassenordner gibt es noch spezifische Ausprägungen wie „Knee High Boots“. DJL unterstützt aktuell keine Verschachtelung von Ordnern beim Einlesen von Trainingsdaten, somit können wir nur die erste Stufe als Tags nutzen.

KI-Training mit dem “Footwear classification model”
Training ist der Prozess zur Erstellung eines ML-Modells, in dem einem Lernalgorithmus Trainingsdaten zur Untersuchung gegeben werden. Der Begriff „Modell“ bezieht sich auf das Artefakt, das während des Trainingsprozesses erzeugt wird. Das Modell enthält Muster, die in den Trainingsdaten gefunden wurden, und kann verwendet werden, um eine Vorhersage (oder Inferenz) zu treffen.

Beim Training werden mehrere Komplettdurchläufe über die Trainingsdaten (sogenannte „Epochen“) durchgeführt, um Muster und Trends in den Daten zu finden, die dann im Modell gespeichert werden. Während des Prozesses wird das Modell anhand der Validierungsdaten auf seine Genauigkeit geprüft. Das Modell wird mit den Erkenntnissen über jede Epoche aktualisiert, wodurch die Genauigkeit des Modells verbessert wird. Für das Training schreiben wir uns eine eigene Trainingsklasse, die unsere Daten einliest, das Modell trainiert und am Ende das Modell in unserem Projekt persistiert.

Für das Laden von Bildern aus Ordnern nutzen wir das DJL ImageFolder Dataset. Ein Dataset in DJL beschreibt eine Speicherung von Trainingsdaten. Es gibt Dataset-Implementierungen, die verwendet werden können, um Daten herunterzuladen (basierend auf einer angegebenen URL), Daten zu extrahieren und Daten automatisch in Trainings- und Validierungssätze zu trennen. Die automatische Trennung ist eine nützliche Funktion, da es wichtig ist, niemals die gleichen Daten bei der Validierung zu verwenden, mit denen das Modell trainiert wurde, um die Leistung des Modells zu validieren, siehe Listing 4.

// create ImageFolder dataset from directory
ImageFolder dataset = initDataset("../ut-zap50k-images");
// Split the dataset set into training dataset and validate dataset
RandomAccessDataset[] datasets = dataset.randomSplit(8, 2);
private static ImageFolder initDataset(String datasetRoot)
 throws IOException, TranslateException {
ImageFolder dataset =
 ImageFolder.builder()
 // retrieve the data
 .setRepositoryPath(Paths.get(datasetRoot))
 .optMaxDepth(10)
 .addTransform(new Resize(
 Models.IMAGE_WIDTH, Models.IMAGE_HEIGHT))
 .addTransform(new ToTensor())
 // random sampling; don't process the data in order
 .setSampling(BATCH_SIZE, true)
 .build();
dataset.prepare();
return dataset;
}
Listing 4: Nutzung von ImageFolder zum Laden des Testdatensatzes

Da wir nun die Schuhdaten in Trainings- und Validierungssätze getrennt habe, werde ich ein neuronales Netzwerk zum Trainieren des Modells verwenden, siehe Listing 5. Das Training wird gestartet, indem die Trainingsdaten als Eingabe in einen Block eingegeben werden. In der DJL-Sprache ist ein „Block“ eine zusammensetzbare Einheit, die ein neuronales Netzwerk bildet. Sie können Blöcke gewissermaßen wie Legosteine kombinieren, um ein komplexes Netzwerk zu bilden. Am Ende des Trainingsprozesses stellt ein Block ein vollständig trainiertes Modell dar. Der erste Schritt besteht darin, eine Modellinstanz durch den Aufruf von Models.get-Model(NUM_OF_OUTPUT, NEW_HEIGHT, NEW_WIDTH) zu erhalten. Die Methode get-Model() erstellt ein leeres Modell, konstruiert das neuronale Netz und setzt es auf das Modell, siehe Listing 6.
Der nächste Schritt ist das Einrichten und Konfigurieren eines Trainers durch Aufruf der Methode model.newTrainer(config). Das config-Objekt wurde durch den Aufruf der Methode setupTrainingConfig(loss) initialisiert, die die Trainingskonfiguration (oder Hyperparameter) festlegt, um zu bestimmen, wie das Netz trainiert wird, siehe Listing 7.

// empty model instance to hold patterns:
try (Model model = Models.getModel();
Trainer trainer = model.newTrainer(config)) {
// metrics collect and report key performance indicators,
// like accuracy
trainer.setMetrics(new Metrics());
Shape inputShape = new Shape(1, 3,
 Models.IMAGE_HEIGHT, Models.IMAGE_HEIGHT);
// initialize trainer with proper input shape
trainer.initialize(inputShape);
// find the patterns in data
EasyTrain.fit(trainer, EPOCHS, datasets[0], datasets[1]);
// set model properties
TrainingResult result = trainer.getTrainingResult();
model.setProperty("Epoch", String.valueOf(EPOCHS));
model.setProperty(
 "Accuracy", String.format("%.5f",
 result.getValidateEvaluation("Accuracy")));
model.setProperty("Loss", String.format("%.5f",
 result.getValidateLoss()));
// save the model after done training for inference later
model.save(modelDir, Models.MODEL_NAME);
// save labels into model directory
Models.saveSynset(modelDir, dataset.getSynset());
}
Listing 5: Training des Modells
public class Models {
 public static Model getModel() {
 // create new instance of an empty model
 Model model = Model.newInstance(MODEL_NAME);
 // Block is a composable unit that forms a neural network;
 // combine them like Lego blocks to form a complex network
 Block resNet50 =
 ResNetV1.builder() // construct the network
 .setImageShape(new Shape(3, IMAGE_HEIGHT, IMAGE_WIDTH))
 .setNumLayers(50)
 .setOutSize(NUM_OF_OUTPUT)
 .build();
 // set the neural network to the model
 model.setBlock(resNet50);
 return model;
 }
}
Listing 6: Aufbau der Modell-Klasse (Fokus getModel)
Trainer trainer = model.newTrainer(config)) {
 // metrics collect and report key performance indicators,
 // like accuracy
 trainer.setMetrics(new Metrics());
 Shape inputShape = new Shape(1, 3, Models.IMAGE_HEIGHT,
 Models.IMAGE_HEIGHT);
 // initialize trainer with proper input shape
 trainer.initialize(inputShape);
 // find the patterns in data
 EasyTrain.fit(trainer, EPOCHS, datasets[0], datasets[1]);
private static TrainingConfig setupTrainingConfig(Loss loss) {
 return new DefaultTrainingConfig(loss)
 .addEvaluator(new Accuracy())
 .addTrainingListeners(TrainingListener.Defaults.logging());
}
Listing 7: Trainingskonfiguration

Grundsätzlich haben wir in DJL an unterschiedlichen Stellen (Model, Trainer, TrainingConfig) die Möglichkeit, spezielle Konfigurationen für den Trainingsvorgang vorzunehmen. Beispiele hierfür sind:

  • newHeight und newWidth: die Form des Bildes.
  • batchSize: die Stapelgröße, die für das Training verwendet wird.
  • numOfOutput: die Anzahl der Beschriftungen; für die Klassifizierung von Schuhen gibt es 4 Beschriftungen.
  • loss: Verlustfunktionen bewerten die Modellvorhersagen im Vergleich zu den echten Beschriftungen und messen, wie gut (oder schlecht) ein Modell ist.

Haben wir alle gewünschten Einstellungen vorgenommen, so können wir dann den Trainingsvorgang starten, indem wir die main-Methode in unserer Testklasse ausführen, siehe Listing 8.

Listing 8: Trainingsfortschritt

Verwendung des generierten Modells
Nun können wir unser Modell verwenden, um Inferenzen für neue Daten durchzuführen. Wir können die Klassifizierung in einem Prozess vornehmen, hierfür nutzen wir unser Modell in unserer neuen Klasse Inference. Das trainierte Modell wurde wie in unserer Trainingsklasse deklariert unter dem Pfad „./models“ abgespeichert (s. Abb. 3), möchten wir es nun nutzen, so erzeugen wir uns zuerst eine leere Modellinstanz durch unsere getModel-Methode. Diese initialisieren wir dann mit unserem trainierten Modell via model.load (Paths.get(modelParamsPath), modelParamsName).

Abb. 3: Ablage des trainierten Modells

Im nächsten Schritt initialisieren wir einen Predictor mit einem von uns konfigurierten Translator. In der DJL bietet ein Translator die Funktionen für die Vorbereitung und Nachbearbeitung für Klassifizierungsvorgänge. Bei CV-Modellen müssen zum Beispiel Bilder in Graustufen umgewandelt oder auch an die definierten Maße des Trainingsmodells angepasst werden. Genau diese Tätigkeiten kann ein Translator übernehmen.

Nun können wir mit der Methode predictor.predict(img) eine Inferenz auf das geladene Modell durchführen, indem wir das zu klassifizierende Bild übergeben. In dieser Implementierung führen wir eine Einzelvorhersage durch (nur ein Bild wird geladen), DJL unterstützt jedoch auch die Batchverarbeitung von mehreren Eingaben. Das Ergebnis der Inferenz wird im Objekt predictResult gespeichert (s. Listing 9), das dann eine Wahrscheinlichkeitsschätzung pro Label enthält.

/Users/parnold/.sdkman/candidates/java/current/bin/java ...
[main] INFO ai.djl.training.listener.LoggingTrainingListener – Training
on: cpu().
[main] INFO ai.djl.training.listener.LoggingTrainingListener – Load MXNet
Engine Version 1.7.0 in 0,068 ms.
Training: 33% | Accuracy: 0,84, SoftmaxCrossEntropyLoss: 0,55
Listing 8: Trainingsfortschritt
[
 class: "Sandals", probability: 0,78711
 class: "Shoes", probability: 0,21042
 class: "Slippers", probability: 0,00222
 class: "Boots", probability: 0,00020
]
Listing 9: Inhalt des Objekts predictResult

Nach der Inferenz wird das Modell automatisch geschlossen, daher ist DJL sehr speichereffizient.

Deep Learning – simpel für den Java-Entwickler

Deep Learning ist ein sehr anspruchsvolles Gebiet. AWS hat sich mit der Deep Java Library zum Ziel gesetzt, dieses komplexe Thema der Entwickler-Community in der bekanntesten Programmiersprache näherzubringen – und erfüllt die Erwartung. Wie gezeigt, ermöglicht DJL, in einfachen Schritten einen Deep-Learning-Use-Case in Java-Applikationen zu integrieren.
Doch damit nicht genug: DJL unterstützt auch bekannte Machine Learning-Plattformen wie TensorFlow, Apache MXNet, PyTorch oder auch ONNX Runtime und ermöglicht es seinen Nutzern, die Modelle für ihre spezifischen Use-Cases zu trainieren.

Mich hat die Deep Java Library vor allem durch ihre simple Integration überzeugt, aber verschaffen Sie sich gerne selbst ein Bild davon. Die DLJ nebst Dokumentation findet sich hier [DLJ].

Weitere Informationen

[DLJ] Deep Java Library, https://djl.ai/

[UTexas] http://vision.cs.utexas.edu/projects/finegrained/utzap50k/

. . .

Author Image
Zu Inhalten
Patrick Arnold ist IT-Consultant & Teil des Technologie-Managements bei der AUSY Technologies Germany AG. Für ihn ist Technologie wie Spielzeug für kleine Kinder, er ist süchtig nach Neuem. Nach einer Oldschool-Ausbildung im Mainframebereich wechselte er in die dezentrale Welt. Sein technologischer Schwerpunkt sind moderne Architektur- und Entwicklungsansätze wie Cloud, API-Management, Continuous Delivery, DevOps und Microservices.

Artikel teilen