Spaces:
Running
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:** | |
| 1. **FP-Gate** (Geld-Leck stopfen) — der wichtigste kurzfristige Effekt. | |
| 2. **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` | |
| (siehe `requirements.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) | |
| ```jsonc | |
| "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) | |
| ```jsonc | |
| "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** von `trash` mit — | |
| 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: | |
| 1. **FP-Kill-Rate** — Anteil der bekannten False Positives (Müll auf leerem | |
| Boden / Natur / Innenraum), die das Gate korrekt unterdrückt. *Hoch = gut.* | |
| 2. **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_detected` behä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_detected` bei sehr niedriger `waste_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 neben `deploy/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):** | |
| 1. **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. | |
| 2. **Stärkeres Backbone** (YOLO-World-L/X oder YOLOE-seg) — deutlich besserer | |
| Zero-Shot. | |
| 3. **Bessere `waste_likelihood`** — Objektzahl + Fläche + Penalty bei dominantem | |
| Nicht-Müll-Objekt, statt nur max-Confidence. | |
| 4. **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:** | |
| 1. **Der Per-Box-Veto schlägt den Ganzbild-Score deutlich** (Pareto): bei ~80 % | |
| FP-Kill steigt die TP-Retention von 28 % auf 80 %. | |
| 2. **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 über | |
| `evaluate_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ädt | |
| `summary_scene_gate.json` als 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:** | |
| 1. **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. | |
| 2. **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. | |
| 3. **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:** | |
| 1. **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). | |
| 2. **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 Flywheel | |
| + `added_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) | |
| 1. **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.* | |
| 2. **Auszahlungs-Stufe:** Bleibt es bis zur Messung strikt advisory (§6)? | |
| *Empfehlung: ja.* | |