Expériences contrôlées
- Leçon 14
- 45 min
- Module 5 : Comparer les systèmes de façon responsable
Leçon 14 sur 19 · Évaluer et tester les systèmes d'IAExpériences contrôlées
1. Définir ce qui est « bon »
2. Créer un jeu de données d'évaluation
3. Choisir les métriques par composant
4. Utiliser des juges humains et des juges modèles
5. Comparer les systèmes de façon responsable
6. Automatiser l'évaluation et la rétroaction
Ce que vous saurez faire
Rendez la comparaison équitable. Changez une seule chose, gardez tout le reste identique, notez chaque réglage et exécutez le modèle plusieurs fois. Une différence dans les résultats aura alors un sens.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les références de base, les candidats et les identifiants d’exécution, vus dans Établir une référence de base.
- La version du jeu de données et la version des entrées, vues dans Étiqueter et versionner.
- L’exécution du candidat et les juges modèles, vus dans Le jugement par modèle.
Ce qu’il vous faut sur votre ordinateur
~/projects/eval-harnessà la fin du module 4, avec.venvactif :python -m pytestaffiche34 passed(34 tests réussis). Sinon, copiez l’instantanéend-of-m04comme l’explique Utiliser un instantané.- Le dépôt des laboratoires dans
~/projects/nextia-learning-labs, avec les fichiers les plus récents.
Aucun compte ni aucune clé. Cette leçon ajoute un paquet, numpy, pour les calculs du module. Testé avec Python 3.12.13 sur macOS (Apple silicon). Les commandes Windows ne sont pas testées.
Deux gâteaux et une nouvelle farine
Section intitulée « Deux gâteaux et une nouvelle farine »Vous voulez savoir si une nouvelle farine donne un meilleur gâteau. Vous préparez donc deux gâteaux et les goûtez. Le gâteau à la nouvelle farine est meilleur. Mais vous avez aussi utilisé un autre four, plus de sucre et un temps de cuisson plus court. Quel changement a fait la différence? Vous ne pouvez pas le dire.
Vous recommencez donc. Même recette, même four, même durée; seule la farine change. Vous notez la recette pour pouvoir la refaire. Et vous préparez chaque gâteau deux fois, car votre four ne donne pas toujours le même résultat. Si les deux gâteaux à l’ancienne farine diffèrent déjà beaucoup, une petite différence entre les farines signifie peu.
Le problème : une différence aux causes multiples
Section intitulée « Le problème : une différence aux causes multiples »Tomás a les chiffres du candidat et veut les comparer. « Le mois dernier, l’ancien assistant a choisi la bonne équipe pour 69 billets de soutien sur 70. Le candidat obtient 88 sur 100 ici. Il fait donc moins bien de 10 points. » Mais les deux chiffres proviennent de billets différents, traités un autre jour. Établir une référence de base a montré que le même assistant obtient 69 sur 70 sur un ensemble, et 97 sur 100 sur un autre. Les billets à eux seuls font varier le chiffre.
Un deuxième problème est moins visible. Le modèle ne donne pas deux fois la même réponse. Priya a envoyé les mêmes 102 billets de développement au candidat cinq fois. À l’exécution 1, le candidat n’a pas répondu au billet de sécurité T-64740 : un parasol est tombé vers un enfant. Personne n’en serait informé. À l’exécution 2, le même billet a été transmis à une personne, comme il le fallait. Si Priya avait fait seulement l’exécution 2, elle aurait indiqué : « aucun problème bloquant sur les données de développement ».
L’idée : une expérience contrôlée
Section intitulée « L’idée : une expérience contrôlée »Une expérience contrôlée change une seule chose et garde tout le reste fixe. Ce que vous changez est la variable testée : ici, la requête (prompt). Tout ce que vous gardez fixe est un élément de contrôle : les cas, le modèle et sa version, le schéma de sortie, la limite de sortie. Une différence dans les résultats vient alors du changement ou du hasard. Il ne reste aucune autre cause.
Il reste donc le hasard. Un modèle de langage est stochastique : il choisit chaque mot avec une part de hasard. Deux exécutions sur la même entrée peuvent donc différer. Une exécution d’un système stochastique est un échantillon de son comportement, comme un gâteau de votre four. Répétez donc l’exécution et examinez la dispersion entre les répétitions avant de vous fier à une différence.
Une expérience contrôlée suit trois règles :
- Gardez les entrées fixes. Les deux systèmes voient les mêmes cas, ce que prouve la même version des entrées.
- Versionnez chaque réglage. L’exécution consigne la requête et son empreinte, le modèle et sa version, ainsi que la version du jeu de données. Changez-en un, et l’identifiant d’exécution change.
- Répétez les exécutions stochastiques. Exécutez chaque système plusieurs fois sur les mêmes cas et rapportez la dispersion, pas la meilleure exécution.
Un petit exemple : deux exécutions, un changement
Section intitulée « Un petit exemple : deux exécutions, un changement »Voici, côte à côte, les réglages de l’exécution 1 de la référence de base et du candidat. Chaque réglage provient de la fiche de l’exécution dans outputs/runs.json. Le schéma et la limite de sortie viennent plutôt du journal d’enregistrement.
Description textuelle du diagramme
Le diagramme présente une ligne par réglage, avec la référence de base à gauche et le candidat à droite. Les identifiants d’exécution diffèrent, car ils désignent le système et la requête. Les cas vus sont identiques : 271 cas avec la version des entrées 3f90ab808275. Le modèle est identique : chat-small, version gpt-6-luna-2026-09-22. La sortie est identique : un schéma strict et une limite de 1 000 jetons. Seule la requête diffère : v2, avec l’empreinte 728add8f0b0d, contre v3, avec l’empreinte 1f6de460aaf2.
Notez la version des entrées. Les étiquettes ont changé après les premières exécutions (32 prochaines étapes, lors de la révision). La version du jeu de données a donc changé. La version des entrées couvre seulement ce que le système a vu : les identifiants des cas, les textes et les pièces jointes. Elle n’a pas changé; les sorties enregistrées restent donc valides. Étiqueter et versionner explique les deux versions.
Vous pouvez vérifier tout cela avec du code, plutôt que de vous fier au diagramme. C’est la prochaine étape.
Ajouter les répétitions au projet
Section intitulée « Ajouter les répétitions au projet »Le projet reçoit les huit exécutions répétées, le code de comparaison et une nouvelle commande. Le code de comparaison utilise numpy, une bibliothèque Python pour calculer rapidement sur des listes de nombres. Vous l’installez avec le nouveau requirements.txt.
-
Récupérez les fichiers les plus récents des laboratoires et allez dans le projet :
Fenêtre de terminal cd ~/projectsgit -C nextia-learning-labs pullcd eval-harness -
Copiez les nouvelles dépendances, les exécutions enregistrées, le code de comparaison et la ligne de commande depuis l’instantané
end-of-m05.Fenêtre de terminal cp ../nextia-learning-labs/C11/snapshots/end-of-m05/requirements.txt .cp ../nextia-learning-labs/C11/snapshots/end-of-m05/outputs/* outputs/cp ../nextia-learning-labs/C11/snapshots/end-of-m05/harness/compare.py harness/cp ../nextia-learning-labs/C11/snapshots/end-of-m05/harness/__main__.py harness/Fenêtre de terminal Copy-Item ..\nextia-learning-labs\C11\snapshots\end-of-m05\requirements.txt .Copy-Item ..\nextia-learning-labs\C11\snapshots\end-of-m05\outputs\* outputs\Copy-Item ..\nextia-learning-labs\C11\snapshots\end-of-m05\harness\compare.py harness\Copy-Item ..\nextia-learning-labs\C11\snapshots\end-of-m05\harness\__main__.py harness\ -
Installez numpy. Le
.venvdoit être actif. Une des dernières lignes commence parSuccessfully installed numpy(numpy installé avec succès) :Fenêtre de terminal python -m pip install -r requirements.txt -
Exécutez les tests. La dernière ligne est
34 passed. Le nouveau code recevra ses tests dans la prochaine leçon.Fenêtre de terminal python -m pytest -
Listez les exécutions. Chaque système a maintenant cinq exécutions : l’exécution 1 sur les 271 cas, les exécutions 2 à 5 sur les 102 cas de développement. L’exécution
practicesert à Compromis et régressions : elle contient une réponse construite, écrite par l’auteur du cours.Fenêtre de terminal python -m harness runsbaseline-v2-chat-small-r1-b6a2b9 271 cases contaminated, dev, holdout gpt-6-luna-2026-09-22 started 2026-10-09T14:56:12-04:00baseline-v2-chat-small-r2-ef711b 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:10:14-04:00baseline-v2-chat-small-r3-feb481 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:17:06-04:00baseline-v2-chat-small-r4-e2030c 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:23:21-04:00baseline-v2-chat-small-r5-3a8295 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:28:29-04:00candidate-v3-chat-small-r1-31b45f 271 cases contaminated, dev, holdout gpt-6-luna-2026-09-22 started 2026-10-09T15:01:11-04:00candidate-v3-chat-small-r2-64de63 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:12:11-04:00candidate-v3-chat-small-r3-548f7f 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:20:06-04:00candidate-v3-chat-small-r4-92e51b 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:25:18-04:00candidate-v3-chat-small-r5-504de7 102 cases dev gpt-6-luna-2026-09-22 started 2026-10-09T15:31:06-04:00strong-v2-chat-strong-r1-160302 102 cases dev gpt-6.1-sol-2026-09-29 started 2026-10-09T15:34:30-04:00practice-v3-chat-small-r1-31b45f 271 cases contaminated, dev, holdout gpt-6-luna-2026-09-22 started 2026-10-09T15:01:11-04:00
Regardez les heures de début. Priya a exécuté la référence de base et le candidat en alternance, à quelques minutes d’intervalle, le même après-midi. Si le fournisseur a changé quelque chose cet après-midi-là, les deux systèmes y ont été exposés. C’est aussi un élément de contrôle.
Vérifier les éléments de contrôle avec du code
Section intitulée « Vérifier les éléments de contrôle avec du code »Démarrez Python dans le dossier du projet (python). Comparez les fiches des deux exécutions 1, champ par champ :
>>> from harness.run import Run, load_runs, load_outputs>>> runs = load_runs()>>> b = runs["baseline-v2-chat-small-r1-b6a2b9"]>>> c = runs["candidate-v3-chat-small-r1-31b45f"]>>> [k for k in Run.model_fields if getattr(b, k) != getattr(c, k)]['run_id', 'system', 'prompt', 'prompt_sha256', 'started', 'finished', 'note']>>> b.model_version == c.model_version, b.inputs_version == c.inputs_version(True, True)Les champs qui diffèrent sont les noms, les heures, la note et la requête. La version du modèle et les entrées sont identiques. C’est une comparaison avec un seul changement, vérifiée par du code.
Les répétitions n’ont vu que les cas de développement. Leur version des entrées diffère donc : elle couvre 102 cas, pas 271. Vérifiez qu’il s’agit de la version des entrées des mêmes 102 cas de développement :
>>> from harness.dataset import load_cases, inputs_version>>> dev = load_cases(split="dev")>>> inputs_version(dev)'9b24fa392841'>>> runs["candidate-v3-chat-small-r3-548f7f"].inputs_version'9b24fa392841'Cinq exécutions de chaque système
Section intitulée « Cinq exécutions de chaque système »Examinez maintenant la dispersion. La commande repeats évalue chaque exécution d’un système sur les 102 cas de développement :
python -m harness repeats baselinepython -m harness repeats candidatebaseline x 5 runs, decisions: totals [90.0, 96.0, 94.0, 92.0, 95.0] out of 102 | min 90, max 96 | 16 cases changed between runsbaseline x 5 runs, criteria: totals [100.0, 97.0, 98.0, 98.0, 97.0] out of 102 | min 97, max 100 | 7 cases changed between runscandidate x 5 runs, decisions: totals [84.0, 81.0, 76.0, 80.0, 81.0] out of 102 | min 76, max 84 | 24 cases changed between runscandidate x 5 runs, criteria: totals [85.0, 80.0, 79.0, 82.0, 84.0] out of 102 | min 79, max 85 | 40 cases changed between runs« Decisions » (décisions) compte les cas où les deux décisions sont correctes : l’équipe, si le cas en a une, et needs_human. « Criteria » (critères) compte les cas où toutes les règles de réponse sont respectées. Le graphique représente les décisions de chaque exécution par un point.
Description textuelle du graphique
Deux rangées de cinq points. La référence de base : 90, 96, 94, 92, 95 sur 102, de 90 à 96. Le candidat : 84, 81, 76, 80, 81 sur 102, de 76 à 84. La pire exécution de la référence de base reste meilleure que la meilleure exécution du candidat.
Lisez le graphique en trois étapes.
- Un système varie de lui-même. La référence de base a pris les deux bonnes décisions sur 90 cas à l’exécution 1, et 96 à l’exécution 2. Rien n’a changé entre ces exécutions. Une différence de 6 cas est normale pour ce système.
- Les étendues ne se chevauchent pas. La pire exécution de la référence de base (90) dépasse la meilleure du candidat (84). Donc, « le candidat prend de moins bonnes décisions » n’est pas un hasard lié à une seule exécution.
- Différents cas varient. Le résultat a changé pour 16 cas entre les exécutions de la référence de base, et pour 24 cas chez le candidat. Un cas qui change est un cas à lire.
Voici un cas qui change. T-81058 demande : « Quelle est l’adresse de votre entrepôt? Je voudrais y déposer mon retour en personne. » Aucune règle n’exige qu’une personne y réponde. Vérifiez ce que la référence de base a répondu à chaque exécution :
>>> base = sorted((r for r in runs.values() if r.system == "baseline"), key=lambda r: r.repeat)>>> [load_outputs(r)["T-81058"].answer["needs_human"] for r in base][True, False, False, False, False]La même requête, le même modèle, le même billet : une fausse alerte à l’exécution 1, et la bonne réponse aux quatre autres.
L’échec silencieux, exécution par exécution
Section intitulée « L’échec silencieux, exécution par exécution »Le plus gros problème du candidat n’est pas une mauvaise décision. C’est l’absence de réponse : le modèle raisonne jusqu’à la limite de 1 000 jetons et ne renvoie rien. Comptez ces échecs dans chaque exécution, puis listez les cas critiques parmi ceux interrompus :
>>> critical = {x.case_id for x in dev if x.critical}>>> cand = sorted((r for r in runs.values() if r.system == "candidate"), key=lambda r: r.repeat)>>> for r in cand:... outs = load_outputs(r)... cut = [x.case_id for x in dev if outs[x.case_id].finish_reason == "length"]... print(r.repeat, len(cut), [i for i in cut if i in critical])...1 11 ['T-64740']2 16 []3 19 ['T-81036']4 14 []5 14 []Description textuelle du diagramme
La partie du haut compte les réponses interrompues à la limite de 1 000 jetons dans chacune des cinq exécutions. La référence de base : 1, 0, 0, 0, 0. Le candidat : 11, 16, 19, 14, 14.
La partie du bas suit deux billets critiques dans les exécutions du candidat. La référence de base a transmis les deux à une personne dans les cinq exécutions. T-64740 est un billet de sécurité : un parasol est tombé vers un enfant. Le candidat n’a pas répondu à l’exécution 1 et l’a transmis à une personne aux exécutions 2 à 5. T-81036 est un billet d’accès : un colis pour le numéro 16 a été laissé au client, qui demande à qui il est destiné. Le candidat l’a transmis à une personne aux exécutions 1, 2, 4 et 5, sans répondre à l’exécution 3.
Sur les cinq exécutions, 34 cas de développement différents ont été interrompus au moins une fois, et seulement 3 à chaque exécution. L’échec passe d’un cas à l’autre. Une seule exécution peut donc le manquer : les exécutions 2, 4 et 5 ne montrent aucun cas critique sans réponse. Deux exécutions sur cinq en montrent un. Une décision de mise en production fondée uniquement sur l’exécution 4 manquerait complètement le problème.
C’est pourquoi les règles de mise en production examinent chaque cas de chaque exécution, et pas seulement une moyenne. Compromis et régressions transforme ces cas sans réponse en problèmes bloquants.
Quand répéter, et à quel coût
Section intitulée « Quand répéter, et à quel coût »Répétez une exécution quand le système est stochastique et que la décision compte. Pour un modèle de langage, c’est presque toujours le cas. Même si un modèle permet de régler le hasard au minimum, ses réponses ne sont pas entièrement reproductibles. La règle reste donc valable.
Combien de répétitions? Examinez d’abord la dispersion d’un système. Si la différence entre les systèmes dépasse largement la dispersion, comme ici, trois à cinq exécutions suffisent. Si la différence est comparable à la dispersion, davantage d’exécutions ne rendront pas un résultat faible plus solide. La prochaine leçon montre comment chiffrer cela.
Les répétitions coûtent de l’argent et du temps. Une exécution du candidat sur les données de développement a coûté environ 0,05 $ US et quelques minutes. C’est peu ici, car l’ensemble et le modèle sont petits. Avec un grand modèle, 10 000 cas et cinq répétitions, c’est une décision budgétaire. Si vous devez économiser, répétez les cas critiques plus souvent que les autres : ils déterminent la mise en production.
Le code déterministe n’a pas besoin de répétitions. Les fonctions d’évaluation dans harness/scorers/ donnent toujours le même résultat sur la même sortie enregistrée. Un seul score suffit donc. C’est une raison d’enregistrer chaque sortie : vous pouvez l’évaluer de nouveau sans frais.
Erreur courante
Rapporter la meilleure exécution
Symptôme : Tomás exécute le candidat cinq fois et rapporte l’exécution 1 : « 84 sur 102, seulement 6 derrière les 90 de la référence de base. » La référence de base vient aussi de l’exécution 1. À l’exécution 3, le candidat obtient 76, soit 14 derrière la même exécution de référence.
Cause : Tomás compare la meilleure de cinq exécutions d’un système à une seule exécution de l’autre. La meilleure exécution est élevée en partie par hasard. La comparaison favorise donc le système qui a eu plus d’essais. La même chose se produit quand vous relancez un test « jusqu’à ce qu’il réussisse ».
Correction : décidez du nombre d’exécutions avant de regarder les résultats. Exécutez les deux systèmes autant de fois sur les mêmes cas. Rapportez chaque exécution : le minimum, le maximum et les problèmes bloquants de chacune. Dans le banc d’évaluation : repeats baseline et repeats candidate, côte à côte.
Exercice guidé : la même vérification pour la référence de base
Section intitulée « Exercice guidé : la même vérification pour la référence de base »- Dans la même session Python, exécutez la boucle de L’échec silencieux, exécution par exécution pour la référence de base : remplacez
candparbase. - Avant de l’exécuter, prédisez : combien de réponses la référence de base perd-elle à cause de la limite à chaque exécution?
- Exécutez-la. Comparez avec le diagramme des cinq exécutions.
Afficher la sortie attendue
1 1 []2 0 []3 0 []4 0 []5 0 []La référence de base a perdu une réponse à l’exécution 1 (T-65450, une question de ramassage, non critique). Elle n’en a perdu aucune aux autres exécutions. L’échec silencieux vient de la nouvelle requête, pas du modèle seul. La requête v3 demande des réponses complètes et précises, et le modèle raisonne plus longtemps pour les écrire.
À vous de jouer : quels changements restent silencieux?
Section intitulée « À vous de jouer : quels changements restent silencieux? »Les décisions du candidat ont changé entre les exécutions pour 24 cas de développement. Combien ont été interrompus dans au moins une exécution?
- Importez
repeat_spreaddepuisharness.compare. - Chargez les sorties des cinq exécutions du candidat dans une liste :
outs = [load_outputs(r) for r in cand]. - Appelez
repeat_spread(dev, outs, "decisions")et récupérez"unstable_cases"(cas instables). - Gardez les cas où au moins une exécution a
finish_reason == "length".
Afficher un exemple de réponse
>>> from harness.compare import repeat_spread>>> outs = [load_outputs(r) for r in cand]>>> unstable = repeat_spread(dev, outs, "decisions")["unstable_cases"]>>> silent = [i for i in unstable if any(o[i].finish_reason == "length" for o in outs)]>>> len(unstable), len(silent)(24, 22)Parmi les 24 cas qui changent, 22 sont interrompus dans au moins une exécution. L’instabilité du candidat vient donc surtout d’une seule défaillance : l’absence de réponse, plutôt que d’un mélange d’erreurs différentes. Cela indique une correction à tester : augmenter la limite de sortie, ou demander moins dans la requête. Chaque correction constitue un nouveau candidat, avec ses propres exécutions contrôlées.
Les chiffres de ce module proviennent des enregistrements de chaque système. Les modèles changent vite; la méthode de comparaison reste valable.
Vérifiez votre compréhension
Omar veut comparer la requête v3 sur le modèle chat-strong à la requête v2 sur chat-small, avec une exécution chacun. Quel est le problème de cette expérience, et comment la modifieriez-vous?
Afficher la réponse
Deux éléments changent à la fois : la requête et le modèle. On ne peut donc attribuer une différence à l’un ou à l’autre. Changez un seul élément par comparaison. Par exemple, comparez la requête v3 sur chat-small à la requête v2 sur chat-small, puis changez le modèle séparément. De plus, une seule exécution par version ne suffit pas pour un modèle stochastique. Exécutez chaque version plusieurs fois sur les mêmes cas, avec la même version des entrées, et indiquez la dispersion.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
L'exécution 4 du candidat ne montre aucun cas critique sans réponse dans les données de développement. Cela prouve-t-il que la défaillance silencieuse est corrigée? Expliquez à l'aide des exécutions répétées.
Afficher la réponse
Non. Avec la même requête et le même modèle, T-64740 est resté sans réponse à l’exécution 1, et T-81036 à l’exécution 3. Les exécutions 2, 4 et 5 n’en montrent aucun. La défaillance passe d’un cas à l’autre, et 34 cas de développement différents ont été interrompus au moins une fois. Une exécution sans problème n’est qu’un échantillon. Pour faire confiance à une correction, répétez les exécutions et vérifiez qu’aucun cas critique ne reste silencieux dans chacune. Vérifiez ensuite les données réservées.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Une expérience contrôlée change un seul élément et garde le reste fixe. Ici, seule la requête a changé : les 271 entrées, la version du modèle et la limite sont restées identiques. Le code l’a vérifié à partir des enregistrements d’exécution.
- Un modèle de langage est stochastique : cinq exécutions de la même référence de base ont donné de 90 à 96 sur 102. Indiquez la dispersion, pas la meilleure exécution.
- L’étendue du candidat (76 à 84) ne chevauche pas celle de la référence de base. Sa baisse sur les décisions ne vient donc pas d’une seule mauvaise exécution.
- Le candidat est interrompu sur 11, 16, 19, 14 et 14 cas de développement. Un billet critique différent reste sans réponse selon l’exécution, et trois exécutions sur cinq n’en montrent aucun.
- Les répétitions coûtent du temps et de l’argent. Répétez l’exécution du système stochastique, pas celle des outils de notation déterministes, et commencez par les cas critiques.
À suivre : quel degré de certitude peut-on avoir sur une différence observée sur 98 ou 102 cas? Intervalles de confiance, comparaisons appariées et sous-groupes.
Leçon suivante : Incertitude et sous-groupes