Construire un jeu de test

Première leçon du cours Évaluer et superviser un système d'IA, en accès libre : environ 20 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Constituer un jeu de test représentatif pour un système d'IA
  • Écrire des vérifications automatiques : correspondance normalisée et règles
  • Utiliser le jeu de test comme test de non-régression avant chaque changement

Jeu de test de référenceMétriques automatiquesNon-régression

Pourquoi un jeu de test ?

Un système à base de modèle de langage ne se teste pas comme une fonction classique : la même question peut recevoir des formulations différentes, et un changement de prompt qui améliore un cas peut en dégrader dix autres. Essayer trois questions à la main avant de mettre en production donne une impression trompeuse de qualité.

La base de toute évaluation est un jeu de test (on parle aussi de golden dataset) : une liste versionnée de cas, chacun avec une entrée et ce qui est attendu. Il joue pour l’IA le rôle des tests unitaires pour le code.

Structure d’un cas

CASES = [
    {"id": "T01", "categorie": "frequent", "input": "Où est ma facture de mars ?", "expected": "facturation"},
    {"id": "T02", "categorie": "limite", "input": "factur mars ???", "expected": "facturation"},
    {"id": "T03", "categorie": "hors_perimetre", "input": "Quelle est la capitale du Pérou ?", "expected": "hors_sujet"},
]

Un bon jeu de test :

  • part de demandes réelles (anonymisées) ou très réalistes, pas uniquement de cas imaginés par l’équipe ;
  • couvre les cas fréquents, les cas limites (fautes de frappe, demandes ambiguës), les demandes hors périmètre et les tentatives de manipulation ;
  • indique pour chaque cas sa catégorie, pour suivre les résultats par type ;
  • est versionné avec le code, et enrichi à chaque incident constaté en production.

Quelques dizaines de cas bien choisis valent mieux que des centaines de cas redondants. Évitez d’y conserver des données personnelles dont vous n’avez pas besoin.

Vérifier automatiquement : correspondance normalisée

Quand la sortie attendue est courte (une catégorie, une date, un identifiant), on la compare au résultat attendu après normalisation : minuscules, accents retirés, espaces superflus supprimés.

import unicodedata

def normalize(text):
    text = unicodedata.normalize("NFKD", text)
    text = "".join(c for c in text if not unicodedata.combining(c))
    return " ".join(text.lower().split()).strip(" .!")

Sans normalisation, « Facturation. » et « facturation » seraient comptés comme différents, et le score refléterait la mise en forme plutôt que la justesse.

Vérifier automatiquement : règles

Quand la réponse est un texte libre, il n’y a pas une seule bonne réponse. On vérifie alors des critères :

  • éléments obligatoires : le délai de retour « 30 jours » doit apparaître ;
  • éléments interdits : aucune promesse de remboursement, aucun numéro de carte ;
  • format : longueur maximale, JSON valide, langue.

Ces règles sont simples, rapides et gratuites. Elles ne mesurent pas tout (le ton, la pertinence), ce que l’on complétera par un modèle juge dans la leçon suivante.

Score et analyse des échecs

On calcule un taux de réussite, mais on regarde surtout les cas en échec. Un score de 90 % peut cacher 0 % sur les demandes hors périmètre si elles sont peu nombreuses. D’où l’intérêt du score par catégorie.

Non-régression

Le jeu de test sert à chaque changement : nouveau prompt, nouveau modèle, nouvelle quantification, nouveaux documents dans la base. Le processus :

  1. exécuter le jeu de test sur la version actuelle et sur la version candidate ;
  2. comparer les scores globaux et par catégorie ;
  3. examiner chaque cas qui passait et qui échoue désormais ;
  4. décider, et conserver les résultats avec la version.

Pensez aussi à la variabilité : avec une température non nulle, un même cas peut réussir puis échouer. Fixez la température à 0 pour l’évaluation quand c’est possible, ou exécutez chaque cas plusieurs fois.

À retenir

  • Un jeu de test versionné, représentatif et enrichi en continu est la base de l’évaluation.
  • Sorties courtes : correspondance normalisée ; sorties libres : règles.
  • On suit le score par catégorie et on lit les échecs.
  • Tout changement passe par le jeu de test avant la production.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 2Python dans le navigateur2 indices

    Évaluer un classifieur simulé

    Écrivez normalize(text) (accents retirés, minuscules, espaces multiples réduits, espaces et . ! retirés aux extrémités), puis evaluate(model, cases) qui renvoie un dictionnaire :

    • total : nombre de cas ;
    • correct : nombre de cas où normalize(model(input)) == normalize(expected) ;
    • accuracy : correct / total ;
    • failures : liste des id en échec, dans l’ordre des cas.
    import unicodedata
    
    CASES = [
        {"id": "T01", "input": "Où est ma facture de mars ?", "expected": "facturation"},
        {"id": "T02", "input": "Je n'arrive plus à me connecter", "expected": "accès"},
        {"id": "T03", "input": "factur mars ???", "expected": "facturation"},
        {"id": "T04", "input": "Quelle est la capitale du Pérou ?", "expected": "hors_sujet"},
    ]
    
    
    def fake_model(text):
        """Classifieur simulé."""
        t = text.lower()
        if "facture" in t:
            return "Facturation."
        if "connecter" in t:
            return "  ACCES "
        return "facturation"
    
    
    def normalize(text):
        ...
    
    
    def evaluate(model, cases):
        ...
  2. CodeDifficulté 2Python dans le navigateur2 indices

    Vérifier des règles sur une réponse libre

    Écrivez check_answer(answer, case) qui renvoie la liste des critères en échec, dans cet ordre :

    • "manque: <texte>" pour chaque élément de case["must_include"] absent (comparaison insensible à la casse) ;
    • "interdit: <texte>" pour chaque élément de case["must_not_include"] présent (insensible à la casse) ;
    • "trop long" si le nombre de mots (split()) dépasse case["max_words"].

    Les trois clés sont facultatives. Écrivez ensuite pass_rate(answers, cases) : la proportion de réponses sans aucun échec.

    def check_answer(answer, case):
        ...
    
    
    def pass_rate(answers, cases):
        ...
  3. QCMDifficulté 21 indice

    Composer le jeu de test

    Votre assistant classe les demandes clients. Votre jeu de test contient 200 cas, tous tirés des demandes les plus fréquentes. Quel est le principal risque ?

    • Le jeu de test est trop grand pour être exécuté
    • Les cas limites, hors périmètre et les tentatives de manipulation ne sont pas évalués
    • Le score sera forcément trop bas

Quiz de fin de leçon

  1. Que permet la normalisation avant comparaison ?

    • D'améliorer le modèle
    • De ne pas compter comme erreurs des différences de casse, d'accents ou d'espaces
    • De réduire le coût de l'évaluation
  2. Quand relancer le jeu de test ?

    • Une fois par an
    • À chaque changement de prompt, de modèle, de paramètres ou de données
    • Seulement après une plainte d'utilisateur
  3. Pourquoi suivre le score par catégorie de cas ?

    • Pour avoir plus de chiffres dans le rapport
    • Parce qu'un bon score global peut masquer un échec complet sur une catégorie rare
    • Parce que c'est obligatoire légalement

La suite du cours

  1. Évaluation par un modèle juge20 min
  2. Mesurer qualité, coût et latence20 min
  3. Dérive et retours utilisateurs20 min
  4. Construire un tableau de suivi20 min

Évaluer et superviser un système d'IA · Avancé · 1 h 40