Ausreißererkennung in Sensordaten braucht kein Machine Learning, um nützlich zu sein. Drei klassische Verfahren – der Z-Score, der Interquartilsabstand (IQR) und der exponentiell gewichtete gleitende Mittelwert (EWMA) – erfassen die meisten Spitzen, Aussetzer und Driften in Temperaturströmen. Dieser Artikel erklärt, wie jedes Verfahren funktioniert und wo es an seine Grenzen stößt, und liefert Python-Code, der alle drei auf einen Live-MQTT-Datenstrom anwendet.
Ausreißer und Anomalien in Sensordatenströmen
Ein Ausreißer ist ein Messwert, der weit von den übrigen entfernt liegt. Eine Anomalie ist ein Messwert oder Muster, das darauf hinweist, dass in der realen Welt etwas Ungewöhnliches geschieht. Beides überschneidet sich, ist aber nicht identisch. Ein einzelner wilder Messwert durch elektrisches Rauschen ist ein Ausreißer, aber keine Prozessanomalie; ein langsamer Anstieg um zwei Grad innerhalb einer Stunde erzeugt womöglich nie einen Ausreißer und kündigt dennoch einen ausfallenden Kühllüfter an.
Die Ausreißererkennung in Sensordaten hat daher zwei Aufgaben:
- Bereinigung. Spitzen und unmögliche Werte entfernen oder markieren, bevor sie Dashboards, Mittelwerte und Modelle erreichen.
- Frühwarnung. Messwerte hervorheben, die Aufmerksamkeit verdienen, weil sie das jüngste Muster durchbrechen.
Die folgenden Methoden decken beides ab, mit unterschiedlichen Stärken. Die übergreifende Architektur einschließlich Machine-Learning-Detektoren beschreibt Methoden der IoT-Anomalieerkennung.
Z-Score: Abstand in Standardabweichungen
Der Z-Score misst, wie viele Standardabweichungen ein Wert vom Mittelwert entfernt ist:
z = (x - mean) / std
Ein Messwert mit einem absoluten Z-Score über 3 gilt nach einer verbreiteten Regel als Ausreißer. Im Streaming-Kontext berechnen Sie Mittelwert und Standardabweichung über ein gleitendes Fenster aktueller Messwerte statt über die gesamte Historie, sodass die Basislinie langsamen Änderungen folgt.
Stärken: leicht zu erklären, schnell, funktioniert gut, wenn das Signal annähernd normalverteilt um ein stabiles Niveau schwankt.
Schwächen: Mittelwert und Standardabweichung werden selbst durch Ausreißer verzerrt. Eine Häufung von Spitzen bläht die Standardabweichung auf und verdeckt die nächste Spitze. Zudem setzt das Verfahren symmetrisches Rauschen voraus, das viele Sensoren nicht haben.
Eine robuste Variante ersetzt den Mittelwert durch den Median und die Standardabweichung durch die mittlere absolute Abweichung vom Median (MAD). Diese Version kommt im Tutorial Echtzeit-Anomalieerkennung in Python zum Einsatz.
IQR: Quartilsgrenzen
Der Interquartilsabstand verwendet Perzentile statt Mittelwerte:
Q1 = 25th percentile, Q3 = 75th percentile, IQR = Q3 - Q1
lower fence = Q1 - 1.5 * IQR
upper fence = Q3 + 1.5 * IQR
Alles außerhalb dieser Grenzen ist ein Ausreißer. Verwenden Sie 3 statt 1,5 für „extreme“ Ausreißer.
Stärken: robust gegenüber genau den Ausreißern, nach denen gesucht wird, keine Normalverteilungsannahme, kommt mit schiefen Daten wie Schwingungsamplituden oder Stromaufnahme zurecht.
Schwächen: benötigt ein ausreichend großes Fenster für stabile Quartile, und bei stark quantisierten Sensoren (zum Beispiel einem Temperatursensor mit 0,5 Grad Auflösung) kann der IQR auf null schrumpfen und jede kleine Änderung markieren. Legen Sie eine Mindestbreite der Grenzen fest, die der Auflösung des Sensors entspricht.
EWMA: geglättete Basislinie mit Kontrollgrenzen
EWMA führt einen laufend geglätteten Wert, bei dem aktuelle Messwerte stärker gewichtet werden:
ewma = alpha * x + (1 - alpha) * ewma_previous
Parallel dazu verfolgen Sie eine exponentiell gewichtete Varianz und markieren Messwerte außerhalb von ewma ± k * ewm_std. Ein kleines Alpha (0,01 bis 0,05) ergibt eine langsame, stabile Basislinie; ein größeres Alpha folgt Änderungen schnell.
Stärken: konstanter Speicherbedarf (zwei Zahlen pro Signal), ideal für Mikrocontroller, passt sich von selbst langsamer Drift an und reagiert als Regelkarte empfindlich auf kleine anhaltende Verschiebungen.
Schwächen: Weil sich das Verfahren anpasst, kann eine langsame Störung in die Basislinie aufgenommen werden. Kombinieren Sie es mit einer absoluten Grenze für Werte, die nie überschritten werden dürfen.
Die Methoden im Vergleich
| Methode | Speicher pro Signal | Robust gegenüber Ausreißern | Kommt mit Schiefe zurecht | Erkennt langsame Drift | Passt auf Mikrocontroller |
|---|---|---|---|---|---|
| Gleitender Z-Score | Fenster mit N Werten | Nein | Nein | Schlecht | Ja, bei kleinem N |
| Robuster Z-Score (Median/MAD) | Fenster mit N Werten | Ja | Teilweise | Schlecht | Ja, bei kleinem N |
| IQR-Grenzen | Fenster mit N Werten | Ja | Ja | Schlecht | Ja, bei kleinem N |
| EWMA-Kontrollgrenzen | Zwei Zahlen | Teilweise | Nein | Ja, bei kleinem Alpha | Problemlos |
Keine Methode gewinnt allein. Eine praxistaugliche Kombination für Temperaturen lautet: harte physikalische Grenzen, IQR für Spitzen und EWMA mit kleinem Alpha für Drift.
Ausreißererkennung in Sensordaten mit Python
Der folgende Code implementiert alle drei Detektoren als kleine Klassen mit einer update-Methode, sodass Sie sie parallel auf denselben Datenstrom anwenden können.
# detectors.py
import math
import statistics
from collections import deque
class RollingZ:
def __init__(self, window=60, k=3.0):
self.buf, self.k = deque(maxlen=window), k
def update(self, x):
flagged = False
if len(self.buf) >= 20:
mean = statistics.fmean(self.buf)
std = statistics.pstdev(self.buf) or 1e-9
flagged = abs(x - mean) / std > self.k
if not flagged:
self.buf.append(x)
return flagged
class IQRFence:
def __init__(self, window=60, k=1.5, min_width=0.2):
self.buf, self.k, self.min_width = deque(maxlen=window), k, min_width
def update(self, x):
flagged = False
if len(self.buf) >= 20:
q1, _, q3 = statistics.quantiles(self.buf, n=4)
iqr = max(q3 - q1, self.min_width)
flagged = x < q1 - self.k * iqr or x > q3 + self.k * iqr
if not flagged:
self.buf.append(x)
return flagged
class EWMA:
def __init__(self, alpha=0.05, k=4.0, warmup=20):
self.alpha, self.k, self.warmup = alpha, k, warmup
self.mean, self.var, self.n = None, 0.0, 0
def update(self, x):
self.n += 1
if self.mean is None:
self.mean = x
return False
diff = x - self.mean
std = math.sqrt(self.var) or 1e-9
flagged = self.n > self.warmup and abs(diff) > self.k * std
if not flagged:
self.mean += self.alpha * diff
self.var = (1 - self.alpha) * (self.var + self.alpha * diff * diff)
return flagged
Jeder Detektor lernt nicht aus den Werten, die er markiert. So verunreinigt eine Spitze die Basislinie nicht.
Anwendung auf einen Live-MQTT-Temperaturstrom
Um das durchgängig auszuprobieren, veröffentlichen Sie simulierte Temperaturen an den öffentlichen MQTT-Broker unter broker.connectmq.com und führen die Detektoren in einem Subscriber aus. Ersetzen Sie t9x4q2 durch Ihre eigene zufällige ID; der öffentliche MQTT-Broker ist für alle lesbar und nur für Tests gedacht, verwenden Sie daher nur synthetische Daten.
# temp_sim.py
import json, random, time, uuid
import paho.mqtt.client as mqtt
TOPIC = "connectmq/demo/t9x4q2/lab/room1/temperature"
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"tsim-{uuid.uuid4().hex[:6]}")
c.connect("broker.connectmq.com", 1883, keepalive=60)
c.loop_start()
base = 21.0
while True:
base += random.gauss(0, 0.01) # langsames Wandern
value = base + random.gauss(0, 0.15)
if random.random() < 0.02:
value += random.choice([-6, 7]) # gelegentliche Spitze
c.publish(TOPIC, json.dumps({"tempC": round(value, 2), "ts": time.time()}))
time.sleep(1)
# temp_outliers.py
import json, uuid
import paho.mqtt.client as mqtt
from detectors import RollingZ, IQRFence, EWMA
TOPIC = "connectmq/demo/t9x4q2/lab/room1/temperature"
detectors = {"z": RollingZ(), "iqr": IQRFence(), "ewma": EWMA()}
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC)
def on_message(client, userdata, msg):
x = json.loads(msg.payload)["tempC"]
flags = {name: d.update(x) for name, d in detectors.items()}
if any(flags.values()):
print(f"{x:6.2f} flagged by: {[n for n, f in flags.items() if f]}")
client.publish(TOPIC.rsplit("/", 1)[0] + "/outliers",
json.dumps({"tempC": x, "flags": flags}), qos=1)
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"tout-{uuid.uuid4().hex[:6]}")
c.on_connect = on_connect
c.on_message = on_message
c.connect("broker.connectmq.com", 1883, keepalive=60)
c.loop_forever()
Starten Sie beide Skripte und beobachten Sie, welche Detektoren übereinstimmen. Typischerweise erfassen alle drei die großen Spitzen, während der Z-Score gelegentlich eine Spitze übersieht, die kurz auf eine andere folgt – genau die oben beschriebene Schwäche.
Erkennung auf dem Gerät oder nach dem Broker
Sie können diese Detektoren an zwei Stellen betreiben, und die Wahl beeinflusst, was Sie veröffentlichen.
Läuft die Erkennung auf dem Sensorknoten, etwa einem ESP32, kann das Gerät jeder Nachricht ein Ausreißer-Flag anhängen oder nur dann veröffentlichen, wenn etwas Interessantes passiert. Das spart Bandbreite und funktioniert auch bei Netzausfall, doch die Schwellenwerte sind fest in der Firmware verankert und schwerer zu ändern.
Läuft die Erkennung nach dem Broker in einem Subscriber wie dem obigen, bleiben die Geräte einfach, und Sie können Methoden und Schwellenwerte jederzeit ändern, mehrere Detektoren parallel vergleichen und die Historie neu verarbeiten. Der Preis dafür ist, dass jeder Rohwert zum Broker übertragen werden muss.
Ein verbreiteter Kompromiss ist eine harte Grenze und eine Prüfung auf eingefrorene Werte auf dem Gerät, die defekte Sensoren sofort erkennt, kombiniert mit den statistischen Detektoren in einem Subscriber-Dienst, wo sie sich leicht abstimmen lassen. Mit einem ConnectMQ-Broker in der Mitte landen beide Ausgaben auf Topics, die dieselben Dashboards lesen können. In einer Fabrik ordnen Sie diese Topics am besten in einen Unified Namespace (UNS) nach ISA-95 ein, etwa acme/werk-muenchen/spritzguss/linie-4/maschine-2/temperature und daneben .../outliers, damit Rohwerte und Markierungen derselben Maschine zusammen auffindbar sind.
Praktische Checkliste
- Wenden Sie zuerst harte physikalische Grenzen an (ein Raumsensor mit 150 Grad hat eine Störung, keinen Ausreißer).
- Erkennen Sie eingefrorene Werte separat, indem Sie über einen Zeitraum auf identische Messwerte prüfen.
- Wählen Sie Fenstergrößen als Zeitspanne statt als Anzahl Messwerte, wenn die Veröffentlichungsintervalle schwanken.
- Legen Sie Mindestbreiten für Bänder entsprechend der Sensorauflösung fest.
- Markieren Sie Ausreißer, statt sie zu löschen, und bewahren Sie den Rohwert für spätere Analysen auf.
- Verlangen Sie zwei oder drei aufeinanderfolgende Markierungen, bevor Sie eine Person alarmieren.
- Überprüfen Sie die Schwellenwerte nach einer Woche mit echten Daten.
Wenn Signale zusammenwirken, zum Beispiel Strom und Durchfluss an einer Pumpe, stoßen diese Methoden pro Signal an ihre Grenzen, und ein multivariates Modell wie Isolation Forest für Sensordaten wird lohnenswert.
Welche Rolle der Broker spielt
Jeder der oben gezeigten Bausteine ist ein gewöhnlicher MQTT-Client: Der Sensor veröffentlicht, der Detektor abonniert und veröffentlicht erneut. Der MQTT-Broker leitet die Nachrichten lediglich weiter, sodass Sie Detektoren hinzufügen, entfernen oder ersetzen können, ohne die Geräte anzufassen. Für echte Einsätze bietet ein privater Cloud-MQTT-Broker wie ConnectMQ zusätzlich TLS, Zugangsdaten und einen Live-Topic-Explorer, in dem Sie Rohwerte und markierte Ausreißer nebeneinander verfolgen können. Weitere Möglichkeiten, Analytik mit Geräten zu kombinieren, zeigt der Leitfaden zu KI und IoT.
Mit ConnectMQ starten
Erstellen Sie unter app.connectmq.com einen Broker im kostenlosen Serverless-Tarif, ganz ohne Kreditkarte, verbinden Sie Ihre Sensoren damit und lassen Sie den Ausreißerdetektor auf Ihre eigenen Daten statt auf den Simulator los.
Häufig gestellte Fragen
Welche Methode zur Ausreißererkennung eignet sich am besten für Temperatursensoren?
Eine Kombination funktioniert am besten: physikalische Grenzen, IQR-Grenzen für Spitzen und EWMA mit kleinem Glättungsfaktor für langsame Drift. Da sich Temperaturen langsam ändern, ist EWMA besonders wirksam.
Welche Fenstergröße sollte ich für gleitende Statistiken verwenden?
Das Fenster sollte genug Zeit abdecken, um normales Rauschen zu erfassen, aber nicht so viel, dass legitime Änderungen ewig brauchen, bis sie übernommen werden. Bei einem Messwert pro Sekunde sind eine bis fünf Minuten ein vernünftiger Ausgangspunkt.
Sollten Ausreißer aus den Daten entfernt werden?
Markieren Sie sie, statt sie zu löschen. Bewahren Sie den Rohwert mit einem Ausreißer-Flag auf, damit Sie Entscheidungen nachvollziehen und Modelle später neu trainieren können.
Können diese Methoden auf einem ESP32 laufen?
Ja. EWMA benötigt nur zwei Gleitkommazahlen pro Signal, und ein gleitendes Fenster mit 30 bis 60 Werten passt problemlos in den Speicher eines ESP32, sodass Sie Ausreißer vor dem Veröffentlichen markieren können.



