Un lecteur a mieux lu mon benchmark que moi
J'ai publié un fine-tune qui battait le prompting de 29 points. Un commentaire m'a dit que le vrai signal était l'écart entre mes deux chutes de score. Il avait raison sur mon tableau trompeur, tort sur le bras gonflé, et les expériences de vérification ont donné une troisième réponse.
J’ai publié un fine-tune LoRA il y a deux semaines. Le titre disait que mon premier jeu de test m’avait menti : sur des données que j’avais générées moi-même, le fine-tune faisait 100% et le prompting 94%, donc le fine-tuning semblait inutile. Sur un jeu de test reconstruit à partir de corpus publics réels, le fine-tune faisait 95% et le prompting s’écroulait à 66%.
Un lecteur, Max Quimby, a laissé un commentaire qui a recadré l’ensemble :
Cinq points et vingt-huit points. Votre fine-tune a perdu le premier en passant au jeu reconstruit, le prompting a perdu le second, et la distance entre ces deux chutes vous dit plus que chacune prise séparément. Elle dit que le jeu d’origine était différentiellement facile pour le modèle promptté, et non uniformément facile pour les deux.
Il a ensuite nommé un mécanisme. Les exemples few-shot font partie de l’entrée de la méthode promptée, donc toute parenté entre le pool d’exemples et le jeu de test gonfle un bras et pas l’autre. Et il a conclu par une règle : quand deux méthodes bougent de façon très différente après un changement de jeu de test, suspectez le jeu avant de suspecter les méthodes.
J’ai passé un après-midi à vérifier. Il avait raison sur le caractère trompeur de mon tableau, tort sur le bras qui était gonflé, et le test direct de son mécanisme a produit une troisième réponse qu’aucun de nous n’avait prévue.
L’erreur dans mon tableau
Voici la comparaison telle que je l’ai publiée.
| jeu v1 (le mien) | jeu v2 (réel) | Chute | |
|---|---|---|---|
| Fine-tune | 100% | 95% | 5 |
| Few-shot (6 exemples) | 94% | 66% | 28 |
| Zero-shot | 88% | 66% | 22 |
Lisez cela comme trois méthodes mesurées sur deux jeux et sa conclusion suit immédiatement. Mais une ligne n’est pas ce qu’elle paraît.
Les lignes few-shot et zero-shot sont deux fois la même méthode : même modèle de base, même prompt, mêmes six exemples écrits à la main, évalués sur deux jeux différents. Propre.
La ligne fine-tune, elle, contient deux adaptateurs différents. Le 100% est un adaptateur entraîné sur des données v1 évalué sur le jeu v1. Le 95% est un autre adaptateur, entraîné sur des données v2, évalué sur le jeu v2. J’ai changé les données d’entraînement et les données de test en même temps, mis les deux nombres sur une seule ligne, et appelé la différence une chute.
Ce cinq n’a jamais mesuré la difficulté du jeu. Le réentraînement sur des données plus dures absorbait discrètement la perte, et mon tableau ne donnait au lecteur aucun moyen de le voir.
Fixer l’artefact
La correction consiste à évaluer un seul artefact sur les deux jeux. L’adaptateur v1 existe toujours dans le dépôt, donc c’est une seule commande :
.venv/bin/python evaluate.py --mode lora \
--adapter-path ./adapters_v1_synthetic \
--limit 400 --tag _v1adapter_realset
| Adaptateur entraîné sur v1 | Exactitude | F1 | Faux positifs | Manqués |
|---|---|---|---|---|
| sur le jeu synthétique v1 | 100% | 1,000 | 0 | 0 |
| sur le jeu réel v2 | 67% | 0,701 | 93 | 39 |
Trente-trois points. Les mêmes poids, le même code, la même forme de prompt. Seul le jeu de test a changé.
Donc l’image corrigée, chaque ligne étant maintenant une seule méthode ou un seul artefact mesuré deux fois :
| Méthode, fixée | jeu v1 | jeu v2 | Chute |
|---|---|---|---|
| Adaptateur entraîné sur données v1 | 100% | 67% | 33 |
| Few-shot, 6 exemples de forme v1 | 94% | 66% | 28 |
| Zero-shot, aucun exemple | 88% | 66% | 22 |
Son intuition était juste et sa conclusion inversée. Le différentiel est réel, mais le fine-tune est le bras le plus gonflé, pas le moins. Et c’est l’ordre qui mérite d’être retenu :
Plus une méthode avait été ajustée à la distribution v1, plus elle a perdu quand cette distribution a disparu. Entraînée sur données v1 : 33 points. Simplement promptée avec des exemples de forme v1 : 28. Jamais exposée à v1 : 22.
Cette monotonie est l’énoncé le plus propre de toute l’affaire. Elle sauve aussi le 95% : réentraîner la même recette sur des données représentatives l’a fait passer de 67 à 95, ce qui fait de ce nombre une affirmation sur les données, pas sur la méthode.
Tester son mécanisme directement
Le plancher de 22 points est ce que le jeu coûte à n’importe quel modèle. Les 6 points au-dessus que le few-shot a payés sont le candidat pour sa parenté de pool d’exemples, et mes exemples rendent le dossier accablant. Voici trois des six, à côté des gabarits du générateur v1 qu’ils instancient :
| Exemple few-shot (écrit à la main) | Gabarit du générateur v1 |
|---|---|
INFO deploy finished commit=a1b2c3d4e5 in 42s | {ts} INFO deploy finished commit={sha} in {n}s |
Refund processed for order ORD-448120, amount 49.90 EUR. | Refund processed for order {oid}, amount 49.90 EUR. |
Ticket opened by Sophie Bernard regarding order ORD-119284. | Ticket opened by {v} concerning order {oid}. |
Mêmes gabarits, instances différentes. L’historique git confirme que je ne les ai jamais touchés entre les deux évaluations : le bras promptté portait donc des indices de forme v1 dans un jeu de test de forme v1, puis les mêmes indices dans un jeu qu’ils ne correspondaient plus.
Si cette parenté est ce qui a acheté les 6 points, remplacer le pool par un pool tiré des données d’entraînement v2 devrait les récupérer. J’ai donc construit un second jeu de six : même nombre, même équilibre 3 positifs / 3 négatifs, mêmes trois types de PII, mais chaque exemple extrait de data/train.jsonl et vérifié absent du jeu de test par correspondance exacte de chaîne.
| Sur le jeu réel v2 (n=400) | Exactitude | F1 | Précision | Rappel |
|---|---|---|---|---|
| Few-shot, exemples v1 écrits à la main | 66,2% | 0,707 | 0,610 | 0,840 |
| Few-shot, exemples v2 du domaine | 67,7% | 0,705 | 0,634 | 0,794 |
Un point et demi. Les deux bras ont tourné sur le jeu de test identique, donc c’est une comparaison appariée et elle mérite un test apparié plutôt qu’un coup d’oeil à la marge d’erreur. Test exact de McNemar sur les 400 paires de prédictions : 21 items que seul le pool v1 a eus justes, 27 que seul le pool v2 a eus justes, p = 0,47.
Rien. Faire correspondre la provenance du pool d’exemples au jeu de test ne récupère aucune exactitude mesurable.
Alors où sont passés les 6 points ?
Retour au jeu v1, où les exemples valaient quelque chose, et comparaison sur le jeu v2 avec le même test apparié :
| Comparaison | Jeu | Différence | p de McNemar |
|---|---|---|---|
| Few-shot contre zero-shot | v1 synthétique | +6 points | non testé (prédictions v1 non journalisées par paire) |
| Few-shot contre zero-shot | v2 réel | +0,2 point | 1,0000 |
Sur le jeu réel, ajouter six exemples au prompt ne fait exactement rien : 53 items que seul le zero-shot a eus justes, 54 que seul le few-shot a eus justes. Un lancer de pièce.
Les six exemples valaient donc 6 points sur le jeu qui partageait leur provenance et 0 point sur celui qui ne la partageait pas. C’est son gonflement différentiel, confirmé. Mais son remède implicite, utiliser des exemples de provenance correspondante, ne récupère pas la perte, parce que le déficit n’a jamais vraiment porté sur la provenance. Six exemples, quelle que soit leur origine, ne peuvent pas exprimer les règles de cette tâche. Sur le jeu v1 facile, ils suffisaient. Sur le vrai, aucun pool de six n’aide, donc il n’y a rien qu’un meilleur choix de six puisse reconquérir.
Le détail par type montre ce que font réellement les exemples du domaine :
| Type de PII | Exemples v1 | Exemples v2 | Écart |
|---|---|---|---|
| téléphone | 0,575 | 0,718 | +0,14 |
| 0,782 | 0,831 | +0,05 | |
| adresse | 0,597 | 0,639 | +0,04 |
| iban | 0,194 | 0,248 | +0,05 |
| nom | 0,663 | 0,593 | -0,07 |
| date de naissance | 0,366 | 0,316 | -0,05 |
Ils redistribuent au lieu d’ajouter. Les types que les exemples démontrent s’améliorent, les types qu’ils omettent se dégradent, et le total bouge à peine. Le prompting few-shot enseigne ce qu’il montre et distrait de tout le reste.
Ce qui survit à tout cela
L’avantage du fine-tune sur le jeu réel n’est pas en doute. Même test apparié, fine-tune contre le meilleur des deux bras promptés : 4 items que seul le few-shot a eus justes, 113 que seul le fine-tune a eus justes, p < 0,0001. Un partage de 113 contre 4 n’est pas un artefact de benchmark.
| Sur le jeu réel v2 (n=400) | Exactitude | F1 | Secondes par ligne |
|---|---|---|---|
| Zero-shot | 66,0% | 0,662 | 0,83 |
| Few-shot, exemples v1 | 66,2% | 0,707 | 1,67 |
| Few-shot, exemples v2 | 67,7% | 0,705 | 1,90 |
| Fine-tune LoRA | 95,0% | 0,950 | 0,91 |
Les règles que je garde
Publiez des nombres à artefact constant, ou dites clairement que ce n’est pas le cas. Ma chute de cinq points était deux adaptateurs différents déguisés en une ligne. Personne ne pouvait l’attraper depuis l’article : c’est ma faute, pas celle du lecteur.
Gardez un bras sans aucune exposition à la distribution d’entraînement. Le zero-shot est la seule raison pour laquelle j’ai pu décomposer 28 en 22 plus 6. Sans un témoin qui n’a jamais vu vos données, une chute n’est qu’une chute.
Une chute différentielle pointe vers le jeu, mais ne dit pas quel bras blâmer. Classez vos méthodes par leur degré d’ajustement à l’ancienne distribution et vérifiez si les chutes suivent cet ordre. Ici, exactement.
Utilisez des tests appariés quand les bras partagent un jeu de test. Deux des trois différences qui semblaient significatives au premier regard (1,5 point, 0,2 point) sont indiscernables du bruit sous McNemar, tandis que celle qui comptait est revenue à p < 0,0001. Des marges d’erreur sur proportions indépendantes m’auraient laissé plaider pour les trois.
Et celle qui pique : un score parfait se fait auditer, un score simplement bon passe en slide. C’est le point de Max, et il a raison. Mon 100% est ce qui m’a fait reconstruire le jeu. S’il avait été à 0,94 je l’aurais publié, et à 0,94 personne n’aurait jamais trouvé l’erreur des deux adaptateurs dans mon tableau non plus.
Reproduire
Tout est dans le dépôt, y compris les deux pools d’exemples, les nouveaux JSON de résultats et les prédictions appariées que lisent les tests de McNemar :
git clone https://github.com/jguillaumesio/lora-pii-detection-mlx
cd lora-pii-detection-mlx
python3 -m venv .venv && .venv/bin/pip install mlx-lm datasets
.venv/bin/python build_dataset.py
.venv/bin/python evaluate.py --mode few-shot --few-shot-set v2 --limit 400 --tag _v2examples
Merci à Max Quimby pour le commentaire. Il m’a coûté un après-midi et une correction, ce qui est le meilleur résultat possible pour un commentaire.