5. Retrieval

Dense Retrieval : Recherche sémantique avec embeddings

5 mars 2026
Équipe Ailog

Maîtrisez le dense retrieval pour une recherche sémantique performante. Embeddings, modèles, indexation vectorielle et optimisations avancées.

Dense Retrieval : Recherche sémantique avec embeddings

Le dense retrieval transforme la recherche d'information en capturant le sens profond des requêtes et documents. Contrairement à la recherche lexicale qui compare des mots, le dense retrieval compare des concepts. Ce guide approfondit les mécanismes, modèles et techniques pour implémenter une recherche sémantique performante dans vos systèmes RAG.

Qu'est-ce que le dense retrieval ?

Le dense retrieval représente chaque texte par un vecteur dense de nombres réels, typiquement entre 384 et 4096 dimensions. Ces vecteurs, appelés embeddings, capturent la sémantique du texte dans un espace mathématique où des concepts similaires sont proches.

Différence avec le sparse retrieval

CaractéristiqueDense RetrievalSparse Retrieval
ReprésentationVecteurs denses (384-4096 dim)Vecteurs sparse (vocabulaire)
CorrespondanceSémantiqueLexicale
"Voiture" = "Automobile"OuiNon
Termes raresMoins performantExcellent
Typos et variantesRobusteSensible

Le dense retrieval excelle quand l'utilisateur formule sa requête différemment du contenu source. "Comment annuler ma commande" trouvera "Procédure de résiliation d'achat" même sans mots communs.

Architecture d'un système dense retrieval

┌──────────────────────────────────────────────────────────────┐
│                    Pipeline Dense Retrieval                   │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   Documents          Modèle           Base Vectorielle       │
│   ┌─────────┐       Embedding         ┌──────────────┐      │
│   │ Doc 1   │──┐    ┌───────┐    ┌───│   Qdrant     │      │
│   │ Doc 2   │──┼───▶│ E5    │────┼───│   Pinecone   │      │
│   │ Doc 3   │──┘    │ BGE   │    │   │   Weaviate   │      │
│   └─────────┘       └───────┘    │   └──────────────┘      │
│                                   │          │              │
│   Query                          │          │ ANN Search   │
│   ┌─────────┐                    │          ▼              │
│   │"Comment │────────────────────┘   ┌──────────────┐      │
│   │annuler" │                        │ Top-K docs   │      │
│   └─────────┘                        └──────────────┘      │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Modèles d'embedding : le comparatif 2024

Le choix du modèle d'embedding impacte directement la qualité de votre retrieval. Voici les options actuelles classées par performance sur les benchmarks MTEB.

Modèles open source

DEVELOPERpython
from sentence_transformers import SentenceTransformer # BGE-M3 : Meilleur rapport qualité/performance multilingue model_bge = SentenceTransformer('BAAI/bge-m3') # E5-Large : Excellent pour le français model_e5 = SentenceTransformer('intfloat/multilingual-e5-large') # GTE-Large : Alternative performante model_gte = SentenceTransformer('thenlper/gte-large')
ModèleDimensionsMTEB FRVitesseUsage
BGE-M3102462.3MoyenProduction multilingue
E5-Large-v2102461.8MoyenFrançais prioritaire
GTE-Large102460.2RapideVolume élevé
All-MiniLM38452.1Très rapidePrototypage

Modèles propriétaires

DEVELOPERpython
import openai # OpenAI text-embedding-3 client = openai.OpenAI() response = client.embeddings.create( model="text-embedding-3-large", input="Comment annuler ma commande ?", dimensions=1536 # Configurable jusqu'à 3072 ) embedding = response.data[0].embedding # Cohere Embed v3 import cohere co = cohere.Client() response = co.embed( texts=["Comment annuler ma commande ?"], model="embed-multilingual-v3.0", input_type="search_query" )
ModèleDimensionsPerformanceCoût/1M tokens
text-embedding-3-large3072Excellent$0.13
text-embedding-3-small1536Très bon$0.02
Cohere embed-v31024Excellent$0.10
Voyage-21024État de l'art$0.12

Optimiser la qualité des embeddings

Préfixes query/passage

Certains modèles nécessitent des préfixes pour distinguer les requêtes des documents :

DEVELOPERpython
from sentence_transformers import SentenceTransformer model = SentenceTransformer('intfloat/multilingual-e5-large') # IMPORTANT : Préfixer correctement query = "query: Comment fonctionne la garantie ?" passages = [ "passage: La garantie couvre les défauts pendant 2 ans.", "passage: Contactez le SAV pour toute réclamation." ] query_embedding = model.encode(query) passage_embeddings = model.encode(passages)

Sans ces préfixes, la performance peut chuter de 5 à 15 points sur les benchmarks.

Normalisation des embeddings

Pour utiliser le produit scalaire au lieu de la similarité cosinus (plus rapide) :

DEVELOPERpython
import numpy as np def normalize(embeddings): norms = np.linalg.norm(embeddings, axis=1, keepdims=True) return embeddings / norms # Embeddings normalisés embeddings_norm = normalize(embeddings) # Maintenant dot product = cosine similarity similarity = np.dot(query_norm, doc_norm.T)

Matryoshka embeddings

Les modèles récents supportent les "Matryoshka embeddings" : des embeddings où les premières dimensions sont les plus informatives.

DEVELOPERpython
# Avec text-embedding-3, vous pouvez réduire les dimensions response = client.embeddings.create( model="text-embedding-3-large", input=texts, dimensions=512 # Au lieu de 3072, avec perte minime ) # Recherche rapide avec dimensions réduites # puis reranking avec dimensions complètes

Cela permet un compromis vitesse/précision configurable.

Indexation vectorielle avancée

Algorithmes ANN (Approximate Nearest Neighbor)

La recherche exacte (brute force) est O(n). Les algorithmes ANN réduisent à O(log n) avec une légère perte de précision.

HNSW (Hierarchical Navigable Small Worlds)

Le plus utilisé. Excellent compromis vitesse/précision.

DEVELOPERpython
from qdrant_client import QdrantClient from qdrant_client.models import VectorParams, HnswConfigDiff client = QdrantClient("localhost", port=6333) # Configuration HNSW optimisée client.create_collection( collection_name="documents", vectors_config=VectorParams( size=1024, distance="Cosine" ), hnsw_config=HnswConfigDiff( m=16, # Connexions par nœud (16-64) ef_construct=100, # Qualité de construction ) ) # À la recherche, ajuster ef pour précision/vitesse results = client.search( collection_name="documents", query_vector=query_embedding, limit=10, search_params={"ef": 128} # Plus haut = plus précis, plus lent )

IVF (Inverted File Index)

Divise l'espace en clusters. Plus rapide mais moins précis.

DEVELOPERpython
# Avec FAISS import faiss # Créer l'index IVF dimension = 1024 nlist = 100 # Nombre de clusters quantizer = faiss.IndexFlatL2(dimension) index = faiss.IndexIVFFlat(quantizer, dimension, nlist) # Entraîner sur les données index.train(embeddings) index.add(embeddings) # Recherche avec nprobe clusters index.nprobe = 10 # Plus haut = plus précis distances, indices = index.search(query_embedding, k=10)

Quantification pour réduire la mémoire

Réduire la taille des vecteurs de 75% avec une perte minimale :

DEVELOPERpython
# Quantification scalaire (int8) from qdrant_client.models import QuantizationConfig, ScalarQuantization client.create_collection( collection_name="documents_quantized", vectors_config=VectorParams(size=1024, distance="Cosine"), quantization_config=QuantizationConfig( scalar=ScalarQuantization( type="int8", quantile=0.99, always_ram=True ) ) ) # 1M vecteurs 1024d : 4GB → 1GB # Perte de recall : ~1-2%

Évaluation du dense retrieval

Métriques essentielles

DEVELOPERpython
def evaluate_retrieval(queries, ground_truth, retriever, k_values=[1, 5, 10]): metrics = {} for k in k_values: recalls = [] precisions = [] for query, relevant_docs in zip(queries, ground_truth): retrieved = retriever.search(query, top_k=k) retrieved_ids = [doc.id for doc in retrieved] # Recall@k hits = len(set(retrieved_ids) & set(relevant_docs)) recall = hits / len(relevant_docs) recalls.append(recall) # Precision@k precision = hits / k precisions.append(precision) metrics[f"recall@{k}"] = np.mean(recalls) metrics[f"precision@{k}"] = np.mean(precisions) # MRR mrr_scores = [] for query, relevant_docs in zip(queries, ground_truth): retrieved = retriever.search(query, top_k=100) for i, doc in enumerate(retrieved): if doc.id in relevant_docs: mrr_scores.append(1 / (i + 1)) break else: mrr_scores.append(0) metrics["mrr"] = np.mean(mrr_scores) return metrics

Benchmarks recommandés

  • BEIR : Benchmark multidomaine pour le retrieval
  • MTEB : Massive Text Embedding Benchmark
  • MS MARCO : Questions-réponses web

Pièges courants et solutions

1. Domain shift

Les modèles génériques performent mal sur du vocabulaire spécialisé.

Solution : Fine-tuning contrastif

DEVELOPERpython
from sentence_transformers import SentenceTransformer, InputExample, losses model = SentenceTransformer('BAAI/bge-m3') # Données d'entraînement : paires (query, positive_doc) train_examples = [ InputExample(texts=["Symptômes COVID", "Fièvre, toux sèche, fatigue"]), InputExample(texts=["Traitement grippe", "Repos, hydratation, paracétamol"]), ] # Loss contrastive train_loss = losses.MultipleNegativesRankingLoss(model) # Fine-tuning model.fit( train_objectives=[(train_dataloader, train_loss)], epochs=3, warmup_steps=100 )

2. Requêtes courtes

Les requêtes de 1-2 mots ont des embeddings peu discriminants.

Solution : Query expansion

DEVELOPERpython
def expand_query(query: str, llm) -> str: if len(query.split()) <= 3: expansion = llm.complete( f"Reformule cette recherche en une phrase complète: {query}" ) return f"{query} {expansion}" return query

3. Documents longs

Les embeddings de documents trop longs perdent en précision.

Solution : Late interaction ou chunking avec agrégation

DEVELOPERpython
def embed_long_document(doc: str, model, chunk_size=512): chunks = split_into_chunks(doc, chunk_size) chunk_embeddings = model.encode(chunks) # Agrégation : moyenne pondérée par position weights = np.exp(-np.arange(len(chunks)) * 0.1) weights /= weights.sum() return np.average(chunk_embeddings, axis=0, weights=weights)

Intégration dans un pipeline RAG

DEVELOPERpython
class DenseRetriever: def __init__(self, model_name: str, collection: str): self.model = SentenceTransformer(model_name) self.client = QdrantClient("localhost", port=6333) self.collection = collection def index(self, documents: list[dict]): embeddings = self.model.encode( [doc["content"] for doc in documents], show_progress_bar=True ) points = [ PointStruct( id=doc["id"], vector=emb.tolist(), payload={"content": doc["content"], **doc.get("metadata", {})} ) for doc, emb in zip(documents, embeddings) ] self.client.upsert(collection_name=self.collection, points=points) def search(self, query: str, top_k: int = 5, filters: dict = None): query_embedding = self.model.encode(f"query: {query}") filter_conditions = self._build_filters(filters) if filters else None results = self.client.search( collection_name=self.collection, query_vector=query_embedding.tolist(), query_filter=filter_conditions, limit=top_k ) return [ {"content": hit.payload["content"], "score": hit.score} for hit in results ]

Prochaines étapes

Le dense retrieval est puissant mais pas universel. Pour certains cas, la recherche lexicale reste supérieure. Découvrez comment dans nos guides connexes :

FAQ

La recherche par mots-clés (BM25) compare les termes exacts entre requête et documents. Le dense retrieval encode requête et documents en vecteurs denses qui capturent le sens sémantique. Ainsi, "Comment annuler ma commande" trouve "Procédure de résiliation d'achat" même sans mots communs. Le dense excelle pour les reformulations, le sparse pour les correspondances exactes (codes, références).
Pour le français, BGE-M3 et E5-Large-v2 offrent les meilleures performances sur les benchmarks MTEB. BGE-M3 (1024 dimensions) est le meilleur choix multilingue en production. Pour des contraintes de vitesse, All-MiniLM (384 dimensions) est 3x plus rapide avec une perte de qualité acceptable. Les modèles propriétaires (OpenAI, Cohere) sont excellents mais impliquent des coûts API et des considérations de souveraineté.
Certains modèles (E5, BGE) sont entraînés avec des préfixes différents pour les requêtes et les documents. Sans ces préfixes, la performance peut chuter de 5-15% sur les benchmarks. Le préfixe indique au modèle le rôle du texte : une requête courte cherchant de l'information vs un passage de document contenant de l'information. Vérifiez toujours la documentation du modèle utilisé.
Trois techniques principales : la quantification scalaire (int8) réduit la taille de 75% avec une perte de recall de 1-2%. Les Matryoshka embeddings permettent de réduire les dimensions (ex: 3072 vers 512) avec perte minimale. L'algorithme IVF divise l'espace en clusters pour accélérer la recherche. Combiner quantification et dimensions réduites permet de stocker 10x plus de vecteurs dans la même mémoire.
Pour du vocabulaire généraliste, les modèles pré-entraînés suffisent. Le fine-tuning devient nécessaire pour des domaines spécialisés (médical, juridique, technique) où le vocabulaire diffère significativement du corpus d'entraînement. Le fine-tuning contrastif avec quelques centaines de paires (requête, document pertinent) peut améliorer le recall de 10-20% sur votre domaine spécifique. ---

Passez au dense retrieval avec Ailog

Implémenter un système dense retrieval performant demande expertise et infrastructure. Avec Ailog, bénéficiez :

  • Modèles d'embedding optimisés pour le français (BGE-M3, E5-Large)
  • Indexation HNSW automatique avec Qdrant
  • Quantification intelligente pour optimiser coûts et performances
  • Fine-tuning assisté sur votre vocabulaire métier

Testez gratuitement et déployez votre recherche sémantique en minutes.

Tags

ragretrievalembeddingsdense retrievalrecherche sémantique

Articles connexes

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !