Amazon DJL – ein neues Deep Learning Framework für Java

Wer auf der JVM und insbesondere in Java mit neuronalen Netzen und Deep Learning experimentieren wollte, für die gab es bisher nur wenig Auswahl. Wer ausschließlich auf Java setzen wollte, kam bisher an DL4J nicht vorbei. Wenn es die JVM, aber nicht unbedingt Java sein muss, kommt auch noch das Scala Frontend von MXNet in […]

Wer auf der JVM und insbesondere in Java mit neuronalen Netzen und Deep Learning experimentieren wollte, für die gab es bisher nur wenig Auswahl. Wer ausschließlich auf Java setzen wollte, kam bisher an DL4J nicht vorbei. Wenn es die JVM, aber nicht unbedingt Java sein muss, kommt auch noch das Scala Frontend von MXNet in Frage. Wen schließlich ein wenig Python nicht schreckt, die kann eine Hybrid Lösung aus TensorFlow und Java probieren, wie wir bereits in früheren Artikeln erläutert haben.

Nun betritt aber ein neuer Wettbewerber die noch übersichtliche Java-Deep-Learning-Landschaft: DJL, Deep Java Learning, ein Java Framework für Deep Learning von Amazon. Während die anderen Silicon Valley Größen bereits ihre jeweils eigenen „Haus-Frameworks“ anbieten (Google: TensorFlow, Facebook: PyTorch, Microsoft: CnTK), hatte Amazon hier bisher keine „eigene“ Lösung, obwohl der Konzern selbst durch Produkte wie Alexa oder die Amazon Cloud APIs bereits für Endverbraucherinnen und Entwicklerinnen kräftig im KI Markt mitmischt. Mit DJL füllt Amazon nun diese Lücke im eigenen Portfolio – und bietet zugleich eine neue Alternative für die bisher von der Deep Learning Szene stiefmütterlich behandelten Java-Programmiererinnen an.

Da es sich um ein von Amazon entwickeltes und veröffentlichtes Framework handelt, ist zu erwarten, dass es sich hier nicht nur um eine Eintagsfliege handelt, sondern dass es auch längerfristig Unterstützung erfährt. Obwohl es sich um ein Apache 2.0-lizenziertes Open Source Projekt handelt, ist die Entwicklung und Betreuung von DJL natürlich nicht nur reiner Altruismus. Als der weltweit größte Cloud Anbieter ist ein „hauseigenes“ Framework für üblicherweise rechenhungrige Deep Learning Aufgaben auch strategisch von Bedeutung.

Der Aufbau von DJL

DJL selbst ist eine Framework-unabhängige Deep Learning API. D.h. man soll in Zukunft verschiedene Deep Learning Frameworks (die i.d.R. alle nativ in C/C++/Cuda geschrieben sind) zur Ausführung der eigentlichen Berechnungen nutzen können. Allerdings ist die einzige zur Zeit voll funktionsfähige Implementierung ein Aufsatz auf die C++-API von MXNet. Es erlaubt Java-Nutzerinnen im Gegensatz zur offiziellen MXNet Java API, die nur das „abspielen“ (Inferenz) von fertigen Modellen ermöglicht, auch die Erstellung und das Training komplett neuer Modelle „from scratch“. Es handelt sich also um ein komplettes und vollwertiges Deep Learning Framework für die MXNet Engine.

Dabei unterstützt DJL nur den Eager Mode von MXNet, also eine imperative Ausführung von Berechnungsschritten, wie sie auch in TensorFlow 2 nun Standard ist, und nicht den Computation Graph von MXNet, den auch TensorFlow 1.0 Nutzerinnen gut kennen. Dies macht das Entwickeln und Debuggen von Modellen wesentlich einfacher, allerdings auf Kosten der Performance.

Für die Zukunft ist auch die Unterstützung von TensorFlow und DL4J als Engines geplant – inwieweit diese Framework-agnostische Strategie sinnvoll und umsetzbar ist, bleibt abzuwarten.

DJL Module und Abhängigkeiten

DJL besteht aus einer Reihe von Modulen, die alle als Gradle / Maven dependency einbindbar sind. Folgende Abhängigkeiten braucht es immer (wenn man die MXNet Engine nutzen will):

  • ai.djl:api: Die core DJL API
  • ai.djl.mxnet:mxnet-engine: Die Implementierung der DJL API mit MXNet – sobald andere Framework-Implementierungen verfügbar sind, könnte man diese hier als Alternative wählen.
  • ai.djl.mxnet:mxnet-native-auto: Diese Abhängigkeit enthält den native code für MXNet. Hierfür gibt es mehrere Alternativen, die -auto Variante lädt die aktuell passende Variante (GPU, CPU) von MXNet herunter. Möchte man eine spezielle Variante, gibt es andere Bibliotheken, mit denen man genau festlegen kann, welche dependency genutzt werden soll. Zum Entwickeln empfieht sich die -auto Variante, bei deployments sollte man anhand der Dokumentation die passende Variante händisch festlegen.

Darüber hinaus gibt es eine Reihe von Abhängigkeiten, die die Arbeit mit vorgefertigten Netzarchitekturen, Modellen und Datensets erleichtern:

  • ai.djl:basicdataset: Öffentlich verfügbare Standard ML Datasets wie (natürlich) MNIST.
  • ai.djl:model-zoo: Fertig trainierte Standardmodelle

DJL Klassen und Interfaces

Da DJL aber noch ziemlich neu ist, gibt es in diesen Modulen noch nicht viel zu finden. Die wichtigsten Klassen bzw. Interfaces, mit denen man sich bei der Nutzung von DJL bekannt machen sollte, sind die folgenden:

  • ai.djl.ndarray.NDArray: Dies ist die wichtigste Klasse in DJL. Sie repräsentiert einen multidimensionalen Array (Tensor) der von der zugrundeliegenden Engine verwaltet wird. Sie bietet eine große Zahl von mathematischen Funktionen an, von einfachen Operationen wie Addition und Multiplikation bis hin zu speziellen Operationen wie Softmax. Benutzt man NDArrays beim Trainieren neuronaler Netze, werden die Gradienten automatisch berechnet und verwaltet. Man kann NDArrays aber auch ganz normal zur schnellen Berechnung in anderen Kontexten benutzen, wenn man schnelle, beschleunigte Tensoroperationen braucht. Die Benennung ist bewusst analog zu dem aus Python bekannten NDArray und soweit möglich sind Namen und Signaturen von Methoden gleich.
  • ai.djl.ndarray.types.Shape: Diese Klasse definiert die Form (Anzahl und Größe von Dimensionen) eines NDArray. Sie ist wichtig, da eine der kniffligsten Aufgaben beim Programmieren eigener Netze oft darin besteht, sicherzustellen, dass die Form von Input und Output aufeinanderfolgender Operationen zusammen passen.
  • ai.djl.ndarray.NDManager: Diese Klasse ist quasi der Einstiegspunkt für eine Engine wie MxNet. Mit dem NDManager kann man NDArray Objekte erzeugen, die die Basis für alle Berechnungen in DJL sind.
  • ai.djl.nn.Block: Hierbei handelt es sich um einen Bestandteil eines neuronalen Netzes, in anderen Frameworks oft auch Layer genannt. Blöcke können selbst wieder Blöcke enthalten, die wieder Blöcke enthalten etc., weshalb wohl der etwas allgemeinere Begriff statt „Layer” gewählt wurde. Jedes neuronale Netz ist selber auf höchster Ebene ein einzelner Block. Es gibt diverse Subklassen, die das Erstellen eigener Blöcke erleichtern.
  • ai.djl.ndarray.NDList: Wie der Name schon suggeriert, handelt es sich hier um eine Liste von NDArray s. Diese Hilfsklasse ist notwendig, da komplexere Architekturen schon einmal für einige Schritte mehrere Eingaben brauchen oder mehrere Ausgaben generieren. Daher hat die wichtigste Funktion eines Blocks, forward, als Ein- und Ausgabewerte NDLists statt NDArrays, um solche Berechnungen abzubilden.
  • ai.djl.Model: Wie der Name schon sagt, ist ein Model ein Modell, also die Architektur eines neuronalen Netzes (= dessen Blöcke) und der gelernten (oder noch zu lernenden) Parameter.
  • ai.djl.training.Trainer: Ein Trainer kann ein Modell trainieren, diese Klasse führt also Backpropagation, also den Deep-Learning-Lernprozess durch. Mit dieser Klasse interagiert man beim Training eines Modells.
  • ai.djl.inference.Predictor: Ein Predictor ist das Gegenstück zum Trainer, wenn man ein bereits trainiertes Model nutzen will. Er erlaubt die Inferenz auf einem neuronalen Netz.

Dazu gibt es noch eine Reihe wichtiger Behelfsklassen und -interfaces wie Dataset, Translator, Record und Batch die dabei helfen, Daten in geeigneter Form in und aus dem Modell zu bekommen. Wie das im Detail geht, werden wir in den nächsten Wochen Schritt für Schritt im Detail anhand von Code Beispielen in einer Tutorial Serie erläutern.

Unsere Erfahrung mit DJL

Wir bei DIVISIO sind jetzt schon große Fans von DJL, da es uns die Möglichkeit gibt, direkt auf der JVM auch komplett neue Architekturen und Erkenntnisse neuer Paper umzusetzen. Es bietet automatische GPU beschleunigte Gradientenberechnung für eigene Layer (Blöcke) – dies fehlt beim Hauptkonkurrenten DL4J leider noch, hier muss man u.U. den Backwards Pass (die Ableitung) neuer Layer noch selber implementieren. Allerdings arbeitet das DL4J-Team mit Hockdruck an dieser Funktionalität, so dass in absehbarer Zeit auch dieses Feature in DL4J Einzug halten wird.

Pro: Schnellere Umsetzung und vereinfachte Fehlersuche

Da DJL komplett in Java zu Hause ist, entfällt die Komplexität, die durch Python/Java Hybridprojekte entsteht. Durch imperative Arbeitsweise von DJL lassen sich auch komplexe Modelle mit z.B. IntelliJ wie normaler Java Code debuggen. Dies beschleunigt die Arbeit und Fehlersuche enorm und vereinfacht die Entwicklung komplexerer Kontrollstrukturen in neuronalen Netzen. Durch unsere erste Projekterfahrung mit DJL konnten wir feststellen, dass der Source Code und die JavaDoc-Dokumentation für ein so neues Projekt schon eindrucksvoll weit fortgeschritten sind. Ein großes Plus ist das hilfsbereite und freundliche Entwicklerteam, das enorm schnell auf Bug Reports und Feature Requests reagiert.

Kontra: (Noch) fehlende ready-made Komponenten

Einziges Manko bisher ist, dass einige High-Level Komponenten, die in anderen Frameworks selbstverständlich sind, in DJL noch fehlen. Für unsere tägliche Arbeit ist dies weniger hinderlich, da unsere aktuellen Projekte in der Regel ohnehin das Implementieren von Custom-Architekturen verlangen. Daher ist für uns die Möglichkeit, komplett neue Komponenten schnell und einfach zu entwickeln, wichtiger als eine große Auswahl fertiger Funktionalität. Für Deep Learning Anfänger kann es aber daher zur Zeit noch etwas knifflig sein, sich zurecht zu finden, wenn z.B. ein Layer Typ, den andere Frameworks selbstverständlich anbieten in DJL noch fehlt. Auch ist die Auswahl fertiger, vortrainierter Modelle noch sehr überschaubar, hier hat DL4J noch eindeutig die Nase vorn.

Als relativ neues Projekt gibt es natürlich auch noch nicht viel Material online abseits der Dokumentation des DJL-Teams selber – StackOverflow ist zur Zeit bei DJL-Fragen noch keine Hilfe, da sich die Community noch im Aufbau befindet. Dies wird sich aber wahrscheinlich in den nächsten Monaten schnell ändern.

Fazit: Deep Learning Framework mit Potential – nicht nur für Spezialisten

Wer sich mit Java und Deep Learning schon gut auskennt, ist bei DJL bereits jetzt gut aufgehoben. Der saubere Sourcecode, das gute Javadoc und das hilfsbereite Entwicklerteam erlauben es schnell in der Entwicklung Fahrt aufzunehmen. Wer noch ein Deep Learning Anfänger ist, wird es etwas schwerer haben, sich zurecht zu finden, da die breite Unterstützung durch Online-Tutorials, StackOverflow-Antworten und Beispielprojekte noch fehlt. Dennoch ist DJL bereits jetzt ein ausgezeichnetes Framework, das die Chance hat, sich zu dem führenden Deep Learning Framework auf der JVM zu entwickeln.

Um Anfängerinnen die Orientierung zu erleichtern und einige Aspekte im Detail zu erläutern, werden wir in den nächsten Wochen Schritt für Schritt das klassische Deep Learning Beispiel in DJL erläutern, die Klassifizierung handgeschriebener Ziffern auf Basis des MNIST Datasets.