Skip to content

feat(v36): ML Lab — les manques qu'on avait délibérément laissés ouverts - #54

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

feat(v36): ML Lab — les manques qu'on avait délibérément laissés ouverts#54
dapiced merged 1 commit into
mainfrom
claude/labml-detailed-plan-nzc98m

Conversation

@dapiced

@dapiced dapiced commented Aug 23, 2026

Copy link
Copy Markdown
Owner

V36 lancée sur ta commande. Chaque point avait été descopé par son nom dans une vague antérieure, pas oublié — les livrer ensemble garde les descopes visibles au lieu de les laisser devenir permanents en silence.

1. Déséquilibre de classes (descopé nommément en V16)

Deux mécanismes, chacun nommé par famille plutôt que caché derrière un mot :

Familles Mécanisme
Régression logistique, gradient boosting La perte est pondérée — là où la perte nous appartient
Arbre, forêt Rééchantillonnage équilibré seedé — ml-cart n'accepte aucun poids

Deux décisions que je veux souligner :

  • Pour le gradient boosting, le gradient ET la hessienne sont mis à l'échelle. Ne pondérer que le gradient gonflerait les valeurs de feuilles au lieu de rééquilibrer les classes — c'est la pondération correcte pour un booster du second ordre.
  • La minorité est sur-échantillonnée jusqu'à la taille de la majorité, jamais l'inverse. Équilibrer en rognant la classe commune jette de vraies observations pour corriger un ratio ; répéter quelques lignes rares est le moindre mal. (Ma première implémentation faisait le contraire et contredisait sa propre documentation — le test unitaire l'a attrapé.)

Désactivé par défaut : sur une cible équilibrée ça ne change rien, et un bouton qui ne fait rien est pire que pas de bouton. Le run annonce la part de la classe majoritaire et le leaderboard le signale au-delà de 60 %.

2. La métrique de classement devient un choix

Accuracy et RMSE étaient imposées — le mauvais critère sur un problème déséquilibré, où un modèle qui ne prédit jamais la classe rare peut dominer le classement et être inutile. Classer sur F1, rappel, précision ou ROC-AUC change réellement l'ordre. Un modèle incapable de produire la métrique choisie descend en dernier plutôt que d'être écarté : il a tourné, la table le dit.

Le classement reste dans le module unique de V35, donc leaderboard, historique, comparaison, rapport HTML et modèle auto-inspecté bougent ensemble.

3. Seuils multiclasses (ouverts depuis V16)

Lus en un-contre-tous : on choisit une classe, on la score contre toutes les autres, mêmes courbes PR et de calibration. Ce que le panneau refuse de laisser croire : ce n'est pas une règle de décision multiclasse complète — deux classes peuvent dépasser leur seuil et rien ici ne dit laquelle l'emporte. Il le dit, plutôt que d'impliquer une règle qu'il ne fournit pas.

4. Ensemble des meilleurs

La moyenne du top 3, construite à partir de modèles déjà entraînés — une passe de plus sur le jeu de test, rien d'autre. La baseline n'en est jamais membre (moyenner un prédicteur constant tire vers la classe majoritaire), et les membres sont choisis par la règle de classement de V35.

Un défaut trouvé par la mesure en cours de route : l'ensemble gagnait sur iris avec un simple vote, parce que k-NN était dans le top 3 et n'a pas de probabilités — ce qui fermait en silence les panneaux seuil, calibration et effets par mot sur le champion. Les membres probabilistes sont désormais préférés ; le vote ne sert que si moins de deux modèles savent exprimer une confiance.

Refusés explicitement

Une dixième famille de modèles, un mode AutoML « on s'occupe de tout », le deep learning tabulaire — les trois sont nommés dans le PLAN avec leur raison.

Vérifications

npm run test 387 tests unitaires (47 fichiers)
npx playwright test 69 e2e, dont 4 nouveaux pour V36
npm run format:check OK
npm run lint 2 avertissements préexistants sur main
npm run typecheck OK
npm run build OK, coquilles prérendues présentes

Les tests dont les comptes de lignes encodaient l'ancien zoo sont mis à jour en explicitant le pourquoi (8 familles + ensemble ; 7 + ensemble en régression), pas contournés.


Generated by Claude Code

Chaque point avait été descopé par son nom dans une vague antérieure, pas
oublié. Les livrer ensemble garde les descopes visibles au lieu de les
laisser devenir permanents en silence.

1) DÉSÉQUILIBRE DE CLASSES, descopé nommément en V16. Deux mécanismes, chacun
NOMMÉ par famille plutôt que caché derrière un mot : la perte est pondérée là
où la perte nous appartient (régression logistique, gradient boosting — le
gradient ET la hessienne sont mis à l'échelle, car ne pondérer que le gradient
gonfle les valeurs de feuilles au lieu de rééquilibrer), et un
rééchantillonnage équilibré seedé sert aux familles ml-cart (arbre, forêt) qui
n'acceptent aucun poids. La minorité est sur-échantillonnée jusqu'à la taille
de la majorité, jamais l'inverse : équilibrer en rognant la classe commune
jette de vraies observations pour corriger un ratio. Désactivé par défaut —
sur une cible équilibrée ça ne change rien, et un bouton qui ne fait rien est
pire que pas de bouton.

2) LA MÉTRIQUE DE CLASSEMENT DEVIENT UN CHOIX. Accuracy et RMSE étaient
imposées, ce qui est le mauvais critère sur un problème déséquilibré : un
modèle qui ne prédit jamais la classe rare peut dominer un classement par
accuracy et être inutile. Classer sur F1, rappel, précision ou ROC-AUC change
réellement l'ordre ; un modèle incapable de produire la métrique choisie
descend en dernier plutôt que d'être écarté.

3) SEUILS MULTICLASSES, ouverts depuis V16, lus en un-contre-tous. Ce que le
panneau refuse de laisser croire : ce n'est pas une règle de décision
multiclasse complète — deux classes peuvent dépasser leur seuil et rien ici ne
dit laquelle l'emporte. Il le dit.

4) ENSEMBLE DES MEILLEURS : la moyenne du top 3, construite à partir de
modèles déjà entraînés. La baseline n'en est jamais membre, les membres sont
choisis par la règle de classement de V35, et les membres probabilistes sont
préférés — une mesure en cours de route a montré l'ensemble gagnant sur iris
avec un simple vote parce que k-NN était dans le top 3, ce qui fermait en
silence les panneaux seuil, calibration et effets par mot sur le champion.

Refusés explicitement : une dixième famille de modèles, un mode AutoML, le
deep learning tabulaire.

Tests dont les comptes de lignes encodaient l'ancien zoo mis à jour en
explicitant le pourquoi (8 familles + ensemble ; 7 + ensemble en régression).

Vérifié : 387 tests unitaires, 69 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 02:20
Copilot AI lite review requested due to automatic review settings August 23, 2026 02:20
@dapiced
dapiced merged commit 8770fac into main Aug 23, 2026
2 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

There are confirmed correctness issues that can crash or misrepresent leaderboard output (non-finite delta formatting) and an inconsistency where summary.ensemble can be recorded even when the ensemble build failed.

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

Pull request overview

This PR delivers the V36 ML Lab feature set by extending the training pipeline, worker protocol, UI panels, and tests to support class-imbalance handling, user-selectable leaderboard ranking metrics, one-vs-rest multiclass threshold analysis, and a “top-3” ensemble model built from already-trained families.

Changes:

  • Add optional class balancing (loss weighting for logistic/GBDT; seeded balanced resampling for tree/forest) and expose imbalance signals in run summaries and the UI.
  • Make the leaderboard rank metric user-selectable (accuracy/F1/recall/precision/ROC-AUC for classification; RMSE/MAE/R² for regression) while keeping ranking logic centralized.
  • Add one-vs-rest multiclass threshold analysis and introduce an ensemble model (top 3, probabilistic-first) added to results/artifacts and surfaced across UI + i18n + tests.
File summaries
File Description
src/locales/fr.json Adds V36 UI strings (ranking metric, ensemble, weighting, one-vs-rest threshold copy).
src/locales/en.json English equivalents for new V36 UI strings.
src/features/ml/worker-protocol.ts Extends threshold-analysis request with optional focusClass.
src/features/ml/train/v36.test.ts New unit tests covering weighting, ranking metric ordering, ensemble planning/building, multiclass thresholds.
src/features/ml/train/types.ts Adds RankingMetric, classWeighting config, and summary fields for imbalance + ensemble.
src/features/ml/train/trainer.ts Computes class weights, emits ensemble result, and records imbalance/ensemble in summary.
src/features/ml/train/trainer.test.ts Updates expected model counts and asserts ensemble presence/behavior.
src/features/ml/train/threshold-analysis.ts Generalizes threshold analysis to multiclass via one-vs-rest (focusClass).
src/features/ml/train/ranking.ts Adds metric-direction map, rankable metrics, and metric-based sorting/gap logic.
src/features/ml/train/models.ts Introduces weighted fitting via resampling for ml-cart families; passes weights into GBDT.
src/features/ml/train/gbdt.ts Adds row-weighted gradients+hessians and weighted prior for binary booster; supports per-class weights.
src/features/ml/train/ensemble.ts New module to plan/build ensemble (probabilities/vote/mean) from trained models.
src/features/ml/train/class-weight.ts New module implementing balanced weights, imbalance detection, and seeded balanced resampling.
src/features/ml/lab-store.ts Adds UI state/actions for class weighting, ranking metric, and threshold focus class.
src/features/ml/data/parse.worker.ts Passes focusClass through to threshold analysis in the worker.
src/features/ml/components/TrainPanel.tsx Adds class-weighting toggle (with imbalance indicator).
src/features/ml/components/ThresholdPanel.tsx Adds multiclass selector + one-vs-rest explanatory note.
src/features/ml/components/LeaderboardTable.tsx Adds rank-metric selector, imbalance hint, weighting + ensemble run-info display.
src/features/ml/components/Leaderboard.tsx Wires rank-metric state into LeaderboardTable.
README.md Updates stated principles and test counts to include V36 behavior.
PLAN.md Reformats/updates plan section to mark V36 delivered and adjust ordering text.
e2e/train.spec.ts Updates expected leaderboard row counts to include ensemble.
e2e/projects.spec.ts Updates expected stored/shared run leaderboard counts to include ensemble.
e2e/offline.spec.ts Updates expected offline leaderboard row count to include ensemble.
e2e/imbalance.spec.ts New e2e coverage for ranking-metric choice, ensemble, weighting announcement, and multiclass thresholds.
Review details

Suppressed comments (1)

src/features/ml/components/LeaderboardTable.tsx:96

  • rankingValue() can return ±Infinity when a model can’t produce the selected ranking metric (e.g. AUC with no probabilities). In delta(), that propagates into value.toFixed(3), which throws a RangeError on non-finite numbers and can crash the leaderboard render.

Guard against non-finite deltas and render an em dash instead.

    const value = higherWins
      ? rankingValue(result, metric) - rankingValue(baseline, metric)
      : rankingValue(baseline, metric) - rankingValue(result, metric);
    const sign = value > 0 ? '+' : '';
    return `${sign}${value.toFixed(3)}`;
  • Files reviewed: 25/25 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.

imbalanced: majorityShare(trainY, classes.length) >= IMBALANCE_THRESHOLD,
}),
...(weights !== undefined && { classWeighting: 'balanced' as const }),
...(plan !== null && { ensemble: plan }),
Comment on lines +61 to +64
// V36: rank on the chosen metric — accuracy is the wrong criterion on an
// imbalanced target, and the order genuinely changes with the choice.
const metric = rankMetric ?? undefined;
const sorted = sortResults(results, taskType, metric);
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