Pinned Post
Ich habe ein ML-Feature gebaut — und nie Python angefasst
- Link abrufen
- X
- Andere Apps
Ich wollte meine Banktransaktionen automatisch kategorisieren. Jeder Reflex sagte: Python, ein separater ML-Service, eine MLOps-Pipeline. Am Ende waren es rund 200 Zeilen Elixir, und das Ganze ist heute das wartungsärmste Feature der ganzen App.
Ich baue privat eine kleine Finanz-App. Sie zieht Kontoumsätze von der Bank und soll sie in Kategorien einsortieren: Lebensmittel, Miete, Versicherung, Einkommen und so weiter. Klingt trivial, ist es aber nicht. Denn Banktexte sehen so aus:
LASTSCHRIFT 1U1 TELECOM GMBH KD-NR 4711
Kryptisch, abgekürzt, oft deutsch und voller Boilerplate. Eine Liste aus if String.contains?-Abfragen würde immer weiter wachsen und trotzdem ständig danebenliegen. Es ist ein Klassifikationsproblem, also ein Fall für Machine Learning.
Spannend finde ich den Reflex, der dann einsetzt. Für die meisten Teams heißt „ML" automatisch „zweite Welt": ein eigener Python-Service, ein separates Repo, irgendwo ein Modell-Artefakt, dazwischen gRPC oder eine Queue. Dazu kommen ein zweites Deployment, eine zweite Sprache und ein zweiter Satz Betriebssorgen. Das eigentliche Feature ist winzig, der Rattenschwanz dahinter dafür umso länger.
Ich habe mich gefragt, ob das wirklich sein muss. Meine App läuft auf Elixir/Phoenix, also auf der Erlang-VM (der „BEAM"). Und das Elixir-Ökosystem hat in den letzten Jahren einen kompletten ML-Stack bekommen: Nx (Tensoren, ähnlich wie NumPy), EXLA (Compilation auf CPU/GPU über Googles XLA) und Bumblebee (vortrainierte Transformer-Modelle direkt von Hugging Face).
Das Ergebnis: Das ML-Feature lebt in derselben Codebasis wie der Rest der App, ohne zweiten Service und ohne zweite Sprache. Das „Modell" ist ein ganz normaler Prozess unter demselben Supervisor wie meine Datenbank-Verbindung:
# einfach ein weiteres Kind, kein externer Dienst.
children = [
Finance.Repo,
{Phoenix.PubSub, name: Finance.PubSub},
Finance.Classifier.Bumblebee # lädt das Modell, startet die Inferenz
]
Wenn der Classifier abstürzt, startet OTP ihn neu. Dieselbe Fehlertoleranz, die meine Web-Requests schützt, schützt also auch die Inferenz, und ich musste dafür nichts Neues lernen.
Was mich an dem Projekt überrascht hat, war nicht, dass es funktioniert, sondern wie wenig Sonderbehandlung ML auf der BEAM überhaupt braucht. Es fühlt sich nicht wie ein angeflanschtes Fremdsystem an, sondern wie ein normaler Teil der Anwendung.
In den nächsten Artikeln gehe ich die Entscheidungen durch, auf denen das Ganze beruht:
- Klassifizieren ohne Trainingsdaten: wie ich ohne ein einziges gelabeltes Beispiel auskomme.
- Lernen ohne Retraining: wie eine Korrektur im UI das Verhalten sofort verbessert.
- Skalieren ohne Infrastruktur: wie das Modell auf einer anderen Maschine läuft, ohne dass mein Code etwas davon merkt.
- Wann man bewusst kein ML nimmt: denn die beste KI-Entscheidung ist manchmal, gar keine zu treffen.
Vielleicht hältst du ML für etwas, das nur Spezialisten in einem eigenen, separaten System bauen. Dabei kann es ein ganz normaler Teil deiner bestehenden App sein.
Im nächsten Teil: Wie man kategorisiert, ohne ein Modell zu trainieren.
→ Weiter zu Teil 2: Klassifikation ohne ein einziges Trainings-Label

Kommentare