2.6 — Clustering (KMeans) et réduction de dimension (PCA)

Navigation : << 2.5-Biais-Variance-CV-ROC | Index

Kernel : Python 3

Introduction

Tous les notebooks précédents (2.1 à 2.5) relevaient de l’apprentissage supervisé : on disposait d’étiquettes y à prédire. Ce notebook aborde l’apprentissage non supervisé : aucune étiquette n’est fournie à l’algorithme. Nous étudions deux techniques canoniques : le clustering (regrouper les observations similaires, ici avec KMeans) et la réduction de dimension (projeter des données à forte dimension dans un espace plus petit tout en préservant l’information, ici l’Analyse en Composantes Principales — ACP / PCA).

Nous travaillons sur les chiffres manuscrits de load_digits : 64 pixels par image (une image 8×8 aplanie en un vecteur de dimension 64) et 10 classes de chiffres (0 à 9). Les algorithmes ne verront jamais les étiquettes — nous les utiliserons uniquement pour vérifier que la structure non supervisée retrouve les classes connues.

Objectifs d’apprentissage

À la fin de ce notebook, vous saurez : 1. Appliquer KMeans et choisir le nombre de clusters (méthode du coude) ; 2. Comprendre l’inertie (somme des carrés des écarts intra-cluster) ; 3. Réduire 64 dimensions à 2 avec la PCA et visualiser la projection ; 4. Interpréter la variance expliquée cumulée pour choisir le nombre de composantes ; 5. Visualiser les mêmes données avec t-SNE et UMAP (réduction non linéaire), mesurer l’effet de la perplexité, et choisir la méthode de réduction selon votre objectif (compression, visualisation, pipeline aval).

Prérequis

  • Notebooks 2.1 (workflow ML), notions de distance euclidienne et de variance.

Référence. MacQueen, J. (1967), Some Methods for Classification and Analysis of Multivariate Observations, Proceedings of the Fifth Berkeley Symposium on Mathematical Statistics and Probability. La méthode des k-means (k-moyennes) y est introduite : on partitionne les observations en k groupes qui minimisent l’inertie intra-cluster (somme des carrés des écarts au centre).

# Configuration et imports pour le notebook 2.6
import numpy as np
import matplotlib.pyplot as plt

from sklearn.datasets import load_digits
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.metrics import confusion_matrix, accuracy_score  # pour la verification uniquement

np.random.seed(42)
print("Configuration OK : 2.6 - Clustering (KMeans) et ACP")
Configuration OK : 2.6 - Clustering (KMeans) et ACP

Auto-évaluation

Ce carnet porte un dispositif d’auto-évaluation formatif : quatre questions, placées avant le parcours (diagnostic de prérequis), au milieu (vérification de la notion) et à la fin (transfert). Aucune note n’est stockée, aucune réponse n’est enregistrée.

Chaque cellule de question s’exécute sans erreur tant que vous n’avez pas répondu ; la correction s’affiche uniquement lorsque vous remplacez reponse=None par la lettre de votre choix, puis ré-exécutez la cellule.

# Dispositif d'auto-evaluation de la serie (module partage, issue #18207)
import pathlib
import sys

for _racine in (pathlib.Path.cwd(), *pathlib.Path.cwd().parents):
    if (_racine / "MyIA.AI.Notebooks").is_dir():
        sys.path.insert(0, str(_racine / "MyIA.AI.Notebooks" / "ML" / "DataScienceWithAgents"))
        break

from auto_evaluation import question  # noqa: E402

Avant de commencer — question de diagnostic

question(
    "Qu'est-ce qui distingue le clustering de l'apprentissage supervisé ?",
    choix=[
        "Il n'y a pas de variable cible : les groupes se définissent par la structure des données, et le numéro d'un cluster n'a pas de sens en lui-même",
        "Le clustering est supervisé, mais sans étiquettes fournies",
        "Le clustering prédit une classe apprise à l'avance",
    ],
    bonne="A",
    explication="Sans cible, il n'y a rien à prédire juste ou faux : on décrit une structure. C'est pourquoi le numéro des clusters est arbitraire, et pourquoi ce carnet les compare aux vraies étiquettes seulement après coup.",
    reponse=None,  # remplacez None par la lettre de votre choix, puis re-executez
    moment="avant",
)
Diagnostic de prérequis — Qu'est-ce qui distingue le clustering de l'apprentissage supervisé ?

  A. Il n'y a pas de variable cible : les groupes se définissent par la structure des données, et le numéro d'un cluster n'a pas de sens en lui-même
  B. Le clustering est supervisé, mais sans étiquettes fournies
  C. Le clustering prédit une classe apprise à l'avance

Réponse non donnée. Remplacez `reponse=None` par la lettre de votre choix dans cette cellule, puis ré-exécutez-la pour afficher la correction.

1. Le jeu de données : chiffres manuscrits

Le jeu de données load_digits contient 1797 images de chiffres manuscrits (0 à 9), chacune codée sur une grille de 8×8 = 64 pixels niveaux de gris. Chaque image, une fois aplanie, devient un vecteur de dimension 64 : c’est ce vecteur que les algorithmes manipulent. Les 10 classes (les chiffres 0 à 9) sont connues via les étiquettes y, mais le clustering et la PCA les ignoreront — nous n’utiliserons y qu’après coup pour vérifier que la structure non supervisée retrouve ces classes.

# Chargement des chiffres manuscrits
digits = load_digits()
X = digits.data       # 1797 images x 64 pixels (vecteurs aplanis)
y = digits.target     # etiquettes 0-9 (utilisees UNIQUEMENT pour verification)

print(f"X shape : {X.shape}")                       # attendu (1797, 64)
print(f"Nombre de classes : {len(np.unique(y))}")   # attendu 10

# Apercu de quelques images
fig, axes = plt.subplots(2, 5, figsize=(8, 4))
for i, ax in enumerate(axes.flat):
    ax.imshow(digits.images[i], cmap='gray_r')
    ax.set_title(f"label={y[i]}")
    ax.axis('off')
plt.suptitle("Exemples de chiffres manuscrits (8x8)")
plt.tight_layout()
plt.show()
X shape : (1797, 64)
Nombre de classes : 10

Lecture du jeu de données

1797 images de 64 pixels : chaque chiffre manuscrit est une grille 8×8 aplatie en un vecteur de 64 intensités de gris — c’est ce que dit le (1797, 64) affiché, et pourquoi chaque ligne de X vit dans un espace à 64 dimensions. Les 10 classes (chiffres 0 à 9) sont à peu près équilibrées, environ 180 images chacune. Le point clé pour la suite : ces étiquettes existent dans le jeu de données, mais le clustering ne les utilisera jamais — elles ne serviront qu’à vérifier, a posteriori, si les groupes trouvés sans supervision coïncident avec les vrais chiffres. C’est ce protocole (apprendre sans les labels, évaluer avec) qui rend ce jeu de données un banc d’essai honnête du non-supervisé.

2. Clustering avec KMeans

KMeans partitionne les données en k clusters. Il procède itérativement : (1) il affecte chaque point au centroïde le plus proche, puis (2) déplace chaque centroïde sur la moyenne de ses points. L’algorithme minimise l’inertie (somme des carrés des distances de chaque point à son centroïde). Il faut fournir k en entrée — nous utilisons 10 ici (nous savons qu’il y a 10 chiffres), puis nous verrons comment choisir k objectivement avec la méthode du coude.

# Clustering KMeans avec 10 clusters
kmeans = KMeans(n_clusters=10, random_state=42, n_init=10)
kmeans.fit(X)

print(f"Inertie (within-cluster sum of squares) : {kmeans.inertia_:.2f}")
print(f"Labels de cluster (10 premiers points) : {kmeans.labels_[:10]}")
print("Attention : les identifiants de cluster sont ARBITRAIRES "
      "(le cluster 3 ne correspond pas forcement au chiffre 3).")
Inertie (within-cluster sum of squares) : 1165256.30
Labels de cluster (10 premiers points) : [1 3 3 6 9 8 5 4 3 8]
Attention : les identifiants de cluster sont ARBITRAIRES (le cluster 3 ne correspond pas forcement au chiffre 3).

Lecture de la première expérience KMeans

L’inertie 1 165 256 est la somme des carrés des distances de chaque point au centre de son cluster : c’est la quantité que KMeans minimise, une mesure de compacité interne — plus elle est basse, plus les boules sont serrées. Le détail affiché juste après est le piège classique du non-supervisé : les labels de cluster sont arbitraires. Le cluster « 3 » de la sortie n’a aucun lien avec le chiffre 3 — l’algorithme numérote ses groupes selon l’ordre d’initialisation, pas selon notre sémantique. Toute correspondance cluster → chiffre demandera une étape d’alignement explicite (ce que fera la matrice de confusion de la section 5). Enfin, demander k=10 est déjà une hypothèse forte : nous savons qu’il y a 10 chiffres, mais en vraie donnée sans étiquette, choisir k serait tout le problème — c’est l’objet de la méthode du coude qui suit.

3. Choisir k : la méthode du coude

L’inertie décroît toujours quand k augmente (plus de clusters = des groupes plus serrés). Il n’existe pas de « vrai » k déductible de la seule inertie, mais la courbe présente souvent un coude (en anglais elbow) : un point d’inflexion où ajouter des clusters n’apporte plus guère de gain. Ce coude est un choix raisonnable pour k.

# Methode du coude : inertie en fonction du nombre de clusters k
inerties = []
for k in range(1, 16):
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X)
    inerties.append(km.inertia_)

plt.figure(figsize=(7, 4))
plt.plot(range(1, 16), inerties, 'o-')
plt.xlabel("Nombre de clusters k")
plt.ylabel("Inertie")
plt.title("Methode du coude")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

print(f"Inertie pour k=10 : {inerties[9]:.2f}")
print("Un coude est visible autour de k=10, coherent avec les 10 chiffres.")

Inertie pour k=10 : 1165256.30
Un coude est visible autour de k=10, coherent avec les 10 chiffres.

Lecture de la courbe du coude

La courbe décroît vite puis s’aplatit : chaque cluster ajouté retire de l’inertie, mais avec un rendement décroissant. Le coude est le point où le gain marginal devient négligeable — payer un cluster de plus ne « serre » presque plus rien. Que ce coude émerge autour de k=10 est une information remarquable : l’algorithme, qui n’a jamais vu les étiquettes, retrouve de lui-même la structure des dix chiffres dans la donnée. C’est le message central du non-supervisé : la classe n’est pas une annotation magique, c’est une régularité de la donnée que la compacité seule suffit à révéler — au prix, ici, d’un œil sur la courbe pour trancher où s’arrêter.

Exercice 1 : la décroissance de l’inertie

Objectif : mesurer la chute d’inertie lorsqu’on passe de k=5 à k=10 clusters, à partir de la liste inerties.

Indice : inerties est une liste 0-indexée (indice 0 = k=1, indice 4 = k=5, indice 9 = k=10). La baisse vaut inerties[4] - inerties[9].

# Exercice 1 : chute d'inertie entre k=5 et k=10
# inerties est une liste 0-indexee : indice 4 = k=5, indice 9 = k=10
baisse_inertie = None  # TODO etudiant : remplacer (inerties[4] - inerties[9])
print(f"Exercice 1 a completer : l'inertie baisse de {baisse_inertie} entre k=5 et k=10")
Exercice 1 a completer : l'inertie baisse de None entre k=5 et k=10

Vérification 1 — lire la courbe du coude

question(
    "L'inertie diminue quand k augmente. Qu'est-ce que cela implique pour la méthode du coude ?",
    choix=[
        "Elle fournit un critère absolu : on retient le k qui minimise l'inertie",
        "Elle ne peut être qu'un repère visuel : on cherche un changement de pente, pas un minimum",
        "Elle prouve que le découpage en k clusters est le bon",
    ],
    bonne="B",
    explication="Comme l'inertie décroît toujours avec k, elle ne peut pas désigner un optimum par elle-même — à la limite, k égal au nombre de points l'annule. Le coude est un changement de régime, lu sur la courbe.",
    reponse=None,  # remplacez None par la lettre de votre choix, puis re-executez
    moment="pendant",
)
Vérification — L'inertie diminue quand k augmente. Qu'est-ce que cela implique pour la méthode du coude ?

  A. Elle fournit un critère absolu : on retient le k qui minimise l'inertie
  B. Elle ne peut être qu'un repère visuel : on cherche un changement de pente, pas un minimum
  C. Elle prouve que le découpage en k clusters est le bon

Réponse non donnée. Remplacez `reponse=None` par la lettre de votre choix dans cette cellule, puis ré-exécutez-la pour afficher la correction.

4. Réduction de dimension : l’ACP (PCA)

Chaque chiffre est un point dans un espace à 64 dimensions — impossible à tracer directement. La PCA (Principal Component Analysis, ou ACP en français) identifie les directions de variance maximale et projette les données sur les premières. La première composante principale capture le plus de variance, la deuxième le résidu orthogonal, etc. Projeter sur les 2 premières composantes donne un nuage de points 2D que l’on PEUT visualiser — et souvent les classes s’y séparent.

Référence. Pearson, K. (1901), On Lines and Planes of Closest Fit to Systems of Points in Space, Philosophical Magazine 2(11):559-572. Article fondateur de l’Analyse en Composantes Principales : la recherche des axes de variance maximale comme projection optimale (au sens des moindres carrés).

# Reduction de dimension : PCA a 2 composantes
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

variance_2d = pca.explained_variance_ratio_.sum()
print(f"Variance expliquee par les 2 premieres composantes : {variance_2d:.3f} "
      f"({variance_2d*100:.1f}%)")

plt.figure(figsize=(7, 5))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='tab10', s=10, alpha=0.6)
plt.colorbar(label="chiffre (vrai label, pour verification)")
plt.xlabel("Composante principale 1")
plt.ylabel("Composante principale 2")
plt.title("Projection PCA (2 composantes) - couleur = vrai chiffre")
plt.tight_layout()
plt.show()
print("Meme en 2D, les classes de chiffres forment des groupes visibles : "
      "la PCA non supervisée retrouve la structure connue.")
Variance expliquee par les 2 premieres composantes : 0.285 (28.5%)

Meme en 2D, les classes de chiffres forment des groupes visibles : la PCA non supervisée retrouve la structure connue.

Lecture de la projection ACP

Les deux premières composantes ne capturent que 28,5 % de la variance : projeter 64 dimensions sur 2 en jette les sept dixièmes. Et pourtant le nuage montre déjà des regroupements — les classes les plus séparées dans l’espace original restent séparées après projection. C’est le compromis de l’ACP : c’est la meilleure projection linéaire possible (au sens de la variance conservée), mais elle reste linéaire — deux chiffres séparés par une frontière courbe dans l’espace original peuvent se chevaucher une fois aplatis. L’exigence des 95 % de l’exercice 2 mesurera exactement ce que coûte la compression : combien de directions faut-il garder pour ne presque rien perdre.

Exercice 2 : combien de composantes garder ?

Les 2 premières composantes ne captent qu’une partie de la variance. Pour compresser sans trop perdre d’information, on cherche le nombre de composantes suffisant à atteindre 95 % de variance cumulée.

Étapes 1. La cellule suivante ajuste une PCA() (toutes les composantes) sur X et définit variance_expliquee (le ratio de variance de chacune des 64 composantes). 2. Calculez la variance cumulée avec np.cumsum(variance_expliquee). 3. Trouvez le premier indice où la cumulée dépasse 0.95 (rappelez-vous d’ajouter 1 pour passer de l’indice au nombre de composantes).

# Exercice 2 : nombre de composantes pour 95% de variance expliquee
# variance_expliquee est le ratio de variance par composante (64 valeurs)
cumul = None  # TODO etudiant : remplacer (np.cumsum(variance_expliquee))
# TODO etudiant : nombre de composantes pour atteindre 95% (premier indice ou cumul >= 0.95)
n_composantes_95 = None  # TODO etudiant : remplacer
print(f"Exercice 2 a completer : {n_composantes_95} composantes suffisent pour 95% de la variance")
Exercice 2 a completer : None composantes suffisent pour 95% de la variance

5. Vérifier le clustering contre les vraies étiquettes

KMeans a trouvé 10 clusters sans voir les étiquettes. Ces clusters correspondent-ils aux 10 classes de chiffres ? On peut le vérifier avec une matrice de confusion entre kmeans.labels_ et y. Attention : les identifiants de cluster étant arbitraires, la matrice est une permutation des colonnes — mais si une structure diagonale se dessine, la méthode non supervisée a bien retrouvé les classes. C’est la démonstration honnête que l’apprentissage non supervisé redécouvre une structure connue.

# Verification : matrice de confusion entre vrais chiffres et clusters KMeans
matrice = confusion_matrix(y, kmeans.labels_)

plt.figure(figsize=(6, 5))
plt.imshow(matrice, cmap='Blues', aspect='auto')
plt.colorbar(label="nombre d'images")
plt.xlabel("Cluster KMeans (identifiant arbitraire)")
plt.ylabel("Vrai chiffre (0-9)")
plt.title("Matrice de confusion : vrai chiffre vs cluster")
plt.tight_layout()
plt.show()

print("Chaque ligne = un vrai chiffre, chaque colonne = un cluster.")
print("Une structure diagonale (a permutation des colonnes pres) signifie")
print("que les clusters correspondent aux classes de chiffres.")

Chaque ligne = un vrai chiffre, chaque colonne = un cluster.
Une structure diagonale (a permutation des colonnes pres) signifie
que les clusters correspondent aux classes de chiffres.

Lecture du résultat — la diagonale à permutation près

La matrice montre une structure quasi diagonale, à une permutation des colonnes près : chaque ligne (vrai chiffre) concentre l’essentiel de sa masse sur une colonne (un cluster) — mais cette colonne change d’une ligne à l’autre, conséquence directe des identifiants de cluster arbitraires. Deux lectures concrètes de ce que la géométrie de l’espace pixel révèle : (1) les cases hors diagonale portant une masse non négligeable indiquent les chiffres que KMeans confond dans cet espace — des écritures dont les vecteurs de 64 pixels se recouvrent ; (2) un chiffre dont la masse se partage entre deux colonnes signale une classe coupée en deux modes d’écriture (deux « styles » du même chiffre que deux clusters distincts absorbent). C’est une information qu’un simple taux de réussite supervisé ne donne pas : la matrice montre où la structure non supervisée échoue, pas seulement combien. La section 7 y répondra par anticipation : ce sont précisément ces recouvrements que la réduction non linéaire vient séparer visuellement.

Exercice 3 : les clusters correspondent-ils aux chiffres ?

Objectif : pour le vrai chiffre « 0 » (les points où y == 0), trouver quel cluster (0 à 9) est le plus fréquent parmi ceux attribués par KMeans.

Étapes 1. Construisez le masque masque_zero = (y == 0). 2. Sélectionnez les clusters de ces points : clusters_zero = kmeans.labels_[masque_zero]. 3. Trouvez le cluster dominant avec np.bincount(clusters_zero).argmax().

# Exercice 3 : pour le vrai chiffre 0, quel cluster (0-9) est le plus frequent ?
masque_zero = (y == 0)                        # les points dont le vrai chiffre est 0
clusters_zero = kmeans.labels_[masque_zero]   # leurs clusters assignes par KMeans
# TODO etudiant : trouver le cluster le plus frequent parmi clusters_zero
cluster_dominant_zero = None  # TODO etudiant : remplacer (np.bincount(clusters_zero).argmax())
print(f"Exercice 3 a completer : le chiffre 0 tombe surtout dans le cluster {cluster_dominant_zero}")
Exercice 3 a completer : le chiffre 0 tombe surtout dans le cluster None

Vérification 2 — ce que sont les composantes

question(
    "Que sont les composantes principales ?",
    choix=[
        "Un sous-ensemble des variables d'origine, les plus importantes",
        "Des combinaisons linéaires de toutes les variables, orthogonales entre elles : une rotation, pas une sélection",
        "Les variables les plus corrélées à la cible",
    ],
    bonne="B",
    explication="L'ACP ne choisit pas des variables : elle change de base. Chaque composante mélange toutes les colonnes d'origine, et le signe d'une composante est arbitraire — c'est ce que montre la reconstruction de cette section.",
    reponse=None,  # remplacez None par la lettre de votre choix, puis re-executez
    moment="pendant",
)
Vérification — Que sont les composantes principales ?

  A. Un sous-ensemble des variables d'origine, les plus importantes
  B. Des combinaisons linéaires de toutes les variables, orthogonales entre elles : une rotation, pas une sélection
  C. Les variables les plus corrélées à la cible

Réponse non donnée. Remplacez `reponse=None` par la lettre de votre choix dans cette cellule, puis ré-exécutez-la pour afficher la correction.

6. Reconstruire une image à partir de la PCA

La PCA est aussi une compression : projeter sur k composantes puis inverse-transformer reconstruit une approximation de l’original. Moins de composantes = plus de compression et plus de flou. Cette section illustre visuellement le compromis entre réduction de dimension et perte d’information.

# Reconstruction d'un chiffre a partir d'un nombre croissant de composantes PCA
image_originale = digits.images[0]
fig, axes = plt.subplots(1, 6, figsize=(12, 3))
axes[0].imshow(image_originale, cmap='gray_r')
axes[0].set_title("Original")
axes[0].axis('off')

for i, n in enumerate([1, 5, 10, 20, 50]):
    p = PCA(n_components=n)
    proj = p.fit_transform(X)
    recon = p.inverse_transform(proj)
    axes[i + 1].imshow(recon[0].reshape(8, 8), cmap='gray_r')
    axes[i + 1].set_title(f"{n} comp.")
    axes[i + 1].axis('off')
plt.suptitle("Reconstruction du chiffre 0 selon le nombre de composantes PCA")
plt.tight_layout()
plt.show()
print("Plus on garde de composantes, plus l'image reconstruite devient nette.")

Plus on garde de composantes, plus l'image reconstruite devient nette.

7. Au-delà de l’ACP : t-SNE et UMAP (réduction non linéaire)

La projection ACP de la section 4 est linéaire : chaque composante est une combinaison linéaire des 64 pixels. C’est une limite structurelle — deux chiffres peuvent être proches dans l’espace pixel tout en étant séparés par une variation non linéaire (la boucle d’un 8, la barre d’un 7), et aucun plan de projection linéaire ne peut les découper proprement. Le résultat s’est vu sur la figure : les classes de chiffres se chevauchent.

t-SNE (t-distributed Stochastic Neighbor Embedding, van der Maaten & Hinton 2008) attaque le problème différemment : au lieu de chercher des axes, il construit une projection 2D qui préserve les voisinages — deux points proches en dimension 64 doivent rester proches en 2D (et réciproquement), en mesurant la « proximité » par des probabilités gaussiennes côté haute dimension et une loi de Student côté basse dimension, puis en minimisant la divergence de Kullback-Leibler entre les deux distributions. La projection obtenue est non linéaire par construction.

Référence. van der Maaten, L. & Hinton, G. (2008), Visualizing Data using t-SNE, Journal of Machine Learning Research 9:2579-2605.

# t-SNE sur les memes digits, cote a cote avec la projection ACP
import time
from sklearn.manifold import TSNE

X_pca_2d = PCA(n_components=2).fit_transform(X)

t0 = time.perf_counter()
tsne = TSNE(n_components=2, perplexity=30, random_state=42, init='pca')
X_tsne = tsne.fit_transform(X)
duree_tsne = time.perf_counter() - t0

fig, axes = plt.subplots(1, 2, figsize=(13, 5.5))
scatter0 = axes[0].scatter(X_pca_2d[:, 0], X_pca_2d[:, 1], c=y, cmap='tab10', s=10, alpha=0.6)
axes[0].set_title("ACP (lineaire) : les classes se chevauchent")
axes[0].set_xlabel("Composante principale 1")
axes[0].set_ylabel("Composante principale 2")
scatter1 = axes[1].scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10, alpha=0.6)
axes[1].set_title(f"t-SNE (perplexite=30) : separation nette des chiffres")
axes[1].set_xlabel("Dimension t-SNE 1")
axes[1].set_ylabel("Dimension t-SNE 2")
fig.colorbar(scatter1, ax=axes[1], label="chiffre (vrai label)")
plt.suptitle("Memes 1797 digits, deux projections : lineaire vs non lineaire")
plt.tight_layout()
plt.show()

print(f"t-SNE sur {X.shape[0]} digits x {X.shape[1]} dimensions : {duree_tsne:.1f} s")
print("La ou l'ACP melange les classes, le t-SNE les separe visuellement presque") 
print("parfaitement - c'est ce que 'non lineaire' achete en visualisation.")

t-SNE sur 1797 digits x 64 dimensions : 3.1 s
La ou l'ACP melange les classes, le t-SNE les separe visuellement presque
parfaitement - c'est ce que 'non lineaire' achete en visualisation.

Lecture du résultat — ce que la séparation veut dire, et ce qu’elle ne veut PAS dire

La figure de droite montre ~10 îlots quasi purs, un par chiffre — le contraste avec l’ACP à gauche est la démonstration visuelle de ce que « non linéaire » apporte. Mais deux garde-fous essentiels avant d’interpréter une carte t-SNE :

  • Les distances ENTRE clusters et les tailles relatives des clusters ne sont PAS interprétables. t-SNE préserve les voisinages locaux, pas les distances globales : deux îlots éloignés sur la carte ne sont pas forcément plus dissemblables que deux îlots proches, et un gros îlot n’est pas forcément une classe plus dispersée dans l’espace original. Seule la cohérence interne d’un groupe (les points d’une même couleur regroupés) est un vrai signal.
  • Le temps est super-linéaire et l’objet n’a pas de transform : chaque nouveau jeu de données impose de refaire l’embedding entier. C’est un outil d’exploration, pas une étape de pipeline — le guide de choix en fin de section formalise cette frontière.

L’effet de la perplexité : le paramètre qui change la carte

La perplexité est le nombre de voisins « effectifs » que t-SNE considère pour chaque point (l’écart-type des Gaussiennes locales s’ajuste pour que chaque point ait ce nombre de voisins perceptibles). Elle n’a pas de valeur universelle — van der Maaten & Hinton la recommandent entre 5 et 50 — et changer la perplexité change la carte. La cellule suivante le montre au lieu de l’affirmer : trois valeurs sur les mêmes données.

# Effet de la perplexite : trois valeurs sur les memes digits
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for ax, perp in zip(axes, [5, 30, 50]):
    emb = TSNE(n_components=2, perplexity=perp, random_state=42, init='pca').fit_transform(X)
    ax.scatter(emb[:, 0], emb[:, 1], c=y, cmap='tab10', s=8, alpha=0.6)
    ax.set_title(f"perplexite = {perp}")
    ax.set_xticks([]); ax.set_yticks([])
plt.suptitle("t-SNE : la meme donnee, trois perplexites")
plt.tight_layout()
plt.show()

print("Perplexite 5  : tres locale - les groupes se fragmentent en petits ilots.")
print("Perplexite 30 : equilibre - dix groupes coherents (valeur par defaut de sklearn).")
print("Perplexite 50 : plus globale - les groupes s'elargissent et se rapprochent.")

Perplexite 5  : tres locale - les groupes se fragmentent en petits ilots.
Perplexite 30 : equilibre - dix groupes coherents (valeur par defaut de sklearn).
Perplexite 50 : plus globale - les groupes s'elargissent et se rapprochent.

Lecture du résultat — une sensibilité à connaître avant de publier une carte

Les trois cartes montrent la même donnée, mais pas la même histoire : à perplexité 5 la structure se fragmente (chaque îlot ne « voit » que ses 5 voisins les plus proches — des micro-groupes apparaissent qui ne sont pas des classes), à 30 les dix chiffres forment des groupes compacts, à 50 les groupes s’étalent et se rapprochent. Aucune de ces trois cartes n’est « fausse » : la perplexité est un choix d’échelle d’analyse, comme le choix d’une bande passante en estimation de densité. En pratique : essayer plusieurs valeurs, et se méfier d’une conclusion qui n’est stable que pour une seule perplexité.

UMAP : la structure globale mieux conservée

UMAP (Uniform Manifold Approximation and Projection, McInnes, Healy & Melville 2018) poursuit le même objectif que t-SNE (préserver les voisinages en 2D) avec deux différences pratiques : son coût passe mieux à l’échelle (graphe de voisinage + optimisation SGD — sur les grands jeux de données il devient nettement plus rapide que t-SNE, dont le coût est super-linéaire ; en revanche, sur un petit jeu comme celui-ci, la première exécution paie la compilation JIT de numba et peut sortir plus lente que t-SNE, comme la cellule ci-dessous le mesure), et il conserve mieux la structure globale — les groupes restent disposés de façon cohérente avec leur similarité d’ensemble, là où t-SNE disperse les îlots arbitrairement. Ses paramètres : n_neighbors (l’équivalent de la perplexité — la taille du voisinage) et min_dist (la distance minimale tolérée entre points projetés).

Dépendance. umap-learn est optionnel (pip install umap-learn) ; la cellule contient une garde d’import — sans la librairie, le notebook poursuit avec un message explicite.

# UMAP sur les memes digits, compare au t-SNE
try:
    import umap
    UMAP_DISPONIBLE = True
except ImportError:
    UMAP_DISPONIBLE = False

if UMAP_DISPONIBLE:
    t0 = time.perf_counter()
    reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, n_components=2, random_state=42)
    X_umap = reducer.fit_transform(X)
    duree_umap = time.perf_counter() - t0

    fig, axes = plt.subplots(1, 2, figsize=(13, 5.5))
    axes[0].scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10, alpha=0.6)
    axes[0].set_title(f"t-SNE ({duree_tsne:.1f} s)")
    scatter = axes[1].scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap='tab10', s=10, alpha=0.6)
    axes[1].set_title(f"UMAP n_neighbors=15 ({duree_umap:.1f} s)")
    fig.colorbar(scatter, ax=axes[1], label="chiffre (vrai label)")
    plt.suptitle("t-SNE vs UMAP sur les memes digits")
    plt.tight_layout()
    plt.show()

    print(f"Durees sur {X.shape[0]} digits : t-SNE {duree_tsne:.1f} s vs UMAP {duree_umap:.1f} s")
    print("Les dix groupes sont nets dans les deux cas ; UMAP conserve en outre")
    print("une disposition globale coherente (groupes semblables plus proches).")
else:
    print("umap-learn n'est pas installe : pip install umap-learn")
    print("La suite du notebook ne depend pas de cette cellule.")

Durees sur 1797 digits : t-SNE 3.1 s vs UMAP 10.1 s
Les dix groupes sont nets dans les deux cas ; UMAP conserve en outre
une disposition globale coherente (groupes semblables plus proches).

Lecture : UMAP face à t-SNE

Sur la comparaison à trois panneaux, UMAP sépare les dix groupes et préserve leur arrangement relatif — les clusters voisins en UMAP sont grosso modo voisins dans la donnée, là où t-SNE optimise les voisinages locaux sans garantie sur les distances entre groupes. Ce « respect de la structure globale » est la principale différence pratique entre les deux moteurs. Le warning affiché n’est pas une erreur : en fixant random_state, on demande une carte reproductible au prix du parallélisme (n_jobs ramené à 1) — un arbitrage assumé en pédagogie, à lever si la vitesse devient le critère. Enfin UMAP est typiquement plus rapide à taille égale, ce qui le rend plausible sur des jeux plus grands que ces 1797 points.

Guide de choix : quelle réduction pour quel objectif ?

Objectif Méthode Pourquoi Limitations à connaître
Compresser / débruiter (section 6) ACP Projection optimale en variance ; inverse_transform reconstruit une approximation Linéaire — perd les variations non linéaires
Visualiser / explorer (cette section) t-SNE / UMAP Séparent des classes que l’ACP chevauche Distances inter-clusters non interprétables (t-SNE) ; perplexité/n_neighbors change la carte ; coût super-linéaire (t-SNE)
Réduire AVANT un modèle (pipeline aval) ACP transform s’applique à de nouvelles données — indispensable en production t-SNE n’a pas de transform propre : il faut refaire l’embedding entier à chaque nouveau point

La règle pratique tient en une ligne : l’ACP est une transformation, t-SNE/UMAP sont des visualisations. On met la première dans un pipeline, les secondes dans un notebook d’exploration. Pour la lecture des espaces latents (autoencodeurs, VAE, embeddings RAG), ce contraste sera le réflexe à réutiliser.

Exercice 4 : le t-SNE vu par KMeans

Les deux moitiés de ce notebook ne se sont encore jamais croisées : X_tsne (la projection t-SNE) a toujours été colorée par le vrai chiffre y. Refaites la visualisation en colorant par le cluster KMeans (kmeans.labels_, section 2) — si le clustering a bien retrouvé la structure des chiffres, les îlots doivent rester homogènes, à une permutation de couleurs près.

Étapes 1. Tracez le scatter de X_tsne coloré par kmeans.labels_ (même modèle d’appel que les figures ci-dessus : plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=kmeans.labels_, cmap='tab10', s=10, alpha=0.6)). 2. Comparez avec la figure colorée par y : combien d’îlots mélangés voyez-vous ?

# Exercice 4 : colorier la projection t-SNE par cluster KMeans
# X_tsne : projection 2D de chaque image (calculee plus haut)
# kmeans.labels_ : cluster (0-9) assigne a chaque image par KMeans (section 2)
# Etape 1 : plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=kmeans.labels_, cmap='tab10', s=10, alpha=0.6)
# Etape 2 : comparer avec la figure coloree par le vrai chiffre y

ilots_melanges = None  # TODO etudiant : combien d'ilots melangez-vous avec la couleur KMeans ?
print(f"Exercice 4 a completer : {ilots_melanges} ilot(s) melange(s) avec la couleur KMeans")
Exercice 4 a completer : None ilot(s) melange(s) avec la couleur KMeans

Vérification 3 — lire une carte t-SNE

question(
    "Deux amas nettement séparés apparaissent sur une carte t-SNE. Que peut-on en dire ?",
    choix=[
        "Que la distance entre les amas est une distance dans l'espace d'origine",
        "Que seule la structure de voisinage est exploitable : la distance entre amas n'est pas interprétable, et la carte change avec la perplexité",
        "Que les amas sont les vraies classes du jeu de données",
    ],
    bonne="B",
    explication="t-SNE et UMAP préservent le voisinage local, pas les distances globales : deux amas écartés sur la carte peuvent être proches dans les données. La carte sert à voir, pas à mesurer — et la perplexité en déplace les frontières.",
    reponse=None,  # remplacez None par la lettre de votre choix, puis re-executez
    moment="pendant",
)
Vérification — Deux amas nettement séparés apparaissent sur une carte t-SNE. Que peut-on en dire ?

  A. Que la distance entre les amas est une distance dans l'espace d'origine
  B. Que seule la structure de voisinage est exploitable : la distance entre amas n'est pas interprétable, et la carte change avec la perplexité
  C. Que les amas sont les vraies classes du jeu de données

Réponse non donnée. Remplacez `reponse=None` par la lettre de votre choix dans cette cellule, puis ré-exécutez-la pour afficher la correction.

8. Au-delà de KMeans : quand un cluster n’est pas une « boule »

Les sections 2-3 ont clusterisé des chiffres avec KMeans : il y a fonctionné, car chaque chiffre forme une masse à peu près compacte. KMeans découpe l’espace avec des frontières de Voronoï (chaque point rejoint le centroïde le plus proche) : il produit donc toujours des clusters convexes, en « boules ». Dès qu’un groupe authentique ne ressemble pas à une boule — deux croissants imbriqués, deux anneaux concentriques — KMeans le coupe en deux morceaux pourtant arbitraires.

Cette section construit deux jeux de données synthétiques où la forme compte, regarde ce que KMeans en fait, puis introduit deux familles qui suivent la forme : DBSCAN (densité) et le clustering hiérarchique (dendrogramme).

# Deux formes non convexes : des demi-lunes imbriquees et deux anneaux concentriques
from sklearn.datasets import make_moons, make_circles
from sklearn.metrics import adjusted_rand_score  # 1 = accord parfait avec les vraies classes, 0 = hasard
from sklearn.preprocessing import StandardScaler

def scatter_2d(X, labels, title, cmap='tab10'):
    plt.figure(figsize=(4.5, 3.5))
    plt.scatter(X[:, 0], X[:, 1], c=labels, cmap=cmap, s=20, alpha=0.75, edgecolors='k', linewidths=0.3)
    plt.title(title, fontsize=10)
    plt.xlabel("x1"); plt.ylabel("x2")
    plt.colorbar(label="classe estimee")
    plt.tight_layout()
    plt.show()

# StandardScaler : DBSCAN et le hierarchique mesurent des DISTANCES -> l'echelle change tout
X_moons, y_moons = make_moons(n_samples=300, noise=0.10, random_state=42)
X_rings, y_rings = make_circles(n_samples=300, factor=0.5, noise=0.03, random_state=42)
X_moons = StandardScaler().fit_transform(X_moons)
X_rings = StandardScaler().fit_transform(X_rings)

# KMeans force k=2 : il ne peut que decouper en deux "boules" a frontiere droite
km_moons = KMeans(n_clusters=2, n_init=10, random_state=42).fit(X_moons)
km_rings = KMeans(n_clusters=2, n_init=10, random_state=42).fit(X_rings)
scatter_2d(X_moons, km_moons.labels_, "KMeans (k=2) sur les demi-lunes")
scatter_2d(X_rings, km_rings.labels_, "KMeans (k=2) sur les anneaux")
print(f"ARI KMeans — demi-lunes : {adjusted_rand_score(y_moons, km_moons.labels_):.2f}")
print(f"ARI KMeans — anneaux   : {adjusted_rand_score(y_rings, km_rings.labels_):.2f}")

ARI KMeans — demi-lunes : 0.48
ARI KMeans — anneaux   : -0.00

Lecture du résultat — pourquoi KMeans échoue ici

Un ARI de 0.48 sur les demi-lunes et ~0 sur les anneaux veut dire que KMeans ne reproduit pas les deux groupes réels. Sur les demi-lunes, il apparie la moitié de chaque croissant : le haut-gauche de l’un finit dans le même cluster que le haut-gauche de l’autre (0.48, très loin du 1.0 qu’un clustering correct donnerait). Sur les anneaux, il assigne un « coin » du cercle extérieur à un cluster et le reste à l’autre. C’est un échec structurel : la frontière de Voronoï est toujours droite, donc le cluster est toujours convexe, et ces deux jeux ne le sont pas.

Ce n’est pas une mauvaise exécution, c’est l’hypothèse du modèle. Pour suivre une forme, il faut un algorithme qui raisonne en distance locale (qui est près de qui) plutôt qu’en distance au centroïde (qui est près du centre).

9. DBSCAN : le clustering par densité

DBSCAN (Density-Based Spatial Clustering of Applications with Noise) ne fixe pas de nombre de clusters : il part d’un point « noyau » (au moins min_samples voisins dans un rayon eps) et étend le cluster aux points atteignables. Deux conséquences :

  • il détecte des formes arbitraires, pas seulement des boules ;
  • il ne force aucun k — mais il introduit un hyperparamètre eps (le rayon) à régler, et il peut laisser des points non assignés (label = -1), traités comme du bruit/outlier.
# DBSCAN est sensible a eps : trop petit -> fragmenté, trop grand -> tout fusionne
from sklearn.cluster import DBSCAN
for eps in [0.2, 0.3, 0.4]:
    db = DBSCAN(eps=eps, min_samples=5).fit(X_moons)
    ncl = len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)
    print(f"  Demi-lunes eps={eps:.1f} : ARI={adjusted_rand_score(y_moons, db.labels_):.2f}  clusters={ncl}  outliers={int((db.labels_==-1).sum())}")

db_moons = DBSCAN(eps=0.3, min_samples=5).fit(X_moons)
db_rings = DBSCAN(eps=0.3, min_samples=5).fit(X_rings)
scatter_2d(X_moons, db_moons.labels_, "DBSCAN (eps=0.3) sur les demi-lunes")
scatter_2d(X_rings, db_rings.labels_, "DBSCAN (eps=0.3) sur les anneaux")
print(f"ARI DBSCAN — demi-lunes : {adjusted_rand_score(y_moons, db_moons.labels_):.2f}  (outliers={int((db_moons.labels_==-1).sum())})")
print(f"ARI DBSCAN — anneaux   : {adjusted_rand_score(y_rings, db_rings.labels_):.2f}  (outliers={int((db_rings.labels_==-1).sum())})")
  Demi-lunes eps=0.2 : ARI=0.50  clusters=7  outliers=25
  Demi-lunes eps=0.3 : ARI=0.99  clusters=2  outliers=2
  Demi-lunes eps=0.4 : ARI=0.00  clusters=1  outliers=0

ARI DBSCAN — demi-lunes : 0.99  (outliers=2)
ARI DBSCAN — anneaux   : 1.00  (outliers=0)

Lecture du résultat — densité, eps et outliers

À eps=0.3, DBSCAN retrouve les deux formes (ARI ≈ 1) : chaque croissant, chaque anneau devient un cluster qui épouse la forme. Le balayage eps montre le mécanisme : trop petit, le cluster se fragmente et une partie des points devient -1 (outliers) ; trop grand, tout se fond en un seul cluster (ARI 0). On n’a pas à choisir k, mais on a remplacé k par eps — le réglage reste, c’est juste un paramètre différent, plus attaché à l’échelle des données (d’où le StandardScaler du début).

Le label = -1 est un avantage d’honnêteté : là où KMeans assigne tout le monde, DBSCAN avoue qu’un point est du bruit s’il n’est dense près de rien.

Exercice 5 : trouver le rayon eps qui fait la balance

La réussite de DBSCAN dépend du rayon eps. Fais-le varier sur les demi-lunes et observe l’ARI, le nombre de clusters et le nombre d’outliers. Trouve le eps qui sépare les deux croissants sans les fragmenter.

# Exercice 5 : trouver le rayon eps qui separe les deux demi-lunes sans les fragmenter
# Etape 1 : faire varier eps et observer ARI / nombre de clusters / outliers
# Etape 2 : retenir le eps qui maximise l'ARI sans outliers qui noient la structure
bon_eps = None  # TODO etudiant : le rayon qui fait la balance
print(f"Exercice 5 a completer : bon eps = {bon_eps}")
Exercice 5 a completer : bon eps = None

10. Le clustering hiérarchique et le dendrogramme

Le clustering hiérarchique agglomératif construit une hiérarchie : on part de chaque point isolé et on fusionne les deux groupes les plus proches, jusqu’à un seul. Le résultat est un dendrogramme (arbre de fusion), que l’on découpe à la hauteur donnant le bon nombre de clusters. Le choix du lien (distance entre deux groupes) change tout :

  • lien simple (single) : la distance la plus courte entre les deux groupes — suit les formes allongées, mais « enchaîne » des points via un pont ;
  • lien complet (complete) : la distance la plus longue — produit des groupes compacts, mais rate les formes allongées.
# Lien simple vs lien complet : le lien choisi change le resultat
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage

for link in ["single", "complete"]:
    a = AgglomerativeClustering(n_clusters=2, linkage=link).fit(X_rings)
    print(f"  Anneaux lien={link:10s} : ARI={adjusted_rand_score(y_rings, a.labels_):.2f}")

ag_moons = AgglomerativeClustering(n_clusters=2, linkage="single").fit(X_moons)
ag_rings = AgglomerativeClustering(n_clusters=2, linkage="single").fit(X_rings)
scatter_2d(X_moons, ag_moons.labels_, "Hierarchique (lien simple) sur les demi-lunes")
scatter_2d(X_rings, ag_rings.labels_, "Hierarchique (lien simple) sur les anneaux")

# Le dendrogramme des anneaux : la hauteur de fusion dit ou couper pour 2 clusters
Z = linkage(X_rings, method="single")
thr = Z[-3, 2]  # distance de fusion du 3e plus grand saut = coupe pour obtenir 2 clusters
plt.figure(figsize=(8, 3.5))
dendrogram(Z, no_labels=True, color_threshold=thr)
plt.axhline(thr, color="k", ls="--", lw=1, label=f"Coupe a {thr:.2f} (2 clusters)")
plt.title("Dendrogramme (lien simple) des anneaux")
plt.xlabel("points (ordre de fusion)"); plt.ylabel("distance de fusion")
plt.legend(); plt.tight_layout(); plt.show()

print(f"ARI hierarchique lien simple — demi-lunes : {adjusted_rand_score(y_moons, ag_moons.labels_):.2f}")
print(f"ARI hierarchique lien simple — anneaux   : {adjusted_rand_score(y_rings, ag_rings.labels_):.2f}")
  Anneaux lien=single     : ARI=1.00
  Anneaux lien=complete   : ARI=0.01

ARI hierarchique lien simple — demi-lunes : 1.00
ARI hierarchique lien simple — anneaux   : 1.00

Lecture du résultat — chaining, coupe et choix du lien

Le dendrogramme se lit de bas en haut : les deux anneaux forment chacun une grande branche, puis tout fusionne en un seul groupe au sommet. Couper à la hauteur du 3ᵉ plus grand saut donne exactement 2 clusters, et le lien simple retrouve les deux anneaux (ARI = 1). Le lien complet les rate : il exige que les deux groupes soient proches en tous points (distance la plus longue), or chaque anneau est un cercle creux dont les points les plus éloignés sont à l’opposé l’un de l’autre.

C’est le compromis du lien : le lien simple enchaîne (chaining) et suit les formes courbes, mais se laisse piéger par un pont de points ; le lien complet ou moyen produit des groupes plus compacts, mais ne suit pas les formes concaves. Il n’y a pas de choix gratuit — c’est le sens du guide qui suit.

Exercice 6 : lien simple ou lien complet sur les demi-lunes ?

Compare l’ARI des deux liens sur X_moons. Lequel suit les deux croissants ? Explique pourquoi l’autre échoue.

# Exercice 6 : meilleur lien pour les demi-lunes
# Etape 1 : comparer l'ARI de "single" et "complete" sur X_moons
# Etape 2 : expliquer pourquoi l'un suit les croissants et pas l'autre
meilleur_lien = None  # TODO etudiant : "single" ou "complete" ?
print(f"Exercice 6 a completer : meilleur lien = {meilleur_lien}")
Exercice 6 a completer : meilleur lien = None

11. Guide de choix : quel algorithme pour quelle forme ?

Algorithme Hypothèse implicite Formes qu’il suit À connaître Résultat ici (demi-lunes / anneaux)
KMeans clusters convexes, isotropes, tailles comparables « boules » il faut fixer k ; frontières droites ; assigne tout le monde 0.48 / 0.00
DBSCAN densité locale : zones denses séparées par du vide formes arbitraires il faut régler eps (+min_samples) ; -1 = outlier ; fragilité à eps 0.99 / 1.00
Hiérarchique aucune forme imposée a priori ce que le lien autorise choix du lien ; dendrogramme = inspection à une hauteur lien simple : 1.00 / 1.00

Le réflexe. Avant tout clustering à base de distance (DBSCAN, hiérarchique, et même KMeans), standardiser les variables — sinon le réglage d’eps ou la distance dépendent de l’unité de chaque axe. Et quand un groupe ne ressemble visiblement pas à une boule, ne pas « tordre » KMeans : changer de modèle.

Exercice 7 : quel algorithme dans quel cas ?

Associe chaque description à la famille la plus adaptée (KMeans, DBSCAN ou hiérarchique) et justifie en une ligne.

# Exercice 7 : quel algorithme pour quelle forme ?
# (a) une base clients : 3 masses homogenes et bien separees       -> ?
# (b) une carte de taches spatiales : des ilots denses et du bruit -> ?
choix_a = None  # TODO etudiant : "KMeans", "DBSCAN" ou "hierarchique" ?
choix_b = None  # TODO etudiant : idem
print(f"Exercice 7 a completer : (a) {choix_a}, (b) {choix_b}")
Exercice 7 a completer : (a) None, (b) None

Synthèse : supervisé vs non supervisé

Le supervisé (notebooks 2.1 à 2.5) dispose d’étiquettes et apprend à prédire. Le non supervisé (ce notebook) n’a pas d’étiquettes et trouve de la structure (clusters) ou compresse (PCA). En pratique, ces approches se combinent souvent : on peut appliquer une PCA avant un clustering, ou avant un modèle supervisé pour accélérer l’apprentissage et réduire le surajustement.

Référence. Hastie, T., Tibshirani, R. & Friedman, J. (2009), The Elements of Statistical Learning, Springer (2e éd.), §14.3, 14.5. — Synthèse du clustering et de la réduction de dimension dans le cadre de l’apprentissage statistique.

Après le parcours — question de transfert

question(
    "Vous cherchez des groupes en forme de croissants, non sphériques. Quel outil choisir ?",
    choix=[
        "KMeans, avec un k plus grand",
        "DBSCAN ou le lien simple du clustering hiérarchique",
        "L'ACP, qui sépare les croissants par ses composantes",
    ],
    bonne="B",
    explication="KMeans découpe l'espace en régions autour de centres : il coupe les croissants. Les méthodes de densité et le lien simple suivent la forme des groupes — c'est ce que montre la comparaison de cette section.",
    reponse=None,  # remplacez None par la lettre de votre choix, puis re-executez
    moment="apres",
)
Transfert — Vous cherchez des groupes en forme de croissants, non sphériques. Quel outil choisir ?

  A. KMeans, avec un k plus grand
  B. DBSCAN ou le lien simple du clustering hiérarchique
  C. L'ACP, qui sépare les croissants par ses composantes

Réponse non donnée. Remplacez `reponse=None` par la lettre de votre choix dans cette cellule, puis ré-exécutez-la pour afficher la correction.

Conclusion : fin du socle ML

Vous avez parcouru les six premiers notebooks de la série 02-ML-Cours : (2.1) le workflow ML, (2.2) la descente de gradient, (2.3) la régression linéaire et logistique, (2.4) les arbres / forêts / ensembles, (2.5) le compromis biais-variance / validation croisée / courbes ROC, et (2.6) le clustering, l’ACP et la réduction non linéaire de visualisation (t-SNE, UMAP). Un étudiant ayant travaillé ces six notebooks dispose du socle ML canonique qui manquait entre NumPy/Pandas et les ateliers agents. La série se poursuit avec 2.7 (modèles non paramétriques), 2.8 (théorie PAC) et 2.9 (grokking).

References

  1. MacQueen, J. (1967). Some Methods for Classification and Analysis of Multivariate Observations. Proceedings of the Fifth Berkeley Symposium on Mathematical Statistics and Probability 1:281-297. — La méthode des k-moyennes, partitionnement par minimisation de l’inertie intra-cluster.
  2. Pearson, K. (1901). On Lines and Planes of Closest Fit to Systems of Points in Space. Philosophical Magazine 2(11):559-572. — Article fondateur de l’Analyse en Composantes Principales (axes de variance maximale).
  3. Jolliffe, I.T. (2002). Principal Component Analysis. Springer (2e éd.). — Référence sur l’ACP : variance expliquée, choix du nombre de composantes.
  4. van der Maaten, L. & Hinton, G. (2008). Visualizing Data using t-SNE. Journal of Machine Learning Research 9:2579-2605. — t-SNE : embedding non linéaire préservant les voisinages, rôle de la perplexité.
  5. McInnes, L., Healy, J. & Melville, J. (2018). UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction. arXiv:1802.03426. — UMAP : graphe de voisinage, rapidité et conservation de la structure globale.
  6. Hastie, T., Tibshirani, R. & Friedman, J. (2009). The Elements of Statistical Learning. Springer (2e éd.), §14.3, 14.5. — Clustering et réduction de dimension.
  7. Pedregosa, F. et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research 12:2825-2830. — KMeans, PCA, TSNE, load_digits.
  8. Deisenroth, M.P., Faisal, A.A. & Ong, C.S. (2020). Mathematics for Machine Learning. Cambridge UP. — ch. 10 (Dimensionality Reduction) pose l’analyse en composantes principales (décomposition en valeurs singulières, variance expliquée cumulée) que ce carnet implémente en PCA.fit_transform. Le lien avec K-means est aussi abordé comme cas particulier de décomposition spectrale (ch. 10.3).
Retour au sommet