Aller au contenu principal

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 :

ScoreInterprétation
> 0.85Très similaire (paraphrases, synonymes)
0.70 – 0.85Thématiquement liés
0.40 – 0.70Vaguement liés
< 0.40Sujets 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