Am Ende eines aufwendigen Entwicklungsprozesses steht ein mit viel Fleiß und Mühe erfolgreich trainiertes TensorFlow-Machine-Learning-Verfahren. Jetzt soll es für einen Kunden in Betrieb genommen werden. Aber wie soll das geschehen? Was muss deployt werden? Natürlich liegt der Einsatz in einer Python-Umgebung, etwa auf einem Django-Webserver oder dem brandneuen TensorFlow Serving nahe. Zum Glück gibt es aber auch eine von Google unterstützte Java-Wrapper-Bibliothek. Wir müssen nicht auf unsere bewährten Java-Tools für den Servereinsatz verzichten. Allerdings tauchen auf dem Weg zum fertigen REST-Service eine Reihe praktischer Hürden auf.
Das richtige Vorgehen lässt sich am besten anhand eines Beispiels erläutern: Wir möchten die sensorische Qualität (wie lecker ist er?) von Wein anhand chemischer und physischer Messwerte vorhersagen. Die Daten hierzu stammen aus dem UCI Machine Learning Repository [1] und wurden im Rahmen eines Papers der Universität Minho in Portugal erfasst. Das komplette Codebeispiel für eine Umsetzung mit TensorFlow ist auf GitHub zu finden [2]. Der Input unseres Modells besteht aus diversen Messwerten wie pH-Wert, Alkohol, Zucker etc. Der Output ist eine Einschätzung der Weinqualität auf einer Skala von 0-10. Die Bewertung der Trainingsdaten haben professionelle Weinkenner übernommen und ist für Forschungszwecke frei zugänglich.
Damit wir ein gelerntes Modell erfolgreich in der Praxis einsetzen können, muss der TensorFlow Code zum Trainieren des Modells so strukturiert sein, dass das gelernte Modell passend exportiert werden kann. Auf der Java-Seite muss das Modell dann wieder richtig eingebunden werden. Wir werden also versuchen, einen KI-Weinkenner zu bauen und über einen Java-Server anzusprechen.
Alle tatsächlichen Berechnungen in TensorFlow werden durch die zugrunde liegende TensorFlow-C++-Bibliothek durchgeführt. Das Python-API, das in allen Beispielen zu sehen ist, ist eigentlich nur ein bequemer Wrapper, um das C++-API zu steuern und leichter nutzbar zu machen. Wenn wir also ein fertig trainiertes Modell nur noch aufrufen wollen, um eine Vorhersage oder Klassifikation vorzunehmen, sind wir auf den Python Wrapper nicht mehr angewiesen.
Nun wissen wir, dass wir für den Einsatz in einem Produktivsystem auf Python verzichten und stattdessen beispielsweise Java (mit JNI) einsetzen können. Aber ist das überhaupt sinnvoll? Vom technischen Standpunkt macht es keinen großen Unterschied. Wenn der Entwickler die unten aufgeführten Klippen erst einmal umschifft hat, ist der Aufwand mit einem Einsatz in beispielsweise Spring oder Django vergleichbar, was die Implementierung betrifft. Auch was die Performance angeht, ergeben sich kaum Unterschiede, da die Berechnungen durch den gleichen nativen in C++ geschriebenen Code durchgeführt werden (Kasten: „Ganz auf Python verzichten?“).
Theoretisch sind alle Operationen direkt auf dem C++ API ausführbar und damit auch in Java. In der Praxis ist das allerdings so, als würde jemand einen riesigen Baum mit einem Taschenmesser fällen. Das Python-API erlaubt derart schnelle Abkürzungen (z. B. in wenigen Zeilen ein neuronales Netz definieren, mit Daten füttern und trainieren), dass TensorFlow-Entwicklung mit einem professionellen Anspruch fürs erste nicht ohne Python auskommt. Soll auch beim Training und der Entwicklung des Modells Java zum Einsatz kommen, empfehle ich einen Blick auf Deeplearning4j (https://deeplearning4j.org). Der Nachteil hier ist aber auch, dass wir auf die umfangreichen ML- und Visualisierungsbibliotheken verzichten müssen (Matplotlib, scikit etc.), die in Python selbstverständlich zum ML-Ökosystem gehören.
Mit Blick auf den Kunden, das Projekt, oder die sonstige eingesetzte Umgebung kann es allerdings durchaus von Vorteil sein, auf Python zu verzichten, die Gründe pro Java und contra Python sind also eher projektbezogen als technisch (Kasten: „Und was ist mit meiner liebsten JVM-Sprache?“). In diesen Szenarien ist ein Einsatz in Java sinnvoll:
Solange die Sprache mit vertretbarem Aufwand Java-Bibliotheken ansprechen kann und Java-Primitive und -Arrays zur Verfügung stehen, kann die hier aufgeführte Methode auch für jede andere JVM-Sprache verwendet werden. Einem Einsatz von TensorFlow in beispielsweise Kotlin, Scala, Clojure und anderen steht also nichts im Wege.
Ein existierendes Java-Serversystem soll um ein ML-Feature erweitert werden. Durch die Integration mit Java reduziert sich der Administrations- und Integrationsaufwand erheblich, da keine zusätzliche Plattform installiert und gewartet werden muss. Auch gegebenenfalls vorhandene Systeme zur Zugangs- und Session-Verwaltung können einfach weiter genutzt werden.
Es existiert ein Team, das für die Integration, Pflege und den Betrieb der Produktivsysteme zuständig ist. Es besteht aus anderen Personen als das ML-Team und setzt auf Java. Vielleicht ist das ML-System von einem externen Dienstleister oder von Freelancern entwickelt worden. In solchen Fällen kann das Team, das dauerhaft für den weiteren Betrieb zuständig ist, mit vertrauten Technologien weiterarbeiten.
Es existieren Einschränkungen beim Kunden, welche Frameworks und Technologien auf Produktivsystemen genutzt werden dürfen. Dies kann insbesondere bei großen Firmen und Konzernen der Fall sein. Hier kann es lange dauern, bis die notwendige Python-Software für den Einsatz zertifiziert ist. Die Möglichkeit, die Integration in Java vorzunehmen, kann das entscheidende Argument sein, einen Projektzuschlag zu bekommen.
Die Entwickler sind keine Python-Fans und möchten die Nutzung von Python möglichst minimieren.
Die Entwickler sind Java-(Kotlin-, Scala- …)Fans und möchten die Nutzung von Java/der JVM möglichst maximieren.
Wie bereits erwähnt ist TensorFlow eigentlich eine in C++ geschriebene, also native, Bibliothek. Java erlaubt uns dank JNI das Ausführen von C++-Code und glücklicherweise stellt Google eine fertige Wrapper-Bibliothek bereit, die uns die mühsame Arbeit mit JNI abnimmt. Wir müssen also nur noch die richtigen Informationen in unserem Python-Code persistieren und in Java laden. Um den Überblick im API-Dschungel von TensorFlow nicht zu verlieren und zu wissen, was überhaupt geladen und gespeichert werden muss, lohnt es sich, einen kurzen Blick auf den inneren Aufbau und die wichtigsten TensorFlow-Persistenzkonzepte zu werfen.
Der Zweck von TensorFlow besteht in numerischen Berechnungen mithilfe von Datenflussgraphen (Data Flow Graphs). Ein TensorFlow-Programm definiert also mathematische Operationen und wie die Daten zwischen den Operationen fließen. Daten werden immer in Form von Tensoren (mehrdimensionalen Arrays) verarbeitet. Daher auch der Name TensorFlow, es geht darum, wie Tensoren vom Input durch mehrere Operationen zum Output fließen. Damit ließe sich alles Mögliche machen, aber es eignet sich besonders zum Trainieren und Ausführen von ML-Algorithmen, da hier viele Daten am besten parallel auf die immer gleiche Art verarbeitet werden müssen.
Der durch unseren Python-Code erstellte Graph könnte bereits in Form einer protobuf-Datei serialisiert werden, allerdings brauchen wir noch einige weitere Zutaten, um unser Modell später für Vorhersagen einsetzen zu können. Während des Trainings ändert sich der Zustand unseres Graphen, unser Modell lernt. Das Gelernte (die Parameter des Modells) wird in Variablen, die ebenfalls Knoten des Graphen sind, gespeichert. Beispielsweise werden während des Trainings die Gewichte eines neuronalen Netzes angepasst. Den Zustand der Variablen eines...