Skip to content

feat(v35): ML Lab — le chiffre cesse de se flatter lui-même - #53

Merged
dapiced merged 1 commit into
mainfrom
claude/labml-detailed-plan-nzc98m
Aug 23, 2026
Merged

feat(v35): ML Lab — le chiffre cesse de se flatter lui-même#53
dapiced merged 1 commit into
mainfrom
claude/labml-detailed-plan-nzc98m

Conversation

@dapiced

@dapiced dapiced commented Aug 23, 2026

Copy link
Copy Markdown
Owner

V35 lancée sur ta commande. Deux défauts de méthode dans du code livré, plus les deux ajouts qui en découlent.

1. Le gagnant était élu sur le jeu de test

Le leaderboard triait neuf modèles sur la métrique calculée sur test et couronnait sorted[0]. Prendre le maximum de neuf tirages sur ~180 lignes biaise le chiffre annoncé vers le haut.

Il y a désormais un troisième split : la validation est taillée dans le train (64/16/20 aux ratios par défaut), la sélection se fait sur validation, et la ligne du champion annonce les deux chiffres et l'écart :

Naive Bayes a été choisi sur la validation à 0,974 et obtient 0,917 sur le jeu de test intact (−0,057).

Garantie de compatibilité vérifiée par un test : les indices de test sont identiques à l'octet près à ce que la même config produisait avant V35. Segments, seuils, incertitude et comparaison de lot voient exactement les mêmes lignes qu'avant. Sous 60 lignes utilisables, le troisième split est refusé par son nom et le labo classe sur test comme avant.

Le classement vit maintenant dans un seul module (ranking.ts), utilisé par le leaderboard, l'historique, la comparaison de runs, le rapport HTML et la sélection automatique du modèle inspecté.

2. La découpe était toujours aléatoire, même sur données datées

Découpe chronologique (les plus anciennes entraînent, les plus récentes testent ; lignes sans date exploitable écartées et comptées) et découpe par groupe (aucun groupe des deux côtés — le même client dans train et test est la même fuite). Proposées quand une colonne s'y prête, et annoncées dans l'info du run.

3. Détecteur de fuite prédictive

V6 attrapait les colonnes qui mappent sur la cible. Celui-ci attrape la colonne seulement prédictive : un stump à une colonne ajusté sur train, mesuré sur validation. Une colonne seule qui lit la cible à ≥ 99 % s'affiche en avertissement cuivre avec son score mesuré — jamais comme une victoire.

4. Classement robuste à la demande

Validation croisée 5×2 sur train+validation (pipeline réajusté dans chaque pli, jeu de test jamais touché) : moyenne, dispersion, et combien de fois le premier a réellement battu le second — « 10 plis sur 10 : l'ordre est stable » ou « 6 sur 10 : traitez-les comme à égalité ».

Un défaut que la vague a exposé, et nommé

Avec le train plus petit, le Naive Bayes gaussien sur ~150 variables TF-IDF sature à exactement 0/1. L'occlusion de V24 mesurait alors exactement zéro pour chaque mot et la carte des effets par mot disparaissait en silence — ce qui se lit « aucun mot ne compte », et c'est faux.

Mesuré avant de conclure : 2 probabilités distinctes sur 48 lignes de test, contre 48 pour la régression logistique et le gradient boosting. La carte se refuse maintenant par son nom et indique un modèle capable de répondre.

J'ai aussi trouvé et corrigé une incohérence que j'avais introduite en cours de route : un quatrième site de classement triait encore sur test et ouvrait un modèle différent de celui couronné.

Tests mis à jour, pas contournés

Cinq tests V25 encodaient l'arithmétique du split à deux voies. Leur intention est préservée : le cap compte toujours les lignes utilisables (train + validation + test), et les tailles des jeux sont ajustées pour que les caps par famille mordent encore.

Vérifications

npm run test 369 tests unitaires (46 fichiers)
npx playwright test 65 e2e, dont 4 nouveaux pour V35
npm run format:check OK
npm run lint 2 avertissements préexistants sur main (badge.tsx, button.tsx)
npm run typecheck OK
npm run build OK, coquilles prérendues présentes

Generated by Claude Code

Deux défauts de méthode dans du code livré, plus les deux ajouts qui en
découlent.

1) LE GAGNANT ÉTAIT ÉLU SUR LE JEU DE TEST. Le leaderboard triait neuf
modèles sur la métrique calculée sur test et couronnait sorted[0] : prendre
le maximum de neuf tirages sur ~180 lignes biaise le chiffre vers le haut.
Il y a désormais un troisième split — la validation est taillée dans le
train (64/16/20 aux ratios par défaut), la sélection se fait sur validation,
et la ligne du champion annonce les deux chiffres et l'écart : « choisi sur
la validation à 0,974, obtient 0,917 sur le jeu de test intact ». Les
indices de test sont identiques à l'octet près à ce que la même config
produisait avant V35 — vérifié par un test — donc segments, seuils,
incertitude et comparaison de lot voient exactement les mêmes lignes.
Sous 60 lignes utilisables, le troisième split est refusé par son nom.

Le classement vit maintenant dans UN module (ranking.ts) : leaderboard,
historique, comparaison de runs, rapport HTML et sélection automatique du
modèle inspecté. C'est précisément ce qui a cassé en cours de route — un
quatrième site triait encore sur test et ouvrait un modèle différent de
celui couronné.

2) LA DÉCOUPE ÉTAIT TOUJOURS ALÉATOIRE, MÊME SUR DONNÉES DATÉES. Découpe
chronologique (les plus anciennes entraînent, lignes sans date exploitable
écartées et comptées) et découpe par groupe (aucun groupe des deux côtés),
proposées quand une colonne s'y prête et ANNONCÉES dans l'info du run.

3) DÉTECTEUR DE FUITE PRÉDICTIVE. V6 attrapait les colonnes qui MAPPENT sur
la cible ; celui-ci attrape la colonne seulement prédictive — un stump à une
colonne ajusté sur train, mesuré sur validation. Une colonne seule qui lit
la cible à 99 % s'affiche en avertissement cuivre avec son score, jamais
comme une victoire.

4) CLASSEMENT ROBUSTE À LA DEMANDE. Validation croisée 5×2 sur
train+validation (pipeline réajusté dans chaque pli, jeu de test jamais
touché) : moyenne, dispersion, et combien de fois le premier a réellement
battu le second — « 10 plis sur 10 : l'ordre est stable » ou « 6 sur 10 :
traitez-les comme à égalité ».

DÉFAUT EXPOSÉ ET NOMMÉ PAR LA VAGUE. Avec le train plus petit, le Naive
Bayes gaussien sur ~150 variables TF-IDF sature à exactement 0/1 : l'occlusion
de V24 mesurait alors exactement zéro pour chaque mot et la carte
disparaissait — ce qui se lit « aucun mot ne compte », et c'est faux.
Mesuré (2 probabilités distinctes sur 48 lignes de test, contre 48 pour la
régression logistique et le gradient boosting), la carte se refuse
maintenant par son nom et indique un modèle capable de répondre.

Les tests V25 qui encodaient l'arithmétique à deux voies sont mis à jour en
préservant leur intention (le cap compte toujours les lignes utilisables ;
les tailles sont ajustées pour que les caps par famille mordent encore).

Vérifié : 369 tests unitaires, 65 e2e, format:check, lint (2 avertissements
préexistants sur main), typecheck, build avec coquilles prérendues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKw6oNC8iZ9Kn7q6x4qom4
@dapiced
dapiced marked this pull request as ready for review August 23, 2026 01:44
Copilot AI lite review requested due to automatic review settings August 23, 2026 01:44
@dapiced
dapiced merged commit a89aab5 into main Aug 23, 2026
3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The UI can currently offer a group split option that the trainer explicitly refuses (fewer than 3 groups), which can hard-fail training and put the lab into a generic error state.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR upgrades the ML Lab’s evaluation methodology (V35) to prevent selection-on-test bias by introducing a three-way split and centralizing ranking/selection logic across the UI surfaces, while adding announced non-random splits, a predictive leakage warning, and an on-demand robust (5×2) cross-validation ranking.

Changes:

  • Introduces a three-way split (train/validation/test), ranks on validation when available, and reports champion val→test gap via a shared ranking.ts.
  • Adds announced chronological and group splitting modes plus a predictive single-column leakage detector.
  • Adds an on-demand 5×2 robust ranking workflow (worker protocol, UI panels, artifacts) and makes word-effects “refuse by name” when probabilities are saturated.
File summaries
File Description
src/locales/fr.json Adds FR strings for validation/test display, split modes, leak warnings, robust ranking, and word-effects refusal text.
src/locales/en.json Adds EN strings for validation/test display, split modes, leak warnings, robust ranking, and word-effects refusal text.
src/features/ml/worker-protocol.ts Extends worker request/response union with robust ranking messages and payload typing.
src/features/ml/train/v35.test.ts New unit tests freezing V35 behavior (3-way split compatibility, splits, leakage scan, ranking, robust rank).
src/features/ml/train/types.ts Extends training/result/insights/summary types with split choice, validation metrics, leak warnings, and words refusal.
src/features/ml/train/trainer.ts Implements validation split, non-random split modes, leak scan integration, and propagates new summary/result fields.
src/features/ml/train/trainer.test.ts Updates existing trainer test to account for (train+validation+test) usable-row accounting.
src/features/ml/train/sampling.test.ts Updates sampling tests for the 3-way split and adjusted caps behavior.
src/features/ml/train/robust.ts Adds robust 5×2 CV ranking implementation and result structure.
src/features/ml/train/ranking.ts Centralizes ranking/crowning logic (validation-first) and champion gap computation.
src/features/ml/train/pipeline.text.test.ts Updates word-effects tests for new return shape and adds saturated-model refusal coverage.
src/features/ml/train/leakage.ts Adds predictive leakage scan using a one-column stump scored on validation.
src/features/ml/train/insights.ts Changes wordEffects to return {words, refusal} and propagates refusal into insights payload.
src/features/ml/projects/types.ts Adds robust ranking artifact to persisted run artifacts.
src/features/ml/projects/report.ts Uses shared ranking rule in HTML report and adds leak + champion-gap notes.
src/features/ml/projects/compare.ts Uses shared ranking rule so compare crowns the same model as the leaderboard while comparing test metrics.
src/features/ml/lab-store.ts Adds robust ranking state/actions and passes split choice through to worker training/tuning/curve/robust calls.
src/features/ml/data/parse.worker.ts Adds robust ranking execution and cancellation to the worker.
src/features/ml/components/TrainPanel.tsx Adds split-mode selector and robust-rank panel to the ML Lab UI.
src/features/ml/components/RunsHistory.tsx Uses shared ranking rule to pick the displayed “best model” consistently.
src/features/ml/components/RunArtifactsView.tsx Renders robust ranking artifact in stored/shared run artifact view.
src/features/ml/components/RobustRankPanel.tsx New UI panel to run/cancel robust ranking and display results.
src/features/ml/components/LeaderboardTable.tsx Displays validation-ranked primary metric (with test beside it), leak warnings, and champion gap line.
src/features/ml/components/insights/WordEffects.tsx Shows an explicit refusal card when word-effects are impossible due to saturated probabilities.
src/features/ml/components/insights/InsightsSection.tsx Renders WordEffects when words exist or when refusal is present.
README.md Updates project principles to reflect validation selection + test reporting, split modes, leak warning, and saturation refusal.
PLAN.md Updates V35 “delivered” record and reflows the plan table accordingly.
e2e/text.spec.ts Updates E2E expectations for word-effects refusal on saturated model and switching to a graded-probability model.
e2e/split.spec.ts New E2E coverage for validation selection/test reporting, robust ranking, leakage warning, and chronological split announcement.
Review details
  • Files reviewed: 29/29 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +42 to +46
...profiles
.filter(
(p) => p.type === 'id' && p.name !== target && p.cardinality < p.rowCount - p.missingCount,
)
.map((p) => ({ mode: 'group' as const, column: p.name })),
Comment on lines 13 to +15
import { TEST_RATIO, TRAIN_SEED, useLabStore } from '@/features/ml/lab-store';
import { VALIDATION_RATIO } from '@/features/ml/train/trainer';
import type { SplitChoice } from '@/features/ml/train/types';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants