Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

🔬 U-Net – Le Guide Ultime

Deep Dive : Segmentation Sémantique (Médicale), Architecture (Encoder/Decoder) & 🚀 Skip Connections.

1.1 Facile

1. C'est quoi U-Net ?

Une architecture CNN (2015) pour la Segmentation Sémantique (pixel-level).

U-Net Segmentation
1.2 Moyen

2. Le Problème (Classification)

Classifier (VGG/ResNet) = "Quoi?". Segmentation = "Où (pixel-par-pixel) ?".

Segmentation Classification
1.3 Moyen

3. 📈 Architecture (Le "U")

Le diagramme. Encoder (Contracting Path) + Decoder (Expanding Path).

Encoder Decoder
1.4 Avancé

4. L'Encoder (Le "Quoi")

La "descente". Un CNN classique (type VGG). Conv(3x3) + MaxPool(2x2). Capture le contexte.

Contracting Path Context
1.5 Avancé

5. Le Decoder (Le "Où")

La "remontée". ConvTranspose2d (Up-sampling) pour reconstruire l'image.

Expanding Path Localization
1.6 Avancé

6. 🚀 L'Innovation : Skip Connections

Le "plugin" clé. Concaténation (pas addition) des features de l'Encoder vers le Decoder.

Skip Connection Concat
2.1 Avancé

7. "Addon": Dice Loss

Le "plugin" de loss. BCE Loss vs Dice Loss (Sørensen-Dice coefficient). Idéal pour le déséquilibre.

Dice Loss Loss Function
2.2 Moyen

8. "Addon": Data Augmentation

Le "plugin" vital (médical). Elastic Deformations (la clé du papier U-Net).

Data Augmentation Elastic
2.3 Avancé

9. Code (PyTorch)

Implémenter DoubleConv, Down, Up (ConvTranspose2d), torch.cat.

PyTorch Code
3.1 Moyen

10. Code (Keras)

API Fonctionnelle (Input, Model). Conv2D, Conv2DTranspose, Concatenate.

Keras TensorFlow
3.2 Avancé

11. Frameworks Dérivés

U-Net++ (Nested), 3D U-Net (plugin 3D pour IRM/Scans), V-Net (Dice Loss).

3D U-Net U-Net++
3.3 Moyen

12. Projets & Liens

Projets (Cellules, Tumeurs, Satellites). Liens (Papier, Kaggle).

Projects Kaggle
1.1 C'est quoi U-Net ?

U-Net est une architecture de Réseau de Neurones Convolutif (CNN) publiée en 2015 (Olaf Ronneberger et al.) spécifiquement pour la segmentation d'images biomédicales (médicales).

Son nom vient de sa forme caractéristique en "U" (voir 1.3).

L'Impact (ISBI 2015)

U-Net a été présenté au concours ISBI 2015 (International Symposium on Biomedical Imaging) et a *gagné* plusieurs challenges de segmentation (ex: segmentation de neurones, de cellules de rétine).

Le Problème (Médical) : En imagerie médicale, on a (souvent) *très peu* de données (ex: 30 images de tumeurs, annotées par des experts) mais on a besoin d'une localisation *extrêmement précise* (pixel-level).

U-Net a été conçu pour résoudre ces deux problèmes :
1. Peu de données : Il utilise massivement la "Data Augmentation" (voir 2.2).
2. Localisation Précise : Il utilise des "Skip Connections" (voir 1.6) pour préserver les détails fins.

Aujourd'hui, U-Net (et ses "plugins" dérivés) est le "ResNet" de la segmentation : c'est le standard de facto pour toute tâche de segmentation (médicale, satellite, ...).

1.2 Le Problème (Classification vs Segmentation)

U-Net ne résout pas le même problème que AlexNet ou ResNet.

TâcheQuestion (Ce que fait l'IA)SortieArchitecture (Ex)
Classification"Quoi ?" (Y a-t-il un "chat" dans l'image ?)1 Label ("chat")AlexNet, VGG, ResNet
Détection d'Objet"Où ?" (Où est le "chat" ? [BBox])N Boîtes ([x, y, w, h])YOLO, Faster R-CNN
Segmentation Sémantique"Où (au pixel près) ?" (Quels *pixels* sont "chat" ? Quels *pixels* sont "herbe" ?)1 Masque (Image) de la même taille que l'entrée.U-Net, FCN
Le Défi de la Segmentation

Un CNN "classique" (comme VGG) est un Encoder : il *détruit* l'information spatiale (le "où") pour *gagner* en information sémantique (le "quoi").
Image (224x224x3) -> ... -> Features (7x7x512) -> Classe (1x1000)

Problème : Pour la segmentation, on a besoin de la sortie (le "où"), mais on a *aussi* besoin du contexte (le "quoi").
Solution (U-Net) : Utiliser un "Encoder" (pour le "quoi") *suivi* d'un "Decoder" (pour reconstruire le "où").

1.3 📈 Architecture (La Forme en "U")

L'architecture U-Net est un "Encoder-Decoder". Elle a deux parties symétriques qui forment un "U".

Diagramme (Conceptuel)
(Input: 572x572)
     |
     ▼
[Bloc 1: Conv 3x3 x2] (570x570) --+ (Skip 1)
     |                            |
     ▼ (MaxPool 2x2)              |
     | (284x284)                  |
     ▼                            |
[Bloc 2: Conv 3x3 x2] (282x282) --+ (Skip 2)
     |                            |
     ▼ (MaxPool 2x2)              |
     | (140x140)                  |
     ... (descente) ...           |
     |                            |
     ▼                            |
[Bottleneck (1024)]               |
     |                            |
     ▼ (Up-Conv 2x2)              |
     | (remontée)                 |
     +----[ Concat ] <-------------+ (Skip 2)
     |                            |
     ▼                            |
[Bloc 3: Conv 3x3 x2]             |
     |                            |
     ▼ (Up-Conv 2x2)              |
     |                            |
     +----[ Concat ] <-------------+ (Skip 1)
     |
     ▼
[Bloc 4: Conv 3x3 x2]
     |
     ▼
[Output: Conv 1x1] (388x388)

Note sur la taille (Padding) : Le papier U-Net *n'utilise pas* de "padding" (rembourrage) dans ses convolutions.
Résultat : L'image "rétrécit" à chaque bloc (572 -> 570). Le masque de sortie (388x388) est *plus petit* que l'image d'entrée (572x572).
Aujourd'hui : Les implémentations modernes (PyTorch/Keras) utilisent padding='same', ce qui rend l'entrée (512x512) et la sortie (512x512) identiques (beaucoup plus simple !).

1.4 L'Encoder (Contracting Path) - Le "Quoi"

L'Encoder (la partie *gauche* du "U", la "descente") est un extracteur de features (caractéristiques). C'est un CNN de classification "classique", très similaire à VGG (voir 1.4, VGG).

Son rôle est de répondre à la question : "Quoi ?" (Quel est le contexte ?).

Structure d'un Bloc (Encoder)

Chaque "bloc" de l'Encoder (ex: Bloc 1) fait deux choses :

  1. Apprendre (Conv) : Applique deux Convolutions 3x3 (avec ReLU). Cela *augmente* la profondeur (filtres).
  2. Réduire (Pool) : Applique un MaxPool 2x2 (stride 2). Cela *réduit* la taille (hauteur/largeur) par 2.
Flux (Encoder)
(Input: 512x512x3)
     |
     ▼
[Bloc 1: Conv 3x3 x2 (64 filtres)] -> (Sortie 512x512x64) -> (Sauvegardé pour Skip 1)
     |
     ▼ (MaxPool 2x2)
     | (256x256x64)
     ▼
[Bloc 2: Conv 3x3 x2 (128 filtres)] -> (Sortie 256x256x128) -> (Sauvegardé pour Skip 2)
     |
     ▼ (MaxPool 2x2)
     | (128x128x128)
     ...
     ▼
(Bottleneck: 64x64x1024)

Plus on "descend", plus l'image est *petite* (localisation faible) mais plus elle est *profonde* (sémantique forte). Au "Bottleneck" (le bas du U), le réseau sait "il y a une cellule au milieu", mais il ne sait plus *exactement* où sont ses bords.

1.5 Le Decoder (Expanding Path) - Le "Où"

Le Decoder (la partie *droite* du "U", la "remontée") est un reconstructeur d'image.
Son rôle est de prendre les "features" sémantiques (le "quoi" du Bottleneck) et de "remonter" (upsample) pour reconstruire un masque de la taille de l'image, répondant à la question : "Où ?" (Localisation).

Le "Plugin" : Convolution Transposée (ConvTranspose2d)

Comment "agrandir" (upsample) une image dans un CNN ? On utilise le "plugin" Convolution Transposée (parfois appelée "Deconvolution", à tort).

C'est "l'inverse" d'une convolution. Un ConvTranspose2d(kernel=2, stride=2) va (en gros) *doubler* la hauteur et la largeur (ex: 64x64 -> 128x128).

Structure d'un Bloc (Decoder)
  1. Remonter (Up-sample) : Applique une ConvTranspose2d (divise les filtres par 2, double la taille).
  2. Concaténer (Concat) : **C'est l'étape clé (voir 1.6)**. On fusionne (concatène) la sortie avec le "Skip" de l'Encoder.
  3. Apprendre (Conv) : Applique deux Convolutions 3x3 (avec ReLU) pour "raffiner" les features fusionnées.
1.6 🚀 L'Innovation : Skip Connections (Concaténation)

C'est la *vraie* "killer feature" de U-Net. C'est le "plugin" (les ponts horizontaux) qui connecte l'Encoder au Decoder.

Le Problème (Sans Skip Connections)

En "descendant" (Encoder), le MaxPool *détruit* l'information spatiale (le "où" exact). En "remontant" (Decoder), on reconstruit une image, mais elle est *floue* (basse résolution), car elle ne se base que sur le "contexte" (le "quoi").

La Solution U-Net : Concaténer

U-Net "court-circuite" (skip) les features de l'Encoder (riches en "détails") et les concatène (les "colle") aux features du Decoder (riches en "contexte").

Diagramme (Bloc "Up")
(Input (Decoder): 64x64x1024)
     |
     ▼
[ Up-Conv 2x2 ] -> (Output: 128x128x512)
     |
     |      (Input (Encoder): 128x128x512) (Via Skip Connection)
     |                   |
     +----[ CONCAT ] <---+
            |
            ▼
 (Output Concaténé: 128x128x1024) (512 + 512 filtres)
            |
            ▼
     [ Conv 3x3 x2 ] -> (Raffine et réduit les filtres)
            |
            ▼
     (Output final: 128x128x512)
U-Net (Concat) vs ResNet (Addition)

ResNet (voir ResNet 1.3) utilise des "Skip Connections" qui *additionnent* (F(x) + x).
U-Net les *concatène* (Concat(F(x), x)). L'addition "mélange" les features, la concaténation "préserve" les deux.

2.1 "Addon": Dice Loss (Gestion du Déséquilibre)

Le choix de la "Loss Function" (fonction de coût) est un "plugin" critique pour la segmentation.

Le Problème (Déséquilibre de Classe)

En imagerie médicale, 99% des pixels sont "Fond" (background) et 1% est "Tumeur" (foreground).

Si on utilise une "Loss" classique (ex: BCE - Binary Cross-Entropy), le réseau a un "cheat code" :
"Si je prédis *toujours* 'Fond' (0), j'aurai une précision (Accuracy) de 99% !"
Le réseau apprend à ignorer la tumeur (la classe minoritaire).

La Solution : Dice Loss (Coefficient de Sørensen-Dice)

La "Dice Loss" est basée sur le "Dice Coefficient", une métrique qui mesure la *similarité* (le "overlap") entre deux sets (A = Prédiction, B = Réalité).

Dice Coefficient = (2 * |A ∩ B|) / (|A| + |B|)     (Score de 0 à 1, 1 = parfait)

Dice Loss = 1 - Dice Coefficient

Pourquoi ça marche ? La Dice Loss *ignore* les "Vrais Négatifs" (le "Fond" qui est correctement prédit "Fond"). Elle ne se concentre *que* sur la similarité entre la *prédiction* et la *réalité* de la classe "Tumeur" (la classe minoritaire). Elle force le réseau à trouver le 1% important.

2.2 "Addon": Data Augmentation (Elastic Deformations)

Le papier U-Net a été conçu pour le domaine médical, où les datasets sont *minuscules* (ex: 30 images). Pour éviter l'overfitting, ils ont utilisé une "Data Augmentation" (plugin) massive et agressive.

1. Augmentations Classiques (Affine)
  • Shift (Décalage)
  • Rotation (aléatoire)
  • Scale (Zoom/Dezoom)
  • Flip (Miroir)
2. L'Innovation U-Net : "Elastic Deformations"

La "killer feature" du papier. L'idée est de simuler les déformations *non-rigides* du tissu biologique.

On applique un "champ de vecteurs" (Perlin noise) aléatoire à l'image, ce qui la "tord" (warp) de façon non-linéaire (comme si on tirait sur un tissu élastique).

Crucial : La *même* transformation doit être appliquée à l'Image (Input) ET au Masque (Target).

Résultat : À partir de 30 images, le réseau en voit des *milliers* de versions (tordues, tournées), le forçant à apprendre la "forme" (topologie) de la cellule, et non sa position exacte.

2.3 Code : Implémentation (PyTorch)

Implémenter U-Net est un excellent exercice PyTorch, car il combine des "plugins" (modules) personnalisés (DoubleConv) et la gestion des "skip connections" (torch.cat).

import torch
import torch.nn as nn

# 1. Le "Bloc" de base (Conv -> BN -> ReLU) x2
class DoubleConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(DoubleConv, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, 3, 1, 1, bias=False), # padding=1 (pour 'same')
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
        )
    def forward(self, x):
        return self.conv(x)

# 2. L'Architecture U-Net
class UNet(nn.Module):
    def __init__(self, in_channels=3, out_channels=1): # (ex: 3 (RGB) -> 1 (Masque binaire))
        super(UNet, self).__init__()

        # --- 3. Encoder (Descente) ---
        self.down1 = DoubleConv(in_channels, 64)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.down2 = DoubleConv(64, 128)
        self.pool2 = nn.MaxPool2d(2, 2)
        # ... (etc. pour down3, down4)

        # --- Bottleneck ---
        self.bottleneck = DoubleConv(512, 1024)

        # --- 4. Decoder (Remontée) ---
        self.upconv1 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)
        self.up1 = DoubleConv(1024, 512) # (512 (up) + 512 (skip))
        self.upconv2 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)
        self.up2 = DoubleConv(512, 256) # (256 (up) + 256 (skip))
        # ... (etc. pour up3, up4)
        
        # --- Tête de sortie ---
        self.out_conv = nn.Conv2d(64, out_channels, kernel_size=1)

    def forward(self, x):
        # 5. Flux "down" (Encoder) + Sauvegarde des "Skips"
        skip1 = self.down1(x)
        x = self.pool1(skip1)
        skip2 = self.down2(x)
        x = self.pool2(skip2)
        
        x = self.bottleneck(x) # (Fond du U)
        
        # 6. Flux "up" (Decoder) + Concaténation des "Skips"
        x = self.upconv1(x)
        x = torch.cat([x, skip2], dim=1) # (L'opération clé !)
        x = self.up1(x)
        
        x = self.upconv2(x)
        x = torch.cat([x, skip1], dim=1) # (L'opération clé !)
        x = self.up2(x)
        
        # Sortie (Logits)
        return self.out_conv(x)
3.1 Code : Implémentation (Plugin Keras)

En Keras/TensorFlow, on utilise l'API Fonctionnelle (pas Sequential) pour gérer les "Skip Connections".

import tensorflow as tf
from tensorflow.keras import layers, Model

def build_unet(input_shape=(512, 512, 3)):
    inputs = layers.Input(shape=input_shape)

    # --- 1. Encoder (Descente) ---
    # (padding='same' garde la taille, plus simple que le papier)
    c1 = layers.Conv2D(64, 3, activation='relu', padding='same')(inputs)
    c1 = layers.Conv2D(64, 3, activation='relu', padding='same')(c1)
    p1 = layers.MaxPooling2D(2)(c1) # (-> 256x256)
    
    c2 = layers.Conv2D(128, 3, activation='relu', padding='same')(p1)
    c2 = layers.Conv2D(128, 3, activation='relu', padding='same')(c2)
    p2 = layers.MaxPooling2D(2)(c2) # (-> 128x128)
    
    # --- Bottleneck ---
    b = layers.Conv2D(256, 3, activation='relu', padding='same')(p2)
    b = layers.Conv2D(256, 3, activation='relu', padding='same')(b)
    
    # --- 2. Decoder (Remontée) ---
    # (Utilise Conv2DTranspose (le "plugin" d'upsampling))
    u1 = layers.Conv2DTranspose(128, 2, strides=2, padding='same')(b) # (-> 256x256)
    # 3. Le "Skip" (Concaténation)
    u1 = layers.Concatenate()([u1, c2]) # (Concatène u1 et c2 (Bloc 2))
    c3 = layers.Conv2D(128, 3, activation='relu', padding='same')(u1)
    
    u2 = layers.Conv2DTranspose(64, 2, strides=2, padding='same')(c3) # (-> 512x512)
    u2 = layers.Concatenate()([u2, c1]) # (Concatène u2 et c1 (Bloc 1))
    c4 = layers.Conv2D(64, 3, activation='relu', padding='same')(u2)

    # --- Tête de Sortie ---
    # (1 filtre + 'sigmoid' pour une segmentation binaire (0 ou 1))
    outputs = layers.Conv2D(1, 1, activation='sigmoid')(c4)

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

model = build_unet()
model.summary()
3.2 Frameworks Dérivés (U-Net++, 3D U-Net)

L'architecture U-Net (2015) a été si influente qu'elle a créé sa propre famille de "plugins" (dérivés).

Modèle (Dérivé)Innovation (Le "Plugin")Cas d'usage
3D U-NetConvolutions 3D (Conv3D).
Remplace tous les Conv2D (H, W) par des Conv3D (H, W, D - Profondeur).
Imagerie Volumétrique.
(IRM, Scans CT). Analyse un "cube" (scan) entier au lieu d'une "slice" (coupe 2D).
U-Net++"Nested" (Imbriqués) Skip Connections.
Ajoute des "mini" U-Nets *à l'intérieur* des skip connections (ponts).
Améliore la "fusion" des features, réduit le "gap" sémantique entre Encoder et Decoder.
V-NetSimilaire à 3D U-Net, mais utilise des "skip connections" résiduelles (Addition, style ResNet) et a popularisé la Dice Loss (voir 2.1).Segmentation 3D (médicale).
U-Net (Backbone ResNet)Utilise un ResNet-34 pré-entraîné (gelé) comme "plugin" Encoder (à la place de l'Encoder "vanilla").Transfer Learning. Très courant quand on a peu de données (Kaggle).
3.3 Projets & Liens
Exemples de Projets
  • Projet 1 (Médical) : Segmentation de tumeurs (ex: BraTS dataset) ou de cellules (le papier original). Utilise 3D U-Net (pour IRM) et Dice Loss (pour déséquilibre).
  • Projet 2 (Satellite) : Segmentation de bâtiments ou de routes depuis des images satellite (ex: SpaceNet dataset). Utilise un U-Net avec un "plugin" Backbone ResNet pré-entraîné.
  • Projet 3 (Industriel) : Détection de défauts (rayures, fissures) sur des pièces métalliques. Utilise U-Net pour la segmentation au pixel près du défaut.