Recherche sémantique vs mots-clés
Objectifs
- Comprendre les limites de la recherche par mots-clés
- Découvrir comment la recherche sémantique résout ces problèmes
- Implémenter une recherche sémantique simple
Les limites de la recherche par mots-clés
La recherche classique (type TF-IDF, BM25, Elasticsearch) repose sur la correspondance lexicale : elle cherche les mots exacts de la requête dans les documents.
Problèmes courants
# Recherche par mots-clés
requete = "comment nourrir un chiot"
# ✅ Trouvé : contient les mêmes mots
doc_a = "Comment nourrir un chiot de 3 mois"
# ❌ Pas trouvé : mots différents, même sens
doc_b = "L'alimentation du jeune chien"
# ❌ Pas trouvé : reformulation
doc_c = "Régime alimentaire pour les chiots nouveau-nés"
# ⚠️ Faux positif : mêmes mots, sens différent
doc_d = "Comment nourrir un projet de startup comme un chiot"
La recherche par mots-clés échoue face aux synonymes, aux reformulations et aux ambiguïtés.
La recherche sémantique
La recherche sémantique compare le sens des textes, pas leurs mots. Elle utilise les embeddings pour trouver les documents dont la signification est la plus proche de la requête.
from openai import OpenAI
import numpy as np
client = OpenAI()
def embed(texte: str) -> np.ndarray:
response = client.embeddings.create(
input=texte,
model="text-embedding-3-large"
)
return np.array(response.data[0].embedding)
# Corpus
documents = [
"Comment nourrir un chiot de 3 mois",
"L'alimentation du jeune chien",
"Régime alimentaire pour les chiots nouveau-nés",
"Comment nourrir un projet de startup comme un chiot",
"Les bases de la cuisine italienne",
]
# Indexer le corpus
corpus_embeddings = np.array([embed(doc) for doc in documents])
# Rechercher
requete = "comment nourrir un chiot"
query_embedding = embed(requete)
# Calculer les similarités
similarites = corpus_embeddings @ query_embedding
# Trier par pertinence
resultats = sorted(
enumerate(similarites), key=lambda x: x[1], reverse=True
)
for idx, score in resultats:
print(f"{score:.4f} | {documents[idx]}")
Résultat attendu :
0.9123 | Comment nourrir un chiot de 3 mois
0.8756 | Régime alimentaire pour les chiots nouveau-nés
0.8421 | L'alimentation du jeune chien
0.4532 | Comment nourrir un projet de startup comme un chiot
0.1245 | Les bases de la cuisine italienne
La recherche sémantique retrouve les documents pertinents même avec des mots différents, et relègue le faux positif en bas du classement.
Comparaison structurée
| Critère | Mots-clés (BM25) | Sémantique (embeddings) |
|---|---|---|
| Synonymes | ❌ Échoue | ✅ Fonctionne |
| Reformulations | ❌ Échoue | ✅ Fonctionne |
| Multilingue | ❌ Impossible | ✅ Possible |
| Fautes d’orthographe | ❌ Échoue | ✅ Tolère |
| Correspondance exacte | ✅ Excellente | ⚠️ Parfois imprécise |
| Noms propres, codes | ✅ Excellente | ⚠️ Peut confondre |
| Vitesse (grand corpus) | ✅ Très rapide | ⚠️ Nécessite un index |
| Coût | ✅ Gratuit | ⚠️ Appels API |
Quand utiliser quoi ?
Recherche par mots-clés
- Recherche de codes produits, références, noms propres
- Filtres exacts (dates, catégories)
- Corpus très volumineux sans budget embedding
Recherche sémantique
- FAQ, support client, documentation
- Recherche en langage naturel
- Corpus multilingue
- Requêtes vagues ou exploratoires
Recherche hybride (le meilleur des deux)
Combinez les deux approches pour couvrir tous les cas. C’est le sujet de la leçon 9.
Implémenter une recherche sémantique complète
class RechercheSemantique:
def __init__(self, model: str = "text-embedding-3-large"):
self.client = OpenAI()
self.model = model
self.documents: list[dict] = []
self.embeddings: np.ndarray | None = None
def indexer(self, documents: list[dict]):
"""Indexe une liste de documents {id, texte, ...}."""
self.documents = documents
textes = [d["texte"] for d in documents]
response = self.client.embeddings.create(
input=textes,
model=self.model
)
vecteurs = sorted(response.data, key=lambda x: x.index)
self.embeddings = np.array(
[v.embedding for v in vecteurs], dtype=np.float32
)
def rechercher(self, requete: str, k: int = 5) -> list[dict]:
"""Recherche les k documents les plus pertinents."""
query_emb = self.client.embeddings.create(
input=requete,
model=self.model
).data[0].embedding
similarites = self.embeddings @ np.array(query_emb)
top_k = np.argsort(similarites)[-k:][::-1]
return [
{**self.documents[i], "score": float(similarites[i])}
for i in top_k
]
# Utilisation
moteur = RechercheSemantique()
moteur.indexer([
{"id": 1, "texte": "Comment installer Python sur Windows"},
{"id": 2, "texte": "Guide de configuration de l'environnement Python"},
{"id": 3, "texte": "Les bases du langage Python"},
])
resultats = moteur.rechercher("mettre en place Python sur mon PC")
for r in resultats:
print(f"[{r['score']:.3f}] {r['texte']}")
Résumé
- La recherche par mots-clés échoue face aux synonymes et reformulations
- La recherche sémantique compare le sens via les embeddings
- Chaque approche a ses forces : la recherche hybride combine les deux
- Un moteur sémantique simple se construit en quelques dizaines de lignes