Aller au contenu principal

Visualiser des embeddings

Mis à jour le 29 juillet 2026

Objectifs

  • Réduire des vecteurs haute dimension pour les visualiser en 2D/3D
  • Utiliser t-SNE et UMAP pour la visualisation
  • Interpréter les clusters visuels

Le défi : 3 072 dimensions

Les embeddings vivent dans un espace à 3 072 dimensions, que personne ne sait se représenter. Pour les regarder, il faut donc les projeter en deux ou trois dimensions, en acceptant une perte d’information mais en préservant l’essentiel : ce qui était proche doit le rester. C’est un outil de diagnostic redoutable — un cluster qui se dédouble ou deux thèmes qui se chevauchent se voient en un coup d’œil, là où aucune métrique agrégée ne les aurait signalés.

Réduction de dimension avec t-SNE

t-SNE (t-distributed Stochastic Neighbor Embedding) est la méthode classique. Elle cherche une disposition en 2D où chaque point conserve son voisinage d’origine. L’exemple ci-dessous construit délibérément un corpus à trois thèmes bien séparés — IA, cuisine, sport — de façon à pouvoir vérifier que la projection retrouve bien la structure attendue.

from openai import OpenAI
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
import numpy as np

client = OpenAI()

# Corpus d'exemple
textes = [
    # Cluster IA
    "L'apprentissage profond utilise des réseaux de neurones",
    "Le machine learning transforme la data science",
    "Les transformers ont révolutionné le NLP",
    "GPT-5 est un grand modèle de langage",
    # Cluster cuisine
    "La recette du gâteau au chocolat",
    "Comment préparer une ratatouille",
    "Les bases de la pâtisserie française",
    "Techniques de cuisson au four",
    # Cluster sport
    "Le marathon de Paris attire des milliers de coureurs",
    "Entraînement fractionné pour la course à pied",
    "Les règles du football expliquées",
    "Nutrition sportive et performance",
]

categories = (["IA"] * 4 + ["Cuisine"] * 4 + ["Sport"] * 4)

# Générer les embeddings
response = client.embeddings.create(
    input=textes,
    model="text-embedding-3-large"
)
embeddings = np.array([d.embedding for d in response.data])

# Réduction t-SNE en 2D
tsne = TSNE(n_components=2, random_state=42, perplexity=4)
coords = tsne.fit_transform(embeddings)

# Visualisation
couleurs = {"IA": "#2563eb", "Cuisine": "#dc2626", "Sport": "#16a34a"}
fig, ax = plt.subplots(figsize=(10, 8))

for cat in couleurs:
    mask = [c == cat for c in categories]
    ax.scatter(
        coords[mask, 0], coords[mask, 1],
        c=couleurs[cat], label=cat, s=100, alpha=0.8
    )
    for i, (x, y) in enumerate(coords[mask]):
        idx = [j for j, m in enumerate(mask) if m][i]
        ax.annotate(
            textes[idx][:30] + "...",
            (x, y), fontsize=7, alpha=0.7
        )

ax.legend()
ax.set_title("Visualisation t-SNE des embeddings")
plt.tight_layout()
plt.savefig("tsne_embeddings.png", dpi=150)
plt.show()

Trois paramètres pilotent le résultat. perplexity contrôle l’équilibre entre structure locale et globale, avec des valeurs typiques de 5 à 50 ; ici elle vaut 4 parce que le corpus ne compte que douze points, et une perplexité supérieure au nombre de voisins disponibles produirait une projection incohérente. n_iter fixe le nombre d’itérations d’optimisation, avec un minimum de 250 et idéalement plus de 1 000. random_state garantit la reproductibilité : sans lui, deux exécutions successives donnent deux figures différentes, ce qui rend toute comparaison impossible.

Réduction avec UMAP

UMAP (Uniform Manifold Approximation and Projection) est souvent préféré à t-SNE parce qu’il est plus rapide et préserve mieux la structure globale — autrement dit, la distance entre deux clusters sur la figure UMAP veut dire quelque chose, alors qu’avec t-SNE elle est arbitraire.

import umap

# Réduction UMAP en 2D
reducer = umap.UMAP(n_components=2, random_state=42, n_neighbors=5)
coords_umap = reducer.fit_transform(embeddings)

fig, ax = plt.subplots(figsize=(10, 8))

for cat in couleurs:
    mask = [c == cat for c in categories]
    ax.scatter(
        coords_umap[mask, 0], coords_umap[mask, 1],
        c=couleurs[cat], label=cat, s=100, alpha=0.8
    )

ax.legend()
ax.set_title("Visualisation UMAP des embeddings")
plt.tight_layout()
plt.savefig("umap_embeddings.png", dpi=150)
plt.show()

Le tableau suivant résume l’arbitrage entre les deux méthodes.

Critèret-SNEUMAP
VitesseLent (O(n²))Rapide
Structure globaleMal préservéeBien préservée
ReproductibilitéVariableStable
Paramètresperplexityn_neighbors, min_dist

Visualisation 3D interactive

Sur un corpus de quelques centaines de points, la figure statique devient illisible : les étiquettes se superposent et l’on ne sait plus quel texte correspond à quel point. Plotly résout le problème en déportant l’information dans une infobulle, et en autorisant la rotation du nuage.

import plotly.express as px
import pandas as pd

# Réduction en 3D
reducer_3d = umap.UMAP(n_components=3, random_state=42, n_neighbors=5)
coords_3d = reducer_3d.fit_transform(embeddings)

df = pd.DataFrame({
    "x": coords_3d[:, 0],
    "y": coords_3d[:, 1],
    "z": coords_3d[:, 2],
    "catégorie": categories,
    "texte": [t[:50] for t in textes]
})

fig = px.scatter_3d(
    df, x="x", y="y", z="z",
    color="catégorie",
    hover_data=["texte"],
    title="Embeddings en 3D"
)
fig.write_html("embeddings_3d.html")
fig.show()

Le fichier HTML produit se partage tel quel : c’est un excellent support pour montrer à une équipe métier, sans une ligne de code, comment ses propres documents se répartissent.

Visualiser l’effet de la réduction de dimensions

La leçon 2 annonçait qu’un vecteur tronqué à 256 dimensions conservait environ 88 % de sa qualité. Ce chiffre reste abstrait tant qu’on ne l’a pas vu. En générant le même corpus à quatre tailles différentes et en projetant chaque version, vous observez directement à quel moment les clusters commencent à se toucher — et vous décidez sur pièces plutôt que sur une règle générale.

dimensions_a_tester = [256, 512, 1536, 3072]

fig, axes = plt.subplots(1, 4, figsize=(24, 5))

for ax, dim in zip(axes, dimensions_a_tester):
    resp = client.embeddings.create(
        input=textes,
        model="text-embedding-3-large",
        dimensions=dim
    )
    embs = np.array([d.embedding for d in resp.data])
    tsne = TSNE(n_components=2, random_state=42, perplexity=4)
    coords = tsne.fit_transform(embs)

    for cat in couleurs:
        mask = [c == cat for c in categories]
        ax.scatter(coords[mask, 0], coords[mask, 1],
                   c=couleurs[cat], label=cat, s=60)
    ax.set_title(f"{dim} dimensions")
    ax.legend(fontsize=7)

plt.tight_layout()
plt.savefig("comparaison_dimensions.png", dpi=150)
plt.show()

Une mise en garde s’impose avant d’interpréter ces figures : la projection déforme toujours. Un point isolé sur le graphique n’est pas nécessairement un point isolé dans l’espace d’origine, et deux clusters collés en 2D peuvent être parfaitement séparés en 3 072 dimensions. La visualisation sert à formuler une hypothèse, jamais à la valider — cette validation relève des métriques que nous verrons en leçon 10.

Résumé

  • t-SNE et UMAP projettent les embeddings en 2D/3D pour la visualisation
  • UMAP est généralement préféré pour sa vitesse et sa fidélité
  • Plotly permet des visualisations 3D interactives
  • La visualisation aide à valider la qualité de vos embeddings et à détecter des anomalies