Construction d'un modèle U-Net
Construire un modèle U-Net à partir de zéro avec TensorFlow ou Keras pour la segmentation d’images est une tâche assez courante en vision par ordinateur. Ici, nous allons créer une version de base du U-Net, en détaillant chaque étape pour comprendre son architecture.
1. Étapes principales de la construction du modèle U-Net :
- Encodeur (Downsampling) : Réduction progressive de la résolution d’image tout en augmentant la profondeur à travers des convolutions et du max-pooling.
- Goulot d’étranglement (Bottleneck) : La partie la plus profonde du réseau, où la résolution est la plus faible mais les caractéristiques sont les plus complexes.
- Décodeur (Upsampling) : Récupération de la résolution d’origine en appliquant des convolutions transposées et en fusionnant les informations avec les connexions en U (skip connections).
2. Implémentation du U-Net avec TensorFlow/Keras
Voici le code pour construire un U-Net complet. Ce modèle est adapté pour la segmentation d’images de type médical ou d’autres applications nécessitant la segmentation pixel par pixel.
Code complet : Construction du U-Net
import tensorflow as tffrom tensorflow.keras import layers, Model
def unet_model(input_shape=(128, 128, 3), num_classes=1): inputs = layers.Input(input_shape)
# Encoder (cheminée descendante) # Bloc 1 conv1 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(inputs) conv1 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(conv1) pool1 = layers.MaxPooling2D((2, 2))(conv1)
# Bloc 2 conv2 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(pool1) conv2 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(conv2) pool2 = layers.MaxPooling2D((2, 2))(conv2)
# Bloc 3 conv3 = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(pool2) conv3 = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(conv3) pool3 = layers.MaxPooling2D((2, 2))(conv3)
# Bloc 4 conv4 = layers.Conv2D(512, (3, 3), activation='relu', padding='same')(pool3) conv4 = layers.Conv2D(512, (3, 3), activation='relu', padding='same')(conv4) pool4 = layers.MaxPooling2D((2, 2))(conv4)
# Goulot d'étranglement (Bottleneck) conv5 = layers.Conv2D(1024, (3, 3), activation='relu', padding='same')(pool4) conv5 = layers.Conv2D(1024, (3, 3), activation='relu', padding='same')(conv5)
# Décodeur (cheminée montante) # Bloc 6 (Upsampling + skip connection avec conv4) up6 = layers.Conv2DTranspose(512, (2, 2), strides=(2, 2), padding='same')(conv5) merge6 = layers.concatenate([up6, conv4], axis=3) conv6 = layers.Conv2D(512, (3, 3), activation='relu', padding='same')(merge6) conv6 = layers.Conv2D(512, (3, 3), activation='relu', padding='same')(conv6)
# Bloc 7 (Upsampling + skip connection avec conv3) up7 = layers.Conv2DTranspose(256, (2, 2), strides=(2, 2), padding='same')(conv6) merge7 = layers.concatenate([up7, conv3], axis=3) conv7 = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(merge7) conv7 = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(conv7)
# Bloc 8 (Upsampling + skip connection avec conv2) up8 = layers.Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(conv7) merge8 = layers.concatenate([up8, conv2], axis=3) conv8 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(merge8) conv8 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(conv8)
# Bloc 9 (Upsampling + skip connection avec conv1) up9 = layers.Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(conv8) merge9 = layers.concatenate([up9, conv1], axis=3) conv9 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(merge9) conv9 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(conv9)
# Sortie : Carte de segmentation (num_classes can be 1 for binary, or >1 for multiclass segmentation) output = layers.Conv2D(num_classes, (1, 1), activation='sigmoid')(conv9)
model = Model(inputs=inputs, outputs=output)
return model
# Créer le modèle U-Netunet = unet_model(input_shape=(128, 128, 3), num_classes=1)unet.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Afficher le résumé du modèleunet.summary()
3. Explication du modèle U-Net
- Entrée : La taille d’entrée par défaut est fixée à
(128, 128, 3)
pour des images RGB de résolution 128x128 pixels. Cela peut être modifié selon vos besoins. - Encodeur :
- Le modèle commence par plusieurs blocs de deux convolutions 2D (chaque fois avec
ReLU
comme fonction d’activation) suivis d’une couche de pooling max (qui réduit de moitié la taille de l’image). - Chaque bloc d’encodage double le nombre de filtres, donc nous avons des convolutions avec 64, 128, 256, et 512 filtres, respectivement.
- Le modèle commence par plusieurs blocs de deux convolutions 2D (chaque fois avec
- Goulot d’étranglement (Bottleneck) :
- Le plus petit niveau spatial (après les étapes de pooling) comporte des convolutions avec 1024 filtres, pour capturer les caractéristiques les plus profondes et abstraites de l’image.
- Décodeur :
- La phase de décodeur commence avec une convolution transposée (ou convolution inverse) qui double la taille spatiale de l’image.
- Ensuite, les skip connections fusionnent les caractéristiques du décodeur avec celles de l’encodeur correspondant pour conserver les informations perdues lors du pooling.
- Sortie :
- La sortie est une carte de segmentation de même taille que l’image d’entrée, avec une seule carte (pour la segmentation binaire).
- La fonction d’activation à la sortie est sigmoïde pour un problème de segmentation binaire. Si vous avez un problème de segmentation multi-classes, vous pouvez utiliser
softmax
et définirnum_classes
> 1.
4. Compilation et entraînement
Une fois que le modèle U-Net est défini, vous pouvez l’entraîner sur un jeu de données avec des images et des masques d’annotations pour la segmentation.
Voici un exemple de comment l’entraîner :
# Exemple de compilation avec un jeu de donnéesunet.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Entraînement du modèlehistory = unet.fit(train_images, train_masks, validation_data=(val_images, val_masks), epochs=20, batch_size=32)
5. Évaluation du modèle
Après l’entraînement, vous pouvez évaluer le modèle sur un ensemble de test ou utiliser les prédictions pour segmenter des images.
# Évaluation du modèle sur le jeu de testloss, accuracy = unet.evaluate(test_images, test_masks)print(f"Test accuracy: {accuracy}")
# Prédictions sur de nouvelles imagespredictions = unet.predict(new_images)
Conclusion
Le modèle U-Net est un réseau de segmentation puissant et flexible. Grâce à sa structure en “U” et ses skip connections, il parvient à bien capturer les détails et à préserver la résolution spatiale, même après plusieurs étapes de pooling. Ce modèle est largement utilisé pour des tâches de segmentation dans des domaines comme l’imagerie médicale, la segmentation sémantique, et bien d’autres.