Distance et similarité cosinus
Objectifs
- Comprendre la similarité cosinus et pourquoi elle est préférée
- Implémenter la comparaison de vecteurs en Python
- Interpréter les scores de similarité
Mesurer la proximité entre vecteurs
Une fois vos textes convertis en vecteurs, la question clé est : comment mesurer si deux vecteurs sont « proches » ? Plusieurs métriques existent.
Distance euclidienne
La distance géométrique classique entre deux points. Simple mais sensible à la magnitude des vecteurs :
import numpy as np
def distance_euclidienne(a: list[float], b: list[float]) -> float:
return np.linalg.norm(np.array(a) - np.array(b))
Produit scalaire (dot product)
La somme des produits élément par élément. Rapide à calculer :
def produit_scalaire(a: list[float], b: list[float]) -> float:
return np.dot(a, b)
Similarité cosinus
La métrique standard pour les embeddings. Elle mesure l’angle entre deux vecteurs, indépendamment de leur longueur :
def similarite_cosinus(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
La similarité cosinus varie de -1 (sens opposé) à +1 (même direction). En pratique, les embeddings OpenAI sont normalisés, donc le produit scalaire et la similarité cosinus donnent le même résultat.
Comparaison pratique
from openai import OpenAI
client = OpenAI()
def obtenir_embedding(texte: str) -> list[float]:
response = client.embeddings.create(
input=texte,
model="text-embedding-3-large"
)
return response.data[0].embedding
# Phrases à comparer
phrases = {
"a": "Le chat dort sur le canapé",
"b": "Le félin sommeille sur le sofa",
"c": "Python est un langage de programmation",
}
embeddings = {k: obtenir_embedding(v) for k, v in phrases.items()}
# Comparer toutes les paires
from itertools import combinations
for (k1, v1), (k2, v2) in combinations(phrases.items(), 2):
sim = similarite_cosinus(embeddings[k1], embeddings[k2])
print(f"sim({k1}, {k2}) = {sim:.4f}")
print(f" {v1}")
print(f" {v2}")
print()
Résultat typique :
sim(a, b) = 0.8923 ← sens très proche
sim(a, c) = 0.1247 ← sujets différents
sim(b, c) = 0.1189 ← sujets différents
Interpréter les scores
Les seuils dépendent de votre cas d’usage, mais voici des repères :
| Score | Interprétation |
|---|---|
| > 0.85 | Très similaire (paraphrases, synonymes) |
| 0.70 – 0.85 | Thématiquement liés |
| 0.40 – 0.70 | Vaguement liés |
| < 0.40 | Sujets différents |
Ces seuils varient selon le modèle et le domaine. Calibrez toujours sur vos propres données.
Recherche du plus proche voisin
La recherche de similarité revient à trouver les k vecteurs les plus proches d’un vecteur requête :
def rechercher_similaires(
query_embedding: list[float],
corpus_embeddings: np.ndarray,
k: int = 5
) -> list[tuple[int, float]]:
"""Retourne les k documents les plus similaires."""
query = np.array(query_embedding)
# Calcul vectorisé de toutes les similarités
similarites = corpus_embeddings @ query
# (fonctionne car les embeddings OpenAI sont normalisés)
# Indices des k meilleurs scores
top_k_indices = np.argsort(similarites)[-k:][::-1]
return [(int(i), float(similarites[i])) for i in top_k_indices]
# Utilisation
corpus = np.load("embeddings.npy") # matrice (n, 3072)
query_emb = obtenir_embedding("Comment installer Python ?")
resultats = rechercher_similaires(query_emb, corpus, k=5)
for idx, score in resultats:
print(f"Document {idx}: score = {score:.4f}")
Avec scikit-learn
Pour des calculs plus avancés, scikit-learn propose des utilitaires optimisés :
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# Matrice de similarité entre tous les documents
corpus = np.array(embeddings_list)
matrice_sim = cosine_similarity(corpus)
print(f"Shape : {matrice_sim.shape}") # (n, n)
print(f"Similarité doc 0 ↔ doc 1 : {matrice_sim[0][1]:.4f}")
Résumé
- La similarité cosinus mesure l’angle entre vecteurs, indépendamment de leur magnitude
- Les embeddings OpenAI sont normalisés : dot product = cosinus
- Un score > 0.85 indique des textes très similaires
- La recherche de voisins proches se fait efficacement avec NumPy ou scikit-learn