docs(wiki): phase B training path and hardware decisions — recorded, not built

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-09-07 10:00:18 +02:00
parent 8bfc29db2a
commit 1a0fe59488
5 changed files with 100 additions and 1 deletions
@@ -0,0 +1,84 @@
---
title: Body-type classifier (phase B) — training path and hardware
type: decision
status: decided 2026-09-07; NOT built (user: "no build just yet")
related: [vision-review-outbox, opencv-anpr-service, venue-modules, fleet-deployment-komodo, technology-stack]
---
# Body-type classifier (phase B) — training path and hardware
The Car Wash category suggestion needs SUV vs sedan, which the phase-A COCO detector cannot give
([[opencv-anpr-service]] §Vehicle body type). Phase B is a **classifier over the detector's crop**,
trained on the reviewer's labels gathered through the [[vision-review-outbox]]. This page records
what the loop looks like, what it runs on, and what is deliberately not done. Discussed and decided
with the user on 2026-09-07; **nothing here is built yet** — the user will say when.
## The loop (as designed)
Today the loop stops at the reviewer's verdict: the collector holds labels + crops and can export
`labels.csv`. Nothing trains, nothing updates a booth. The rest of the path, each step a place
where a person decides:
1. **Train** — a one-off job (`apps/trainer`, Python/uv like the vision service) on the
collector's host reads the export and the crops straight off the collector volume, splits by
TIME (validation = newer cars than training, so the number reflects tomorrow's traffic), and
fine-tunes a small **BSD-licensed torchvision backbone** (the licence rule applies to weights as
much as code; timm/ImageNet weights only if their terms are checked). Outputs three files: the
ONNX classifier, a sidecar (class list, preprocessing constants, version), and a metrics report
(accuracy per class + confusion matrix). It **refuses to write the model** below a validation
floor the owner sets — a bad model never becomes a file. Class imbalance (nine sedans in ten)
is weighted in the loss and reported; classes with too few labels are dropped from that run.
2. **Evaluate before anything ships** — the owner reads the report. 85–95 % on frontal gate views
is the expectation once tuned; enough to *flag*, never to *bill* (the flag records, the site
threshold exists for exactly this).
3. **Publish** — weights are not code and do not live in git: a versioned file in the Gitea
package registry / a release asset, fetched by URL like the YOLOX weights.
4. **Bake and build** — the vision Dockerfile fetches that version at build time; a second stage in
`vehicle.py` runs the classifier on the detector's box and replaces `car` with the finer class
when confident, else keeps YOLOX's answer. One model path setting like the YOLOX one; off when
unset. The contract, the mapping chips and the flag do not change — the vocabulary already holds
sedan/hatchback/suv/minivan/pickup.
5. **Deploy** — a TAG bump on the booth's stack. **A booth gets a model the way it gets code**: a
pinned release you can see and roll back. No runtime model fetch (air-gapped appliance,
read-only model path — [[vision-service-hardening]]).
Retrain when the labels have grown meaningfully (every few hundred new verdicts at first). First
run needs roughly **200 reviewed crops per class that matters** (Vetura and SUV at least). Once a
model exists, its predictions on the *unlabelled* pile checked against a small reviewed sample are
the ongoing accuracy check without labelling everything — which is why every entry is sent, not a
sample ([[vision-review-outbox]] §The entry stream).
## Hardware (decided 2026-09-07)
What the owner has: an **NVIDIA Quadro FX 3800** (in hand, not installed), and in
`art-docker-station` an **Intel Xeon E3-1225 v5** (4 Skylake cores, AVX2, no AVX-512) with the
**Intel HD P530** iGPU.
- **Quadro FX 3800 — stays in the drawer.** 2009, GT200, compute capability 1.3, 1 GB. CUDA dropped
that generation in 2015; no PyTorch build of the last decade can use it. Installing it buys a
heater and a driver problem.
- **HD P530 — not for training.** Usable for *inference* via OpenVINO, irrelevant here: inference
runs on the booths' CPUs, which already do YOLOX in ~250 ms.
- **The Xeon does the job.** The problem is small (a few thousand 224-px crops, ten classes, a
small pretrained backbone). Two modes the trainer should offer:
- *Feature extraction + a small head* — run every crop once through the frozen backbone, cache
the feature vectors, train a classifier on top: minutes for a few thousand crops, seconds to
retrain when labels arrive. Expected to carry most of the accuracy on frontal gate views.
- *Full fine-tune* — unfreeze and train end to end: roughly an hour per run on four cores with
a mobile-sized backbone. The step to take when the cheap mode plateaus.
Training is occasional and unattended, so an hour on a CPU is a non-issue; the data is already
on that host, so nothing moves.
- **Consequences for the build:** the trainer image is **CPU-only PyTorch** (< 1 GB, not the 5 GB
CUDA build); the `trainer` seam in `docker-compose.collector.yml` drops the NVIDIA device
reservation (one-line change if a modern card ever lands in the host; the trainer should pick
up CUDA when present).
- **If faster is ever wanted:** a used mid-range card of the last few generations (~€200) turns
the hour into a minute, given a slot and a PSU. **Renting a cloud GPU is rejected**: the crops
would leave the premises, and even scrubbed of plates and site that runs against the whole
privacy design of the outbox.
## Not built
`apps/trainer`, the classifier stage in `vehicle.py`, the publish step, the compose `trainer`
profile (still commented, still with the GPU reservation to remove). First real run waits for
the first few hundred reviewed labels on the collector.