Aller au contenu
Développement medium

Embeddings : transformer du texte en vecteurs pour la recherche sémantique

17 min de lecture

logo python

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.

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
└──── Recette

Dans 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 :

ApprocheMéthodeLimite
Recherche exacteMots-clés (BM25, TF-IDF)“voiture” ≠ “automobile”
Recherche sémantiqueEmbeddings + 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)
Fenêtre de terminal
# Environnement Python 3.10+
python3 -m venv embeddings-test
source embeddings-test/bin/activate
# sentence-transformers (inclut PyTorch)
pip install sentence-transformers numpy

Gé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 multilingue
model = SentenceTransformer("intfloat/multilingual-e5-small")
# Encoder des textes
textes = [
"Ansible est un outil d'automatisation",
"Docker crée des conteneurs",
"La recette du gâteau au chocolat"
]
# Générer les embeddings
embeddings = 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 : 3
Dimensions : 384
Type : float32

Chaque texte est transformé en un vecteur de 384 nombres à virgule flottante.

Les modèles E5 (comme multilingual-e5-small) utilisent des préfixes pour distinguer les requêtes des documents :

PréfixeUsageExemple
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..."])

Une fois les embeddings générés, il faut mesurer leur proximité. La métrique la plus utilisée est la 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érentes
textes = [
"Ansible automatise la configuration des serveurs",
"Comment gérer l'infrastructure avec Ansible ?",
"La recette du gâteau au chocolat"
]
# Avec préfixes E5
passages = [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)
MétriqueFormuleUsage
Cosinus(a · b) / (‖a‖ × ‖b‖)Standard pour embeddings normalisés
Dot producta · 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_score
import numpy as np
a = model.encode(["passage: Ansible"])
b = model.encode(["passage: Kubernetes"])
# Cosinus
print(f"Cosinus : {cos_sim(a, b)[0][0]:.4f}")
# Dot product
print(f"Dot product : {dot_score(a, b)[0][0]:.4f}")
# Distance euclidienne
dist = 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 SentenceTransformer
from sentence_transformers.util import cos_sim
import numpy as np
model = SentenceTransformer("intfloat/multilingual-e5-small")
# Base de documents
documents = [
"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 documents
doc_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]
# Test
question = "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 code

Le système trouve Ansible comme meilleur résultat, même si le mot “serveur” n’apparaît pas dans le document.

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çais
question_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 chocolat

Le choix du modèle impacte la qualité, la vitesse et la compatibilité linguistique.

ModèleDimensionsLanguesTailleUsage
intfloat/multilingual-e5-small384100+471 MoMultilingue, bon compromis
intfloat/multilingual-e5-base768100+1.1 GoMultilingue, meilleure qualité
all-MiniLM-L6-v2384EN80 MoAnglais, très rapide
BAAI/bge-m31024100+2.2 GoÉtat de l’art multilingue

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")

Test sur un CPU Intel i7 (encodage de 100 phrases) :

ModèleTempsScore MTEB (retrieval)
all-MiniLM-L6-v20.8s56.3
multilingual-e5-small1.2s61.5
multilingual-e5-base2.8s64.2
bge-m34.5s68.4

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.8863
Moyen 113 0.8641
Long 340 0.8680

Les embeddings sont la pierre angulaire du RAG. Voici comment ils s’intègrent :

from sentence_transformers import SentenceTransformer
import chromadb
# 1. Charger le modèle (une fois)
model = SentenceTransformer("intfloat/multilingual-e5-small")
# 2. Configurer ChromaDB
client = chromadb.PersistentClient(path="./ma_base")
collection = client.get_or_create_collection(
name="documentation",
metadata={"hnsw:space": "cosine"} # Utiliser cosinus
)
# 3. Indexer des documents
def 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. Rechercher
def 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
# Usage
indexer(
documents=["Ansible automatise...", "Docker conteneurise..."],
ids=["doc1", "doc2"]
)
resultats = rechercher("Comment automatiser mes serveurs ?")
print(resultats["documents"])

Encodez plusieurs textes à la fois pour de meilleures performances :

# ✗ Lent : un par un
for doc in documents:
emb = model.encode(doc)
# ✓ Rapide : par batch
embeddings = model.encode(documents, batch_size=32, show_progress_bar=True)

Si votre base vectorielle attend des vecteurs normalisés :

embeddings = model.encode(documents, normalize_embeddings=True)
model = SentenceTransformer("intfloat/multilingual-e5-small", device="cuda")
ErreurCauseSolution
Scores toujours ~0.8Préfixes manquants (E5)Ajouter query:/passage:
Résultats non pertinentsMauvais modèle pour la langueUtiliser un modèle multilingue
Mémoire insuffisanteModèle trop grosUtiliser multilingual-e5-small
Encodage lentBatch size trop petitAugmenter batch_size
Point cléRecommandation
Modèle débutantintfloat/multilingual-e5-small
Préfixes E5query: pour questions, passage: pour documents
MétriqueSimilarité cosinus (ou dot product si normalisé)
Taille texte idéale100-300 caractères par chunk
Batch encodingToujours encoder par lots

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.