Module 6 — Reclassement des passages retenus
À la sortie du module 5, on dispose d'une liste ordonnée d'une vingtaine de passages candidats. La suite du système ne pourra en accueillir que quatre à six, faute de place dans la consigne finale. Le reclassement — le reranking — est l'étape qui choisit précisément lesquels garder, et dans quel ordre. C'est souvent le meilleur rapport gain sur effort du système entier.
Pourquoi la recherche seule ne suffit pas
L'index vectoriel encode la question sans avoir vu les passages, et chaque passage sans avoir vu la question. La similarité cosinus mesure une compatibilité générale, pas une adéquation fine. Un encodeur croisé — cross-encoder — présente au modèle la question et le passage collés ensemble et lui demande un score de pertinence appris sur des milliers d'exemples humains annotés.
Voici la différence en une image :
biencodeur (recherche dense) :
question --> [ modele ] --> vecteur q
passage --> [ modele ] --> vecteur p
score = cosinus(q, p)
encodeur croise (reclassement) :
[ question | passage ] --> [ modele ] --> score de pertinence
Le second est plus lent — il traite chaque paire une par une, sans réutiliser un index — mais plus précis. La bonne architecture combine les deux : on rappelle largement avec la recherche hybride (20 candidats), on reclasse finement avec l'encodeur croisé (garder les 5 meilleurs).
Un encodeur croisé en pratique
Un modèle utilisable et libre pour le français : BAAI/bge-reranker-v2-m3, multilingue et de taille raisonnable.
from sentence_transformers import CrossEncoder
reclasseur = CrossEncoder("BAAI/bge-reranker-v2-m3")
def reclasser(question, candidats, k=5):
paires = [(question, c["texte"]) for c in candidats]
scores = reclasseur.predict(paires, batch_size=8)
ordonnes = sorted(zip(candidats, scores), key=lambda x: -x[1])
return [c for c, _ in ordonnes[:k]]
Sur le fil rouge, mesuré sur cent questions annotées, l'ajout du reclassement fait passer la précision au premier rang de 0,68 à 0,86. C'est le passage d'un système qui fait souvent lire un mauvais passage en premier à un système qui met le bon en tête presque tout le temps.
Combien de candidats reclasser
Le nombre de candidats à envoyer au reclasseur est un compromis :
| Candidats | Temps de reclassement | Précision au premier rang |
|---|---|---|
| 5 | 0,08 s | 0,72 |
| 10 | 0,15 s | 0,81 |
| 20 | 0,30 s | 0,86 |
| 50 | 0,75 s | 0,87 |
| 100 | 1,50 s | 0,87 |
La saturation vers 20 est typique : au-delà, les candidats supplémentaires sont si peu pertinents que même un excellent reclasseur ne peut plus rien en tirer. Vingt candidats est le bon défaut, quitte à en monter à 30 pour des corpus très hétérogènes.
La diversité, un critère à part entière
Les cinq meilleurs passages classés par pertinence individuelle peuvent tous venir du même document, souvent le plus long, souvent le plus récent. La réponse générée va se répéter, et les questions qui exigent de croiser deux sources échouent.
L'algorithme MMR (Maximal Marginal Relevance) équilibre pertinence et diversité. À chaque étape, on choisit le passage qui maximise :
où est l'ensemble déjà retenu. ignore la diversité, ignore la pertinence ; en pratique 0,6 à 0,7 donne un bon équilibre.
import numpy as np
def mmr(candidats, vecteurs, vec_question, k=5, lam=0.7):
similarites_q = vecteurs @ vec_question
selectionnes, restants = [], list(range(len(candidats)))
while len(selectionnes) < k and restants:
if not selectionnes:
meilleur = max(restants, key=lambda i: similarites_q[i])
else:
def marginal(i):
pertinence = similarites_q[i]
redondance = max(vecteurs[i] @ vecteurs[j] for j in selectionnes)
return lam * pertinence - (1 - lam) * redondance
meilleur = max(restants, key=marginal)
selectionnes.append(meilleur)
restants.remove(meilleur)
return [candidats[i] for i in selectionnes]
On applique MMR après le reclassement, pas à la place : le reclasseur trie sur la pertinence, MMR ajuste pour éviter la redondance parmi les tops.
Diversité par source
Une variante plus simple, mais très efficace en entreprise : imposer un plafond de deux passages maximum par document dans la sélection finale. On garde alors le meilleur passage de chaque document dans l'ordre, jusqu'à cinq passages issus d'au moins trois documents distincts.
def diversifier_par_source(passages, k=5, par_source=2):
compte, retenus = {}, []
for p in passages:
src = p["source"]
if compte.get(src, 0) < par_source:
retenus.append(p)
compte[src] = compte.get(src, 0) + 1
if len(retenus) == k:
break
return retenus
Cette règle n'est pas aussi élégante que MMR mais elle est explicable aux utilisateurs — « au plus deux extraits par document » — ce qui compte en interne.
Un reclasseur ajouté sans mesure préalable coûte 100 à 300 millisecondes par requête sans gain garanti. La bonne discipline : mesurer d'abord la précision au premier rang sans reclassement sur son jeu annoté ; si elle dépasse 0,85, un reclasseur apportera moins que ce qu'il coûte. Sur le fil rouge des procédures internes, on est typiquement à 0,68, donc le gain est réel — mais il n'est pas universel.
Le coût, mesuré et non deviné
Un reclasseur tourne sur processeur graphique idéalement, mais tient aussi sur processeur pour des débits modérés. Sur un ordinateur portable moderne, bge-reranker-v2-m3 traite environ 100 paires par seconde. Vingt candidats coûtent donc 0,2 seconde de latence supplémentaire à la question, ce qui reste imperceptible pour l'utilisateur.
Sur un accélérateur, on descend sous les 50 millisecondes. À l'échelle d'un intranet, on peut donc conserver le reclasseur sur presque tous les cas d'usage sans discussion.
Si la latence devient critique — assistant intégré à un chat où la réponse doit apparaître en moins d'une seconde — on peut sauter le reclassement quand le score dense du premier candidat est très supérieur au score du dixième (ratio > 2). C'est un signe de forte confiance de la recherche ; le reclassement n'y changera rien. Sur le fil rouge, cela évite environ 30 % des reclassements pour une perte de précision de moins d'un point.
En résumé
- Un encodeur croisé juge la paire (question, passage) et fait mieux qu'une simple similarité vectorielle, en particulier au premier rang.
- Le bon débit est vingt candidats à reclasser pour garder cinq passages ; monter au-delà de trente ne gagne plus rien.
- La diversité est un critère à part entière : MMR ou un plafond « au plus deux passages par document » évitent qu'une source unique monopolise la sélection.
- Le coût est modéré (0,1 à 0,3 seconde) et se contourne, sur les questions à haute confiance, par un saut conditionnel.
Module suivant : assembler ces passages dans une consigne finale qui cite ses sources et sait s'abstenir.