Au-delà de la similarité cosinus : comparaison de cinq techniques de récupération RAG
Le cosinus, BM25, le RRF hybride, la réévaluation par encodeur croisé et le MMR expliqués avec du code, les compromis à prendre ainsi qu’une approche axée sur la requête pour déterminer quelle technique de récupération votre RAG nécessite.
Sources: **Beyond Cosine Similarity: The 5 RAG Retrieval Techniques That Actually Move the Needle**
Deux phrases, « le chat était assis sur le tapis » et « un félin se reposait sur la moquette », ne partagent aucun mot, pourtant un modèle d’embedding leur attribue une similarité d’environ 0,85. C’est ainsi que la similarité cosinus a acquis sa réputation, et c’est aussi là que se terminent la plupart des tutoriels sur RAG : montrer la similarité cosinus, coller un extrait, et passer à autre chose.
collection.query(query_embeddings=embedding, n_results=5)
Cette méthode fonctionne. C’est aussi seulement l’une des cinq techniques de recherche, chacune conçue pour un type de problème différent. S’appuyer uniquement sur la cosinus signifie que des catégories entières de requêtes échouent silencieusement, sans erreur, et les utilisateurs reçoivent simplement des réponses incorrectes. Voici ce que mesure réellement la cosinus, où elle échoue, ainsi que les quatre autres techniques, accompagnées d’une méthode pour choisir celle à utiliser.
Ce que mesure la cosinus
Un modèle d’incorporation convertit du texte en un vecteur, c’est-à-dire une liste de centaines ou de milliers de nombres. Les textes ayant des significations similaires se transforment en vecteurs orientés dans la même direction. La similarité cosinus compare l’angle séparant deux vecteurs plutôt que la distance entre eux.
C’est le point clé. Une phrase courte et un paragraphe complet peuvent avoir la même signification. Si leurs vecteurs pointent dans la même direction, la cosinus indique un score élevé. La longueur, qui se manifeste par la magnitude, constitue du bruit, tandis que la direction représente le sens.
Les scores typiques se présentent comme ceci :
| Paire | Score |
|---|---|
| "Le chat était assis sur le tapis" et "Un félin se reposait sur la moquette" | 0,85 |
| ">Comment fonctionne l’attention ?" et ">Expliquez le mécanisme du transformateur" | 0,79 |
| ">Langage de programmation Python" et ">Python, l’espèce de serpent" | 0,38 |
| ">J’aime le café du matin" et ">Théorie de l’entanglement quantique" | 0,03 |
La première ligne montre pourquoi le cosinus est impressionnant : aucune parole en commun, pourtant un score de 0,85. Le modèle a appris que "chat" et "félins" se trouvent dans des régions proches de l’espace sémantique, et le cosinus le détecte sans aucune correspondance de mots-clés.
Pourquoi pas la distance euclidienne ?
L’alternative évidente est la distance euclidienne, qui correspond littéralement à l’éloignement entre deux vecteurs. Le problème : si l’on répète un document dix fois, son vecteur devient dix fois plus grand, ce qui le fait paraître très éloigné du document original même si son sens reste inchangé. La cosinus ignore ce facteur ; seule la direction compte.
Avertissement concernant les scores
Les scores de cosinus ne sont pas universels. Un score de 0,7 obtenu avec all-MiniLM-L6-v2 n’a pas la même signification qu’un score de 0,7 obtenu avec text-embedding-3-large, car chaque modèle dispose de son propre espace d’incorporation et de sa propre densité. Calibrez les seuils de score en fonction de votre propre ensemble d’évaluation, et ne copiez pas des valeurs issues d’un tutoriel utilisant un autre modèle.
Une analogie de festival pour les non-ingénieurs
Au préalable, imaginons un modèle mental des cinq techniques. Vous vous trouvez dans un festival de musique à la recherche d’un ami parmi la foule, et vous disposez de trois stratégies possibles.
- Par direction (cosinus). Vous vous mettez tous les deux face à la scène. La même direction signifie que c’est la même personne, peu importe l’éloignement entre vous. L’angle vers lequel vous pointez est important, tandis que l’écart entre vous ne l’est pas.
- Par étiquette nominative (recherche par mot-clé, BM25). Vous dites « Y a-t-il quelqu’un nommé Alex ? » Cette méthode fonctionne parfaitement si l’étiquette nominative correspond exactement, mais elle échoue avec tous les Alexis, Al ou Sandy qui sont en réalité la même personne sous un autre nom.
- Par visage et étiquette nominative (méthode hybride). Vous vérifiez les deux critères. La personne correspondant aux deux critères passe en tête de liste, et celle ne correspondant qu’à un seul critère est néanmoins repérée. Cela offre la couverture la plus large.
La leçon que la plupart des tutoriels omettent : aucune méthode unique ne permet de trouver tout le monde. La technique que vous choisissez détermine quels résultats, et quelles personnes, vous manquerez définitivement.
Cinq techniques, une à la fois
1. Similitude cosinus, la valeur par défaut
Fonctionnement. Il compare la direction sémantique des vecteurs de la requête et du document.
Quand l’utiliser. Lorsque les requêtes sont en langue naturelle et qu’aucuns termes exacts ne sont requis : RAG général, réponse à des questions sur des documents, tout ce qui implique beaucoup de paraphrasage.
Ses limites. Les chaînes de caractères littérales. Un code de produit, un numéro de facture ou un nom de personne qui apparaît à peine dans les données d’entraînement peuvent faire échouer complètement la méthode du cosinus. Les vecteurs de "GPT-4o" et "GPT-4" peuvent être suffisamment proches pour générer du bruit plutôt que un signal clair.
Dans le code :
collection.query(
query_embeddings=query_embedding,
n_results=5
)
Équilibre à trouver. Le sens prime sur la formulation exacte. C’est rapide, suffisamment précis et ne nécessite aucune configuration supplémentaire.
2. Recherche par mots-clés BM25, l’outil de précision
Fonctionnement. Il évalue les documents en fonction de la fréquence d’apparition des mots de la requête à l’intérieur d’eux, en appliquant un facteur de pénalité selon la fréquence de ces mots dans l’ensemble du corpus. Il ne utilise pas d’embeddings, seulement un comptage.
Quand l’utiliser. Lorsque la requête dépend d’un identifiant exact qui doit correspondre caractère par caractère, comme un numéro de facture, le nom d’un modèle avec une date de sortie ou une référence CVE.
Ce qu’il manque. Tout sens. « Feline » ne correspondra pas aux documents qui mentionnent « chat ». Les synonymes et les paraphrases lui échappent.
Résultat significatif. Dans un petit projet développé de zéro, l’ajout de BM25 aux côtés de la méthode cosinus a permis d’augmenter le taux de rappel de 0,25 à 0,75. La raison en est que les requêtes réelles des utilisateurs contenaient des noms de produits exacts que la méthode cosinus confondait avec des correspondances approximatives au lieu de les identifier directement. Considérez cela comme un seul point de données, et non comme une valeur universelle.
Équilibre à trouver. Précision pour les correspondances exactes au prix d’aucune généralisation sémantique.
3. Recherche hybride, la solution par défaut en production
Fonctionnement. La méthode exécute simultanément la méthode cosinus et BM25, puis fusionne les deux listes de classement à l’aide de la Reciprocal Rank Fusion (RRF). Aucune de ces méthodes ne couvre à elle seule tous les types de requêtes.
À l’utiliser quand. Les requêtes combinent le langage naturel avec des termes spécifiques, ce qui décrit presque tous les systèmes de production. Un utilisateur qui demande "quelle est la politique de remboursement pour la commande n°8821 ?" utilise à la fois un terme sémantique (politique de remboursement) et une correspondance exacte (n°8821).
Étape de fusion. Chaque technique restitue sa propre liste classée. RRF attribue à chaque document un score en fonction de sa position dans chacune des listes, soit 1 divisé par (k plus le rang), où k est une constante, généralement 60, puis additionne les scores obtenus grâce aux différentes techniques. Les documents bien classés dans les deux listes remontent en tête.
# Pseudocode for RRF merge
def rrf_score(rank, k=60):
return 1 / (k + rank)
def merge_results(cosine_results, bm25_results):
scores = {}
for rank, doc in enumerate(cosine_results):
scores[doc.id] = scores.get(doc.id, 0) + rrf_score(rank)
for rank, doc in enumerate(bm25_results):
scores[doc.id] = scores.get(doc.id, 0) + rrf_score(rank)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
Sur AWS, OpenSearch prend en charge cela nativement : vous configurez une requête hybride avec un paramètre de poids, et il gère la fusion en interne.
Équilibre à trouver. Un taux de rappel global optimal, mais à un coût de récupération environ deux fois plus élevé, ce qui est généralement justifié.
4. Réclassement avec un cross-encoder
Fonctionnement. Une approche en deux étapes. La première étape récupère les 20 meilleurs candidats grâce à une recherche cosinus ou hybride. La deuxième étape passe chaque candidat ainsi que la requête à un cross-encoder, qui évalue la pertinence en les examinant simultanément.
Quand l’utiliser. Lorsque les meilleurs résultats sont pertinents mais dans le mauvais ordre, et que la précision est plus importante que la latence. Il doit être utilisé dans la deuxième étape et jamais lors du premier passage.
Pourquoi deux étapes ? Un encodeur croisé évalue bien mieux la pertinence que la méthode cosinus, mais on ne peut pas l’appliquer à toute une base de données. Vingt appels au modèle au lieu d’un sont acceptables. Dix mille appels au moment de la requête, en revanche, ne le sont pas.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')# Stage 1: retrieve candidates
candidates = collection.query(query_embeddings=embedding, n_results=20)# Stage 2: rerank
pairs = [(query, chunk) for chunk in candidates['documents'][0]]
scores = reranker.predict(pairs)
top_5 = sorted(zip(scores, candidates['documents'][0]), reverse=True)[:5]
Avertissement important. Ne utilisez pas le réclassement en première étape. Le coût en termes de latence est élevé, car vous remplacez une recherche d’embedding par vingt inférences complètes du modèle.
Équilibre à trouver. Cela offre la plus grande précision, mais aussi les coûts les plus élevés ; utilisez-le donc uniquement en deuxième étape.
5. Relevance marginale maximale (MMR) pour la variété
Fonctionnement. Il équilibre la proximité avec la requête et la similarité avec les fragments déjà sélectionnés. Chaque nouveau choix doit être pertinent et différent de ce qui a déjà été choisi.
Quand l’utiliser. Lorsque tous les principaux fragments disent la même chose, par exemple pour une question de synthèse générale où chaque résultat réaffirme un même fait.
La règle lambda.
lambda = 1.0signifie une relevance pure, identique à la méthode cosinus standard
lambda = 0.0 signifie une diversité pure, en ignorant complètement la pertinencelambda = 0.5 constitue un point de départ équilibré à ajusterdef mmr_select(query_embedding, candidates, lambda_param=0.5, k=5):
selected = []
remaining = list(candidates)
while len(selected) < k and remaining:
mmr_scores = []
for doc in remaining:
relevance = cosine_similarity(query_embedding, doc.embedding)
if selected:
redundancy = max(cosine_similarity(doc.embedding, s.embedding)
for s in selected)
else:
redundancy = 0
mmr_scores.append(lambda_param * relevance - (1 - lambda_param) * redundancy)
best = remaining[mmr_scores.index(max(mmr_scores))]
selected.append(best)
remaining.remove(best)
return selected
Où cela brille. Résumés, questions de recherche et tout cas où le lecteur souhaite une couverture des idées plutôt qu’une approfondissement d’une seule.
Équilibre à trouver. Cela élimine la redondance, mais lambda nécessite des ajustements.
Sélection : laissez vos requêtes décider
Au préalable de choisir une technique, étudiez 20 requêtes réelles provenant de vos utilisateurs ou de votre domaine. Les requêtes hypothétiques ne comptent pas.
- 80 % ou plus sont en langue naturelle simple. Le cosinus est votre choix par défaut. Commencez par là, car il est rapide, précis et ne nécessite aucune infrastructure supplémentaire.
- 30 % ou plus contiennent des termes exacts, des codes, des IDs ou des noms propres. Ajoutez BM25. Une fois ce schéma identifié, la recherche hybride devient le choix par défaut en production, il faut donc mettre en œuvre la fusion RRF. L’amélioration du taux de rappel est généralement rapide et significative.
Ce que les tutoriels omettent
Les techniques peuvent entrer en conflit, plutôt que de se compléter simplement. Dans une expérience, l’ajout de l’expansion des requêtes, qui génère plusieurs variantes de la requête et effectue des recherches pour chacune d’elles, par-dessus la recherche hybride semblait être une solution gagnante. Une couverture plus large devrait théoriquement améliorer les résultats. Or, les scores sont passés de 0,807 à 0,702. De plus en plus de signaux de recherche amplifient autant le bruit que le signal, et les variantes supplémentaires entraînent l’inclusion de fragments adjacents mais incorrects. La recherche hybride fonctionnait déjà bien, et l’expansion a ajouté des interférences. Ajoutez une technique à la fois, mesurez son efficacité individuellement avant de les combiner, et n’oubliez pas que l’ordre compte, car ce qui est utile isolément peut nuire lorsqu’il est combiné.
Le produit scalaire n’est pas le cosinus. Certaines bases de données vectorielles utilisent par défaut le produit scalaire. Ces deux méthodes ne coïncident que lorsque les vecteurs sont normalisés unitairement. Si votre modèle d’embedding ne normalise pas sa sortie, les scores obtenus par le produit scalaire ne peuvent pas être comparés entre différentes requêtes ; ainsi, un score de 0,9 pour une requête et un autre de 0,9 pour une autre requête signifient des choses différentes. Vérifiez ce que votre base de données calcule réellement. ChromaDB, par exemple, est généralement décrit comme utilisant une métrique par défaut différente de celle de Qdrant, il convient donc de vérifier les deux dans la documentation actuelle.
Les requêtes déterminent la technique, pas les documents. Les ingénieurs passent souvent du temps à classer leur corpus en tant que technique ou conversationnel, long ou court. Cela a de l’importance pour le découpage en blocs. Pour choisir une technique de récupération, l’analyse des requêtes est bien plus cruciale. Un corpus technique recherché à l’aide de questions en langue naturelle constitue un problème de cosinus, tandis que le même corpus recherché par numéro de pièce constitue un problème hybride.
Le pipeline complet
Voici la conception en couches que de nombreux systèmes RAG en production finissent par adopter :
User Query
↓
[Optional: HyDE — if query is vague]
↓
Stage 1 Retrieval: Hybrid (Cosine + BM25 → RRF merge)
→ Top 20 candidates
↓
[Optional: Cross-encoder reranking — if precision matters]
→ Top 5 candidates
↓
[Optional: MMR — if results are redundant]
→ Final k candidates
↓
LLM Context Assembly + Generation
Tous les systèmes n’ont pas besoin de chaque couche. Commencez par la méthode cosinus, ajoutez une approche hybride lorsque des erreurs de correspondance exacte apparaissent, ajoutez un réclassement lorsque l’ordre des résultats est incorrect et ajoutez le MMR lorsque les mêmes résultats se répètent. Mesurez à chaque étape, car ce qui fonctionne bien pour un système peut nuire à un autre.
Le chiffre qui compte
Les rapports issus d’expériences de récupération montrent souvent un écart de rappel entre les recherches basées uniquement sur le cosinus et celles utilisant une approche hybride sur des ensembles de requêtes mixtes ; dans un petit projet, ce chiffre est passé de 0,25 à 0,75. Même si cela reste approximatif pour vos données, cela signifie que près de trois fois plus de réponses correctes parviennent au modèle linguistique avant qu’il n’écrive un mot, sans aucune modification de l’ingénierie des prompts, sans mise à jour du modèle et sans réorganisation de l’infrastructure. Tout revient donc à bien choisir la méthode de récupération.