Evaluation et benchmarks#

L’évaluation des grands modèles de langage est un problème fondamentalement différent de l’évaluation des modèles supervisés classiques. En classification ou en régression, on dispose d’une vérité terrain unique et de métriques bien définies (accuracy, MSE). Pour un LLM qui génère du texte libre, la question « cette réponse est-elle correcte ? » n’admet souvent pas de réponse binaire. La qualité d’un résumé, la pertinence d’une explication ou la créativité d’un texte sont des jugements intrinsèquement subjectifs et multidimensionnels.

Ce chapitre présente les principaux outils d’évaluation des LLM, depuis les métriques automatiques héritées du traitement du langage naturel (BLEU, ROUGE, BERTScore) jusqu’aux approches modernes fondées sur le jugement humain (Chatbot Arena, classements Elo) ou sur l’utilisation d’un LLM comme juge. Nous implémentons chaque métrique à partir de zéro pour en comprendre la mécanique interne, avant de discuter des benchmarks standardisés qui structurent la recherche actuelle.

L’enjeu est considérable : les décisions de déploiement, de financement et de recherche reposent sur ces évaluations. Un benchmark mal conçu ou une métrique mal choisie peut orienter un domaine entier dans une direction sous-optimale. Comprendre les forces et les limites de chaque approche d’évaluation est donc une compétence essentielle pour tout praticien de l’IA.

Pourquoi évaluer est difficile#

L’évaluation des LLM se heurte à plusieurs difficultés fondamentales qui n’existent pas dans l’apprentissage supervisé classique.

Génération ouverte. Un LLM produit du texte libre : pour une même question, il existe une infinité de réponses valides. Demander « Expliquez la gravité » peut donner lieu à des réponses de longueurs, de styles et de niveaux de détail très différents, toutes parfaitement acceptables.

Subjectivité. La qualité d’un texte est en partie subjective. Deux évaluateurs humains peuvent légitimement être en désaccord sur la clarté d’une explication ou la pertinence d’un résumé. Cette variabilité inter-annotateurs pose un problème de reproductibilité.

Réponses multiples valides. En traduction automatique, « The cat is on the mat » peut être traduit par « Le chat est sur le tapis », « Le chat se trouve sur le tapis » ou « Sur le tapis se trouve le chat ». Toutes ces traductions sont correctes, mais une métrique qui compare à une seule référence pénalisera les reformulations légitimes.

Optimisation des benchmarks (benchmark gaming). Dès qu’un benchmark devient un critère de succès, les modèles peuvent être entraînés sur les données de test, les hyperparamètres ajustés spécifiquement, et les résultats présentés de manière sélective. Ce phénomène, lié à la loi de Goodhart, est une menace permanente pour la validité des évaluations.

Remarque 46

L’évaluation d’un LLM est nécessairement multidimensionnelle. Un modèle peut exceller en raisonnement mathématique mais produire des textes peu fluides, ou inversement. Aucune métrique scalaire unique ne peut capturer toutes les facettes de la qualité d’un modèle de langage. C’est pourquoi la communauté utilise des batteries de benchmarks couvrant des capacités variées : connaissances générales, raisonnement logique, génération de code, suivi d’instructions, sécurité, etc.

Métriques de génération : BLEU, ROUGE, BERTScore#

Les premières métriques d’évaluation automatique ont été développées pour la traduction automatique et le résumé. Elles reposent sur la comparaison entre un texte généré (l”hypothèse) et un ou plusieurs textes de référence.

BLEU#

Le score BLEU (Bilingual Evaluation Understudy), proposé par Papineni et al. en 2002, est la métrique historique de la traduction automatique. Il mesure la précision des n-grammes : quelle fraction des n-grammes de l’hypothèse apparaît dans la référence ?

Définition 39 (Score BLEU)

Soient \(h\) une hypothèse et \(r\) une référence. La précision modifiée des n-grammes est

\[p_n = \frac{\sum_{g \in \mathcal{N}_n(h)} \min\bigl(\text{count}(g, h),\; \text{count}(g, r)\bigr)}{\sum_{g \in \mathcal{N}_n(h)} \text{count}(g, h)}\]

\(\mathcal{N}_n(h)\) désigne l’ensemble des n-grammes distincts de \(h\) et \(\text{count}(g, s)\) le nombre d’occurrences du n-gramme \(g\) dans \(s\). Le modificateur \(\min\) (dit clipping) empêche de compter un n-gramme plus de fois qu’il n’apparaît dans la référence.

Le score BLEU combine les précisions pour \(n = 1, \ldots, N\) (typiquement \(N = 4\)) avec une pénalité de brièveté :

\[\text{BLEU} = \text{BP} \cdot \exp\left(\sum_{n=1}^{N} w_n \log p_n\right), \qquad w_n = \frac{1}{N}\]
\[\begin{split}\text{BP} = \begin{cases} 1 & \text{si } |h| \geq |r| \\ \exp\left(1 - \frac{|r|}{|h|}\right) & \text{sinon} \end{cases}\end{split}\]

Propriété 11 (Précision des n-grammes)

La précision modifiée \(p_n\) possède les propriétés suivantes :

  • \(0 \leq p_n \leq 1\) pour tout \(n\).

  • \(p_n\) est une mesure de précision : elle évalue quelle fraction de l’hypothèse est « couverte » par la référence.

  • \(p_n\) décroît généralement avec \(n\) : il est plus facile de trouver des unigrammes communs que des séquences de 4 mots identiques.

  • Le clipping empêche l’exploitation triviale qui consisterait à répéter un mot fréquent de la référence.

Implémentons le score BLEU à partir de zéro.

Hide code cell source

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
np.random.seed(42)

Hide code cell source

def get_ngrams(tokens, n):
    """Extraire les n-grammes d'une liste de tokens."""
    return [tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1)]

def modified_precision(hypothesis, reference, n):
    """Précision modifiée des n-grammes (avec clipping)."""
    hyp_counts = Counter(get_ngrams(hypothesis, n))
    ref_counts = Counter(get_ngrams(reference, n))
    clipped = {ng: min(c, ref_counts.get(ng, 0)) for ng, c in hyp_counts.items()}
    denom = sum(hyp_counts.values())
    return sum(clipped.values()) / denom if denom > 0 else 0.0

def brevity_penalty(hypothesis, reference):
    """Pénalité de brièveté."""
    c, r = len(hypothesis), len(reference)
    return 1.0 if c >= r else np.exp(1 - r / c)

def bleu_score(hypothesis, reference, max_n=4):
    """Score BLEU (jusqu'aux n-grammes d'ordre max_n)."""
    bp = brevity_penalty(hypothesis, reference)
    log_avg = 0.0
    for n in range(1, max_n + 1):
        pn = modified_precision(hypothesis, reference, n)
        if pn == 0:
            return 0.0
        log_avg += (1.0 / max_n) * np.log(pn)
    return bp * np.exp(log_avg)

Exemple 31 (Calcul de BLEU sur un exemple)

Considérons la référence « le chat est assis sur le tapis » et deux hypothèses :

ref  = "le chat est assis sur le tapis".split()
hyp1 = "le chat est sur le tapis".split()       # bonne traduction, mot manquant
hyp2 = "le le le le le le le".split()            # spam de "le"

Pour hyp1, les unigrammes communs sont nombreux (6 sur 6 sont dans la référence), mais il manque « assis ». Pour hyp2, le clipping limite le comptage de « le » à 2 (son nombre dans la référence), d’où \(p_1 = 2/7\).

Hide code cell source

ref = "le chat est assis sur le tapis".split()
hyp1 = "le chat est sur le tapis".split()
hyp2 = "le le le le le le le".split()

for name, hyp in [("hyp1 (bonne traduction)", hyp1),
                  ("hyp2 (spam de 'le')", hyp2)]:
    print(f"\n--- {name} ---")
    for n in range(1, 5):
        print(f"  p_{n} = {modified_precision(hyp, ref, n):.4f}")
    print(f"  BP   = {brevity_penalty(hyp, ref):.4f}")
    print(f"  BLEU = {bleu_score(hyp, ref, max_n=4):.4f}")
--- hyp1 (bonne traduction) ---
  p_1 = 1.0000
  p_2 = 0.8000
  p_3 = 0.5000
  p_4 = 0.0000
  BP   = 0.8465
  BLEU = 0.0000

--- hyp2 (spam de 'le') ---
  p_1 = 0.2857
  p_2 = 0.0000
  p_3 = 0.0000
  p_4 = 0.0000
  BP   = 1.0000
  BLEU = 0.0000

Remarque 47

Le score BLEU présente plusieurs limitations bien connues :

  • Il ne tient pas compte de la sémantique : deux synonymes sont traités comme des mots différents.

  • Il pénalise les reformulations légitimes qui n’utilisent pas les mêmes mots que la référence.

  • Il mesure la précision mais pas le rappel : il ne vérifie pas que l’hypothèse couvre tout le contenu de la référence.

  • Il est sensible à la tokenisation et à la casse.

ROUGE#

ROUGE (Recall-Oriented Understudy for Gisting Evaluation), proposé par Lin en 2004, est la métrique standard du résumé automatique. Contrairement à BLEU, ROUGE se concentre sur le rappel : quelle fraction du contenu de la référence est présente dans l’hypothèse ?

Définition 40 (Métriques ROUGE)

ROUGE-N (rappel des n-grammes) :

\[\text{ROUGE-N} = \frac{\sum_{g \in \mathcal{N}_n(r)} \min\bigl(\text{count}(g, h),\; \text{count}(g, r)\bigr)}{\sum_{g \in \mathcal{N}_n(r)} \text{count}(g, r)}\]

En pratique, on utilise ROUGE-1 (\(n=1\)) et ROUGE-2 (\(n=2\)).

ROUGE-L (plus longue sous-séquence commune) : soit \(\text{LCS}(h, r)\) la longueur de la LCS entre \(h\) et \(r\).

\[R_{\text{LCS}} = \frac{\text{LCS}(h, r)}{|r|}, \qquad P_{\text{LCS}} = \frac{\text{LCS}(h, r)}{|h|}\]
\[\text{ROUGE-L} = F_{\text{LCS}} = \frac{(1 + \beta^2) \cdot R_{\text{LCS}} \cdot P_{\text{LCS}}}{R_{\text{LCS}} + \beta^2 \cdot P_{\text{LCS}}}\]

avec typiquement \(\beta = 1\) (F1-mesure classique).

Remarque 48

ROUGE et BLEU sont complémentaires : BLEU mesure la précision (les mots générés sont-ils dans la référence ?) tandis que ROUGE mesure le rappel (les mots de la référence sont-ils dans l’hypothèse ?). Un résumé trop court obtient un bon BLEU mais un mauvais ROUGE ; un résumé trop long obtient un bon ROUGE mais un mauvais BLEU. En pratique, on reporte souvent la F1-mesure de ROUGE qui combine précision et rappel.

Hide code cell source

def lcs_length(x, y):
    """Longueur de la plus longue sous-séquence commune (LCS), O(|x|*|y|)."""
    m, n = len(x), len(y)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if x[i - 1] == y[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[m][n]

def rouge_l(hypothesis, reference, beta=1.0):
    """ROUGE-L (F-mesure basée sur la LCS)."""
    lcs = lcs_length(hypothesis, reference)
    if len(reference) == 0 or len(hypothesis) == 0:
        return 0.0
    recall = lcs / len(reference)
    precision = lcs / len(hypothesis)
    if recall == 0 and precision == 0:
        return 0.0
    return (1 + beta**2) * precision * recall / (recall + beta**2 * precision)

def rouge_n(hypothesis, reference, n=1):
    """ROUGE-N (rappel des n-grammes)."""
    hyp_ng = Counter(get_ngrams(hypothesis, n))
    ref_ng = Counter(get_ngrams(reference, n))
    overlap = sum(min(hyp_ng[g], ref_ng[g]) for g in ref_ng)
    total = sum(ref_ng.values())
    return overlap / total if total > 0 else 0.0

Exemple 32 (Calcul de ROUGE-L sur un exemple)

Considérons la référence « le chat dort sur le canapé » et l’hypothèse « le chat est allongé sur le canapé bleu ». La LCS est « le chat sur le canapé » (longueur 5) :

ref = "le chat dort sur le canapé".split()       # 6 tokens
hyp = "le chat est allongé sur le canapé bleu".split()  # 8 tokens
# Recall = 5/6, Precision = 5/8

Hide code cell source

ref = "le chat dort sur le canapé".split()
hyp = "le chat est allongé sur le canapé bleu".split()

print(f"Longueur LCS     : {lcs_length(hyp, ref)}")
print(f"ROUGE-L (F1)     : {rouge_l(hyp, ref):.4f}")
print(f"ROUGE-1 (rappel) : {rouge_n(hyp, ref, n=1):.4f}")
print(f"ROUGE-2 (rappel) : {rouge_n(hyp, ref, n=2):.4f}")
Longueur LCS     : 5
ROUGE-L (F1)     : 0.7143
ROUGE-1 (rappel) : 0.8333
ROUGE-2 (rappel) : 0.6000

BERTScore#

Les métriques précédentes comparent des mots de manière exacte. BERTScore, proposé par Zhang et al. en 2020, utilise les représentations contextuelles d’un modèle BERT pré-entraîné pour mesurer la similarité sémantique entre l’hypothèse et la référence.

Définition 41 (BERTScore)

Soient \(\mathbf{h}_1, \ldots, \mathbf{h}_m\) les embeddings contextuels des tokens de l’hypothèse et \(\mathbf{r}_1, \ldots, \mathbf{r}_n\) ceux de la référence. BERTScore calcule :

\[R_{\text{BERT}} = \frac{1}{n} \sum_{j=1}^{n} \max_{i} \; \cos(\mathbf{r}_j, \mathbf{h}_i), \qquad P_{\text{BERT}} = \frac{1}{m} \sum_{i=1}^{m} \max_{j} \; \cos(\mathbf{h}_i, \mathbf{r}_j)\]
\[F_{\text{BERT}} = 2 \cdot \frac{P_{\text{BERT}} \cdot R_{\text{BERT}}}{P_{\text{BERT}} + R_{\text{BERT}}}\]

\(\cos(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|}\). BERTScore peut reconnaître que « chien » et « canidé » sont sémantiquement proches, là où BLEU et ROUGE les traiteraient comme entièrement différents.

Comparaison des métriques#

Comparons le comportement de BLEU, ROUGE-1 et ROUGE-L sur plusieurs paires hypothèse/référence.

Hide code cell source

pairs = [
    ("le chat dort sur le tapis".split(),
     "le chat dort sur le tapis".split(), "Identique"),
    ("le chat dort sur le tapis".split(),
     "le chat est couché sur le tapis".split(), "Reformulation"),
    ("le chat dort sur le tapis".split(),
     "le chien court dans le jardin".split(), "Hors sujet"),
    ("le chat dort sur le tapis".split(),
     "le chat dort".split(), "Trop court"),
    ("le chat dort sur le tapis".split(),
     "sur le tapis le chat dort paisiblement ce soir".split(), "Trop long"),
]

labels = [p[2] for p in pairs]
bleu_vals = [bleu_score(p[1], p[0], max_n=4) for p in pairs]
rouge_l_vals = [rouge_l(p[1], p[0]) for p in pairs]
rouge_1_vals = [rouge_n(p[1], p[0], n=1) for p in pairs]

x = np.arange(len(labels))
width = 0.25
fig, ax = plt.subplots(figsize=(11, 5))
ax.bar(x - width, bleu_vals, width, label="BLEU-4", color="#4C72B0")
ax.bar(x, rouge_1_vals, width, label="ROUGE-1", color="#55A868")
ax.bar(x + width, rouge_l_vals, width, label="ROUGE-L", color="#C44E52")
ax.set_xlabel("Type de paire")
ax.set_ylabel("Score")
ax.set_title("Comparaison BLEU / ROUGE sur différentes paires hypothèse-référence")
ax.set_xticks(x)
ax.set_xticklabels(labels, rotation=15, ha="right")
ax.set_ylim(0, 1.1)
ax.legend()
plt.show()
_images/44caaeb0b6ae0c787fc80e6fe685108fee247e8f968fe2bc5a3a8fc89361a86b.png

Perplexité et métriques de modèle de langue#

La perplexité est la métrique intrinsèque fondamentale pour évaluer un modèle de langue. Elle mesure à quel point le modèle est « surpris » par une séquence de tokens.

Définition 42 (Perplexité)

Soit un modèle de langue \(P\) et une séquence de tokens \(x_1, x_2, \ldots, x_N\). La perplexité est

\[\text{PPL} = \exp\left(-\frac{1}{N} \sum_{i=1}^{N} \log P(x_i \mid x_{<i})\right)\]

La perplexité s’interprète comme le nombre moyen de choix que le modèle hésite entre à chaque position. Un modèle parfait a une perplexité de 1 ; un modèle uniforme sur un vocabulaire de taille \(V\) a une perplexité de \(V\). Plus la perplexité est basse, meilleur est le modèle. Elle est liée à l’entropie croisée \(H\) par \(\text{PPL} = \exp(H)\).

Remarque 49

La perplexité a des limitations importantes. Elle mesure la qualité des probabilités du modèle, pas l’utilité de ses réponses. Un modèle peut avoir une excellente perplexité sur Wikipédia tout en étant incapable de suivre des instructions. De plus, la perplexité dépend du corpus et de la tokenisation : des comparaisons entre modèles n’ont de sens que si le vocabulaire et le corpus sont identiques.

Hide code cell source

def compute_perplexity(probs_sequence):
    """Perplexité à partir d'une séquence de probabilités P(x_i|x_{<i})."""
    N = len(probs_sequence)
    log_likelihood = sum(np.log(p) for p in probs_sequence)
    return np.exp(-log_likelihood / N)

# Démonstration sur un vocabulaire jouet V = 6
vocab = ["le", "chat", "dort", "chien", "mange", "ici"]
V = len(vocab)

print("=== Modèle parfait ===")
print(f"  Perplexité = {compute_perplexity([1.0, 1.0, 1.0]):.2f}")

print("\n=== Modèle uniforme ===")
print(f"  Perplexité = {compute_perplexity([1/V]*3):.2f}  (= |V| = {V})")

print("\n=== Bon modèle ===")
print(f"  Perplexité = {compute_perplexity([0.8, 0.7, 0.6]):.2f}")

print("\n=== Mauvais modèle ===")
print(f"  Perplexité = {compute_perplexity([0.2, 0.1, 0.15]):.2f}")
=== Modèle parfait ===
  Perplexité = 1.00

=== Modèle uniforme ===
  Perplexité = 6.00  (= |V| = 6)

=== Bon modèle ===
  Perplexité = 1.44

=== Mauvais modèle ===
  Perplexité = 6.93

Benchmarks standardisés#

Les benchmarks standardisés permettent de comparer les modèles sur des tâches reproductibles. Chaque benchmark cible une capacité spécifique.

Exemple 33 (Principaux benchmarks pour LLM)

Benchmark

Domaine

Format

Métrique

MMLU

Connaissances (57 matières)

QCM (4 choix)

Accuracy

HumanEval

Code Python

Génération

pass@k

GSM8K

Maths (primaire)

Résolution

Accuracy

MATH

Maths (avancé)

Résolution

Accuracy

TruthfulQA

Véracité

QCM / génération

Accuracy

MT-Bench

Suivi d’instructions

Multi-tour

Note LLM (1-10)

ARC

Raisonnement scientifique

QCM

Accuracy

HellaSwag

Sens commun

Complétion

Accuracy

MMLU évalue les connaissances sur 57 matières académiques. HumanEval teste la génération de code avec la métrique pass@\(k\). GSM8K et MATH testent le raisonnement mathématique à des niveaux croissants. TruthfulQA évalue la propension à reproduire des idées fausses courantes. MT-Bench utilise un LLM-juge pour noter la qualité conversationnelle.

Propriété 12 (Propriétés d’un bon benchmark)

Un benchmark d’évaluation de LLM devrait satisfaire :

  • Discriminant : il sépare efficacement les modèles de niveaux différents.

  • Non saturé : les meilleurs modèles n’atteignent pas le plafond.

  • Non contaminable : les données de test ne sont pas facilement mémorisables à partir du web.

  • Représentatif : il couvre les capacités réellement utiles en pratique.

  • Reproductible : le protocole d’évaluation est entièrement spécifié.

Visualisons les performances comparées de plusieurs modèles sur ces benchmarks.

Hide code cell source

benchmarks = ["MMLU", "HumanEval", "GSM8K", "MATH", "TruthfulQA",
              "ARC", "HellaSwag", "MT-Bench"]
models = {
    "GPT-4":    [86, 67, 92, 52, 59, 96, 95, 89.6],
    "Claude 3": [86, 71, 95, 60, 62, 96, 93, 90.0],
    "LLaMA 3":  [79, 62, 84, 30, 51, 93, 88, 81.0],
    "Mistral":  [75, 40, 74, 28, 50, 89, 84, 76.0],
}

# Radar chart
angles = np.linspace(0, 2 * np.pi, len(benchmarks), endpoint=False).tolist()
angles += angles[:1]

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
colors = ["#4C72B0", "#55A868", "#C44E52", "#8172B3"]

for (model, vals), color in zip(models.items(), colors):
    values = vals + [vals[0]]
    ax.plot(angles, values, 'o-', linewidth=2, label=model, color=color)
    ax.fill(angles, values, alpha=0.08, color=color)

ax.set_xticks(angles[:-1])
ax.set_xticklabels(benchmarks, fontsize=10)
ax.set_ylim(0, 100)
ax.set_title("Performances comparées sur les benchmarks majeurs\n(données illustratives)",
             fontsize=12, y=1.08)
ax.legend(loc="upper right", bbox_to_anchor=(1.3, 1.1), fontsize=10)
plt.show()
_images/097fb0cb016b68d7bdacf74d15058b5956a8dde637a544bcde8450e6654dd4ea.png

Évaluation par LLM (LLM-as-a-judge)#

Face aux limitations des métriques automatiques, une approche récente consiste à utiliser un LLM puissant comme juge pour évaluer les sorties d’autres modèles. Cette méthode, popularisée par Zheng et al. (2023) avec MT-Bench, exploite la capacité des LLM à produire des jugements nuancés et argumentés.

Définition 43 (LLM-as-a-judge)

L’évaluation par LLM-juge consiste à soumettre la sortie d’un modèle à un LLM puissant accompagnée d’un prompt d’évaluation structuré. Deux modes sont possibles :

  1. Notation directe (single answer grading) : le juge attribue une note (par exemple 1 à 10) avec une justification.

  2. Comparaison par paires (pairwise comparison) : le juge indique quelle réponse est meilleure (A, B ou égalité) avec une justification.

Le mode pairwise est généralement plus fiable car il est plus facile de comparer deux textes que d’attribuer une note absolue.

Remarque 50

L’évaluation par LLM-juge souffre de biais systématiques :

  • Biais de position : le juge tend à favoriser la réponse présentée en premier (ou en second). On atténue ce biais en évaluant chaque paire dans les deux ordres.

  • Biais de verbosité : préférence pour les réponses plus longues, même moins précises.

  • Auto-favoritisme : un LLM-juge tend à préférer les réponses de modèles de la même famille.

  • Biais de style : préférence pour un style particulier (listes à puces, formulations académiques).

Malgré ces biais, l’accord entre les jugements de GPT-4 et ceux d’évaluateurs humains experts dépasse 80 %, comparable à l’accord inter-annotateurs humain.

Exemple 34 (Prompt d’évaluation LLM-juge)

Un prompt typique pour l’évaluation pairwise :

[Instruction] Veuillez agir comme un juge impartial et évaluer la qualité
des réponses fournies par deux assistants IA à la question ci-dessous.

[Question] {question}

[Réponse A] {réponse_A}

[Réponse B] {réponse_B}

Évaluez selon les critères suivants : pertinence, exactitude, clarté.
Expliquez votre raisonnement, puis indiquez votre verdict final :
[[A]], [[B]] ou [[Égalité]].

Le prompt est conçu pour minimiser les biais en demandant une justification avant le verdict, forçant le juge à articuler son raisonnement.

Évaluation humaine et classements Elo#

Malgré les progrès des métriques automatiques, l”évaluation humaine reste la référence pour mesurer la qualité perçue des modèles de langage.

Exemple 35 (Chatbot Arena)

Chatbot Arena (LMSYS, UC Berkeley) est une plateforme ouverte d’évaluation par crowdsourcing :

  1. Un utilisateur pose une question libre.

  2. Deux modèles anonymes répondent en parallèle.

  3. L’utilisateur vote pour la meilleure réponse (A, B ou égalité).

  4. Les identités des modèles sont révélées après le vote.

Les votes sont agrégés en un classement Elo (Bradley-Terry). Plus d’un million de votes ont été collectés, offrant un signal statistiquement robuste sur les préférences humaines. L’avantage principal est que les questions sont réelles et diversifiées, non optimisables.

Définition 44 (Classement Elo)

Le système Elo attribue à chaque modèle un score reflétant sa force relative. Après un match entre \(A\) et \(B\) de scores \(R_A\) et \(R_B\) :

  1. Probabilité attendue (modèle Bradley-Terry) :

\[E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}}, \qquad E_B = 1 - E_A\]
  1. Mise à jour après le résultat \(S_A \in \{0, 0{,}5, 1\}\) :

\[R_A' = R_A + K \cdot (S_A - E_A)\]

\(K\) est le facteur de mise à jour. Un modèle qui gagne contre un adversaire mieux classé gagne beaucoup de points.

Remarque 51

Le système Elo converge vers les forces réelles des modèles à condition que le nombre de matchs soit suffisant et que les matchups soient diversifiés. En pratique, Chatbot Arena utilise une variante du modèle Bradley-Terry estimé par maximum de vraisemblance sur l’ensemble des votes, plutôt que la mise à jour séquentielle classique. Cela produit des intervalles de confiance et permet de tester si la différence entre deux modèles est statistiquement significative.

Simulons un classement Elo pour illustrer la convergence des scores.

Hide code cell source

def elo_expected(r_a, r_b):
    """Probabilité attendue de victoire de A contre B."""
    return 1.0 / (1.0 + 10.0 ** ((r_b - r_a) / 400.0))

def elo_update(r_a, r_b, result_a, K=32):
    """Mise à jour des scores Elo après un match."""
    e_a = elo_expected(r_a, r_b)
    return r_a + K * (result_a - e_a), r_b + K * ((1 - result_a) - (1 - e_a))

# 5 modèles de forces différentes
true_strengths = {"Modèle A": 1250, "Modèle B": 1150,
                  "Modèle C": 1050, "Modèle D": 950, "Modèle E": 850}
model_names = list(true_strengths.keys())
n_models = len(model_names)

elo_ratings = {name: 1000.0 for name in model_names}
history = {name: [1000.0] for name in model_names}
rng = np.random.RandomState(42)

for _ in range(500):
    i, j = rng.choice(n_models, size=2, replace=False)
    a, b = model_names[i], model_names[j]
    p_a = elo_expected(true_strengths[a], true_strengths[b])
    draw = rng.random()
    result = 1.0 if draw < p_a * 0.9 else (0.5 if draw < p_a * 0.9 + 0.1 else 0.0)
    elo_ratings[a], elo_ratings[b] = elo_update(elo_ratings[a], elo_ratings[b], result)
    for name in model_names:
        history[name].append(elo_ratings[name])

fig, axes = plt.subplots(1, 2, figsize=(13, 5))
colors = ["#4C72B0", "#55A868", "#C44E52", "#8172B3", "#CCB974"]

for (name, hist), color in zip(history.items(), colors):
    axes[0].plot(hist, label=name, color=color, linewidth=1.5, alpha=0.85)
    axes[0].axhline(y=true_strengths[name], color=color, linestyle='--', alpha=0.3)
axes[0].set_xlabel("Nombre de matchs")
axes[0].set_ylabel("Score Elo")
axes[0].set_title("Convergence des scores Elo")
axes[0].legend(fontsize=9)

final = sorted(elo_ratings.items(), key=lambda x: x[1], reverse=True)
axes[1].barh([x[0] for x in final], [x[1] for x in final],
             color=[colors[model_names.index(x[0])] for x in final])
axes[1].set_xlabel("Score Elo final")
axes[1].set_title("Classement après 500 matchs")
for i, (n, v) in enumerate(final):
    axes[1].text(v + 5, i, f"{v:.0f}", va='center', fontsize=10)
plt.show()
_images/ea0f79cfd1602e2c4cda48701c64cf9b7d519b93837d2671a66fcaa5b022d509.png

Limites et bonnes pratiques#

Contamination des données#

Remarque 52

La contamination des données survient lorsque les données de test d’un benchmark apparaissent dans le corpus d’entraînement. Comme les LLM sont entraînés sur d’immenses volumes de texte du web, il est difficile de garantir l’absence de contamination. Un modèle peut alors « résoudre » un benchmark par mémorisation plutôt que par raisonnement. Des études ont montré des baisses de 10 à 15 points lorsque les questions contaminées sont retirées.

Saturation des benchmarks#

Remarque 53

La saturation se produit lorsque les meilleurs modèles atteignent le plafond théorique, rendant le benchmark incapable de discriminer. HellaSwag (de ~30 % à >95 %) et MMLU (de ~30 % à >90 %) en sont des exemples. Un benchmark saturé impose de concevoir de nouveaux benchmarks plus difficiles (MMLU-Pro, GPQA, FrontierMath).

Hide code cell source

years = [2020.0, 2020.5, 2021.0, 2021.5, 2022.0, 2022.5,
         2023.0, 2023.5, 2024.0, 2024.5, 2025.0]
models_mmlu = ["GPT-3", "Codex", "PaLM", "GPT-3.5", "GPT-4 (early)",
               "Claude 2", "GPT-4", "Claude 3", "GPT-4o", "Claude 3.5", "o3"]
accuracy = [43.9, 52.0, 62.9, 70.0, 78.1, 78.5, 86.4, 86.3, 88.7, 88.7, 91.8]

fig, ax = plt.subplots(figsize=(11, 5))
ax.plot(years, accuracy, 'o-', color="#4C72B0", linewidth=2, markersize=7)

for yr, acc, name in zip(years, accuracy, models_mmlu):
    ax.annotate(name, (yr, acc), textcoords="offset points",
                xytext=(0, 2.5 if acc < 85 else -4), fontsize=8,
                ha='center', color="#333333")

ax.axhline(y=89.8, color='#C44E52', linestyle='--', linewidth=1.5,
           alpha=0.7, label="Performance humaine estimée (~89.8 %)")
ax.fill_between([2019.5, 2025.5], 89.8, 100, alpha=0.05, color='#C44E52')
ax.set_xlabel("Année")
ax.set_ylabel("Accuracy (%)")
ax.set_title("Saturation progressive de MMLU")
ax.set_xlim(2019.5, 2025.5)
ax.set_ylim(30, 100)
ax.legend(loc="lower right", fontsize=10)
plt.show()
_images/64b2657ce3e7adada6eedff82e67e4cee4503cab55835529e3d1a15e7f1c7759.png

Loi de Goodhart et bonnes pratiques#

Remarque 54

La loi de Goodhart stipule : « Quand une mesure devient un objectif, elle cesse d’être une bonne mesure. » Dès qu’un benchmark devient le critère de référence, les incitations à l’optimiser (contamination, entraînement ciblé, sélection des résultats) dégradent sa capacité à mesurer ce qu’il est censé mesurer. C’est pourquoi la communauté doit utiliser des méthodes d’évaluation dynamiques comme Chatbot Arena, où les questions ne sont pas fixées à l’avance.

Pour une évaluation rigoureuse des LLM, plusieurs principes se dégagent :

  1. Évaluation multi-benchmark. Ne jamais se fier à un seul benchmark. Combiner connaissances (MMLU), raisonnement (GSM8K, MATH), code (HumanEval), sécurité (TruthfulQA) et qualité conversationnelle (MT-Bench).

  2. Vérification de contamination. Tester si le modèle a vu les données de test, en utilisant des variantes paraphrasées.

  3. Évaluation humaine complémentaire. Pour les tâches ouvertes, intégrer une évaluation humaine même partielle.

  4. Transparence méthodologique. Documenter version du modèle, température, prompt exact, nombre de tentatives.

  5. Métriques adaptées à la tâche. BLEU/ROUGE pour traduction et résumé, pass@\(k\) pour le code, accuracy pour les QCM, jugement LLM ou humain pour les tâches ouvertes.

Résumé#

Ce chapitre a présenté les principales méthodes d’évaluation des grands modèles de langage.

  1. Évaluer un LLM est intrinsèquement difficile en raison de la génération ouverte, de la subjectivité, de l’existence de réponses multiples valides et des risques de benchmark gaming.

  2. BLEU mesure la précision des n-grammes entre une hypothèse et une référence, avec une pénalité de brièveté. C’est la métrique historique de la traduction, mais elle ignore la sémantique.

  3. ROUGE mesure le rappel des n-grammes (ROUGE-N) ou la couverture par la plus longue sous-séquence commune (ROUGE-L). C’est la métrique standard du résumé automatique.

  4. BERTScore utilise les embeddings contextuels d’un modèle BERT pour capturer la similarité sémantique, surmontant la limitation de la correspondance exacte de mots.

  5. La perplexité \(\text{PPL} = \exp(-\frac{1}{N}\sum \log P(x_i \mid x_{<i}))\) mesure la surprise du modèle de langue. Plus elle est basse, meilleur est le modèle, mais elle ne capture pas l’utilité des réponses.

  6. Les benchmarks standardisés (MMLU, HumanEval, GSM8K, MATH, TruthfulQA, ARC, HellaSwag, MT-Bench) testent des capacités spécifiques mais souffrent de contamination et de saturation.

  7. L’évaluation par LLM-juge offre une alternative scalable avec un accord supérieur à 80 % avec les experts, mais présente des biais de position, de verbosité et d’auto-favoritisme.

  8. L’évaluation humaine et les classements Elo (Chatbot Arena) restent le gold standard. Le modèle Bradley-Terry convertit les comparaisons par paires en classement global.

  9. Les limites incluent la contamination, la saturation, la loi de Goodhart et la nécessité d’une évaluation multidimensionnelle.

  10. Les bonnes pratiques imposent une évaluation multi-benchmark, une vérification de contamination, une complémentarité entre métriques automatiques et jugement humain, et une transparence méthodologique.