Beobachtbarkeit und Drift
Protokolliere Kennzahlen, die einen unbemerkten Genauigkeitsverlust aufdecken, prüfe Konfidenzverteilungen auf Drift und richte nur bei Signalen mit konkretem Handlungsbedarf Alarme ein.
Ein Bildverarbeitungsdienst kann verfügbar und schnell sein – und trotzdem falsche Ergebnisse liefern. Eine Kamera wird angestoßen, die Beleuchtung ändert sich oder eine neue Produktlinie kommt hinzu, und das Modell liefert weiterhin Boxen, während alle Infrastruktur-Dashboards grün bleiben. In dieser Lektion verschob eine Verdunkelung des Clips, die die Abenddämmerung simulierte, die mittlere Konfidenz nur um 0,01 – in einem Diagramm kaum sichtbar. Ein zweiseitiger KS-Test erkannte die Veränderung dennoch mit p = 0,007. Du erstellst diese Prüfung und die zugehörige Protokollierung.
Praxisnah
Drei Signalebenen#
| Ebene | Frage | Signale |
|---|---|---|
| Infrastruktur | Ist der Dienst verfügbar und hält er Schritt? | Latenz, Durchsatz, verworfene Frames, CPU- und GPU-Auslastung |
| Modellverhalten | Verhält sich das Modell wie letzte Woche? | Erkennungen pro Frame, mittlere Konfidenz, Klassenverteilung, Boxgrößen |
| Modellgenauigkeit | Liefert das Modell weiterhin richtige Ergebnisse? | mAP auf einem zurückgehaltenen Datensatz (Leistungskennzahlen), stichprobenartige menschliche Prüfung, Fehlalarmrate |
Die meisten Teams decken die erste Ebene mit Standardwerkzeugen ab und belassen es dabei. Auf der zweiten und dritten Ebene versagen Bildverarbeitungssysteme unbemerkt; der Leitfaden zur Modellüberwachung behandelt den gesamten Wartungsprozess.

Wenn du über die Ultralytics Platform bereitstellst, zeigt dir die Bereitstellungsseite die Infrastrukturebene (Anfragen, Fehlerrate, P95-Latenz) und Anfrageprotokolle, die du wie oben nach Fehlern filtern kannst; die zugehörige Lektion Gesundheit und Genauigkeit überwachen behandelt diese Aspekte. Die Verhaltens- und Genauigkeitsebenen musst du selbst aufbauen – genau darum geht es im Rest dieser Lektion.
Kennzahlen pro Frame erfassen#
Jedes Results-Objekt enthält Zeitmessungen und Boxen. Schreibe pro Frame eine JSON-Zeile und leite sie an das von dir verwendete Kennzahlensystem weiter:
import json
import time
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
with open("metrics.jsonl", "w") as log:
for r in model.predict(video, stream=True, verbose=False):
conf = r.boxes.conf
record = {
"ts": time.time(),
"camera": "lobby-1",
"inference_ms": round(r.speed["inference"], 1),
"detections": len(r.boxes),
"mean_conf": round(conf.mean().item(), 3) if len(conf) else None,
"confs": [round(c, 3) for c in conf.tolist()], # Rohwerte für den Drifttest
}
log.write(json.dumps(record) + "\n")
print({k: v for k, v in record.items() if k != "confs"}){'ts': 1792003254.33, 'camera': 'lobby-1', 'inference_ms': 34.7, 'detections': 22, 'mean_conf': 0.543}Wofür die einzelnen Felder stehen:
- inference_ms: Alarm auslösen, wenn p95 dein Budget überschreitet.
- detections: Ein plötzlicher Rückgang auf nahezu null deutet meist auf eine blockierte oder defekte Kamera hin; ein Anstieg auf einen Szenenwechsel oder Fehlalarme.
- mean_conf: eine schnelle Trendlinie, aber allein zu grob, um Drift zu erkennen.
- confs: die vollständige Verteilung, die der folgende Drift-Test benötigt.
Konfidenzverteilungen auf Drift testen#
Datenverschiebungen zeigen sich in den Konfidenzen, lange bevor jemand neue Daten labelt. Erfasse beim Start Konfidenzen als Basiswert, erfasse ein aktuelles Zeitfenster und vergleiche die beiden Verteilungen mit einem Kolmogorov-Smirnov-Test für zwei Stichproben (pip install scipy). Dieses Skript simuliert die Abenddämmerung, indem es denselben Clip abdunkelt:
import cv2
import numpy as np
from scipy.stats import ks_2samp
from ultralytics import YOLO
from ultralytics.utils.downloads import safe_download
model = YOLO("yolo26n.pt")
video = str(safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"))
def confidences(darken=1.0):
"""Collect every detection confidence from the clip, optionally darkened to simulate dusk."""
cap, out = cv2.VideoCapture(video), []
while True:
ok, frame = cap.read()
if not ok:
break
out += model((frame * darken).astype(np.uint8), verbose=False)[0].boxes.conf.tolist()
return np.array(out)
baseline = confidences() # what "normal" looked like at launch
for name, recent in (("same scene", confidences()), ("dusk (35% brightness)", confidences(darken=0.35))):
stat, p = ks_2samp(baseline, recent)
print(f"{name:22s} n={len(recent):4d} mean_conf={recent.mean():.3f} KS={stat:.3f} p={p:.2g}")same scene n=1376 mean_conf=0.519 KS=0.000 p=1
dusk (35% brightness) n=1337 mean_conf=0.508 KS=0.064 p=0.0068Gehe die Ergebnisse Zeile für Zeile durch:
- Gleiche Szene: Identische Verteilungen ergeben eine KS-Statistik von 0 und p = 1 – die Kontrollbedingung.
- Abenddämmerung: 39 Erkennungen weniger und ein Mittelwert, der nur um 0.011 niedriger liegt – das würde in einem Diagramm niemand bemerken. Die KS-Statistik (der größte Abstand zwischen den beiden kumulativen Verteilungen) beträgt 0.064, und p = 0.0068 < 0.01 zeigt, dass die Verschiebung real ist.
Ein Driftalarm ist ein Anlass, Beispielbilder zu prüfen, kein Beweis für einen Fehler. Ist die Verschiebung real, erfasse und label die Bilder der betroffenen Kameras: Der Leitfaden zur Datenerfassung und Annotation beschreibt, was du erfassen solltest, und der Leitfaden zum Fine-Tuning erklärt das erneute Training. Die Boxgrößen (Breite × Höhe) liefern ein weiteres einfaches Drift-Signal: Neue Halterungen und Objektive machen sich dort zuerst bemerkbar.
Wöchentlich einen Holdout-Datensatz validieren#
Verhaltenssignale zeigen, dass sich etwas geändert hat; nur Labels zeigen, ob die Genauigkeit gesunken ist. Halte einen Holdout-Datensatz mit 200–500 gelabelten Produktionsbildern vor und validiere jede Modellversion sowie jede Woche:
yolo val model=yolo26n.pt data=coco8.yaml > weekly_metrics.txt # use your production model and holdout YAMLErfasse mAP50-95 und AP pro Klasse im zeitlichen Verlauf und löse einen Alarm aus, wenn mAP von einer Woche zur nächsten um mehr als 1 Punkt sinkt. Am häufigsten ist ein Modellwechsel die Ursache, der eigentlich nichts ändern sollte; an zweiter Stelle steht Drift. Eine Konfusionsmatrix desselben Holdout-Datensatzes macht Rückgänge bei einzelnen Klassen offensichtlich.

Der Holdout-Datensatz liefert die Antwort auf die Frage in diesem Diagramm. Trainiere erneut, wenn die Qualität laut Holdout zu sinken beginnt oder wenn genügend neue gelabelte Daten aus den von Drift betroffenen Bedingungen vorliegen – nicht nach einem festen Kalender.
Alarme bei Aktionen auslösen, nicht bei Zahlen#
| Signal | Alarm auslösen, wenn | Maßnahme |
|---|---|---|
p95 inference_ms | Mehr als 3 Minuten über dem Budget | Auf weitere Instanzen skalieren oder zum optimierten Export wechseln |
| Erkennungen pro Bild | 5 Minuten lang um mindestens 50 % gesunken | Kamerabild prüfen, dann die Modellversion |
| Verworfene Bilder | 10 Minuten lang über 5 % | Kapazität hinzufügen oder FPS pro Stream senken |
| KS-Test der Konfidenzen | p < 0.01 in täglichen Zeitfenstern | Bilder prüfen, dann den Holdout ausführen |
| mAP50-95 des Holdout-Datensatzes | Von einer Woche zur nächsten um mindestens 1 Punkt gesunken | Erfassen, labeln und Fine-Tuning durchführen |
Stolperfallen#
Symptom: Der KS-Test schlägt jeden Abend und jeden Montag an. Lösung: Tages- und Wochenzyklen sind tatsächliche Verteilungsänderungen. Vergleiche Gleiches mit Gleichem (dieselben Kameras, dieselben Uhrzeiten, derselbe Wochentag) und verwende Zeitfenster mit Tausenden von Detektionen.
Symptom: Die Holdout-mAP sieht gut aus, während Nutzer Fehlklassifikationen melden. Lösung: Der Holdout muss der aktuellen Produktion entsprechen. Aktualisiere ihn jedes Quartal mit neu beschrifteten Frames von den wichtigen Kameras.
Für jeden Alarm muss eine nächste Maßnahme schriftlich festgehalten sein. Ein Signal, das du lediglich beobachten möchtest, gehört auf ein Dashboard und nicht in den Pager.
Lösung anzeigen
Behalte das erste fehlerfreie Zeitfenster der Ausgabe von monitor.py als baseline.jsonl bei und vergleiche jedes neue Zeitfenster damit:
import json
import numpy as np
from scipy.stats import ks_2samp
def load(path):
"""Flatten the per-frame 'confs' lists written by monitor.py."""
with open(path) as f:
return np.array([c for line in f for c in json.loads(line)["confs"]])
baseline, recent = load("baseline.jsonl"), load("metrics.jsonl")
stat, p = ks_2samp(baseline, recent)
print(f"KS={stat:.3f} p={p:.2g} -> {'review frames' if p < 0.01 else 'ok'}")KS=0.000 p=1 -> okDas ist das erwartete Ergebnis, wenn beide Dateien aus demselben Filmmaterial stammen. Führe den Test täglich über einen Scheduler aus, sende die Statistik und den p-Wert an dasselbe Metriksystem wie die Protokolle pro Bild und leite review frames an eine Person weiter, nicht an einen Pager.