t-SNE place chaque ligne d'un tableau de dizaines de colonnes sur une carte en 2D, en s'efforçant que les voisins d'origine restent voisins. Les groupes naturels apparaissent comme des îlots. C'est un outil d'exploration et de contrôle visuel, pas un modèle : la taille des îlots et les distances entre eux ne se lisent pas.
Un plan de table pour 1 800 invités décrits chacun par 64 caractéristiques : chacun doit être assis près de ceux qui lui ressemblent le plus, quitte à déformer la salle.
Dans l'espace d'origine, chaque point répartit une probabilité de voisinage sur les autres, forte pour les plus proches. La perplexité règle le nombre effectif de voisins pris en compte.
Au hasard ou selon une ACP. On y définit les mêmes probabilités avec une loi de Student (le t de t-SNE), à queues épaisses, qui laisse de la place pour écarter les groupes.
Par descente de gradient, les points bougent jusqu'à ce que les voisinages de la carte ressemblent le plus possible à ceux d'origine (divergence de Kullback-Leibler minimale).
Chaque image de 8 x 8 pixels devient une ligne de 64 valeurs. Avant d'entraîner un modèle de lecture automatique, on veut voir si les chiffres forment des groupes distincts et repérer les images ambiguës.
La carte 2D regroupe les images en îlots qui correspondent presque chacun à un chiffre. Une image isolée dans l'îlot d'un autre chiffre est à revoir : chiffre mal écrit ou étiquette fausse. Le chiffre ne sert qu'à colorer la carte, t-SNE ne l'utilise pas.
On classe chaque image d'après ses 5 plus proches voisins sur la carte. Sur le jeu d'exemple, 97,6 % des images sont bien classées avec t-SNE, contre 60,3 % avec une ACP à 2 axes : t-SNE garde les voisinages que l'ACP écrase.
Noms donnés pour R (Rtsne) et Python (scikit-learn).
Nombre effectif de voisins considérés, 30 par défaut, en général entre 5 et 50. Petite, elle fait éclater les groupes en petits îlots ; grande, elle privilégie la structure d'ensemble. On compare deux ou trois valeurs.
Rtsne réduit d'abord les données à 50 axes par ACP, ce qui accélère et débruite. En Python, on applique soi-même une ACP au-delà de 50 colonnes environ.
Trop peu d'itérations laissent une carte inachevée. Deux graines donnent des cartes différentes mais des groupes semblables : on fixe la graine pour reproduire une présentation.
# Chiffres manuscrits : t-SNE en R
library(Rtsne)
library(class)
chiffres <- read.csv("chiffres_manuscrits.csv")
X <- as.matrix(chiffres[, names(chiffres) != "chiffre"]) / 16 # 64 pixels entre 0 et 1
y <- factor(chiffres$chiffre) # pas utilisé par t-SNE, seulement pour vérifier
set.seed(42)
tsne <- Rtsne(X, dims = 2, perplexity = 30, check_duplicates = FALSE)
acp <- prcomp(X)$x[, 1:2]
# Les voisins sur la carte 2D sont-ils du même chiffre ? (k plus proches voisins, leave-one-out)
cat("t-SNE :", round(mean(knn.cv(tsne$Y, y, k = 5) == y), 3), "\n")
cat("ACP :", round(mean(knn.cv(acp, y, k = 5) == y), 3), "\n")
# Position moyenne de chaque chiffre, puis la carte colorée par chiffre
print(round(rowsum(tsne$Y, y) / as.vector(table(y)), 1))
plot(tsne$Y, col = rainbow(10)[as.integer(y)], pch = 19, cex = 0.5, xlab = "t-SNE 1", ylab = "t-SNE 2")
# Chiffres manuscrits : t-SNE en Python
import pandas as pd
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
chiffres = pd.read_csv("chiffres_manuscrits.csv")
X = chiffres.drop(columns="chiffre") / 16 # 64 pixels entre 0 et 1
y = chiffres["chiffre"] # pas utilisé par t-SNE, seulement pour vérifier
carte_tsne = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X)
carte_acp = PCA(n_components=2).fit_transform(X)
# Les voisins sur la carte 2D sont-ils du même chiffre ? (k plus proches voisins)
for nom, carte in [("t-SNE", carte_tsne), ("ACP", carte_acp)]:
score = cross_val_score(KNeighborsClassifier(n_neighbors=5), carte, y, cv=5).mean()
print(f"{nom} : {score:.1%} des images bien classées par leurs 5 voisins sur la carte 2D")
resultat = pd.DataFrame(carte_tsne.astype(float), columns=["x", "y"]).assign(chiffre=y)
print(resultat.groupby("chiffre")[["x", "y"]].mean().round(1))
On lit les voisinages : des points proches se ressemblent dans les données d'origine. La taille des îlots, la distance entre îlots et les axes n'ont pas de sens. Deux îlots éloignés ne sont pas forcément plus différents que deux îlots proches.
La valeur par défaut, 30, convient souvent. On la teste entre 5 et 50 : une structure stable sur plusieurs valeurs est fiable, une structure qui n'apparaît qu'à une seule valeur est suspecte. La perplexité doit rester nettement inférieure au nombre de lignes.
UMAP est plus rapide sur les gros volumes, garde en général mieux la structure d'ensemble et sait projeter de nouvelles lignes. t-SNE reste très bon pour séparer finement des groupes locaux. UMAP est devenu le choix par défaut, t-SNE sert de point de comparaison.
Plus rapide, garde en général mieux la structure d'ensemble et sait placer de nouvelles lignes sur la carte.
Voir la fiche → le premier réflexeLinéaire, rapide et reproductible. Ses axes s'interprètent, mais les groupes se chevauchent souvent en 2D.
Voir la fiche → l'autre approche non linéaireSuit les distances le long des données. Utile pour une structure continue, moins pour des groupes séparés.
Voir la fiche →Dataistudio forme les équipes au machine learning et à l'IA, sur des cas concrets.
Nous utilisons des cookies de mesure d'audience et de suivi publicitaire pour comprendre la fréquentation du site et l'efficacité de nos annonces. Rien n'est déposé sans votre accord. En savoir plus