SOM : Guide Complet — Cartes Auto-Organisatrices de Kohonen

SOM : Guide Complet — Cartes Auto-Organisatrices de Kohonen

Self-Organizing Map (SOM) : Guide Complet des Cartes Auto-Organisatrices de Kohonen

Résumé

Les Self-Organizing Maps (SOM), ou cartes auto-organisatrices, constituent l’un des algorithmes d’apprentissage non supervisé les plus élégants jamais conçus. Introduites par le professeur finlandais Teuvo Kohonen en 1982, ces réseaux de neurones artificiels possèdent une propriété remarquable : ils projettent des données de haute dimension sur une carte bidimensionnelle tout en préservant la topologie de l’espace d’entrée. En d’autres termes, les échantillons similaires dans l’espace original se retrouvent proches les uns des autres sur la carte. Cette caractéristique fait des SOM un outil puissant pour la visualisation de données multidimensionnelles, le clustering, et l’exploration de structures cachées dans des jeux de données complexes. Contrairement aux méthodes de clustering traditionnelles comme K-Means, une SOM ne produit pas simplement des groupes isolés : elle construit une représentation spatiale organisée où la proximité géographique sur la carte reflète la similarité entre les données.

Principe Mathématique de l’Algorithme

Le fonctionnement d’une Self-Organizing Map repose sur un mécanisme d’apprentissage compétitif qui s’articule autour de trois étapes fondamentales. Chaque étape est essentielle pour comprendre comment la carte parvient à s’organiser d’elle-même.

Étape 0 : Initialisation

On dispose d’une grille de neurones (généralement rectangulaire ou hexagonale). Chaque neurone i possède un vecteur de poids wᵢ de même dimension que les données d’entrée. Ces poids sont initialisés aléatoirement, ce qui signifie qu’au départ, la carte est complètement désorganisée et ne reflète aucune structure des données.

Étape 1 : Recherche du Best Matching Unit (BMU)

Pour chaque échantillon d’entraînement x, on cherche le neurone dont le poids est le plus proche de x selon une métrique de distance, typiquement la distance euclidienne :

$$c = \arg\min_i ||\mathbf{x} – \mathbf{w}_i||$$

Le neurone c ainsi identifié est appelé le Best Matching Unit (BMU), c’est-à-dire l’unité de meilleure correspondance. C’est le neurone dont le vecteur de poids ressemble le plus à l’échantillon actuel. Cette compétition entre neurones est au cœur de l’apprentissage des SOM.

Étape 2 : Mise à jour des poids

Une fois le BMU identifié, on met à jour non seulement ses poids, mais aussi ceux de ses voisins sur la grille. La règle de mise à jour est la suivante :

$$\mathbf{w}_i(t+1) = \mathbf{w}_i(t) + \alpha(t) \cdot h(c, i, t) \cdot (\mathbf{x} – \mathbf{w}_i(t))$$

où :

  • α(t) est le taux d’apprentissage (learning rate) à l’itération t, qui décroît au cours du temps.
  • h(c, i, t) est la fonction de voisinage gaussienne qui mesure l’influence du BMU c sur le neurone voisin i.

Étape 3 : Fonction de voisinage

La fonction de voisinage détermine comment un neurone i est affecté par la présence du BMU c :

$$h(c, i, t) = \exp\left(-\frac{||\mathbf{r}_c – \mathbf{r}_i||^2}{2 \cdot \sigma(t)^2}\right)$$

r_c et r_i sont les positions respectives des neurones c et i sur la grille bidimensionnelle. La fonction gaussienne garantit que les neurones proches du BMU sont fortement mis à jour, tandis que les neurones éloignés le sont très peu.

Décroissance des paramètres

Pour assurer la convergence de l’algorithme, deux paramètres décroissent exponentiellement au fil des itérations :

$$\alpha(t) = \alpha_0 \cdot \exp(-t / \text{max_iterations})$$

$$\sigma(t) = \sigma_0 \cdot \exp(-t / \text{max_iterations})$$

  • α₀ est le taux d’apprentissage initial.
  • σ₀ est le rayon de voisinage initial.

Cette décroissance est cruciale : dans les premières itérations, un large rayon de voisinage permet à la carte de s’organiser globalement. Progressivement, le rayon se réduit et l’apprentissage devient plus local, affinant les détails de la carte. C’est ce processus qui permet à la SOM de préserver la topologie : les neurones proches sur la grille finissent par avoir des poids similaires, reflétant ainsi la structure des données d’entrée.

Intuition : L’Archipel des Écosystèmes

Imaginez un archipel d’îles disposées sur une grille régulière au milieu de l’océan. Chaque île représente un neurone de la carte auto-organisatrice. Au début, chaque île possède un habitat complètement aléatoire : certaines sont désertiques, d’autres sont couvertes de forêts tropicales, certaines sont enneigées, d’autres sont des marécages. Aucun ordre n’est visible depuis le ciel.

Maintenant, imaginez que des animaux représentant vos données arrivent progressivement, un par un. Chaque animal cherche l’île dont l’habitat lui correspond le mieux. Un ours polaire choisira naturellement l’île enneigée, un toucan préférera l’île tropicale. Cette île élue est le Best Matching Unit (BMU) pour cet animal.

Mais voici la magie de l’algorithme : quand une île reçoit un visiteur, elle adapte son habitat pour encore mieux correspondre à l’animal, et ses îles voisines font de même, mais dans une moindre proportion. L’île elle-même change beaucoup, ses voisines directes changent un peu, et les îles plus lointaines ne changent presque pas. C’est la fonction de voisinage en action.

Au fil du temps, avec des milliers d’animaux qui visitent l’archipel, quelque chose de remarquable se produit : les îles proches les unes des autres finissent par avoir des habitats similaires. L’archipel s’organise spontanément ! Les îles enneigées se regroupent dans un coin, les forêts tropicales dans un autre, les déserts dans un troisième. La carte devient une représentation organisée des écosystèmes présents dans les données. Les animaux similaires se retrouvent toujours dans la même région de l’archipel. La topologie est préservée : si vous marchez sur la carte, vous traversez des écosystèmes qui changent graduellement, sans saut brutal.

C’est exactement ce que fait une Self-Organizing Map avec vos données : elle construit une carte où la proximité spatiale reflète la similarité sémantique.

Implémentation Python

Implémentation from-scratch d’une SOM 2D

Voici une implémentation complète d’une Self-Organizing Map à partir de zéro, sans dépendances autres que NumPy et Matplotlib. Cette version utilise une grille rectangulaire et une fonction de voisinage gaussienne.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler

class SelfOrganizingMap:
    """
    Implémentation from-scratch d'une Self-Organizing Map (SOM)
    de Kohonen avec grille 2D rectangulaire.
    """

    def __init__(self, map_width, map_height, input_dim,
                 sigma0=None, learning_rate0=0.5, n_iterations=10000,
                 seed=42):
        self.map_width = map_width
        self.map_height = map_height
        self.input_dim = input_dim
        self.n_iterations = n_iterations
        self.learning_rate0 = learning_rate0
        # Rayon initial : max de la moitié des dimensions de la carte
        self.sigma0 = sigma0 if sigma0 else max(map_width, map_height) / 2.0

        rng = np.random.RandomState(seed)
        # Initialisation aléatoire des poids
        self.weights = rng.rand(map_width, map_height, input_dim)

        # Grille de positions (coordonnées 2D)
        self.grid_x = np.arange(map_width)
        self.grid_y = np.arange(map_height)
        self.pos_x, self.pos_y = np.meshgrid(self.grid_x, self.grid_y)
        self.positions = np.column_stack([self.pos_x.ravel(), self.pos_y.ravel()])

    def _compute_distances(self, x):
        """Calcule la distance euclidienne entre x et tous les poids."""
        diff = self.weights - x[np.newaxis, np.newaxis, :]
        return np.sqrt(np.sum(diff ** 2, axis=2))

    def find_bmu(self, x):
        """Trouve le Best Matching Unit pour un échantillon x."""
        distances = self._compute_distances(x)
        idx = np.argmin(distances)
        bmu_x, bmu_y = np.unravel_index(idx, (self.map_width, self.map_height))
        return bmu_x, bmu_y

    def _gaussian_neighborhood(self, bmu_x, bmu_y, sigma):
        """Calcule la fonction de voisinage gaussienne pour tous les neurones."""
        dist_sq = ((self.pos_x - bmu_x) ** 2 + (self.pos_y - bmu_y) ** 2)
        neighborhood = np.exp(-dist_sq / (2 * sigma ** 2))
        return neighborhood

    def fit(self, X, verbose=True):
        """Entraîne la SOM sur les données X."""
        for t in range(self.n_iterations):
            # Sélection aléatoire d'un échantillon
            idx = np.random.randint(0, len(X))
            x = X[idx]

            # Décroissance des paramètres
            alpha = self.learning_rate0 * np.exp(-t / self.n_iterations)
            sigma = self.sigma0 * np.exp(-t / self.n_iterations)

            # Trouver le BMU
            bmu_x, bmu_y = self.find_bmu(x)

            # Fonction de voisinage
            neighborhood = self._gaussian_neighborhood(bmu_x, bmu_y, sigma)
            neighborhood = neighborhood[:, :, np.newaxis]  # Broadcasting

            # Mise à jour des poids
            self.weights += alpha * neighborhood * (x - self.weights)

            if verbose and t % 1000 == 0:
                print(f"Itération {t}/{self.n_iterations} | "
                      f"α={alpha:.4f} | σ={sigma:.4f}")

        return self

    def transform(self, X):
        """Projette les données X sur la carte (retourne les coordonnées BMU)."""
        bmu_coords = []
        for x in X:
            bx, by = self.find_bmu(x)
            bmu_coords.append((bx, by))
        return np.array(bmu_coords)

    def u_matrix(self):
        """Calcule la U-Matrix (carte des distances moyennes entre voisins)."""
        u = np.zeros((self.map_width, self.map_height))
        for i in range(self.map_width):
            for j in range(self.map_height):
                neighbors = []
                for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
                    ni, nj = i+di, j+dj
                    if 0 <= ni < self.map_width and 0 <= nj < self.map_height:
                        dist = np.linalg.norm(self.weights[i,j] - self.weights[ni,nj])
                        neighbors.append(dist)
                if neighbors:
                    u[i,j] = np.mean(neighbors)
                else:
                    u[i,j] = 0
        return u

Application sur le jeu de données Iris

Illustrons maintenant l’utilisation de notre SOM sur le célèbre jeu de données Iris de Fisher, un classique de l’apprentissage automatique contenant 150 échantillons de trois espèces d’iris.

# Chargement et normalisation des données
iris = load_iris()
X = iris.data
y = iris.target
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

# Création et entraînement de la SOM
som = SelfOrganizingMap(
    map_width=10,
    map_height=10,
    input_dim=4,
    sigma0=4.0,
    learning_rate0=0.5,
    n_iterations=50000,
    seed=42
)
som.fit(X_scaled, verbose=True)

# Visualisation de la U-Matrix
u_mat = som.u_matrix()
plt.figure(figsize=(8, 6))
plt.imshow(u_mat.T, cmap='viridis')
plt.colorbar(label="Distance moyenne entre neurones voisins")
plt.title("U-Matrix de la SOM appliquée aux données Iris")
plt.xlabel("Coordonnée x sur la grille")
plt.ylabel("Coordonnée y sur la grille")
plt.tight_layout()
plt.savefig("som_iris_umatrix.png", dpi=150)
plt.show()

# Marquer les BMU de chaque classe sur la grille
bmu_coords = som.transform(X_scaled)
plt.figure(figsize=(8, 6))
colors = ['red', 'green', 'blue']
species = ['Setosa', 'Versicolor', 'Virginica']
for cls in range(3):
    mask = y == cls
    bx, by = bmu_coords[mask].T
    plt.scatter(bx, by, c=colors[cls], label=species[cls], alpha=0.6,
                edgecolors='black', s=100)
plt.title("Projection SOM — Espèces d'Iris sur la grille")
plt.xlabel("Coordonnée x")
plt.ylabel("Coordonnée y")
plt.legend()
plt.gca().set_xlim(-1, som.map_width)
plt.gca().set_ylim(-1, som.map_height)
plt.tight_layout()
plt.savefig("som_iris_clusters.png", dpi=150)
plt.show()

Utilisation de MiniSom

Pour des applications pratiques, la bibliothèque MiniSom offre une implémentation robuste et optimisée des SOM. Elle s’installe facilement via pip.

pip install minisom
from minisom import MiniSom

# Configuration de MiniSom
som_minisom = MiniSom(
    x=10, y=10,
    input_len=4,
    sigma=4.0,
    learning_rate=0.5,
    neighborhood_function='gaussian'
)

# Initialisation aléatoire
som_minisom.random_weights_init(X_scaled)

# Entraînement avec décroissance
som_minisom.train_random(
    X_scaled,
    num_iteration=50000,
    verbose=True
)

# Visualisation de la carte des poids (weight map)
plt.figure(figsize=(10, 8))
for i in range(4):
    plt.subplot(2, 2, i+1)
    plt.imshow(som_minisom.get_weights()[:, :, i].T, cmap='plasma')
    feature_names = ['Long. sépale', 'Larg. sépale', 'Long. pétale', 'Larg. pétale']
    plt.title(f"Carte des poids — {feature_names[i]}")
    plt.colorbar()
plt.tight_layout()
plt.savefig("som_weight_maps.png", dpi=150)
plt.show()

Clustering de Couleurs RGB avec SOM

Un exemple particulièrement visuel de l’utilité des SOM est le clustering de couleurs : on prend une image, on extrait tous ses pixels RGB, et on entraîne une SOM pour les regrouper en une palette représentative.

from skimage import io

# Charger une image (remplacer par votre chemin)
image = io.imread("photo.jpg")
pixels = image.reshape(-1, 3).astype(np.float64) / 255.0

# SOM pour extraire une palette de 16 couleurs (4x4)
color_som = SelfOrganizingMap(
    map_width=4,
    map_height=4,
    input_dim=3,
    sigma0=2.0,
    learning_rate0=0.5,
    n_iterations=20000,
    seed=123
)
color_som.fit(pixels, verbose=False)

palette = color_som.weights.reshape(4, 4, 3)

# Affichage de la palette
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].imshow(image)
axes[0].set_title("Image originale")
axes[0].axis("off")

axes[1].imshow(palette)
axes[1].set_title("Palette SOM (4×4)")
axes[1].axis("off")

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

# Reconstruire l'image avec la palette réduite
from scipy.spatial.distance import cdist
distances = cdist(pixels, color_som.weights.reshape(16, 3))
assigned = np.argmin(distances, axis=1)
reconstructed = color_som.weights.reshape(16, 3)[assigned].reshape(image.shape)

plt.imshow(reconstructed)
plt.title(f"Image reconstruite avec {16} couleurs SOM")
plt.axis("off")
plt.savefig("som_reconstructed.png", dpi=150)
plt.show()

Cette application démontre parfaitement la puissance des SOM : la carte a automatiquement extrait une palette de 16 couleurs dominantes qui préservent la distribution chromatique de l’image originale, tout en organisant ces couleurs de manière topologique sur la grille.

Hyperparamètres Clés

Le choix des hyperparamètres est déterminant pour la qualité d’une Self-Organizing Map. Voici les principaux :

Hyperparamètre Rôle Valeur typique Impact
map_width Nombre de neurones selon l’axe x 10-30 Détermine la résolution de la carte
map_height Nombre de neurones selon l’axe y 10-30 Détermine la résolution de la carte
sigma₀ (initial) Rayon de voisinage gaussien au démarrage max(width, height)/2 Contrôle l’organisation globale initiale
learning_rate₀ (initial) Taux d’apprentissage au démarrage 0.3 – 0.7 Vitesse de convergence initiale
n_iterations Nombre total d’itérations d’entraînement 10 000 – 100 000 Qualité finale de l’organisation

Conseils pratiques

  • Taille de la carte : une règle empirique suggère environ 5 × √N neurones, où N est le nombre d’échantillons. Pour 1000 données, une grille de 15×15 (225 neurones) est appropriée.
  • σ₀ trop petit : la carte se fragmente et ne s’organise pas globalement.
  • σ₀ trop grand : l’organisation est trop lente et les détails locaux sont mal capturés.
  • learning_rate trop élevé : instabilité et oscillation des poids.
  • learning_rate trop faible : convergence très lente, carte sous-optimale.
  • Nombre d’itérations : il faut suffisamment d’itérations pour que σ(t) et α(t) décroissent jusqu’à des valeurs très faibles, garantissant la stabilisation finale.
  • Normalisation des données : toujours normaliser les données avant l’entraînement (MinMaxScaler ou StandardScaler). Les SOM sont sensibles aux échelles différentes entre variables.

Avantages et Limites

Avantages

  1. Préservation topologique : la caractéristique distinctive des SOM. Les données similaires sont proches sur la carte, offrant une visualisation intuitive de structures de haute dimension.
  2. Visualisation intuitive : la U-Matrix révèle clairement les frontières entre clusters, là où les distances entre neurones voisins sont élevées.
  3. Non-supervisé et sans hypothèses : contrairement à K-Means, aucune hypothèse sur la forme des clusters n’est nécessaire (sphéricité, taille égale, etc.).
  4. Robustesse au bruit : la nature collective de l’apprentissage (mise à jour du voisinage) rend les SOM moins sensibles aux outliers que les méthodes individuelles.
  5. Détection de clusters naturels : la carte révèle la structure sous-jacente des données sans qu’il soit nécessaire de spécifier le nombre de clusters à l’avance.
  6. Interprétabilité : on peut visualiser la carte des poids pour chaque caractéristique individuellement, ce qui fournit une compréhension fine de ce que chaque région de la carte représente.

Limites

  1. Non-déterminisme : l’initialisation aléatoire des poids et l’ordre d’échantillonnage rendent les résultats non reproductibles sans fixer une graine aléatoire.
  2. Choix des hyperparamètres délicat : la taille de la carte, sigma, le learning rate et le nombre d’itérations nécessitent souvent de l’expérience et des essais multiples.
  3. Coût computationnel : chaque itération nécessite de calculer la distance entre l’échantillon et tous les poids. Pour de très grandes cartes et beaucoup de données, l’entraînement peut être long.
  4. Pas de métrique de qualité intrinsèque : contrairement au K-Means qui minimise l’inertie intra-cluster, les SOM n’ont pas de score objectif unique pour évaluer la qualité du résultat.
  5. Difficile à évaluer quantitativement : la visualisation est subjective, et il existe peu de métriques standards pour comparer deux cartes.
  6. Sensibilité à la normalisation : des variables non normalisées dominent la distance euclidienne et biaisent complètement le résultat.

4 Cas d’Usage Concrets

1. Exploration de données clients en marketing

Les SOM sont excellentes pour segmenter une base clientèle sans hypothèses préalables sur le nombre de segments. En projetant des données démographiques, comportementales et transactionnelles sur une carte 2D, on découvre des zones naturelles : les clients premium, les clients occasionnels, les nouveaux utilisateurs, etc. La topologie de la carte révèle aussi les transitions entre segments, identifiant les clients à la frontière qui pourraient basculer d’un comportement à un autre.

2. Analyse de documents et de textes

En représentant chaque document par un vecteur TF-IDF ou des embeddings (Word2Vec, BERT), une SOM peut organiser automatiquement une collection documentaire. Les articles sur des thèmes similaires se regroupent sur la carte. Un journaliste pourrait ainsi explorer des archives de milliers d’articles en naviguant visuellement sur la carte, identifiant des thématiques émergentes et des articles outliers qui méritent attention.

3. Diagnostic médical par imagerie

Les SOM peuvent analyser des caractéristiques extraites d’images médicales (radiographies, IRM, scanners) pour détecter des anomalies. En entraînant une carte sur des tissus sains, les régions correspondant à des tissus pathologiques apparaissent comme des zones distinctes de la U-Matrix, facilitant le repérage visuel de zones suspectes par des radiologues.

4. Contrôle qualité industriel

Dans l’industrie manufacturière, les SOM surveillent des centaines de capteurs simultanément. Un fonctionnement normal occupe une région spécifique de la carte. L’apparition d’échantillons dans de nouvelles zones signale une dérive ou un défaut naissant. Cette approche détecte des anomalies multivariées que des seuils univariés sur chaque capteur individuellement manqueraient complètement.

Voir Aussi


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.