
Comment un ordinateur peut-il comprendre que “voiture” et “automobile” sont des synonymes ? Grâce aux embeddings : des représentations numériques qui capturent le sens des mots et des phrases. Ce guide vous explique comment les utiliser pour construire un moteur de recherche sémantique.
À la fin de ce guide, vous saurez générer des embeddings, calculer des similarités et choisir le bon modèle pour votre projet.
Qu’est-ce qu’un embedding ?
Section intitulée « Qu’est-ce qu’un embedding ? »Un embedding est un vecteur de nombres (par exemple 384 dimensions) qui représente le sens d’un texte dans un espace mathématique.
"Ansible gère les serveurs" → [0.12, -0.34, 0.78, ..., 0.56]"Kubernetes orchestre les pods" → [-0.23, 0.45, 0.12, ..., 0.89]"La recette du gâteau" → [0.67, 0.23, -0.45, ..., -0.12]L’idée clé : les textes au sens similaire ont des vecteurs proches. Les textes différents ont des vecteurs éloignés.
Espace vectoriel (simplifié en 2D) :
DevOps ────┬──── Ansible │ │ │ ├── Kubernetes │ │ └──── Docker
Cuisine ───┬──── Gâteau │ └──── RecetteDans cet espace, “Ansible” est proche de “Kubernetes” mais loin de “Gâteau”.
Pourquoi les embeddings sont essentiels pour le RAG
Section intitulée « Pourquoi les embeddings sont essentiels pour le RAG »Un système RAG a besoin de trouver les passages pertinents pour répondre à une question. Deux approches :
| Approche | Méthode | Limite |
|---|---|---|
| Recherche exacte | Mots-clés (BM25, TF-IDF) | “voiture” ≠ “automobile” |
| Recherche sémantique | Embeddings + similarité | Comprend les synonymes et le contexte |
Exemple :
Question : "Comment gérer mes serveurs automatiquement ?"Document : "Ansible est un outil d'automatisation IT."
Recherche exacte : ✗ (pas de mot commun)Recherche sémantique : ✓ (sens très proche)Prérequis
Section intitulée « Prérequis »# Environnement Python 3.10+python3 -m venv embeddings-testsource embeddings-test/bin/activate
# sentence-transformers (inclut PyTorch)pip install sentence-transformers numpyGénérer des embeddings avec sentence-transformers
Section intitulée « Générer des embeddings avec sentence-transformers »La bibliothèque sentence-transformers fournit une API simple pour encoder du texte :
from sentence_transformers import SentenceTransformer
# Charger un modèle multilinguemodel = SentenceTransformer("intfloat/multilingual-e5-small")
# Encoder des textestextes = [ "Ansible est un outil d'automatisation", "Docker crée des conteneurs", "La recette du gâteau au chocolat"]
# Générer les embeddingsembeddings = model.encode(textes, show_progress_bar=False)
print(f"Nombre de textes : {len(embeddings)}")print(f"Dimensions : {embeddings.shape[1]}")print(f"Type : {embeddings.dtype}")Résultat :
Nombre de textes : 3Dimensions : 384Type : float32Chaque texte est transformé en un vecteur de 384 nombres à virgule flottante.
Les préfixes E5 : query vs passage
Section intitulée « Les préfixes E5 : query vs passage »Les modèles E5 (comme multilingual-e5-small) utilisent des préfixes pour distinguer les requêtes des documents :
| Préfixe | Usage | Exemple |
|---|---|---|
query: | Question de l’utilisateur | "query: Comment installer Ansible ?" |
passage: | Document à indexer | "passage: Ansible est un outil..." |
# ✗ Sans préfixe (moins précis)embeddings = model.encode(["Comment installer Ansible ?"])
# ✓ Avec préfixes (recommandé pour E5)query_emb = model.encode(["query: Comment installer Ansible ?"])doc_emb = model.encode(["passage: Ansible est un outil d'automatisation..."])Mesurer la similarité entre vecteurs
Section intitulée « Mesurer la similarité entre vecteurs »Une fois les embeddings générés, il faut mesurer leur proximité. La métrique la plus utilisée est la similarité cosinus.
Similarité cosinus
Section intitulée « Similarité cosinus »La similarité cosinus mesure l’angle entre deux vecteurs :
- 1.0 = vecteurs identiques (même direction)
- 0.0 = vecteurs perpendiculaires (aucune relation)
- -1.0 = vecteurs opposés (sens contraire)
from sentence_transformers.util import cos_sim
# Trois textes avec des relations différentestextes = [ "Ansible automatise la configuration des serveurs", "Comment gérer l'infrastructure avec Ansible ?", "La recette du gâteau au chocolat"]
# Avec préfixes E5passages = [f"passage: {t}" for t in textes]embeddings = model.encode(passages)
# Matrice de similaritéprint("Matrice de similarité cosinus :")print(cos_sim(embeddings, embeddings))Résultat :
tensor([[1.0000, 0.9012, 0.7845], [0.9012, 1.0000, 0.7623], [0.7845, 0.7623, 1.0000]])Lecture :
- Texte 1 ↔ Texte 2 : 0.90 (très similaires — même sujet : Ansible)
- Texte 1 ↔ Texte 3 : 0.78 (différents — DevOps vs cuisine)
- Diagonale : 1.00 (un texte est identique à lui-même)
Autres métriques de similarité
Section intitulée « Autres métriques de similarité »| Métrique | Formule | Usage |
|---|---|---|
| Cosinus | (a · b) / (‖a‖ × ‖b‖) | Standard pour embeddings normalisés |
| Dot product | a · b | Équivalent au cosinus si vecteurs normalisés |
| Euclidienne | ‖a - b‖ | Distance (inverser le sens : 0 = proche) |
from sentence_transformers.util import cos_sim, dot_scoreimport numpy as np
a = model.encode(["passage: Ansible"])b = model.encode(["passage: Kubernetes"])
# Cosinusprint(f"Cosinus : {cos_sim(a, b)[0][0]:.4f}")
# Dot productprint(f"Dot product : {dot_score(a, b)[0][0]:.4f}")
# Distance euclidiennedist = np.linalg.norm(a - b)print(f"Euclidienne : {dist:.4f}")Recherche sémantique : trouver les documents pertinents
Section intitulée « Recherche sémantique : trouver les documents pertinents »L’usage principal des embeddings est la recherche sémantique : trouver les documents les plus proches d’une question.
from sentence_transformers import SentenceTransformerfrom sentence_transformers.util import cos_simimport numpy as np
model = SentenceTransformer("intfloat/multilingual-e5-small")
# Base de documentsdocuments = [ "Ansible est un outil d'automatisation IT open source", "Les playbooks Ansible sont écrits en YAML", "Docker permet de créer des conteneurs isolés", "Kubernetes orchestre les conteneurs dans un cluster", "Terraform gère l'infrastructure as code",]
# Indexer les documentsdoc_embeddings = model.encode([f"passage: {d}" for d in documents])
def rechercher(question: str, top_k: int = 3) -> list[tuple[str, float]]: """Recherche les documents les plus pertinents.""" query_emb = model.encode([f"query: {question}"])
# Calculer les similarités scores = cos_sim(query_emb, doc_embeddings)[0]
# Trier par score décroissant indices = np.argsort(scores.numpy())[::-1][:top_k]
return [(documents[i], float(scores[i])) for i in indices]
# Testquestion = "Comment automatiser la gestion de mes serveurs ?"resultats = rechercher(question)
print(f"Question : {question}\n")print("Résultats :")for doc, score in resultats: print(f" [{score:.3f}] {doc}")Résultat :
Question : Comment automatiser la gestion de mes serveurs ?
Résultats : [0.876] Ansible est un outil d'automatisation IT open source [0.812] Les playbooks Ansible sont écrits en YAML [0.798] Terraform gère l'infrastructure as codeLe système trouve Ansible comme meilleur résultat, même si le mot “serveur” n’apparaît pas dans le document.
Similarité cross-lingue (multilingue)
Section intitulée « Similarité cross-lingue (multilingue) »Les modèles multilingues comme E5 comprennent plusieurs langues dans le même espace vectoriel :
textes = [ ("FR", "Comment installer Ansible sur Ubuntu ?"), ("EN", "How to install Ansible on Ubuntu?"), ("FR", "Déployer une application avec Kubernetes"), ("EN", "Deploy an application with Kubernetes"), ("FR", "La recette du gâteau au chocolat"),]
embeddings = model.encode([f"passage: {t[1]}" for t in textes])
# Question en françaisquestion_fr = "query: Guide d'installation d'Ansible"query_emb = model.encode([question_fr])
print(f"Question (FR) : '{question_fr[7:]}'")print("\nScores par document :")
scores = cos_sim(query_emb, embeddings)[0]for i, (lang, texte) in enumerate(textes): print(f" [{lang}] {scores[i]:.3f} - {texte}")Résultat :
Question (FR) : 'Guide d'installation d'Ansible'
Scores par document : [FR] 0.914 - Comment installer Ansible sur Ubuntu ? [EN] 0.891 - How to install Ansible on Ubuntu? [FR] 0.875 - Déployer une application avec Kubernetes [EN] 0.859 - Deploy an application with Kubernetes [FR] 0.846 - La recette du gâteau au chocolatChoisir le bon modèle d’embeddings
Section intitulée « Choisir le bon modèle d’embeddings »Le choix du modèle impacte la qualité, la vitesse et la compatibilité linguistique.
Modèles recommandés
Section intitulée « Modèles recommandés »| Modèle | Dimensions | Langues | Taille | Usage |
|---|---|---|---|---|
intfloat/multilingual-e5-small | 384 | 100+ | 471 Mo | Multilingue, bon compromis |
intfloat/multilingual-e5-base | 768 | 100+ | 1.1 Go | Multilingue, meilleure qualité |
all-MiniLM-L6-v2 | 384 | EN | 80 Mo | Anglais, très rapide |
BAAI/bge-m3 | 1024 | 100+ | 2.2 Go | État de l’art multilingue |
Critères de choix
Section intitulée « Critères de choix »Recommandation : intfloat/multilingual-e5-small
- Multilingue (FR, EN, etc.)
- Bon équilibre qualité/vitesse
- Fonctionne bien sur CPU
- Préfixes
query:/passage:à ajouter
model = SentenceTransformer("intfloat/multilingual-e5-small")Recommandation : all-MiniLM-L6-v2
- Ultra rapide (80 Mo)
- Excellent pour le prototypage
- Pas de préfixes nécessaires
model = SentenceTransformer("all-MiniLM-L6-v2")Recommandation : BAAI/bge-m3
- État de l’art sur MTEB
- Supporte dense + sparse retrieval
- 1024 dimensions (plus précis)
- Plus lourd (2.2 Go)
model = SentenceTransformer("BAAI/bge-m3")Benchmark de performance
Section intitulée « Benchmark de performance »Test sur un CPU Intel i7 (encodage de 100 phrases) :
| Modèle | Temps | Score MTEB (retrieval) |
|---|---|---|
| all-MiniLM-L6-v2 | 0.8s | 56.3 |
| multilingual-e5-small | 1.2s | 61.5 |
| multilingual-e5-base | 2.8s | 64.2 |
| bge-m3 | 4.5s | 68.4 |
Impact de la longueur du texte
Section intitulée « Impact de la longueur du texte »Les embeddings ont un comportement différent selon la longueur du texte :
texte_court = "Ansible gère les serveurs"texte_moyen = "Ansible est un outil d'automatisation IT qui permet de gérer la configuration des serveurs de manière déclarative"texte_long = texte_moyen + ". Il utilise SSH pour se connecter aux serveurs distants et exécuter des tâches. Cette approche agentless simplifie l'infrastructure."
embs = model.encode([ f"passage: {texte_court}", f"passage: {texte_moyen}", f"passage: {texte_long}"])
query = "query: Outil pour gérer l'infrastructure serveurs"query_emb = model.encode([query])
print(f"Question : '{query[7:]}'")print(f"\n{'Version':<10} {'Longueur':>10} {'Score':>10}")print("-" * 35)for i, (nom, texte) in enumerate([ ("Court", texte_court), ("Moyen", texte_moyen), ("Long", texte_long)]): score = cos_sim(query_emb, embs[i:i+1])[0][0] print(f"{nom:<10} {len(texte):>10} {score:.4f}")Résultat typique :
Question : 'Outil pour gérer l'infrastructure serveurs'
Version Longueur Score-----------------------------------Court 25 0.8863Moyen 113 0.8641Long 340 0.8680Intégration dans un pipeline RAG
Section intitulée « Intégration dans un pipeline RAG »Les embeddings sont la pierre angulaire du RAG. Voici comment ils s’intègrent :
from sentence_transformers import SentenceTransformerimport chromadb
# 1. Charger le modèle (une fois)model = SentenceTransformer("intfloat/multilingual-e5-small")
# 2. Configurer ChromaDBclient = chromadb.PersistentClient(path="./ma_base")collection = client.get_or_create_collection( name="documentation", metadata={"hnsw:space": "cosine"} # Utiliser cosinus)
# 3. Indexer des documentsdef indexer(documents: list[str], ids: list[str]): embeddings = model.encode([f"passage: {d}" for d in documents]) collection.add( embeddings=embeddings.tolist(), documents=documents, ids=ids )
# 4. Rechercherdef rechercher(question: str, n_results: int = 3): query_emb = model.encode([f"query: {question}"]) resultats = collection.query( query_embeddings=query_emb.tolist(), n_results=n_results ) return resultats
# Usageindexer( documents=["Ansible automatise...", "Docker conteneurise..."], ids=["doc1", "doc2"])
resultats = rechercher("Comment automatiser mes serveurs ?")print(resultats["documents"])Optimisations pour la production
Section intitulée « Optimisations pour la production »Batch encoding
Section intitulée « Batch encoding »Encodez plusieurs textes à la fois pour de meilleures performances :
# ✗ Lent : un par unfor doc in documents: emb = model.encode(doc)
# ✓ Rapide : par batchembeddings = model.encode(documents, batch_size=32, show_progress_bar=True)Normalisation explicite
Section intitulée « Normalisation explicite »Si votre base vectorielle attend des vecteurs normalisés :
embeddings = model.encode(documents, normalize_embeddings=True)GPU (si disponible)
Section intitulée « GPU (si disponible) »model = SentenceTransformer("intfloat/multilingual-e5-small", device="cuda")Erreurs courantes et solutions
Section intitulée « Erreurs courantes et solutions »| Erreur | Cause | Solution |
|---|---|---|
| Scores toujours ~0.8 | Préfixes manquants (E5) | Ajouter query:/passage: |
| Résultats non pertinents | Mauvais modèle pour la langue | Utiliser un modèle multilingue |
| Mémoire insuffisante | Modèle trop gros | Utiliser multilingual-e5-small |
| Encodage lent | Batch size trop petit | Augmenter batch_size |
À retenir
Section intitulée « À retenir »| Point clé | Recommandation |
|---|---|
| Modèle débutant | intfloat/multilingual-e5-small |
| Préfixes E5 | query: pour questions, passage: pour documents |
| Métrique | Similarité cosinus (ou dot product si normalisé) |
| Taille texte idéale | 100-300 caractères par chunk |
| Batch encoding | Toujours encoder par lots |