YELY_AI_Module / docs /MONITORING.md
danielxdata's picture
Ajoute le suivi des echecs et la boucle de correction pompiste
3020394
|
Raw
History Blame Contribute Delete
13.2 kB
# Monitoring et apprentissage continu (`monitoring/`)
Deux besoins distincts couverts ici : **savoir comment le modèle se comporte
en production** (suivi des performances), et **transformer les erreurs
observées en nouvelles données d'entraînement** (apprentissage continu).
Aucun des deux ne modifie le comportement de `/analyze` — ce sont des
composants qui lisent/complètent les traces déjà produites par l'API.
## 1. Suivi des performances
Chaque appel à `POST /analyze` écrit déjà une ligne JSON dans `logs/api.log`
(requête sans l'image + réponse complète + `model_version`). `monitoring/metrics.py`
parse ce fichier et calcule, sans état en mémoire ni base de données :
- `total_requests` : nombre d'appels journalisés.
- `success_rate` : proportion de réponses `success=true`.
- `avg_confidence_score` : moyenne des `confidence_score` renvoyés.
- `blocking_causes` : histogramme des `message` pour les réponses bloquées
(utile pour voir si le facteur limitant en démo est le flou, l'incohérence,
ou la confiance — voir `docs/LIMITATIONS.md`).
- `image_quality_distribution` : histogramme des `image_quality` observés.
Deux façons de le lire :
```bash
# en ligne de commande, pour un rapport texte
cd yely_ai_module
python -m monitoring.metrics
# via l'API elle-même, pendant la démo
curl http://127.0.0.1:8000/metrics
```
`GET /metrics` renvoie le même dictionnaire que `compute_metrics()`, sans
paramètre — il relit `logs/api.log` à chaque appel, donc reflète l'état
courant sans redémarrer le serveur.
`web/stats.html` est un tableau de bord minimal pour la soutenance : il
appelle `GET /metrics` (URL de l'API dérivée de celle configurée dans
`web/index.html`, stockée dans le même `localStorage`) et affiche requêtes
totales, taux de succès, confiance moyenne, répartition qualité image et
causes de blocage, avec auto-rafraîchissement toutes les 15s. Nécessite que
`allow_methods` inclue `GET` dans le middleware CORS de `app/main.py`
(sinon le navigateur bloque la lecture de la réponse, même si l'appel
aboutit côté serveur — piège déjà rencontré avec `/analyze` en `POST`).
`model_version` (variable d'environnement `MODEL_VERSION`, valeur par défaut
`crnn_v2_70.2pct`) est ajouté à chaque entrée de log — pas à la réponse
publique de `/analyze`, pour ne pas polluer le contrat API §13 avec un champ
technique. Il permet de comparer `logs/api.log` avant/après un
ré-entraînement en filtrant par version, une fois plusieurs modèles utilisés
en séquence (voir §3).
## 2. Boucle d'apprentissage continu (`monitoring/feedback.py`)
### Principe
Chaque photo reçue par `/analyze` est déjà sauvegardée dans
`photos/<transaction_id>.jpg` et référencée dans la réponse
(`photo_reference`). Cette boucle réutilise cette photo pour transformer une
lecture erronée, corrigée a posteriori par un pompiste ou un contrôle YELY,
en nouvelle donnée d'entraînement — sans jamais toucher au dataset pendant
qu'un entraînement est en cours (leçon retenue d'un crash réel, voir
`docs/TRAINING.md`).
### Étape 1 — collecte : `POST /feedback`
```bash
curl -X POST http://127.0.0.1:8000/feedback \
-F "photo_reference=3f2a1c9e-....jpg" \
-F "corrected_prix=10000" \
-F "corrected_volume=14.28" \
-F "corrected_prix_litre=700" \
-F "corrected_by=pompiste-7"
```
- `photo_reference` (requis) : valeur renvoyée par `/analyze` dans la
réponse d'origine — l'API vérifie que la photo existe encore dans
`photos/` avant d'accepter la correction (`404` sinon).
- Au moins un des trois champs `corrected_prix` / `corrected_volume` /
`corrected_prix_litre` est requis ; les champs non fournis restent
inconnus (pas déduits automatiquement).
- Chaque correction est ajoutée en une ligne à `monitoring/feedback.jsonl`
(append-only, jamais réécrit sur cet endpoint) via
`monitoring.feedback.record_feedback`.
**En pratique (démo/soutenance), pas besoin de `curl`** : `web/stats.html`
liste les transactions bloquées (`GET /failures`, 20 dernières, avec la
photo servie par `GET /photos/{photo_reference}`) avec un bouton
« Corriger » par entrée, qui ouvre `web/feedback.html?ref=<photo_reference>`
— la référence est déjà pré-remplie, la photo s'affiche automatiquement, il
ne reste qu'à saisir les valeurs correctes et valider. Cette page reste
aussi utilisable seule (référence saisie à la main) si besoin.
### Étape 2 — conversion : `monitoring/feedback.py`
```bash
cd yely_ai_module
python -m monitoring.feedback
```
Pour chaque entrée de `feedback.jsonl` pas encore convertie :
1. Relit la photo dans `photos/`.
2. Relance `detect_screen_region` (même fonction que l'API) pour localiser
l'écran et produire un crop, sauvegardé dans `monitoring/feedback_crops/`.
3. Ajoute une entrée à `annotator/annotations/annotations.json`, **au même
format** que l'annotation manuelle (`lcd_bbox`, `lcd_crop`, `fields`), en
utilisant les valeurs corrigées comme champs.
4. Marque l'entrée `converted=true` dans `feedback.jsonl` pour ne pas la
reconvertir au prochain passage.
**Point important** : la détection d'écran automatique n'est pas fiable à
100% (voir `docs/LIMITATIONS.md`, point 2 — 86% des images d'origine sont
tombées sur un repli approximatif). Les entrées générées ici sont donc
marquées `"status": "pending_review"` (pas `"annotated"`) : une relecture
humaine rapide via l'outil `annotator/` (vérifier que `lcd_bbox` cadre bien
l'écran) reste nécessaire avant de les inclure dans un ré-entraînement. Ce
choix est délibéré — préférer une étape manuelle courte à l'injection
silencieuse de crops mal cadrés dans le dataset, qui dégraderait la
précision plutôt que de l'améliorer (cf. la cause n°2 de la limitation à
70.2%).
#### Relire les entrées `pending_review` (`annotator/annotate.py --review-pending`)
`annotate.py` a un mode dédié qui ne parcourt pas un dossier d'images mais
relit directement les entrées `status="pending_review"` d'`annotations.json`
— une par une, avec le rectangle et les valeurs déjà pré-remplis (à
confirmer ou corriger) au lieu de repartir de zéro :
```bash
cd annotator
python annotate.py --review-pending
```
Marche à suivre pour chaque image affichée :
1. Le rectangle vert affiché est le `lcd_bbox` détecté automatiquement au
moment de la conversion — regarde s'il cadre bien l'écran LCD.
2. **S'il est correct** : appuie sur `S` ou `Entrée` directement, pas besoin
de redessiner.
3. **S'il est mal cadré** (coupe un chiffre, déborde sur le boîtier...) :
appuie sur `R` pour l'effacer, puis dessine un nouveau rectangle
(clic + glisser) avant `S`/`Entrée`.
4. Le terminal demande ensuite de confirmer les valeurs (prix, volume,
prix/litre...) — elles sont **déjà pré-remplies** avec la correction
envoyée par le pompiste (visibles entre crochets `[...]`) : appuie sur
`Entrée` pour chaque champ correct, ou retape la valeur si besoin de
l'ajuster.
5. `N` pour ignorer une entrée douteuse (photo illisible, correction qui ne
semble pas fiable) — elle passe en `status="skipped"` et ne sera plus
proposée ni utilisée à l'entraînement.
6. `Q`/`Esc` pour arrêter la session en cours ; la progression déjà validée
est sauvegardée, il suffit de relancer `--review-pending` plus tard pour
reprendre (seules les entrées encore `pending_review` sont reproposées).
Chaque entrée confirmée passe automatiquement en `status="annotated"` — la
suite (§3) la traite alors exactement comme une annotation manuelle
classique. `python annotate.py --review` (sans `--review-pending`) affiche
à tout moment un résumé indiquant combien d'entrées restent `[à relire]`.
### Étape 3 — ré-entraînement périodique
Une fois un nombre suffisant d'entrées `pending_review` relues et passées à
`"annotated"` (ex. tous les 30-50 nouvelles corrections, à ajuster selon le
volume réel observé en production) :
```bash
cd annotator
python split_lines.py # nettoie et régénère train/val (fix leak inclus)
cd ../train
python prepare_doctr_dataset.py
python finetune_doctr.py --epochs 60
```
Voir `docs/TRAINING.md` pour le détail de chaque étape et les pièges déjà
rencontrés (ne pas modifier le dataset pendant l'entraînement, toujours
repartir d'un dossier de sortie nettoyé).
### Étape 4 — versioning du modèle actif
Chaque nouveau modèle entraîné est écrit dans son propre dossier
(`train/models/v<N>/`), jamais en écrasant le précédent — l'incident de ce
projet où un entraînement de test a écrasé le meilleur checkpoint (55.3%)
avec un résultat plus faible (44.7%) a motivé ce choix. Pour déployer une
nouvelle version :
1. Copier le `.pt` retenu vers `yely_ai_module/models/crnn_fuel_pump_best.pt`
(écrase le fichier servi par l'API, mais l'ancien reste dans `train/models/v<N>/`
si un retour arrière est nécessaire).
2. Mettre à jour `MODEL_VERSION` (variable d'environnement du déploiement,
ex. Hugging Face Spaces → Settings → Variables) pour que les nouvelles
entrées de `logs/api.log` soient distinguables des précédentes dans
`monitoring/metrics.py`.
3. Redémarrer le Space (ou le processus `uvicorn`) — le modèle est chargé une
seule fois au démarrage (singleton `_get_model()`), un nouveau fichier sur
disque n'est pas repris à chaud.
## 3. Stockage persistant (obligatoire pour que tout ceci survive en production)
Par défaut, un Hugging Face Space Docker écrit sur le disque **éphémère**
du conteneur : `photos/`, `logs/api.log` et `monitoring/feedback.jsonl`
disparaissent à chaque redémarrage/veille du Space (les Spaces gratuits se
mettent en veille après une période d'inactivité). Pour une démo tenue en
une seule session continue ce n'est pas gênant, mais **la boucle de
feedback et l'historique `/metrics`/`/failures` n'ont de sens que si ces
fichiers survivent** — c'est là qu'intervient le stockage persistant.
### Ce que fait le code
`app/main.py`, `monitoring/metrics.py` et `monitoring/feedback.py` lisent
tous la même variable d'environnement `YELY_DATA_DIR` : si elle est
définie, `photos/`, `logs/` et `monitoring/feedback.jsonl` sont placés sous
ce répertoire au lieu du dossier de l'application. Par défaut (variable
absente), tout reste sous `yely_ai_module/` comme avant — donc rien ne
casse en local ou sur un Space sans stockage persistant.
### Activer le stockage persistant sur le Space
1. Ouvre ton Space → **Settings** → section **Persistent storage**. C'est
une option payante (facturée au mois, plusieurs tailles proposées) —
vérifie le tarif affiché à cet instant sur la page, il peut avoir changé.
Pour une démo, la plus petite taille suffit largement (quelques photos +
logs texte).
2. Active-la et choisis une taille. Hugging Face monte un disque dans le
conteneur — le chemin de montage est indiqué dans l'interface au moment
de l'activation (généralement `/data`).
3. Toujours dans **Settings** → **Variables and secrets** → **New variable**
(pas *secret*, cette valeur n'a rien de sensible) :
- Nom : `YELY_DATA_DIR`
- Valeur : le chemin de montage indiqué à l'étape 2 (ex. `/data`)
4. Redémarre le Space (**Settings** → **Restart this Space**, ou un simple
push suffit à le redéployer) pour que la variable soit prise en compte.
5. **Vérifier que ça persiste vraiment** : envoie une image via `/analyze`
(ou le formulaire `web/index.html`), consulte `GET /metrics`
(`total_requests` ≥ 1), puis redémarre le Space depuis Settings et
rappelle `GET /metrics` — si `total_requests` n'est pas retombé à 0,
c'est branché correctement.
### Si tu ne veux pas payer pour la démo
Pas bloquant : sans stockage persistant, tout fonctionne normalement tant
que le Space ne redémarre pas pendant la session de démonstration
(soutenance en continu). C'est seulement l'historique qui ne survit pas
d'une session à l'autre — acceptable pour une démo ponctuelle, à mentionner
comme limitation connue si le sujet est posé (`docs/LIMITATIONS.md`).
## 4. Ce qui est réellement implémenté vs conçu
Fonctionnel et testé (`tests/test_monitoring.py`) :
`POST /feedback`, `GET /metrics`, `GET /failures`, `GET /photos/{photo_reference}`,
`monitoring/metrics.py`,
`monitoring/feedback.py::record_feedback` et `::convert_feedback_to_annotations`.
Le mode `annotate.py --review-pending` n'a pas de test automatisé (outil
interactif OpenCV, nécessite un affichage) — vérifié manuellement.
Conçu mais volontairement manuel (pas automatisé, par choix — voir §2 ci-dessus) :
la relecture des entrées `pending_review` et le déclenchement du
ré-entraînement lui-même, qui restent des actions humaines délibérées plutôt
qu'un cron — le volume de données actuel (quelques centaines d'images) ne
justifie pas encore une automatisation complète, et une relecture humaine
reste la meilleure garde-fou contre la dégradation du dataset tant que le
découpage en lignes n'est pas plus robuste.