Section 11.4.16 : Embeddings et bases vectorielles
🎯 Objectif pédagogique
Comprendre les embeddings (représentations vectorielles), les bases vectorielles, et les opérations de similarité. Vous serez capable de créer, stocker et rechercher des embeddings pour alimenter vos systèmes RAG et de recommandation.
Les embeddings — L'ADN du texte
Marc a vu les embeddings dans la section RAG. Maintenant, on va en profondeur. Un embedding est un vecteur de nombres (ex: [0.12, -0.34, 0.56, ...]) qui capture le sens d'un texte. Deux textes similaires auront des embeddings proches dans l'espace vectoriel.
Créer et comparer des embeddings
from openai import OpenAI
import numpy as np
client = OpenAI()
def get_embedding(text, model="text-embedding-3-small"):
"""Obtenir l'embedding d'un texte."""
response = client.embeddings.create(
model=model,
input=text
)
return np.array(response.data[0].embedding)
def cosine_similarity(a, b):
"""Calculer la similarité cosinus entre deux vecteurs."""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Démonstration
texts = [
"Data Analyst spécialisé en finance",
"Analyste de données secteur financier",
"Chef cuisinier passionné de gastronomie",
"Python developer with SQL skills",
"Développeur Python avec compétences SQL"
]
embeddings = \{text: get_embedding(text) for text in texts\}
# Matrice de similarité
print("Similarités :")
for i, t1 in enumerate(texts):
for j, t2 in enumerate(texts):
if j > i:
sim = cosine_similarity(embeddings[t1], embeddings[t2])
print(f" \{sim:.3f\} | '\{t1[:40]\}' ↔ '\{t2[:40]\}'")
# Résultats attendus :
# ~0.95 | "Data Analyst finance" ↔ "Analyste données financier" (synonymes)
# ~0.90 | "Python developer SQL" ↔ "Développeur Python SQL" (traduction)
# ~0.30 | "Data Analyst finance" ↔ "Chef cuisinier" (différents)
Modèles d'embedding
| Modèle | Provider | Dimensions | $/M tokens | Qualité |
|---|---|---|---|---|
| text-embedding-3-small | OpenAI | 1536 | 0.02$ | Bon |
| text-embedding-3-large | OpenAI | 3072 | 0.13$ | Excellent |
| voyage-3 | Voyage AI | 1024 | 0.06$ | Très bon |
| all-MiniLM-L6-v2 | HuggingFace | 384 | Gratuit* | Correct |
| nomic-embed-text | Nomic | 768 | Gratuit* | Bon |
*Gratuit si exécuté localement (GPU recommandé)
Bases vectorielles — Stocker et rechercher
# ═══ ChromaDB — Simple et local ═══
import chromadb
client = chromadb.PersistentClient(path="./vector_db")
# Créer une collection
collection = client.get_or_create_collection(
name="job_offers",
metadata=\{"hnsw:space": "cosine"\} # Distance cosinus
)
# Indexer des offres d'emploi
offers = [
\{"id": "offer_1", "text": "Data Analyst Python SQL Paris 50k", "company": "TechCorp"\},
\{"id": "offer_2", "text": "ML Engineer TensorFlow Remote 70k", "company": "AIStartup"\},
\{"id": "offer_3", "text": "Business Analyst Excel Finance Lyon 45k", "company": "BankCo"\},
\{"id": "offer_4", "text": "Data Scientist Python R Deep Learning 65k", "company": "DataLab"\},
\{"id": "offer_5", "text": "Analyste financier programmation Python 55k", "company": "FinTech"\},
]
collection.upsert(
ids=[o["id"] for o in offers],
documents=[o["text"] for o in offers],
metadatas=[\{"company": o["company"]\} for o in offers]
)
# Recherche sémantique
results = collection.query(
query_texts=["Poste data avec Python pour quelqu'un venant de la finance"],
n_results=3,
where=None # Pas de filtre metadata
)
print("Top 3 offres similaires :")
for i, (doc, meta) in enumerate(zip(results["documents"][0], results["metadatas"][0])):
print(f" \{i+1\}. [\{meta['company']\}] \{doc\}")
# Résultat : "Analyste financier Python" et "Data Analyst Python SQL" en top
Recherche hybride — Sémantique + Filtres
# Combiner similarité sémantique et filtres metadata
results = collection.query(
query_texts=["Machine learning engineer"],
n_results=5,
where=\{"company": \{"$ne": "BankCo"\}\}, # Exclure BankCo
# Ou : where=\{"salary_min": \{"$gte": 50000\}\}
)
# ChromaDB supporte les opérateurs :
# $eq, $ne : égal, différent
# $gt, $gte, $lt, $lte : comparaisons
# $in, $nin : dans / pas dans une liste
# $and, $or : combinaisons
Applications des embeddings au-delà du RAG
# 1. Système de recommandation
def recommend_similar_offers(offer_id, collection, n=5):
"""Recommander des offres similaires."""
offer = collection.get(ids=[offer_id])
results = collection.query(
query_texts=offer["documents"],
n_results=n + 1 # +1 car l'offre elle-même sera dans les résultats
)
# Exclure l'offre d'origine
return [(doc, meta) for doc, meta, id_ in
zip(results["documents"][0], results["metadatas"][0], results["ids"][0])
if id_ != offer_id]
# 2. Détection de doublons
def find_duplicates(collection, threshold=0.95):
"""Trouver les documents quasi-identiques."""
all_docs = collection.get()
duplicates = []
for i, doc in enumerate(all_docs["documents"]):
results = collection.query(query_texts=[doc], n_results=5)
for j, (sim_doc, distance) in enumerate(
zip(results["documents"][0], results["distances"][0])
):
if distance < (1 - threshold) and results["ids"][0][j] != all_docs["ids"][i]:
duplicates.append((all_docs["ids"][i], results["ids"][0][j], 1 - distance))
return duplicates
# 3. Clustering sémantique
def cluster_by_topic(texts, n_clusters=3):
"""Grouper des textes par thème."""
from sklearn.cluster import KMeans
embeddings = [get_embedding(t) for t in texts]
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
labels = kmeans.fit_predict(embeddings)
clusters = \{\}
for text, label in zip(texts, labels):
clusters.setdefault(label, []).append(text)
return clusters
Coût des embeddings : dérisoire
text-embedding-3-small coûte 0.02$ par million de tokens. Indexer 10 000 offres d'emploi (~500k tokens) coûte ~0.01$. La recherche est gratuite (calcul local). Les embeddings sont la brique IA la moins chère et la plus utile.
🏋️ Exercice pratique (25 minutes)
- →Créez des embeddings pour 10 textes variés et comparez les similarités
- →Stockez-les dans ChromaDB et implémentez la recherche
- →Testez la recherche hybride (similarité + filtre)
- →Implémentez un mini-système de recommandation
Pourquoi deux textes en langues différentes ("Data Analyst" et "Analyste de données") ont-ils des embeddings similaires ?
Quelle est la différence entre une base de données classique et une base vectorielle ?