U-Net : Guide Complet — Architecture de Segmentation d’Images

U-Net : Guide Complet — Architecture de Segmentation d'Images

U-Net

Résumé

U-Net est une architecture de réseaux de neurones convolutionnels spécialement conçue pour la segmentation sémantique d’images. Introduite par Olaf Ronneberger, Philipp Fischer et Thomas Brox en 2015 dans leur article fondateur « U-Net: Convolutional Networks for Biomedical Image Segmentation », cette architecture a révolutionné le domaine de la vision par ordinateur, particulièrement dans l’imagerie biomédicale.

Contrairement aux architectures de classification traditionnelle qui produisent un simple label en sortie, U-Net génère une carte de segmentation pixel par pixel. Chaque pixel de l’image d’entrée se voit attribuer une classe, ce qui permet de délimiter précisément les contours et les régions d’intérêt. Cette approche a rendu possible la détection automatique de tumeurs, la segmentation cellulaire microscopique, et bien d’autres applications critiques.

Le nom « U-Net » provient directement de la forme caractéristique de son architecture : un encodeur (bras descendant) qui extrait les caractéristiques de l’image, suivi d’un décodeur (bras montant) qui reconstruit la carte de segmentation, les deux bras étant reliés par des connexions de saut horizontales. Cette structure symétrique en forme de U est à la fois élégante et remarquablement efficace.

U-Net s’est imposée comme l’architecture de référence pour toute tâche nécessitant une compréhension fine de la structure spatiale des images, et demeure aujourd’hui la base de nombreuses variantes modernes.

Principe mathématique

Architecture symétrique en U

L’architecture de U-Net se compose de deux chemins principaux formant ensemble la lettre U :

Le chemin contractant (encodeur) suit l’approche classique des réseaux convolutionnels. Il alterne des blocs de deux convolutions 3×3 avec activation ReLU, suivies d’un max pooling 2×2 avec un pas de 2 pour le sous-échantillonnage. À chaque étape de pooling, le nombre de canaux de caractéristiques (features maps) est doublé. Mathématiquement, si l’entrée est X0 de dimensions H × W × C0, après la l-ième étape de pooling, les dimensions deviennent :

Hl = H / 2l, Wl = W / 2l, Cl = C0 × 2l

Cette réduction progressive de la résolution spatiale permet au réseau de capturer un contexte global de plus en plus large, au prix d’une perte d’information spatiale fine.

Le chemin expansif (décodeur) effectue le processus inverse. Chaque étape consiste en une upconvolution (transposée de convolution) permettant de doubler la résolution spatiale tout en réduisant de moitié le nombre de canaux. La formule de l’upconvolution est :

(X * K)i,j = Σm Σn Xi+m, j+n · Km,n

où K est le noyau de convolution transposée. Après chaque upconvolution, la carte de caractéristiques est concaténée avec la carte correspondante du chemin contractant via les connexions de saut (skip connections).

Skip connections et concaténation

Les connexions de saut constituent l’innovation clé de U-Net. Elles permettent de transférer les informations de haute résolution du chemin contractant vers le chemin expansif. À chaque niveau du décodeur, on concatène :

Fdéc(l) = [ UpConv(Fdéc(l+1)), Fenc(l) ]

où [·, ·] désigne la concaténation le long de l’axe des canaux. Cette opération permet au décodeur de récupérer les détails spatiaux fins que le pooling avait progressivement supprimés, tout en bénéficiant du contexte sémantique riche accumulé au fond du U.

Fonction de coût

Pour l’entraînement, U-Net utilise typiquement la cross-entropy pixel-wise combinée avec le coefficient de Dice :

LCE = − Σi=1N Σc=1C yi,c · log(ŷi,c)

où yi,c est la vérité terrain pour le pixel i et la classe c, et ŷi,c est la prédiction du réseau.

Le Dice loss, particulièrement utile pour les problèmes de segmentation déséquilibrée, se calcule comme suit :

Dice = (2 · Σi yi ŷi) / (Σi yi + Σi ŷi)

et le loss correspondant est LDice = 1 − Dice. Cette métrique est plus robuste que la cross-entropy lorsque les classes sont fortement déséquilibrées, situation fréquente en segmentation biomédicale où la région d’intérêt peut ne représenter qu’une infime partie de l’image.

Intuition : comprendre pourquoi U-Net fonctionne

Imaginez que vous essayez de retracer les contours d’un organe sur une image radiologique. Vous avez besoin de deux choses essentielles : comprendre le contexte global (où se situe l’organe dans l’ensemble du corps, quelle est sa forme générale) et voir les détails précis (les contours exacts, les petites irrégularités de la surface).

L’encodeur de U-Net fonctionne exactement comme votre cerveau lorsque vous zoomez progressivement sur une image. À chaque étape de pooling, il réduit la résolution mais élargit son champ de compréhension. C’est comme si vous regardiez d’abord une miniature de l’image pour saisir la structure d’ensemble : « Ah, voici le foie, il est dans le quadrant supérieur droit, il a une forme allongée. » Cette vue d’ensemble est indispensable — sans elle, on ne pourrait jamais distinguer un vaisseau sanguin d’une fissure dans l’image.

Le décodeur, lui, fait le chemin inverse. Il redessine pixel par pixel la carte de segmentation, en utilisant le contexte global capturé au fond du U. Mais il a besoin de plus que du contexte : il a besoin des détails fins que l’encodeur avait extraits à chaque niveau de résolution. C’est là qu’interviennent les skip connections.

Les skip connections fonctionnent comme si, tout en redessinant les contours à haute résolution, vous pouviez jeter un coup d’œil simultané sur la version détaillée de l’image originale à cet endroit précis. L’encodeur a déjà calculé les features de haute résolution (bords, textures, variations locales) — les skip connections les livrent directement au décodeur au moment opportun.

En résumé, l’analogie intuitive est la suivante : l’encodeur zoome vers l’extérieur pour comprendre le « quoi » et le « où », le décodeur zoome vers l’intérieur pour dessiner le « comment exactement » en réutilisant les détails via les skip connections. C’est cette combinaison unique de compréhension globale et de précision locale qui donne à U-Net sa puissance exceptionnelle.

Implémentation Python

Voici une implémentation complète de U-Net en Keras/TensorFlow, avec les blocs de convolution, les connexions de saut, et l’entraînement sur des masques de segmentation binaires.

import tensorflow as tf
from tensorflow.keras import layers, models, backend as K
import numpy as np
import matplotlib.pyplot as plt

def conv2d_block(inputs, num_filters, kernel_size=3, padding='same', activation='relu'):
    """Bloc de deux convolutions 3x3 + ReLU, comme décrit dans l'article original."""
    x = layers.Conv2D(num_filters, kernel_size, padding=padding, activation=activation)(inputs)
    x = layers.Conv2D(num_filters, kernel_size, padding=padding, activation=activation)(x)
    x = layers.BatchNormalization()(x)
    return x

def build_unet(input_size=(256, 256, 1), base_filters=64, dropout_rate=0.1):
    """
    Construction du modèle U-Net.

    Args:
        input_size : dimensions de l'image d'entrée (H, W, canaux)
        base_filters : nombre de filtres au premier niveau
        dropout_rate : taux de dropout pour la régularisation

    Returns:
        Modèle Keras U-Net compilé
    """
    inputs = layers.Input(input_size)

    # === Chemin contractant (encodeur) ===
    # Niveau 1
    c1 = conv2d_block(inputs, base_filters * 1)
    p1 = layers.MaxPooling2D((2, 2))(c1)
    p1 = layers.Dropout(dropout_rate)(p1)

    # Niveau 2
    c2 = conv2d_block(p1, base_filters * 2)
    p2 = layers.MaxPooling2D((2, 2))(c2)
    p2 = layers.Dropout(dropout_rate)(p2)

    # Niveau 3
    c3 = conv2d_block(p2, base_filters * 4)
    p3 = layers.MaxPooling2D((2, 2))(c3)
    p3 = layers.Dropout(dropout_rate)(p3)

    # Niveau 4
    c4 = conv2d_block(p3, base_filters * 8)
    p4 = layers.MaxPooling2D((2, 2))(c4)
    p4 = layers.Dropout(dropout_rate)(p4)

    # === Niveau bottleneck (fond du U) ===
    c5 = conv2d_block(p4, base_filters * 16)

    # === Chemin expansif (décodeur) ===
    # Niveau 4 — upconvolution + skip connection
    u6 = layers.Conv2DTranspose(base_filters * 8, (2, 2), strides=(2, 2), padding='same')(c5)
    concat6 = layers.Concatenate()([u6, c4])
    c6 = conv2d_block(concat6, base_filters * 8)

    # Niveau 3
    u7 = layers.Conv2DTranspose(base_filters * 4, (2, 2), strides=(2, 2), padding='same')(c6)
    concat7 = layers.Concatenate()([u7, c3])
    c7 = conv2d_block(concat7, base_filters * 4)

    # Niveau 2
    u8 = layers.Conv2DTranspose(base_filters * 2, (2, 2), strides=(2, 2), padding='same')(c7)
    concat8 = layers.Concatenate()([u8, c2])
    c8 = conv2d_block(concat8, base_filters * 2)

    # Niveau 1
    u9 = layers.Conv2DTranspose(base_filters * 1, (2, 2), strides=(2, 2), padding='same')(c8)
    concat9 = layers.Concatenate()([u9, c1])
    c9 = conv2d_block(concat9, base_filters * 1)

    # === Couche de sortie ===
    outputs = layers.Conv2D(1, (1, 1), activation='sigmoid')(c9)

    model = models.Model(inputs=[inputs], outputs=[outputs])
    return model

# === Métrique Dice ===
def dice_coefficient(y_true, y_pred, smooth=1.0):
    """Coefficient de Dice pour l'évaluation de la segmentation."""
    y_true_f = K.flatten(y_true)
    y_pred_f = K.flatten(y_pred)
    intersection = K.sum(y_true_f * y_pred_f)
    return (2.0 * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)

def dice_loss(y_true, y_pred):
    """Dice loss pour l'entraînement."""
    return 1.0 - dice_coefficient(y_true, y_pred)

def bce_dice_loss(y_true, y_pred):
    """Combinaison de Binary Cross-Entropy et Dice loss."""
    bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
    dice = dice_loss(y_true, y_pred)
    return bce + dice

# === Construction et compilation du modèle ===
model = build_unet(input_size=(256, 256, 1), base_filters=64)
model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),
    loss=bce_dice_loss,
    metrics=[dice_coefficient]
)
model.summary()

# === Exemple d'entraînement fictif ===
# images_train: (N, 256, 256, 1) — images en niveaux de gris
# masks_train: (N, 256, 256, 1) — masques binaires (0 ou 1)
#
# model.fit(
# images_train, masks_train,
# batch_size=16,
# epochs=50,
# validation_split=0.15,
# callbacks=[
# tf.keras.callbacks.EarlyStopping(patience=8, restore_best_weights=True),
# tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=4)
# ]
# )

Cette implémentation respecte fidèlement l’architecture originale. Remarquez plusieurs points importants :

  • Les blocs de convolution utilisent systématiquement deux convolutions 3×3 avec ReLU, exactement comme spécifié dans l’article de 2015.
  • Le dropout est appliqué uniquement dans l’encodeur pour régulariser sans perturber excessivement le décodeur.
  • La couche BatchNormalization stabilise l’entraînement et accélère la convergence.
  • La fonction de loss combinée (binary cross-entropy + Dice) offre le meilleur des deux mondes : la BCE pénalise les erreurs pixel par pixel tandis que le Dice optimise le recouvrement global entre la prédiction et la vérité terrain.

Hyperparamètres

Le choix des hyperparamètres influence considérablement les performances de U-Net. Voici les paramètres clés à considérer :

Hyperparamètre Valeur par défaut Plage recommandée Rôle
Depth (profondeur) 4 3–5 Nombre de niveaux de pooling/upconv. Plus la profondeur est grande, plus le contexte capturé est large, mais le coût computationnel augmente exponentiellement.
Filters de base 64 32–128 Nombre de filtres au premier niveau. Détermine la capacité du modèle à extraire des caractéristiques. Un nombre trop faible sous-apprend, trop élevé, surapprend.
Activation de sortie sigmoid sigmoid / softmax Sigmoid pour la segmentation binaire (1 canal), softmax pour la segmentation multi-classes (C canaux).
Fonction de loss BCE + Dice BCE, Dice, Focal, Tversky Le choix dépend du déséquilibre des classes. Pour les cas fortement déséquilibrés (< 5% de pixels positifs), le Focal Loss ou le Tversky Loss sont préférables.
Optimiseur Adam Adam, SGD, AdamW Adam converge rapidement. SGD avec momentum peut parfois obtenir de meilleurs résultats finaux avec un learning rate bien calibré et une planification en cosine annealing.
Learning rate 1e-4 1e-5 – 3e-3 Un learning rate initial trop élevé peut faire diverger l’entraînement. Un scheduler (réduction progressive) est fortement recommandé.
Batch size 16 8–32 Limité par la mémoire GPU. Des batch sizes plus grands stabilisent le gradient mais peuvent nuire à la généralisation.
Dropout rate 0.1 0.0 – 0.5 Régularisation. Utile quand le jeu d’entraînement est petit, mais excessif, il empêche l’apprentissage des patterns complexes.
Taille d’entrée 256×256 128×128 – 512×512 Doit être divisible par 2depth. Les tailles plus grandes capturent plus de contexte mais nécessitent plus de mémoire.

Avantages et Limites

Avantages

U-Net présente de nombreux atouts qui expliquent sa popularité durable :

  • Fonctionne avec peu de données. L’article original montrait d’excellents résultats avec seulement 30 images d’entraînement, grâce à l’augmentation de données en ligne (data augmentation) et à l’architecture efficace.
  • Précision pixel par pixel. Contrairement aux méthodes de classification puis localisation, U-Net segmente directement chaque pixel, offrant des contours d’une précision remarquable.
  • Architecture modulaire et extensible. La structure en U se prête naturellement à des variantes : ajouter plus de profondeur, changer les blocs de convolution (ResNet, EfficientNet), ou incorporer des mécanismes d’attention.
  • Skip connections efficaces. Le transfert direct d’informations de l’encodeur au décodeur évite la perte d’information spatiale fine, un problème récurrent dans les architectures encoder-decoder classiques.
  • Adaptabilité multisecteur. Bien que née pour l’imagerie biomédicale, U-Net s’applique avec succès à la détection routière, la télédétection, l’analyse de matériaux, et bien d’autres domaines.

Limites

Malgré ses qualités, U-Net présente certaines faiblesses connues :

  • Réceptivité limitée au contexte très large. Avec une profondeur standard de 4 niveaux, le champ récepteur effectif reste modeste. Les très grandes structures ou les relations à longue distance dans l’image peuvent échapper au réseau. C’est précisément cette limitation qui a motivé les architectures à attention (comme les Vision Transformers).
  • Difficulté avec les classes fortement déséquilibrées. Même avec un Dice loss, quand les pixels positifs représentent moins d’un pourcent de l’image totale, le réseau peut avoir du mal à converger vers une solution utile sans un rééchantillonnage agressif.
  • Coût computationnel. Les skip connections nécessitent de stocker toutes les cartes intermédiaires de l’encodeur pendant l’entraînement, ce qui augmente significativement la consommation de mémoire GPU par rapport à une architecture séquentielle simple.
  • Dépendance à la taille d’entrée fixe. U-Net requiert des images de dimensions prédéfinies (divisibles par la puissance de 2 correspondant à la profondeur). Cela impose du redimensionnement ou du découpage (tiling) pour les images de taille arbitraire.
  • Absence de modélisation explicite des relations spatiales globales. Contrairement aux Transformers qui capturent automatiquement les relations entre tous les pixels, U-Net repose exclusivement sur les opérations de convolution locales, limitant sa capacité à comprendre les structures complexes à longue portée.

4 Cas d’usage concrets

1. Segmentation de tumeurs en imagerie médicale

C’est l’application originelle et la plus impactante de U-Net. En IRM cérébrale, U-Net segmente automatiquement les tumeurs (gliomes, métastases) en produisant des cartes de segmentation précises qui guident les neurochirurgiens dans la planification des interventions. Les variantes 3D de U-Net (V-Net) étendent cette approche aux volumes complets d’imagerie, permettant une segmentation tridimensionnelle directe sans traitement tranche par tranche. En radiologie thoracique, U-Net délimite les nodules pulmonaires sur les scanners, facilitant le dépistage précoce du cancer du poumon.

2. Détection et segmentation routière en conduite autonome

Les véhicules autonomes utilisent des architectures dérivées de U-Net pour segmenter en temps réel la scène routière : voies de circulation, piétons, obstacles, marquages au sol, feux de signalisation. Chaque pixel de l’image caméra est classifié, permettant au système de perception de comprendre précisément la structure de l’environnement. La rapidité d’inférence de U-Net (particulièrement dans ses versions optimisées comme Fast-SCNN) la rend compatible avec les contraintes temps réel de la conduite autonome.

3. Analyse de cellules en biologie microscopique

La microscopie à fluorescence produit des images de cellules souvent superposées et de formes variables. U-Net excelle dans la segmentation individuelle de chaque cellule, permettant aux biologistes de quantifier automatiquement le nombre de cellules, leur morphologie, leur état de division, ou la localisation subcellulaire de protéines marquées. L’article original de 2015 démontrait déjà cette capacité, et les outils dérivés comme CellPose (basé sur U-Net) sont devenus la référence en analyse d’images microscopiques.

4. Cartographie satellitaire et télédétection

U-Net est massivement utilisée pour l’analyse d’images satellitaires : détection de zones urbanisées, classification de la couverture terrestre (forêts, eaux, cultures, zones bâties), suivi de la déforestation, cartographie des inondations. Les variantes adaptées utilisent des images multi-spectrales en entrée (au-delà du RGB classique) et produisent des cartes de segmentation couvrant des centaines de kilomètres carrés avec une résolution métrique. Des organisations comme l’ESA (Agence Spatiale Européenne) intègrent U-Net dans leurs pipelines de traitement d’images Copernicus.

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.