Spaces:
Running
Phase A — „Ist das Müll?"-Szenen-Gate (Open-Vocabulary)
Stand: 2026-07-25. Umsetzung von
VISION_ROADMAP.md §7.5.2. Diese Datei ist die Spezifikation + der Contract-Vorschau für Dev/Web/Admin — sie legt fest, was Phase A ist, wie die API sich additiv erweitert und wie wir es messen, bevor irgendetwas Live-Auszahlungen berührt. Kein Live-Contract-Change ohne die Messung aus §5 und Founder-Sign-off.
1. Warum (in einem Satz)
Das Modell hat nie gelernt, wie Nicht-Müll aussieht — deshalb „sieht" es zu
86 % „paper" auf einer grauen Fläche (dokumentiert in VISION_ROADMAP.md §7.1).
Jeder solche False Positive zahlt unverdiente TrashCoin aus und belastet die
Review-Queue. Phase A gibt dem Modell den fehlenden Weltkontext: „das ist eine
Pflanze / ein Stuhl / eine Katze — kein Müll" — und liefert nebenbei das
verkaufbare Szenen-Inventar „3 Flaschen + 1 Katze".
Zwei Nutzen aus einem Modell:
- FP-Gate (Geld-Leck stopfen) — der wichtigste kurzfristige Effekt.
- Szenen-Inventar (Nicht-Müll benennen + zählen) — der sichtbare „ChatGPT-kann-das-auch"-Konter und die Basis für den Verkauf an Industrien.
2. Architektur — Zwei-Schichten-Strategie (aus Roadmap §7.2, konkretisiert)
Bild
│
┌─────▼───────────────────────────────────────────────┐
│ Fundament (die Augen): Open-Vocabulary-Detektor │ ← von der Stange
│ benennt WAS im Bild ist: bottle, cat, plant, chair, │ (YOLOE / YOLO-World)
│ can, cup, bag, person, tree, road … │
└─────┬───────────────────────────────────────────────┘
│ Objekt-Inventar mit Boxen + Labels
┌─────▼───────────────────────────────────────────────┐
│ Alami-Fach-Kopf (das Gehirn): unsere Domänen-Logik │ ← nur Alami hat das
│ • ist das Objekt Müll? (Waste-Vokabular-Mapping) │ (aus Flywheel +
│ • welches Material-Bucket? welche Entsorgung? │ Fach-Wissen)
│ • Nicht-Müll (Pflanze/Tier/Möbel) → explizit raus │
└─────┬───────────────────────────────────────────────┘
│
┌─────▼───────────────────────────────────────────────┐
│ Fusion mit dem bestehenden YOLOv8-Seg-Detektor #13 │
│ (Material-Feinklassifikation bleibt unser Modell) │
└──────────────────────────────────────────────────────┘
Wichtig: Der bestehende Material-Detektor (#13, live) wird nicht ersetzt. Das Open-Vocab-Fundament liegt daneben und liefert (a) das FP-Veto und (b) das Nicht-Müll-Inventar. Die Material-Feinklassen (plastic/glass/…) kommen weiter aus unserem trainierten Modell — dort ist unser Datengraben.
3. Backbone-Entscheidung (ML-Engineering-Call, begründet)
| Kandidat | Open-Vocab | Lokalisiert | CPU-tauglich (HF Space) | Stack-Fit | Verdikt |
|---|---|---|---|---|---|
| YOLOE / YOLO-World v2 | ✅ | ✅ (Boxen) | ✅ (S/M, ONNX) | ✅ ultralytics 8.4 + ONNX — schon im Serving | gewählt |
| CLIP (Zero-Shot, ganzes Bild) | ✅ | ❌ (kein Box) | ✅ (billig) | teils | nur als Ultra-Cheap-Preview-Gate |
| Grounding-DINO / OWLv2 | ✅✅ (stärker) | ✅ | ❌ (GPU/$$-Space nötig) | neuer Stack | zu schwer für jetzt |
Gewählt: YOLOE/YOLO-World v2 → ONNX. Gründe:
- Kein neuer Stack: Serving läuft bereits auf
ultralytics+onnxruntime(sieherequirements.txt,ml/serving/model_loader.py). Das Fundament nutzt denselben Export- und Lade-Pfad wie unser Material-Modell. - CPU-tauglich: Die S/M-Größe läuft auf dem kostenlosen HF-Space (heutige Betriebsbasis), ohne auf einen GPU-Space ($$) zu wechseln.
- Liefert beides: Boxen + offene Labels → FP-Gate und Inventar aus einem Modell.
- Training/Export schwer, Inferenz leicht — genau wie beim Material-Modell:
schwere Schritte laufen auf Kaggle-GPU (bestehende
ml/kaggle/-Straße), das Serving lädt nur das exportierte ONNX.
4. API-Erweiterung — strikt additiv (Hard Contract §5.1)
Der Mobile-Client hängt an der exakten Shape von /predict und /v1/analyze
(schemas.py, Kommentar Zeile 6–9). Nichts Bestehendes ändert sich. Neu
kommt ausschließlich additiv hinzu:
4.1 AnalyzeResponse.summary — zwei neue Felder (advisory)
"summary": {
"item_count": 3,
"trash_detected": true, // UNVERÄNDERT in Semantik (siehe §6!)
"materials": { "plastic": 3 },
"total_weight_estimate_g": 180.0,
"weight_source": "material_prior_v0",
// --- NEU, additiv, advisory ---
"waste_likelihood": 0.94, // 0..1: Wahrscheinlichkeit, dass die Szene echt Müll enthält
"scene_gate_version": "yoloe-s-20260801"
}
4.2 Neuer optionaler scene-Block auf AnalyzeResponse (default weglassbar)
"scene": {
"non_waste_objects": [
{ "label": "cat", "label_de": "Katze", "confidence": 0.88, "bbox_norm": [x,y,w,h] },
{ "label": "plant", "label_de": "Pflanze", "confidence": 0.79, "bbox_norm": [x,y,w,h] }
],
"inventory_text": "3 Plastikflaschen · 1 Katze · 1 Pflanze", // fertige Anzeige
"counts": { "waste": 3, "non_waste": 2 }
}
4.3 domain-Achse (aus Roadmap §2, additiv)
- Heute:
domain="trash". Der Scene-Gate läuft innerhalb vontrashmit — bestehende Clients bekommen das FP-Veto automatisch, ohne Code-Change. - Zusätzlich neu:
domain="scene"= reine Objekt-/Szenen-Inventarisierung für externe API-Kunden (Industrie), ohne Material-Auszahlungs-Logik.
Für Dev/Web heißt das: keine Breaking Changes. Ihr könnt die neuen Felder ignorieren, bis ihr sie nutzt. Die Feedback-v2-Payload bleibt unverändert.
5. Mess-Protokoll — erst messen, dann verdrahten (wie bei Lauf B)
Kein Live-Deploy, bevor wir auf einem Eval-Set gemessen haben. Zwei Zahlen entscheiden:
- FP-Kill-Rate — Anteil der bekannten False Positives (Müll auf leerem Boden / Natur / Innenraum), die das Gate korrekt unterdrückt. Hoch = gut.
- TP-Retention — Anteil echter Müll-Detektionen, die das Gate fälschlich unterdrückt. Muss ~0 bleiben — ein False Negative heißt: ehrlicher Nutzer bekommt 0 TC (Hard Contract §2). Das ist die Schmerzgrenze.
Eval-Set (zusammenzustellen):
- Positive: echte Müll-Bilder aus unserem bestehenden Val-Set (#13).
- Negative: leerer Boden/Natur (Auto-Negatives Runde 3,
harvest_negatives.py), plus Innenraum/Tier/Möbel/Pflanze aus lizenzsauberen offenen Sets. - Inventar-Stichprobe: ~200 handgelabelte Bilder für die Zähl-Genauigkeit („3 Flaschen + 1 Katze" — stimmt die Zählung?).
Metriken kommen als summary_scene_gate.json neben summary_eval.json
(gleiche Konvention wie evaluate.py) → fließen als Snapshot in die Chronik.
Gate-Kriterium für Live-Schaltung (Vorschlag): FP-Kill ≥ 60 % bei TP-Retention ≥ 99 %. Wird die Schmerzgrenze verletzt, bleibt das Gate advisory.
6. Auszahlungs-Sicherheit — der sensible Punkt (Founder + Dev)
trash_detected speist in der App ai_status → beeinflusst TC. Deshalb:
- Stufe advisory (Start): Das Gate liefert nur
waste_likelihood+scene-Inventar.trash_detectedbehält exakt die heutige Semantik (len(objects) > 0). Die App zahlt wie bisher. Null Risiko für ehrliche Nutzer. - Stufe aktiv (später, nur mit Messung §5 + Founder- und Dev-Sign-off):
Das Gate darf
trash_detectedbei sehr niedrigerwaste_likelihoodüberstimmen (FP-Veto). Erst wenn TP-Retention nachweislich ~100 % ist.
Hard Contracts bleiben unangetastet: KI setzt nie TC-Beträge (§5.1), Submit-Pfad
bleibt non-throwing (§5.2), ai_status-Vokabular fix (§5.3).
7. Serving-Integration (Latenz-Budget)
- Snapped Photo (
log=true): voller Scene-Gate läuft mit. Latenz-Aufschlag akzeptabel (einmal pro Einreichung). - Live-Preview (
log=false, ~1/s): hier zählt jede ms. Optionen: (a) Gate im Preview auslassen und nur auf dem Schnappschuss laufen, oder (b) nur der ultra-billige Ganzbild-Confidence-Check im Preview. Default: (a) — Preview bleibt so schnell wie heute. - Modell-Load wie
model_loader.py: ONNX-Session einmal beim Boot, Bundle-Dir über Env — das Scene-Gate-ONNX kommt als zweites Bundle nebendeploy/latest.
8. Rollout-Stufen
| Stufe | Inhalt | Berührt Live-Auszahlung? |
|---|---|---|
| A0 | Prototyp + Eval-Harness offline, Backbone auf Eval-Set messen | nein |
| A1 | Scene-Gate-ONNX exportiert (Kaggle), ins Serving als 2. Bundle | nein (advisory) |
| A2 | scene-Block + waste_likelihood additiv in /v1/analyze live |
nein (advisory) |
| A3 | FP-Veto auf trash_detected — nur nach §5-Messung + Sign-off |
ja |
8b. A0 — Erste Messung (2026-07-25, Floor-Zahl)
Erster Lauf des Mess-Harness auf 127 echten Bildern (lizenzsauber via
harvest_negatives.py / openverse CC0): 47 Litter-Positive, 80 Negative
(32 leere Szenen + 48 Nicht-Müll-Objekte: Katze/Hund/Pflanze/Stuhl/Auto/Bank).
Backbone: YOLO-World v2 S, zero-shot, unkalibriert (bewusst die einfachste
Stufe).
| τ (waste_likelihood) | TP-Retention (echten Müll behalten) | FP-Kill (Nicht-Müll ablehnen) |
|---|---|---|
| 0.10 | 0,79 | 0,59 |
| 0.20 | 0,49 | 0,74 |
| 0.40 | 0,28 | 0,94 |
Gate-Kriterium (FP-Kill ≥ 0,60 bei TP-Retention ≥ 0,99): NICHT erfüllt. Der einzige Punkt mit ~100 % TP-Retention ist τ=0, der 0 FPs killt.
Ehrliche Einordnung — das ist ein FLOOR, nicht die echte Zahl:
- Daten-Mismatch: openverse-Web-Bilder („litter on street" liefert auch Luftaufnahmen/Kunstfotos) sind viel härter als unser kuratiertes, nah aufgenommenes Val-Set (#13) — das reale Einsatz-Regime. Die maßgebliche Messung läuft auf Kaggle mit unserem Val-Set.
- Roheste mögliche Definition:
waste_likelihood = max Müll-Confidence, ein globaler Threshold, S-Modell, keine Kalibrierung, kein Prompt-Tuning. - Signal ist da: bei τ=0,10 behalten wir 79 % des echten Mülls und killen 59 % der False Positives. Nur noch nicht an der Schmerzgrenze.
Ungenutzte Hebel (Reihenfolge für A1):
- Per-Box-Veto statt Ganzbild-Score — der eigentliche Design-Punkt (§2): das Gate vetoiert einzelne Boxen unseres Detektors („diese Box ist eine Katze/ leerer Boden"), nicht das ganze Bild. Inhärent präziser als der hier gemessene Ganzbild-Score.
- Stärkeres Backbone (YOLO-World-L/X oder YOLOE-seg) — deutlich besserer Zero-Shot.
- Bessere
waste_likelihood— Objektzahl + Fläche + Penalty bei dominantem Nicht-Müll-Objekt, statt nur max-Confidence. - Per-Prompt/Material-Thresholds + Prompt-Engineering.
Schlussfolgerung: Genau deshalb advisory-first + messen-vor-schalten. Die
naive Version fasst Auszahlungen nicht an — bestätigt. Das Messgerüst steht, die
Hebel sind klar. Ergebnis-JSON-Konvention: summary_scene_gate.json.
8c. A1 — Per-Box-Veto (2026-07-25, lokales Experiment)
Der eigentliche Design-Hebel (§2) gebaut und lokal gemessen: unser Detektor #13
(ONNX, lokal) liefert Material-Boxen, das Open-Vocab-Modell liefert Kontext, der
Per-Box-Veto entfernt eine Box nur, wenn ein diskretes Nicht-Müll-Objekt sie
sicher überlagert (TP-sichere Grundregel, ml/scene_gate/veto.py). Gleiches
127-Bild-Set wie A0.
| Verfahren | TP-Retention (gefeuert) | FP-Kill (gefeuert) |
|---|---|---|
| Ganzbild-Score (A0-Floor) | 0,28 @ FP-Kill 0,80 | — |
| Per-Box-Veto v1 (Flächen vetoieren) | 0,80 | 0,81 |
| Per-Box-Veto v2 (Flächen vetoieren NICHT) | 0,93 | 0,68 |
Zwei bestätigte Erkenntnisse:
- Der Per-Box-Veto schlägt den Ganzbild-Score deutlich (Pareto): bei ~80 % FP-Kill steigt die TP-Retention von 28 % auf 80 %.
- Hintergrund-Flächen dürfen nicht vetoieren. „grass/road/floor/sky" aus der Veto-Liste zu nehmen (Müll liegt AUF ihnen) hob die TP-Retention 80 %→93 %. Preis: einige Leerboden-FPs überleben — die brauchen ein eigenes Signal (Confidence-Downgrade bei „nur Fläche, kein Objekt, keine Müll-Bestätigung"), keinen Flächen-Veto (der würde echten Müll auf Flächen bestrafen).
Randbefund zum FP-Problem: Unser Detektor #13 feuerte auf 39 % zufälliger Nicht-Müll-Bilder einen False Positive — der Per-Box-Veto (v2) entfernt zwei Drittel davon, ohne echten Müll anzufassen.
Grenzen dieses Laufs (ehrlich): openverse-Web-Bilder, S-Backbone, Detektor-Recall auf den harten Positiven nur 32 % (Detektor-, nicht Veto-Limit). Die 99 %-Schmerzgrenze ist noch nicht erreicht. Deshalb der nächste Schritt auf UNSEREM Val-Set (Kaggle) mit stärkerem Backbone + Inspektion der Falsch-Vetos.
8d. Maßgebliche Messung auf Kaggle — verdrahtet (ready to dispatch)
Die autoritative Messung läuft dort, wo die Daten liegen (Founder-Entscheid 2026-07-25):
- Kernel:
ml/kaggle/scene_gate_kernel.py— baut einen echten Val-Split (TACO-Positive + openverse-Hard-Negatives), lädt den deployten Detektor #13 (deploy/latest) + ein stärkeres Open-Vocab-Backbone und misst überevaluate_valset()beide Betriebsarten (whole_image vs per_box_veto) mit τ-Sweep. Ground Truth = Label-Datei nicht-leer. - Workflow:
.github/workflows/scene-gate-eval.yml—workflow_dispatch, Backbone wählbar (S/M/L/X, Default L), pollt bis fertig, lädtsummary_scene_gate.jsonals Artefakt. Gleiche Kaggle-Secrets wie der Trainings-Workflow. - Kern-Logik lokal getestet (
test_scene_gate.py, Mock-Detektor/-Weltmodell): Ground-Truth-aus-Labels + Sweep + Veto-Pfad. Der Kernel ist dünner Glue.
Auslösen: GitHub → Actions → „Scene-Gate Eval on Kaggle" → Run. Ergebnis-JSON fließt als Snapshot in die Chronik; erst danach A2-Entscheidung (additive API).
8e. Maßgebliche Messung — ERGEBNIS (2026-07-25, Kaggle GPU, Lauf #3)
Backbone yolov8l-worldv2, deployter Detektor #13 (deploy/latest),
Val-Set 620 Bilder (339 mit Müll, 281 Hard-Negatives). Kriterium:
FP-Kill ≥ 0,60 und TP-Retention ≥ 0,99. Artefakt: scene-gate-eval-3
(Actions-Run 30178326681).
| τ | whole_image TP / FP-Kill | per_box_veto TP / FP-Kill |
|---|---|---|
| 0,02 | 0,761 / 0,569 | 0,569 / 0,801 |
| 0,20 | 0,596 / 0,747 | 0,569 / 0,801 |
| 0,30 | 0,466 / 0,819 | 0,507 / 0,826 |
| 0,40 | 0,348 / 0,883 | 0,378 / 0,883 |
| 0,60 | 0,201 / 0,950 | 0,168 / 0,950 |
| 0,80 | 0,027 / 0,989 | 0,083 / 0,979 |
Verdikt: gate_passed = false in BEIDEN Betriebsarten. Der empfohlene
Betriebspunkt fällt auf τ=0 zurück (= gar kein Gating) — es existiert kein
Schwellwert, der gleichzeitig ≥99 % echten Müll behält und ≥60 % Nicht-Müll
killt.
Drei belastbare Erkenntnisse:
- Recall-Klippe am ersten Schwellwert. Von τ=0 auf τ=0,02 bricht die TP-Retention sofort ein: whole_image 100 %→76 %, per_box 100 %→57 %. D.h. das Open-Vocab-Modell gibt ~24 % (whole_image) bzw. ~43 % (per_box) echter Müllbilder ein waste_likelihood von ~0 — es „sieht" den Müll dort gar nicht. Das ist ein Coverage-/Recall-Problem, kein Schwellwert-Tuning-Problem.
- Umkehrung gegenüber dem lokalen 127-Bild-Vorexperiment (§8c). Dort schlug per_box_veto das Ganzbild klar; auf dem autoritativen Set ist es umgekehrt — whole_image behält mehr echten Müll (0,76 vs 0,57 bei τ=0,02). Ursache: der Per-Box-Pfad erbt den Recall-Deckel des Detektors #13 (findet der Detektor die Box nicht, hat der Veto nichts zu bewerten → Likelihood 0). Der Ganzbild-Pfad umgeht den Detektor und ist daher recall-robuster.
- Als reiner FP-Killer (Recall-Verlust in Kauf genommen) liefert per_box bei τ≈0,02 einen realen Punkt: 80 % der Hintergrund-FPs weg, 57 % echter Müll bleibt. Für ein hartes Auszahlungs-Gate zu verlustig, als weiches Confidence-Downgrade-Signal aber verwertbar.
Konsequenz (ML-Call):
- Kein hartes Gate ausliefern. §6 bleibt: strikt advisory, keine Auszahlungs-Kopplung.
- Nächster billiger Diagnoseschritt: waste_likelihood-Histogramm über die Positiven, die 0-Signal-Fälle inspizieren. Zwei Hebel — (a) Open-Vocab-Prompts verbreitern (Vokabular deckt viele Müllarten nicht), (b) Detektor-Recall heben.
- Ein einzelner X-Backbone-Lauf zur Absicherung, dass es keine reine Kapazitätsgrenze ist (Erwartung: hilft wenig gegen eine Coverage-Klippe).
- Falls 99 %-Recall Produktziel bleibt: das Gate sollte eher ein gelernter Kopf / die Detektor-Confidence sein als Zero-Shot-Open-Vocab.
8f. 0-Signal-Diagnose — verdrahtet (2026-07-26)
Der Diagnoseschritt aus §8e ist jetzt im selben Kaggle-Durchlauf gebaut
(evaluate_valset(collect_diagnosis=True) + bucket_misses(), lokal getestet).
Jeder Lauf schreibt zusätzlich diagnosis_scene_gate.json + Kontaktbögen der
übersehenen Bilder. Die entscheidende Frage, die er beantwortet — warum
bekommt echter Müll likelihood ~0:
model_saw_nothing(n_scene_dets = 0): Das Open-Vocab-Modell fand gar nichts → reine Detektions-/Recall-Lücke (Vokabular oder Backbone zu schwach).saw_but_not_waste(Objekte erkannt, aber keins zählt als Müll) → Vokabular-/Label-Mapping-Lücke (das Ding ist da, aber nicht in unserer Müll-Sprache: Kippe, Textil, Kleinteile).- Überlagernd:
too_dark(mittlere Helligkeit),tiny_image. - Für den Per-Box-Pfad zusätzlich
no_detector_box(Detektor #13 fand nichts → belegt den in §8e vermuteten Recall-Deckel direkt an den Zahlen).
Das Verhältnis model_saw_nothing : saw_but_not_waste entscheidet den Hebel:
überwiegt Ersteres → stärkeres/anderes Backbone; überwiegt Letzteres → Prompts/
Vokabular erweitern. Feldsignal dazu: added_items aus dem Feedback-v2 (§ api.md)
liefert laufend reale Miss-Beispiele aus der Produktverteilung.
8g. 0-Signal-Diagnose — ERGEBNIS (2026-07-26, L, T4)
339 Positive, Backbone yolov8l-worldv2, Detektor #13. (Ausgeführt direkt via
Kaggle-Kernel v4, nicht Actions — Kontingent erschöpft; Wichtig fürs Repro:
der Kernel MUSS mit --accelerator NvidiaTeslaT4 laufen. Ein Lauf ohne das Flag
landet auf einer P100/sm_60, die das aktuelle PyTorch nicht unterstützt → alle
Inferenzen crashen → degeneriertes „100 % übersehen". Das Workflow-YAML setzt das
Flag; beim Direkt-Push nicht vergessen.)
| Pfad | übersehen (≤0,02) | Aufschlüsselung |
|---|---|---|
| whole_image (A0) | 81/339 (24 %) | saw_but_not_waste 76 · model_saw_nothing 5 · too_dark 0 · tiny_image 0 |
| per_box_veto (A1) | 146/339 (43 %) | no_detector_box 129 · boxes_present_but_zero 17 |
Klartext — die Recall-Klippe ist KEINE Blindheit und KEIN Dunkelheits-/Größenproblem:
- Ganzbild: 94 % der Misses (76/81) sind
saw_but_not_waste. Das Open-Vocab- Modell sieht die Szene, aber das Müllstück wird nicht als Müll gewertet. Der Blick auf die Kontaktbögen zeigt warum: kleiner/verstreuter Müll in unruhigen Naturszenen (Sand, Schlick, Gras, flaches Wasser, Laub/Muscheln) — niedrige Salienz, nicht fehlende Fähigkeit. Nur 5/339 waren echte Blindheit (Mikro-Müll auf Textur).too_dark/tiny_image= 0 → kein Faktor. → Hebel: Vokabular/Prompts + Salienz, NICHT größeres Backbone. Konkret: Klein-Müll-Prompts ergänzen (litter, wrapper, cigarette butt, bottle cap, plastic scrap), Kachel-/Zoom-Inferenz für kleine Objekte, conf-Schwelle senken. Ein X-Backbone würde die Salienz-Klippe kaum bewegen (nur 5 echte Blindfälle). - Per-Box: 88 % der Misses (129/146) sind
no_detector_box. Der Engpass ist der Recall-Deckel des Detektors #13 selbst (kein Box → nichts zu vetoen). → Hebel: mehr/härtere Detektor-Trainingsdaten — genau das, was der Flywheeladded_items(Feedback-v2) jetzt laufend einsammeln.
Fazit: Das „Gate fällt bei 99 % Recall durch" ist erklärt — realer Müll ist
oft klein/niedrig-salient. §6 bleibt bis dahin advisory. Artefakte:
diagnosis_scene_gate.json, misses_saw_not_waste.jpg, misses_saw_nothing.jpg
(Kaggle-Kernel alami-vision-scene-gate v4).
8h. Hebel getestet: Kachel-Inferenz + Klein-Müll-Prompts — KEIN Pareto-Gewinn (2026-07-26)
Hypothese aus §8g (kleiner/niedrig-salienter Müll → Auflösung ist der Hebel)
sauber getestet: 2×2-Kachel-Inferenz (SAHI, ml/scene_gate/tiling.py) + 4
spezifische Klein-Müll-Prompts, Lauf v5 (L, T4) vs Baseline v4.
Roh sah es gut aus (Ganzbild-Miss 24 %→11 %, model_saw_nothing 5→0). Aber
bei gleichem FP-Kill verschwindet der Gewinn — es war nur ein verschobener
Betriebspunkt auf derselben Kurve:
| FP-Kill ≥ | Baseline TP | Kacheln TP | Δ |
|---|---|---|---|
| 0,60 | 0,714 | 0,752 | +0,04 |
| 0,80 | 0,510 | 0,510 | ±0,00 |
| 0,90 | 0,339 | 0,357 | +0,02 |
| 0,95 | 0,227 | 0,159 | −0,07 |
(per_box_veto: flach ±0,003 — erwartet, der Pfad ist detektor-limitiert.)
Grund: Kacheln heben die Müll-Confidence auf Positiven UND Negativen (ein Sand-/Gras-Ausschnitt triggert öfter ein spurioses „Plastikfetzen"). Die Trennschärfe bleibt gleich. Der Limiter ist die Score-Aggregation („max Müll-Confidence im Bild"), nicht Auflösung oder Vokabular.
Konsequenz: Kachel-Code bleibt dormant (default tile_grid=0, getestet,
wiederverwendbar), wird nicht ausgeliefert. Nächster Hebel adressiert die
Trennschärfe direkt: ein gelernter Szenen-Score (kleine Klassifikation über
Features des Open-Vocab-/Detektor-Outputs statt roher Max-Confidence),
CV-validiert auf dem Val-Set — bewegt er die Pareto-Front, lohnt Serving; wenn
nicht, ist Zero-Shot nachweislich gedeckelt und der Weg führt über Feld-Daten
(added_items) + einen echten trainierten Kopf.
8i. Gelernter Szenen-Score — DURCHBRUCH (2026-07-26)
Der Hebel aus §8h getestet: statt waste_likelihood = max(Müll-Confidence) ein
gelerntes lineares Logit über 11 Output-Features (ml/scene_gate/features.py:
max/sum/mean/count/area der Müll-Confidences, Nicht-Müll-Max/-Count, Müll-Anteil,
Detektor-Box-Statistik). Trainer ml/scripts/train_scene_score.py (numpy,
kein sklearn), fair out-of-fold (5-Fold-CV) auf dem Val-Set (620 Bilder).
Max TP-Retention bei gleichem FP-Kill (OOF):
| FP-Kill ≥ | Baseline (max-conf) | Gelernt | Δ |
|---|---|---|---|
| 0,60 | 0,743 | 0,971 | +0,23 |
| 0,80 | 0,501 | 0,879 | +0,38 |
| 0,90 | 0,327 | 0,699 | +0,37 |
| 0,95 | 0,206 | 0,581 | +0,38 |
AUC 0,73 → 0,91. Anders als Kacheln bewegt der gelernte Score die Pareto-Front massiv — bei FP-Kill 0,80 steigt die TP-Retention von 50 % auf 88 %. Damit ist bestätigt: der Limiter war die Score-Aggregation, nicht das Modell/die Auflösung.
Feature-Wichtigkeit (interpretierbar): stärkstes Signal ist ov_nonwaste_max
negativ (−1,50) — ein sicher erkanntes Nicht-Müll-Objekt spricht gegen eine
Auszahlung (die Veto-Intuition, gelernt), dazu ov_waste_frac/det_max_conf
positiv. Das Gate sagt also: „viel Müll-Anteil + Detektor-Confidence UND kein
dominantes Nicht-Müll-Objekt".
Status: Modell-Artefakt ml/scene_gate/scene_score.json + reiner Scorer
features.learned_score() (kein sklearn/torch → serving-fähig). Ehrliche
Grenze: trainiert auf 620 TACO-Bildern = Obergrenze-Indikation, nicht die
App-Verteilung; 99 %-Recall-Hardgate ist noch nicht erreicht (88 % @ FP-Kill 0,80).
Aber als advisory FP-Killer jetzt stark genug, um Wert zu liefern — und über
added_items-Feld-Daten neu trainierbar.
Nächster Schritt (Task 3): den gelernten Score als waste_likelihood in
build_scene verdrahten (Fallback max-conf, wenn Artefakt fehlt), dann Serving-
Integration (Open-Vocab im Space) — jetzt datenbelegt lohnend.
8j. Serving-Machbarkeit — GEMESSEN, nicht vermutet (2026-07-26)
Die offene Frage war: der Space fährt onnxruntime auf cpu-basic, kein
torch. Läuft der Gate dort überhaupt? Antwort: ja.
1. ONNX-Export funktioniert. YOLO-Worldv2 backt bei set_classes() die
Text-Embeddings ein. Weil unser Vokabular fest ist, braucht die Laufzeit
danach kein CLIP und kein torch — der Output ist ein normales Detektions-
Modell (1, 72, 8400) = 4 Box-Koordinaten + 68 Klassen (= unsere Prompts).
Script: ml/scripts/export_scene_gate_onnx.py → Bundle im gleichen Format wie
deploy/latest, damit ModelBundle es ohne neuen Inferenz-Code lädt (§7).
2. Latenz auf 2 Threads (cpu-basic-Näherung), gemessen:
| Modell | Latenz (median) | |
|---|---|---|
| Detektor #13 (heute live) | 13 MB | 106 ms |
| Szenen-Gate S (neu) | 50 MB | 224 ms |
| Summe pro Schnappschuss | ~330 ms |
Verdikt: tragbar — aber nur auf dem Schnappschuss-Pfad (log=true), wie §7
ohnehin vorsieht. Der Live-Preview bleibt unangetastet. L ist für CPU-Serving
zu schwer → Serving-Kandidat ist S; die §8i-Messung lief auf L, der
Score muss für S neu trainiert/geprüft werden (offen, siehe unten).
3. End-to-End verifiziert (Detektor #13 + Gate + gelernter Score auf echten
Fotos) — und der §8i-Gewinn wird dabei sichtbar: Auf einem der saw_but_not_waste-
Miss-Bilder findet das Open-Vocab-Modell null Müll-Objekte (counts.waste=0),
die alte max-conf-Baseline hätte also 0.0 gesagt. Der gelernte Score zieht aus
Detektor-Boxen + Kontext 0,61; eine leere Fläche bekommt 0,22.
Nebenbefund + Fix: ModelBundle.predict() stürzte bei reinen Detektions-
Bundles ab (mask_coef ist dann None, wurde aber indiziert). Fiel nie auf,
weil Detektor #13 einen Seg-Kopf hat. Behoben — betrifft jedes maskenlose Bundle.
Offen vor dem Ausrollen:
Score für S validieren→ erledigt, siehe §8k.scene-Block-Kontrakt: Rückmeldung von PO/Monorepo (docs/SCENE_BLOCK_CONTRACT.md).- Bundle-Auslieferung in den Space (Größe/Boot-Zeit) + Env-Schalter.
8k. Hält der Score mit dem servebaren Backbone? — JA (2026-07-26)
§8i wurde auf L gemessen, Serving läuft aber auf S (CPU-Budget, §8j). Deshalb S-Feature-Dump + Retrain, gleiche Methode. Fairer Vergleich: die 281 Negative des L-Laufs sind eine Teilmenge der 480 des S-Laufs, Positive identisch → beide auf exakt denselben 620 Bildern ausgewertet.
| Konfiguration | AUC | TP @ FP-Kill 0,80 |
|---|---|---|
| L + max-conf (Ausgangslage) | 0,73 | 0,50 |
| L + gelernt (§8i) | 0,91 | 0,88 |
| S + max-conf | 0,66 | 0,39 |
| S + gelernt (ausgeliefert) | 0,85 | 0,77 |
Verdikt: S trägt. Der entscheidende Vergleich ist nicht S-gelernt vs L-gelernt (−0,11), sondern S + gelernt (0,77) vs L + max-conf (0,50): das billige, servebare Backbone mit gutem Score schlägt das teure mit naivem Score deutlich. Serving mit S ist damit verantwortbar — Gate-Kosten 224 ms statt eines nicht-servebaren L.
Wichtig (Fallstrick): Die Features sind backbone-spezifisch — ein auf
L trainierter Score passt nicht auf S-Verteilungen. Das ausgelieferte Artefakt
ml/scene_gate/scene_score.json ist deshalb auf S-Features trainiert und
trägt das Feld backbone; train_scene_score.py --backbone schreibt es mit.
Wer das Serving-Backbone wechselt, muss den Score neu trainieren.
Ehrliche Einordnung: 0,77 TP-Retention bei 80 % FP-Kill heißt: als
advisory FP-Killer stark (4 von 5 Fehl-Auszahlungen abfangbar, 77 % echter
Müll unbehelligt, Rest in Human-Review). Für ein hartes Auto-Credit-Gate
(≥99 % Recall) reicht es weiterhin nicht — dafür braucht es Feld-Daten
(added_items) und einen trainierten Kopf statt Zero-Shot.
9. Offene Entscheidungen (Founder)
- Erst-Fokus: FP-Killer zuerst (Geld-Leck, messbar, risikoarm) — oder das sichtbare Voll-Inventar zuerst (Demo-Wow, „ChatGPT-Konter")? Empfehlung: ein Modell baut beides; wir validieren + schalten den FP-Nutzen zuerst, das Inventar läuft additiv mit.
- Auszahlungs-Stufe: Bleibt es bis zur Messung strikt advisory (§6)? Empfehlung: ja.