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ère | t-SNE | UMAP |
|---|---|---|
| Vitesse | Lent (O(n²)) | Rapide |
| Structure globale | Mal préservée | Bien préservée |
| Reproductibilité | Variable | Stable |
| Paramètres | perplexity | n_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