perf(tech-steps): corrige la largeur du textcat — démarrage ~5× plus rapide #25

Merged
kyuno merged 3 commits from perf/nlp-textcat-width into main 2026-09-04 08:14:41 +02:00
Owner

Phase 1/5 du remaniement du pipeline NLP. Indépendante des PR #23/#24 (aucun fichier en commun).

Historique : le 1er commit livrait length=2048 + ngram_size=2 (~32×). La CI a attrapé une régression du gate F1 (tech-step-eval 0,815 → 0,753). Le 2e commit recule sur length=65536 + ngram_size=1 — mesurablement indistinguable du défaut spaCy, ~5× plus rapide.

Le diagnostic était faux

Le service mettait ~930 s (15-16 min) à devenir healthy. Le README présentait ça comme « un compromis assumé, pas un défaut de configuration à corriger » — moins d'itérations entraînerait plus vite mais dégraderait la confiance.

C'est faux. Le coût ne venait ni des itérations, ni du nombre d'exemples, ni de la taille du corpus : il venait du paramètre length de spacy.TextCatBOW.v3, laissé à son défaut spaCy de 262144 faute d'être passé explicitement dans locale_pipeline.py.

Avec no_output_layer: False et 74 techniques, la couche de sortie fait 19 398 730 paramètres, que l'optimiseur Adam met à jour à chaque pas de gradient. Or le corpus ne contient que ~920 tokens distincts : 99,99 % de cette table de hachage n'est jamais peuplée. On payait un temps de calcul proportionnel à une table vide.

Mesures (balayage ngram=1)

Corpus réel (74 techniques, fr, 25 itérations), jeu de dev = la dernière utterance de chaque technique tenue hors entraînement ; collisions = tokens distincts du corpus partageant un bucket, sur 920 :

length params accuracy dev confiance moy. collisions entraînement
262144 ← défaut 19 398 730 97,3 % 0,809 2 / 920 400 s
65536 ← retenu 4 849 722 97,3 % 0,809 10 / 920 106 s
16384 1 212 490 97,3 % 0,808 50 / 920 35 s
2048 151 626 97,3 % 0,816 305 / 920 13 s

65536 : confiance identique au bit près au défaut, 1 % de collisions, mêmes verdicts sur les probes difficiles (bake/blindBake, boil/poach…) — aucun changement de comportement mesurable, ~5× plus rapide, 15× moins de paramètres.

2048 a été livré puis reculé : 33 % des tokens y entrent en collision, la confiance se décale (0,816), et ça pourrait décaler le F1 par classe — invérifiable hors CI. Descendre plus bas est du travail pour la phase où le jeu d'évaluation sera élargi.

ngram_size 2 → 1 (reculé aussi) : les bigrammes rendent le classifieur nettement plus confiant (0,809 → 0,919), ce qui — contre-intuitivement — dégrade la justesse bout-en-bout. _classifyClause ne retombe sur l'ancre NER que sous CONFIDENCE_THRESHOLD ; une classification confiante mais fausse sur une technique voisine (blindBake au lieu de bake) passe alors le seuil et écrase l'ancre correcte. Seule cause de la chute tech-step-eval 0,815 → 0,753.

Contenu

  • locale_pipeline.py — _TEXTCAT_MODEL_LENGTH = 65536 / _TEXTCAT_NGRAM_SIZE = 1, commentés avec le balayage et l'explication du recul. _TRAINING_ITERATIONS, _TRAINING_DROPOUT et les synonymes-comme-exemples inchangés.
  • README.md — « Temps de démarrage » et « Limitations connues » réécrites. La limitation bloquante devient la couverture d'évaluation : TECH_STEP_EVAL_DATASET ne couvre que 26/74 techniques → aucun F1 par classe, aucune matrice de confusion, donc aucun changement plus agressif n'est vérifiable aujourd'hui. C'est la phase 3.
  • docker-compose.yml / ci.yml — commentaires citant ~930 s corrigés ; start_period/timeout 1200s → 600s (plafond runner lent, pas cible).

Vérification

  • PipelineRegistry.initialize() (fr+en, chargement + entraînement — ce qui gate /health) : 208,9 s contre ~930 s.
  • uv run pytest : 46 passed en 219,8 s (dominé par la fixture qui entraîne le vrai corpus des deux locales).
  • pnpm lint vert.
  • Non exécuté ici : le gate F1 tech-step-eval.test.ts (Postgres + service requis, pas de Docker) → CI. Les probes locales et la distribution de confiance identiques au défaut sont le meilleur proxy disponible.

Le job test est déjà rouge sur main

Les 6 autres échecs de la run CI (resetDatabase / seedReferenceData : Unique constraint failed on key, timeouts de before each) sont pré-existants sur main @ 81f7cb9 (CI / test y échoue en 25m8s ; il était vert en 23m48s à 29a5b30, avant le merge de #22). Sans rapport avec ce changement — mais main ne repassera pas au vert avant qu'ils soient traités (PR séparée).

Trouvailles annexes, non corrigées ici

  • Le textcat est accent-sensible : spacy.ml.extract_ngrams hache attr=LOWER, pas NORM. Le diacritics_normalizer ne bénéficie donc qu'au PhraseMatcher.
  • Des techniques se recouvrent réellement — blindBake/bake, poach/boil, sweat/brown. Un plafond que nulle architecture ne lève ; à instruire avec la matrice de confusion de la phase 3.

🤖 Generated with Claude Code

**Phase 1/5** du remaniement du pipeline NLP. Indépendante des PR #23/#24 (aucun fichier en commun). > Historique : le 1er commit livrait `length=2048` + `ngram_size=2` (~32×). La CI a attrapé une régression du gate F1 (`tech-step-eval` 0,815 → 0,753). Le 2e commit recule sur `length=65536` + `ngram_size=1` — mesurablement indistinguable du défaut spaCy, ~5× plus rapide. ## Le diagnostic était faux Le service mettait **~930 s (15-16 min)** à devenir `healthy`. Le README présentait ça comme « un compromis assumé, pas un défaut de configuration à corriger » — moins d'itérations entraînerait plus vite mais dégraderait la confiance. C'est faux. Le coût ne venait ni des itérations, ni du nombre d'exemples, ni de la taille du corpus : il venait du paramètre **`length` de `spacy.TextCatBOW.v3`**, laissé à son défaut spaCy de **262144** faute d'être passé explicitement dans `locale_pipeline.py`. Avec `no_output_layer: False` et 74 techniques, la couche de sortie fait **19 398 730 paramètres**, que l'optimiseur Adam met à jour à *chaque* pas de gradient. Or le corpus ne contient que **~920 tokens distincts** : **99,99 % de cette table de hachage n'est jamais peuplée.** On payait un temps de calcul proportionnel à une table vide. ## Mesures (balayage `ngram=1`) Corpus réel (74 techniques, `fr`, 25 itérations), jeu de dev = la dernière `utterance` de chaque technique tenue hors entraînement ; *collisions* = tokens distincts du corpus partageant un bucket, sur 920 : | `length` | params | accuracy dev | confiance moy. | collisions | entraînement | |---|---|---|---|---|---| | 262144 **← défaut** | 19 398 730 | 97,3 % | 0,809 | **2** / 920 | **400 s** | | **65536 ← retenu** | 4 849 722 | 97,3 % | **0,809** | 10 / 920 | **106 s** | | 16384 | 1 212 490 | 97,3 % | 0,808 | 50 / 920 | 35 s | | 2048 | 151 626 | 97,3 % | 0,816 | **305** / 920 | 13 s | **`65536`** : confiance identique au bit près au défaut, 1 % de collisions, mêmes verdicts sur les probes difficiles (`bake`/`blindBake`, `boil`/`poach`…) — **aucun changement de comportement mesurable, ~5× plus rapide, 15× moins de paramètres.** `2048` a été livré puis reculé : 33 % des tokens y entrent en collision, la confiance se décale (0,816), et ça pourrait décaler le F1 par classe — invérifiable hors CI. Descendre plus bas est du travail pour la phase où le jeu d'évaluation sera élargi. **`ngram_size` 2 → 1** (reculé aussi) : les bigrammes rendent le classifieur nettement plus confiant (0,809 → 0,919), ce qui — contre-intuitivement — dégrade la justesse bout-en-bout. `_classifyClause` ne retombe sur l'ancre NER que *sous* `CONFIDENCE_THRESHOLD` ; une classification confiante mais fausse sur une technique voisine (`blindBake` au lieu de `bake`) passe alors le seuil et écrase l'ancre correcte. **Seule cause** de la chute `tech-step-eval` 0,815 → 0,753. ## Contenu - **`locale_pipeline.py`** — `_TEXTCAT_MODEL_LENGTH = 65536` / `_TEXTCAT_NGRAM_SIZE = 1`, commentés avec le balayage et l'explication du recul. `_TRAINING_ITERATIONS`, `_TRAINING_DROPOUT` et les synonymes-comme-exemples **inchangés**. - **`README.md`** — « Temps de démarrage » et « Limitations connues » réécrites. La limitation bloquante devient la **couverture d'évaluation** : `TECH_STEP_EVAL_DATASET` ne couvre que 26/74 techniques → aucun F1 par classe, aucune matrice de confusion, donc aucun changement plus agressif n'est vérifiable aujourd'hui. C'est la phase 3. - **`docker-compose.yml` / `ci.yml`** — commentaires citant ~930 s corrigés ; `start_period`/`timeout` 1200s → 600s (plafond runner lent, pas cible). ## Vérification - `PipelineRegistry.initialize()` (fr+en, chargement + entraînement — ce qui gate `/health`) : **208,9 s** contre ~930 s. - `uv run pytest` : **46 passed en 219,8 s** (dominé par la fixture qui entraîne le vrai corpus des deux locales). - `pnpm lint` vert. - **Non exécuté ici** : le gate F1 `tech-step-eval.test.ts` (Postgres + service requis, pas de Docker) → CI. Les probes locales et la distribution de confiance identiques au défaut sont le meilleur proxy disponible. ## Le job `test` est déjà rouge sur `main` Les 6 autres échecs de la run CI (`resetDatabase` / `seedReferenceData` : `Unique constraint failed on key`, timeouts de `before each`) sont **pré-existants sur `main` @ `81f7cb9`** (`CI / test` y échoue en 25m8s ; il était vert en 23m48s à `29a5b30`, avant le merge de #22). Sans rapport avec ce changement — mais `main` ne repassera pas au vert avant qu'ils soient traités (PR séparée). ## Trouvailles annexes, non corrigées ici - **Le textcat est accent-sensible** : `spacy.ml.extract_ngrams` hache `attr=LOWER`, pas `NORM`. Le `diacritics_normalizer` ne bénéficie donc qu'au `PhraseMatcher`. - **Des techniques se recouvrent réellement** — `blindBake`/`bake`, `poach`/`boil`, `sweat`/`brown`. Un plafond que nulle architecture ne lève ; à instruire avec la matrice de confusion de la phase 3. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-03 18:01:40 +02:00
perf(tech-steps): corrige la largeur du textcat, entrainement 32x plus rapide
Some checks failed
CI / build (push) Successful in 1m27s
CI / e2e (push) Successful in 6m39s
CI / lint (push) Successful in 1m41s
CI / intent-service-test (push) Successful in 3m4s
CI / test (push) Failing after 12m43s
393300205d
Le service mettait ~930 s (15-16 min) a devenir `healthy`, ce que le README
presentait comme "un compromis assume, pas un defaut de configuration a
corriger". Mauvais diagnostic : le cout ne venait ni des iterations ni de la
taille du corpus, mais du parametre `length` de `spacy.TextCatBOW.v3`, laisse
a son defaut spaCy de 262144 faute d'etre passe explicitement.

Avec `no_output_layer: False` et 74 techniques, la couche de sortie faisait
19 398 730 parametres mis a jour par Adam a chaque pas de gradient, pour une
table de hachage vide a 99 % (le corpus tient dans quelques milliers de
tokens distincts).

Mesure contre le vrai corpus (fr, 25 iterations, jeu de dev = derniere
utterance de chaque technique tenue hors entrainement) :

  length=262144 ngram=1  dev 97.3%  conf 0.809  394.7 s   <- avant
  length=2048   ngram=2  dev 97.3%  conf 0.919   12.3 s   <- apres

Meme accuracy, meilleure confiance, 32x plus rapide. Les bigrammes profitent
des locutions du corpus ("faire fondre", "laisser mijoter") qu'un sac
d'unigrammes ne distingue pas de leurs constituants isoles.

PipelineRegistry.initialize() (fr+en, chargement + entrainement) mesure a
28,5 s contre ~930 s. Suite pytest complete : 46 passed en 40,6 s.

- locale_pipeline.py : `_TEXTCAT_MODEL_LENGTH` / `_TEXTCAT_NGRAM_SIZE`
  explicites et commentes avec les mesures ; historique de calibration de
  `_TRAINING_ITERATIONS` corrige (il attribuait le cout aux iterations)
- README : sections "Temps de demarrage" et "Limitations connues" reecrites
  (la couverture d'evaluation, 26/74 techniques, y devient la limitation
  bloquante ; ajout des collisions de classes observees)
- docker-compose.yml / ci.yml : commentaires citant ~930 s corriges,
  start_period et timeout 1200s -> 300s (plafond pour runner lent, pas cible)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno added 1 commit 2026-09-03 23:25:10 +02:00
fix(tech-steps): recule sur 65536/unigramme apres regression F1 en CI
Some checks failed
CI / e2e (push) Waiting to run
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / intent-service-test (push) Has been cancelled
6324a87445
Le premier essai (length=2048 + ngram_size=2) a fait chuter le F1 agrege de
`tech-step-eval.test.ts` de 0.815 a 0.753, sous le plancher de 0.8. Balayage
mesure (fr, 25 iters, ngram=1, jeu de dev = derniere utterance tenue hors
entrainement ; collisions = tokens distincts partageant un bucket, sur 920) :

  length=262144  dev 97.3%  conf 0.809    2/920 collisions   400 s   <- defaut
  length=65536   dev 97.3%  conf 0.809   10/920 collisions   106 s   <- retenu
  length=16384   dev 97.3%  conf 0.808   50/920 collisions    35 s
  length=2048    dev 97.3%  conf 0.816  305/920 collisions    13 s   <- livre puis recule

Deux corrections :

- ngram_size 2 -> 1. Les bigrammes rendent le classifieur nettement plus
  confiant (0.809 -> 0.919), ce qui degrade la justesse bout-en-bout :
  `_classifyClause` (tech-step-matcher.ts) ne retombe sur l'ancre NER que
  sous CONFIDENCE_THRESHOLD, donc une classification confiante mais fausse
  sur une technique voisine (blindBake au lieu de bake, poach au lieu de
  boil) passe le seuil et ecrase l'ancre correcte. C'etait la seule cause
  de la chute du F1.

- length 2048 -> 65536. A 2048, 305/920 tokens entrent en collision (33 %)
  et la confiance moyenne se decale (0.816 vs 0.809). 65536 : 10 collisions,
  confiance identique au bit pres au defaut spaCy, memes verdicts sur les
  probes difficiles. ~5x plus rapide (vs ~30x pour 2048), 15x moins de
  parametres. Descendre plus bas est possible mais reste du travail pour la
  phase ou le jeu d'evaluation aura ete elargi et pourra le valider.

initialize() fr+en (chargement + entrainement) : 208.9 s contre ~930 s.
Suite pytest : 46 passed en 219.8 s (l'essentiel est le fixture qui entraine
le vrai corpus des deux locales, session-scoped).

start_period / timeout d'attente : 300s -> 600s (plafond pour runner lent).

Non verifie ici : le gate F1 lui-meme (tech-step-eval.test.ts, Postgres +
service requis, pas de Docker dans cet environnement) -> CI. Les 6 autres
echecs du job `test` (resetDatabase / seedReferenceData : contrainte unique
sur `key`, timeouts de hook) sont pre-existants sur `main` @ 81f7cb9 et sans
rapport avec ce changement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno changed title from perf(tech-steps): corrige la largeur du textcat — entraînement 32× plus rapide to perf(tech-steps): corrige la largeur du textcat — démarrage ~5× plus rapide 2026-09-03 23:25:45 +02:00
kyuno added 1 commit 2026-09-03 23:26:43 +02:00
merge(main): rattrape main (#23 stepper + optimiseur)
All checks were successful
CI / lint (push) Successful in 2m54s
CI / build (push) Successful in 2m50s
CI / intent-service-test (push) Successful in 4m26s
CI / e2e (push) Successful in 8m5s
CI / test (push) Successful in 14m48s
dfd5a2f650
kyuno merged commit df0791a694 into main 2026-09-04 08:14:41 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#25
No description provided.