Pinned Post

NodePulse: so your Lightning node speaks up before it gets expensive

Bild
  A Lightning node is an odd kind of device. It sits on a shelf somewhere, a Raspberry Pi with an SSD, and as long as everything works you don’t look at it for weeks. That is exactly the problem. When LND hangs, the SSD fills up or a peer has been offline for days, nobody tells you. You only notice when a payment fails. Or worse: when an HTLC is about to expire and nobody reacts. RaspiBlitz comes with a lot, but not with alerting that actively nudges me. The ready-made solutions I looked at were either too big (Prometheus, Grafana and Alertmanager on a Pi that is supposed to run a Bitcoin node) or they ran as a service somewhere else, with access to my node data. I didn’t want either. So I built NodePulse. A small program that runs directly on the node, checks every minute whether everything is fine, and sends me a Telegram message when it isn’t. And another one once the problem is gone. On top of that there is a dashboard in the browser. In this article I explain what NodePulse...

Wann du KEIN ML nehmen solltest

Nach vier Artikeln über das ML in meiner App kommt der ehrlichste: Mein liebster Teil des Klassifikations-Features ist der Teil, der gar kein ML ist. Denn die reifste KI-Entscheidung ist oft, keine zu treffen.

Diese Serie war ein Plädoyer für pragmatisches ML in Elixir. Sie wäre aber unehrlich, wenn ich nicht auch über die Grenze schreiben würde, über die Stelle also, an der ich das Modell bewusst NICHT entscheiden lasse.

Erinnerung an die geschichtete Pipeline aus Artikel 3: Bevor das Embedding-Modell überhaupt gefragt wird, läuft eine deterministische Regel-Engine. Und die ist kein Notbehelf, sondern Absicht.

Warum überhaupt Regeln, wenn ich doch ein Modell habe? Weil manche Muster gar nicht unsicher sind. Meine Miete geht jeden Monat an denselben Vermieter, mein Gehalt kommt vom selben Arbeitgeber, und den ÖPNV-Abbucher kenne ich ohnehin. „Ähnlichkeit schätzen" ist hier die falsche Operation, denn ich weiß die Antwort. Ein Modell darauf anzusetzen würde eine sichere Tatsache in eine Wahrscheinlichkeit verwandeln, und das ist ein Rückschritt, kein Fortschritt.

Eine Regel ist deshalb schlicht: eine Menge von Bedingungen, die alle zutreffen müssen, und die erste passende aktive Regel gewinnt.

# Alle Bedingungen müssen matchen. Keine "Wahrscheinlichkeit",
# kein Schwellwert, eine Regel trifft zu oder nicht.
def matches?(%Rule{conditions: conditions}, transaction) do
conditions != [] and Enum.all?(conditions, &condition_match?(&1, transaction))
end

Die Bedingungen arbeiten auf den konkreten Feldern einer Transaktion (Empfänger, Auftraggeber, Verwendungszweck, IBAN, Betrag), und zwar mit genau den Operatoren, die ein Mensch erwartet:

# Text: contains / equals / starts_with
"contains" -> String.contains?(actual, expected)
"equals" -> actual == expected
"starts_with" -> String.starts_with?(actual, expected)

# Betrag: lt / gt / between (inklusiv, reihenfolge-unabhängig)
"lt" -> Decimal.lt?(amount, value)
"gt" -> Decimal.gt?(amount, value)
"between" -> not below_low? and not above_high?

Und entscheidend: Regeln speisen nicht in das ML-Lernsignal aus Artikel 3 ein:

# Anders als set_group/2 schreibt eine Regel KEINE ClassifierCorrection.
# Regeln sind deterministisch und bleiben bewusst aus dem ML-Lernsignal.
defp apply_rule(transaction, rule) do
# ... weist die Gruppe der Regel zu, merged die Tags ...
end

Warum diese Trennung? Weil Regel und Modell verschiedene Wahrheiten ausdrücken. Eine Regel sagt: „Das ist per Definition so." Eine Korrektur sagt: „Das Modell hat geraten und lag daneben, hier ein Beispiel." Würde ich Regeln als Trainingsbeispiele einspeisen, würde ich das Modell mit Dingen „verbessern", die es nie hätte raten müssen, und damit das Signal verwässern, das aus echten Fehlern kommt.

Das Muster, das dahinter steht, gilt weit über meine App hinaus:

  • Deterministisch, wenn du die Antwort kennst. Regeln sind vorhersehbar, sofort wirksam, in Sekunden zu debuggen („welche Regel hat gegriffen?") und brauchen keine Rechenpower. Wenn ein if reicht, ist ein neuronales Netz keine Eleganz, sondern Ballast.
  • ML, wenn der Raum offen ist. Das Modell verdient sich seinen Platz genau dort, wo ich nicht alle Fälle aufzählen kann: beim unbekannten Händler, der neuen Abbuchung, dem kreativ formatierten Verwendungszweck.
  • Lass beide das tun, was sie am besten können. Die geschichtete Pipeline ist kein Kompromiss, sondern eine Arbeitsteilung: Gewissheit zuerst, Schätzung als Auffangnetz.

Der Hype legt nahe, möglichst viel durch ein Modell zu schicken. Gutes Engineering fragt das Gegenteil: Wie wenig muss durch das Modell, damit der teure, unsichere Teil so klein wie möglich bleibt? Jede Transaktion, die eine Regel abfängt, ist eine, bei der ich mir um Schwellwerte, Fehlklassifikation und Modell-Verfügbarkeit keine Gedanken machen muss.

Das ist für mich der Kern guter ML-Integration. Elixir macht es leicht, weil deterministischer Code und Modell-Inferenz im selben Modul nebeneinander stehen, ohne Sprachgrenze und ohne Service-Hop. Das Beste am ML in meiner App ist, dass ich es nur dort einsetze, wo es sich wirklich lohnt.

Danke fürs Lesen dieser Serie. Eine kleine App, fünf Entscheidungen, kein Python. Wenn dich der Code interessiert oder du selbst mit Nx/Bumblebee liebäugelst, schreib mir einfach, ich teile gern mehr.

Kommentare

Beliebte Posts aus diesem Blog

Splitting an ML model and a web app across two BEAM nodes — the technical blueprint

Jido in Practice: Agents in Elixir as Composable Actions

A Foundation Model in the BEAM: On-Chain Anomalies with Google TimesFM in Elixir