
Hallo allerseits! Heute werden wir über die Implementierung von maschinellem Lernen auf Scala sprechen. Ich werde zunächst erklären, wie wir zu einem solchen Leben gekommen sind. Daher hat unser Team lange Zeit alle Funktionen des maschinellen Lernens in Python genutzt. Es ist praktisch, es gibt viele nützliche Bibliotheken für die Datenaufbereitung, eine gute Infrastruktur für die Entwicklung, ich meine Jupyter Notebook. Alles wäre in Ordnung, aber mit dem Problem der Parallelisierung von Berechnungen in der Produktion konfrontiert, und entschied sich, Scala in der Produktion zu verwenden. Warum nicht, dachten wir, es gibt Unmengen von Bibliotheken, sogar Apache Spark ist in Scala geschrieben! Gleichzeitig entwickeln wir heute Modelle in Python und wiederholen dann das Training in Scala für die weitere Serialisierung und Verwendung in der Produktion. Aber wie sie sagen, steckt der Teufel im Detail.
Ich möchte sofort klarstellen, lieber Leser, dass dieser Artikel nicht geschrieben wurde, um Pythons Ruf für maschinelles Lernen zu untergraben. Nein, das Hauptziel ist es, die Tür zur Welt des maschinellen Lernens auf Scala zu öffnen, einen kurzen Überblick über den alternativen Ansatz zu geben, der sich aus unserer Erfahrung ergibt, und Ihnen zu sagen, auf welche Schwierigkeiten wir gestoßen sind.
In der Praxis stellte sich heraus, dass nicht alles so erfreulich war: Es gibt nicht viele Bibliotheken, die die klassischen Algorithmen für maschinelles Lernen implementieren, und solche, bei denen es sich häufig um OpenSource-Projekte ohne die Unterstützung großer Anbieter handelt. Ja, natürlich gibt es Spark MLib, aber es ist stark mit dem Apache Hadoop-Ökosystem verbunden, und ich wollte es wirklich nicht in die Microservice-Architektur ziehen.
Was gebraucht wurde, war eine Lösung, die die Welt retten und einen erholsamen Schlaf zurückbringen würde, und sie wurde gefunden!
Was brauchst du
Bei der Auswahl eines Werkzeugs für maschinelles Lernen gingen wir von folgenden Kriterien aus:
- es sollte einfach sein;
- Trotz seiner Einfachheit hat niemand die breite Funktionalität aufgehoben.
- Ich wollte wirklich in der Lage sein, Modelle im Webinterpreter zu entwickeln und nicht über die Konsole oder ständige Zusammenstellungen und Zusammenstellungen.
- Die Verfügbarkeit von Dokumentation spielt eine wichtige Rolle.
- Im Idealfall würde es Unterstützung geben, zumindest bei der Beantwortung von Github-Problemen.
Was haben wir gesehen?
- Apache Spark MLib : passte nicht zu uns. Wie oben erwähnt, ist dieser Satz von Bibliotheken stark an den Apache Hadoop-Stack und den Spark Core selbst gebunden, der zu schwer ist, um darauf basierende Microservices zu erstellen.
- Apache PredictionIO : Ein interessantes Projekt, viele Mitwirkende, es gibt Dokumentation mit Beispielen. Tatsächlich ist dies ein REST-Server, auf dem sich Modelle drehen. Es gibt vorgefertigte Modelle, zum Beispiel die Textklassifizierung, deren Einführung in der Dokumentation beschrieben wird. In der Dokumentation wird beschrieben, wie Sie Ihre Modelle hinzufügen und trainieren können. Wir haben nicht gepasst, da Spark unter der Haube verwendet wird und dies eher aus dem Bereich einer monolithischen Lösung als aus einer Microservice-Architektur stammt.
- Apache MXNet : Ein interessantes Framework für die Arbeit mit neuronalen Netzen. Scala und Python werden unterstützt. Dies ist praktisch. Sie können ein neuronales Netz in Python trainieren und dann das gespeicherte Ergebnis von Scala laden, wenn Sie eine Produktionslösung erstellen. Wir verwenden es in Produktionslösungen, dazu gibt es hier einen separaten Artikel.
- Smile : Sehr ähnlich dem Scikit-Learn-Paket für Python. Es gibt viele Implementierungen klassischer Algorithmen für maschinelles Lernen, eine gute Dokumentation mit Beispielen, Unterstützung für Github, einen integrierten Visualizer (unterstützt von Swing). Mit Jupyter Notebook können Sie Modelle entwickeln. Das ist genau das, was Sie brauchen!
Umweltvorbereitung
Also haben wir uns für Smile entschieden. Ich werde Ihnen erklären, wie Sie es im Jupyter Notebook am Beispiel des k-means Clustering-Algorithmus ausführen. Als erstes müssen wir Jupyter Notebook mit Scala-Unterstützung installieren. Dies kann über pip erfolgen oder ein bereits zusammengestelltes und konfiguriertes Docker-Image verwenden. Ich bin für eine einfachere, zweite Option.
Um Jupyter mit Scala anzufreunden, wollte ich BeakerX verwenden, das Teil des Docker-Images ist, das im offiziellen BeakerX-Repository verfügbar ist. Dieses Bild wird in der Smile-Dokumentation empfohlen und kann folgendermaßen ausgeführt werden:
Aber hier wartete das erste Problem: Zum Zeitpunkt des Schreibens des Artikels war BeakerX 1.0.0 im Beakerx / Beakerx-Image installiert, und Version 1.4.1 war bereits im offiziellen Github des Projekts verfügbar (genauer gesagt, die neueste Version 1.3.0, aber der Assistent enthält 1.4.1, und es funktioniert :-)).
Es ist klar, dass ich mit der neuesten Version arbeiten möchte, also habe ich mein eigenes Image basierend auf BeakerX 1.4.1 zusammengestellt. Ich werde Sie nicht mit dem Inhalt der Docker-Datei langweilen, hier ist ein
Link dazu.
Übrigens, für diejenigen, die mein Bild verwenden, gibt es einen kleinen Bonus: Im Beispielverzeichnis gibt es ein Beispiel k-means für eine zufällige Sequenz mit Plotten (dies ist keine völlig triviale Aufgabe für Scala-Notizbücher).
Laden Sie Smile in Jupyter Notebook herunter
Hervorragend vorbereitete Umgebung! Wir erstellen ein neues Scala-Notizbuch in einem Ordner in unserem Verzeichnis und müssen dann die Bibliotheken von Maven herunterladen, damit das Lächeln funktioniert.
%%classpath add mvn com.github.haifengl smile-scala_2.12 1.5.2
Nach dem Ausführen des Codes wird eine Liste der heruntergeladenen JAR-Dateien in seinem Ausgabeblock angezeigt.
Nächster Schritt: Importieren der erforderlichen Pakete, damit das Beispiel funktioniert.
import java.awt.image.BufferedImage import java.awt.Color import javax.imageio.ImageIO import java.io.File import smile.clustering._
Daten für das Clustering vorbereiten
Jetzt lösen wir das folgende Problem: Erzeugen eines Bildes, das aus Zonen mit drei Grundfarben besteht - Rot, Grün und Blau (R, G, B). Eine der Farben auf dem Bild wird sich durchsetzen. Wir gruppieren die Pixel des Bildes, nehmen den Cluster, in dem sich die meisten Pixel befinden, ändern ihre Farbe in Grau und erstellen aus allen Pixeln ein neues Bild. Erwartetes Ergebnis: Die Zone der vorherrschenden Farbe wird grau, der Rest der Zone ändert ihre Farbe nicht.
Als Ergebnis der Ausführung dieses Codes wird das folgende Bild angezeigt:

Nächster Schritt: Konvertieren Sie das Bild in eine Reihe von Pixeln. Mit Pixel meinen wir eine Entität mit den folgenden Eigenschaften:
- breite Seitenkoordinate (x);
- schmale Seitenkoordinate (y);
- Farbwert;
- optionaler Wert der Klassen- / Clusternummer (bevor das Clustering abgeschlossen ist, ist es leer).
Als Entität ist es zweckmäßig, die Fallklasse zu verwenden:
case class Pixel(x: Int, y: Int, rgbArray: Array[Double], clusterNumber: Option[Int] = None)
Hier wird für die Farbwerte das
rgbArray
Array mit drei Werten für Rot, Grün und Blau verwendet (z. B. für das rote
Array(255.0, 0, 0)
).
Damit ist die Datenvorbereitung abgeschlossen.
Pixel-Farbclustering
Wir haben also eine Sammlung von Pixeln mit drei Primärfarben, sodass wir die Pixel in drei Klassen gruppieren.
In der Dokumentation wird empfohlen, den Parameter run im Bereich von 10 bis 20 einzustellen.
Wenn dieser Code ausgeführt wird, wird ein Objekt vom Typ
KMeans
erstellt. Der Ausgabeblock enthält Informationen zu den Ergebnissen des Clusters:
K-Means distortion: 0.00000 Clusters of 230400 data points of dimension 3: 0 50813 (22.1%) 1 51667 (22.4%) 2 127920 (55.5%)
Ein Cluster enthält mehr Pixel als der Rest. Jetzt müssen wir unsere Pixelsammlung mit Klassen von 0 bis 2 markieren.
Bild neu streichen
Sie müssen nur noch den Cluster mit der größten Anzahl von Pixeln auswählen und alle in diesem Cluster enthaltenen Pixel in Grau streichen (ändern Sie den Wert des
rgbArray
Arrays).
Es gibt nichts Kompliziertes, nur nach Clusternummer gruppieren (dies ist unsere
Option:[Int]
), die Anzahl der Elemente in jeder Gruppe zählen und den Cluster mit der maximalen Anzahl von Elementen herausziehen. Ändern Sie als Nächstes die Farbe nur für die Pixel, die zum gefundenen Cluster gehören, in Grau.
Erstellen Sie ein neues Bild und speichern Sie die Ergebnisse.
Sammeln eines neuen Bildes aus der Pixelsammlung:
Das haben wir am Ende getan.

Wir speichern beide Bilder.
ImageIO.write(testImage, "png", new File("testImage.png")) ImageIO.write(modifiedImage, "png", new File("modifiedImage.png"))
Fazit
Maschinelles Lernen auf Scala existiert. Um die grundlegenden Algorithmen zu implementieren, ist es nicht erforderlich, eine große Bibliothek zu ziehen. Das obige Beispiel zeigt, dass Sie während der Entwicklung nicht auf die üblichen Mittel verzichten können, dasselbe Jupyter-Notebook kann leicht mit Scala befreundet werden.
Für einen vollständigen Überblick über alle Funktionen von Smile reicht natürlich ein Artikel nicht aus, und dieser wurde nicht in die Pläne aufgenommen. Die Hauptaufgabe - die Tür zur Welt des maschinellen Lernens auf Scala zu öffnen - ist meiner Meinung nach abgeschlossen. Ob Sie diese Tools verwenden und vor allem in die Produktion ziehen oder nicht, liegt ganz bei Ihnen!
Referenzen