Architectures Fondamentales des Réseaux Convolutifs : De la Classification à la Segmentation Sémantique

  1. Introduction aux Réseaux de Neurones Convolutifs Classiques (CNN)

1.1 Principes Fondamentaux

L'architecture standard d'un Réseau de Neurones Convolutifs (CNN) est généralement séquentielle, comportant plusieurs types de couches interconectées : une couche d'entrée, des couches de convolution (souvent suivies d'une fonction d'activation ReLU), des couches de pooling, des couches entièrement connectées et enfin une couche de sortie. Ce type de réseau est schématiquement représenté comme suit :

Les réseaux CNN traditionnels, en particulier ceux utilisés pour la classification d'images, se terminent par une ou plusieurs couches entièrement connectées. Après application d'une fonction d'activation comme Softmax, ces couches produisent une distribution de probabilité sur les classes. Cette information de probabilité est de nature unidimensionnelle, signifiant qu'elle attribue une seule étiquette de classe à l'image entière, sans identifier la catégorie de chaque pixel individuel.

Les couches entièrement connectées sont cruciales dans les architectures CNN classiques. Placées après les blocs de convolution et de pooling, elles connectent chaque neurone à tous les neurones de la couche précédente. Leur rôle est d'agréger les caractéristiques locales discriminantes apprises par les couches antérieures pour former une représentation globale de l'image, qui est ensuite utilisée pour la classification finale.

1.2 VGG16 : Une Architecture CNN emblématique

VGG16 est un réseau de neurones convolutifs profondément influent, reconnu pour sa simplicité et son efficacité. Il se compose de 13 couches de convolution, 5 couches de pooling et 3 couches entièrement connectées. Ses caractéristiques principales sont :

  • Couches de Convolution : Toutes utilisent des filtres 3x3 avec un pas de 1 et un padding "same", suivis d'une activation ReLU, pour extraire des caractéristiques locales de l'image.
  • Couches de Pooling : Elles emploient un noyau 2x2 avec un pas de 2 pour un max-pooling, réduisant ainsi la taille des cartes de caractéristiques.
  • Couches Entièrement Connectées : Deux couches, chacune avec 4096 neurones, servent à la classification.
  • Couche de Sortie : Une dernière couche entièrement connectée de 1000 neurones (ou 10 dans l'exemple de code ci-dessous) avec une fonction d'activaiton Softmax, produisant la distribution de probabilité des classes.

L'architecture VGG16 présente des avantages et des inconvénients notables :

Avantages :

  • C'est une architecture relativement profonde, permettant l'extraction de caractéristiques sémantiques riches et hiérarchiques.
  • L'utilisation uniforme de petits noyaux de convolution (3x3) simplifie la conception et permet une extraction de caractéristiques multi-échelle implicite grâce à l'empilement de couches.

Inconvénients :

  • Le grand nombre de paramètres (en particulier dans les couches entièrement connectées) rend le réseau susceptible au surapprentissage, surtout avec des jeux de données de petite taille. Des techniques comme l'augmentation de données ou la régularisation sont souvent nécessaires.
  • L'entrée ne peut pas être trop petite en raison des multiples couches de pooling qui réduisent drastiquement la résolution des cartes de caractéristiques.

Voici une illustration du processus de construction d'un modèle VGG16 en utilisant une approche de type PyTorch ou TensorFlow/Keras :


import torch
import torch.nn as nn
import torch.nn.functional as F

class VGG16Classifier(nn.Module):
    def __init__(self, num_output_classes=10):
        super(VGG16Classifier, self).__init__()

        # Helper function to define a block of convolutional layers followed by pooling
        def create_conv_block(input_channels, output_channels, num_conv_layers):
            layers = []
            for _ in range(num_conv_layers):
                layers.append(nn.Conv2d(input_channels, output_channels, kernel_size=3, padding=1))
                layers.append(nn.ReLU(inplace=True))
                input_channels = output_channels # Update for subsequent conv in the same block
            layers.append(nn.BatchNorm2d(output_channels)) # As per original example
            layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
            return nn.Sequential(*layers)

        # Feature extraction layers (mimicking VGG16 blocks)
        # Input image assumed 224x224x3
        self.feature_extractor = nn.Sequential(
            create_conv_block(3, 64, 2),    # Output: 112x112x64
            create_conv_block(64, 128, 2),  # Output: 56x56x128
            create_conv_block(128, 256, 3), # Output: 28x28x256
            create_conv_block(256, 512, 3), # Output: 14x14x512
            create_conv_block(512, 512, 3)  # Output: 7x7x512
        )

        # Classifier layers
        self.classification_head = nn.Sequential(
            nn.Flatten(),
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_output_classes) # Softmax will typically be applied by the loss function
        )

    def forward(self, x):
        features = self.feature_extractor(x)
        output = self.classification_head(features)
        return output

# Example usage (assuming an input tensor of size 1x3x224x224)
# model = VGG16Classifier(num_output_classes=10)
# dummy_input = torch.randn(1, 3, 224, 224)
# output = model(dummy_input)
# print(output.shape) # Expected: torch.Size([1, 10])

  1. Architectures Avancées pour la Segmentation Sémantique

2.1 Réseaux Entièrement Convolutifs (FCN)

Contrairement aux CNN traditionnels qui se limitent à la classification au niveau de l'image, les Réseaux Entièrement Convolutifs (FCN), introduits dans l'article "Fully Convolutional Networks for Semantic Segmentation", ont révolutionné le domaine en proposant une classification au niveau du pixel. Cette avancée a permis de résoudre le problème de la segmentation sémantique, où chaque pixel d'une image est assigné à une catégorie sémantique spécifique, offrant une compréhension détaillée des objets et scènes.

Les améliorations clés apportées par les FCN par rapport aux CNN classiques sont les suivantes :

  • Les FCN opèrent de bout en bout, transformant une image d'entrée de taille arbitraire en une carte de segmentation de même taille, avec une classification au niveau du pixel.
  • La conversion des couches entièrement connectées en couches de convolution permet de préserver l'information spatiale des caractéristiques extraites.
  • L'introduction de couches de déconvolution (également appelées convolutions transposées) assure un sur-échantillonnage pour restaurer la résolution spatiale à celle de l'image d'entrée, produisant une sortie plus proche de l'image réelle.
  • L'intégration de connexions résiduelles (skip-connections) est une innovation majeure. Elles fusionnent les informations de localisation des couches superficielles (haute résolution, peu sémantique) avec les informations sémantiques des couches profondes (basse résolution, riche en sémantique), améliorant la précision de la segmentation, notamment pour les détails fins.

Les FCN ont été testés sur plusieurs architectures CNN, et le VGG16 s'est avéré être la meilleure base pour la partie extracteur de caractéristiques. Le processus de sur-échantillonnage dans les FCN a donné naissance à différentes variantes, telles que FCN-32s, FCN-16s et FCN-8s, qui diffèrent par le point de fusion des connexions résiduelles et le facteur de sur-échantillonnage :

  1. FCN-32s : Convertit les couches entièrement connectées en couches convolutives et sur-échantillonne directement les caractéristiques finales par un facteur de 32, sans connexions résiduelles.
  2. FCN-16s : Utilise une connexion résiduelle pour fusionner les caractéristiques de la couche pool4 (du VGG16) avec les caractéristiques sur-échantillonnées de la couche conv7, puis sur-échantillonne par 16.
  3. FCN-8s : Ajoute une connexion résiduelle supplémentaire en fusionnant les caractéristiques de la couche pool3 avec celles de la couche pool4 et conv7 combinées, puis sur-échantillonne par 8.

Bien que les FCN aient été pionniers, leurs résultats peuvent manquer de finesse, produisant des segmentations parfois floues ou trop lisses, avec une gestion imparfaite de certains détails. Néanmoins, l'approche des FCN a fourni un cadre conceptuel fondamental pour les architectures de segmentation ultérieures.

2.2 Réseaux U-Net

Le réseau U-Net, présenté dans l'article "Convolutional Networks for Biomedical Image Segmentation", a été conçu initialement pour la segmentation d'images médicales. Il partage des objectifs similaires aux FCN mais se distingue par plusieurs aspects :

  • Une architecture de bout en bout avec une structure symétrique de type encodeur-décodeur.
  • L'utilisation intensive de connexions résiduelles.

Dans la structure U-Net :

  • La voie de contraction (encodeur), située à gauche, est dédiée à l'extraction de caractéristiques. Elle compredn une série de blocs de convolution et de max-pooling pour réduire progressivement la résolution spatiale et augmenter la profondeur des caractéristiques (par exemple, 64, 128, 256, 512, 1024 filtres). Contrairement à certaines implémentations, les convolutions originales de U-Net utilisaient un padding nul, ce qui entraînait une légère perte de résolution à chaque couche de convolution, rendant la résolution de l'image de sortie non strictement égale à celle de l'entrée.
  • La voie d'expansion (décodeur), située à droite, vise à restaurer la carte de caractéristiques à sa résolution d'origine. Elle implique des opérations de sur-échantillonnage (par déconvolution ou interpolation) et des blocs de convolution.
  • Les connexions résiduelles sont un élément crucial du U-Net. Elles fusionnent les cartes de caractéristiques à haute résolution de l'encodeur avec les cartes de caractéristiques sur-échantillonnées du décodeur. Ce processus combine les informations de localisation précises des couches peu profondes avec les informations sémantiques riches des couches profondes. La fusion se fait généralement par concaténation après une opération de "copy and crop" (copie et recadrage), où les cartes de l'encodeur (plus grandes en raison du padding nul) sont recadrées pour correspondre à la taille des cartes du décodeur avant d'être concaténées.
  1. FCNVMB : Une Adaptation du U-Net pour les Données Sismiques

3.1 Présentation et Innovations de FCNVMB

Le modèle FCNVMB (Fully Convolutional Network for Velocity Model Building) est une adaptation du réseau U-Net conçue spécifiquement pour la construction de modèles de vitesse sismiques. Il partage la structure symétrique encodeur-décodeur et l'utilisation de connexions résiduelles avec le U-Net, mais introduit des innovations pour le domaine sismique :

  • Modification des Données d'Entrée : Contrairement aux U-Net traditionnels qui traitent des images RVB, le FCNVMB prend en entrée des "coups de tir" sismiques. Le nombre de canaux d'entrée correspond au nombre de sources sismiques, chaque coup de tir représentant des données sismiques provenant de différentes positions de source pour un même modèle.
  • Transformation du Domaine de Sortie : Pour la construction du modèle de vitesse, le réseau mappe les données du domaine (x, t) (position, temps) au domaine (x, z) (position, profondeur), en construisant simultanément le modèle de vitesse. La couche de sortie est modifiée pour avoir un seul canal, représentant le modèle de vitesse.
  • Apprentissage Supervisé : FCNVMB est un réseau de neurones entièrement convolutif profond basé sur l'apprentissage supervisé. Il est entraîné sur des paires de données (données sismiques d'entrée et modèles de vitesse cibles) générées à partir de modèles d'équations d'ondes acoustiques (données synthétiques) ou de jeux de données tels que le modèle de sel SEG. Le réseau apprend une relation de mappage non linéaire entre les données sismiques et le modèle de vitesse sous-jacent.

Voici une implémentation simplifiée des composants clés du FCNVMB :


import torch
import torch.nn as nn
import torch.nn.functional as F

# Bloc de double convolution utilisé dans FCNVMB
class DoubleConvolutionBlock(nn.Module):
    def __init__(self, input_features, output_features, apply_batchnorm=True):
        super().__init__()
        layers = [
            nn.Conv2d(input_features, output_features, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        ]
        if apply_batchnorm:
            layers.append(nn.BatchNorm2d(output_features))

        layers.extend([
            nn.Conv2d(output_features, output_features, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        ])
        if apply_batchnorm:
            layers.append(nn.BatchNorm2d(output_features))

        self.conv_sequence = nn.Sequential(*layers)

    def forward(self, x):
        return self.conv_sequence(x)

# Unité de sous-échantillonnage pour l'encodeur (chemin descendant)
class DownsamplingStage(nn.Module):
    def __init__(self, input_feature_maps, output_feature_maps, apply_batchnorm):
        super(DownsamplingStage, self).__init__()
        self.conv_block = DoubleConvolutionBlock(input_feature_maps, output_feature_maps, apply_batchnorm)
        self.max_pool = nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)

    def forward(self, input_tensor):
        convolved_tensor = self.conv_block(input_tensor)
        pooled_tensor = self.max_pool(convolved_tensor)
        return pooled_tensor

# Unité de sur-échantillonnage pour le décodeur (chemin ascendant)
class UpsamplingStage(nn.Module):
    def __init__(self, input_channels_decoder_path, channels_from_encoder_skip, use_deconvolution):
        super(UpsamplingStage, self).__init__()
        
        # Output channels for the upsampler (typically matches the skip connection channels)
        self.upsampler_output_channels = channels_from_encoder_skip 
        
        if use_deconvolution:
            self.upsampler = nn.ConvTranspose2d(input_channels_decoder_path, self.upsampler_output_channels, kernel_size=2, stride=2)
        else:
            self.upsampler = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)

        # The convolution block after concatenation
        # Input channels = upsampled channels + skip channels
        self.double_conv = DoubleConvolutionBlock(self.upsampler_output_channels + channels_from_encoder_skip,
                                                  channels_from_encoder_skip, 
                                                  True) # Batch norm is usually applied here

    def forward(self, encoder_features, decoder_input_features):
        upsampled_decoder_features = self.upsampler(decoder_input_features)

        # Calculate padding to match spatial dimensions for concatenation
        height_diff = upsampled_decoder_features.size()[2] - encoder_features.size()[2]
        width_diff = upsampled_decoder_features.size()[3] - encoder_features.size()[3]

        # Pad encoder_features to match upsampled_decoder_features
        # F.pad expects (left, right, top, bottom)
        padded_encoder_features = F.pad(encoder_features,
                                        [width_diff // 2, (width_diff + 1) // 2,
                                         height_diff // 2, (height_diff + 1) // 2])

        # Concatenate along the channel dimension
        merged_features = torch.cat([padded_encoder_features, upsampled_decoder_features], dim=1)
        return self.double_conv(merged_features)

# Architecture principale du FCNVMB
class FCNVMBNetwork(nn.Module):
    def __init__(self, num_output_classes, input_channel_count, use_deconvolution_up, use_batchnorm_layers):
        super(FCNVMBNetwork, self).__init__()
        self.use_deconv = use_deconvolution_up
        self.input_channels = input_channel_count
        self.apply_bn = use_batchnorm_layers
        self.output_classes = num_output_classes

        # Nombre de canaux pour les différentes couches
        channel_counts = [64, 128, 256, 512, 1024]

        # Chemins de l'encodeur (descendants)
        self.encoder_block1 = DownsamplingStage(self.input_channels, channel_counts[0], self.apply_bn)
        self.encoder_block2 = DownsamplingStage(channel_counts[0], channel_counts[1], self.apply_bn)
        self.encoder_block3 = DownsamplingStage(channel_counts[1], channel_counts[2], self.apply_bn)
        self.encoder_block4 = DownsamplingStage(channel_counts[2], channel_counts[3], self.apply_bn)

        # Bloc central (bottleneck)
        self.bottleneck_block = DoubleConvolutionBlock(channel_counts[3], channel_counts[4], self.apply_bn)

        # Chemins du décodeur (ascendants)
        # Chaque étape d'upsampling prend les caractéristiques du chemin décodeur précédent et les canaux du skip-connection
        self.decoder_block4 = UpsamplingStage(channel_counts[4], channel_counts[3], self.use_deconv)
        self.decoder_block3 = UpsamplingStage(channel_counts[3], channel_counts[2], self.use_deconv)
        self.decoder_block2 = UpsamplingStage(channel_counts[2], channel_counts[1], self.use_deconv)
        self.decoder_block1 = UpsamplingStage(channel_counts[1], channel_counts[0], self.use_deconv)

        # Couche de sortie finale
        self.final_convolution = nn.Conv2d(channel_counts[0], self.output_classes, kernel_size=1)

    def forward(self, input_data, desired_output_dimensions):
        # Encoder path - outputs of each downsampling stage are stored for skip connections
        # Note: 'pooledX' are the outputs *after* max-pooling, as per the FCNVMB code logic
        pooled1 = self.encoder_block1(input_data)
        pooled2 = self.encoder_block2(pooled1)
        pooled3 = self.encoder_block3(pooled2)
        pooled4 = self.encoder_block4(pooled3)

        # Bottleneck processing
        center_output = self.bottleneck_block(pooled4)

        # Decoder path with skip connections
        # The 'encoder_features' in UpsamplingStage are the *pooled* features from the encoder side
        upsampled4 = self.decoder_block4(pooled4, center_output)
        upsampled3 = self.decoder_block3(pooled3, upsampled4)
        upsampled2 = self.decoder_block2(pooled2, upsampled3)
        upsampled1 = self.decoder_block1(pooled1, upsampled2)

        # Final cropping to match the desired output dimensions (e.g., model de vitesse size)
        # This assumes the upsampled output might have a small border (e.g., 1 pixel)
        cropped_final_output = upsampled1[:, :, 1 : 1 + desired_output_dimensions[0],
                                                1 : 1 + desired_output_dimensions[1]].contiguous()

        return self.final_convolution(cropped_final_output)

# Exemple d'utilisation (assumant une entrée sismique avec 16 sources, sortie 1 canal pour le modèle de vitesse)
# model_fcnvmb = FCNVMBNetwork(num_output_classes=1, input_channel_count=16, use_deconvolution_up=True, use_batchnorm_layers=True)
# dummy_seismic_input = torch.randn(1, 16, 256, 256) # Exemple de taille HxW pour un "shot gather"
# target_velocity_model_size = (128, 128) # Taille attendue du modèle de vitesse (HxW)
# output_velocity_model = model_fcnvmb(dummy_seismic_input, target_velocity_model_size)
# print(output_velocity_model.shape) # Expected: torch.Size([1, 1, 128, 128])

3.2 Réflexions Complémentaires et Pistes Futures du FCNVMB

L'étude du FCNVMB a soulevé plusieurs questions et ouvert des voies de recherche :

  1. Dépendance aux Données d'Entraînement : Le FCNVMB est fortement dépendant du jeu de données d'entraînement. Prédire avec précision des modèles de vitesse complexes (comme le modèle de sel SEG) ou réels est difficile si le modèle n'a pas été exposé à une diversité suffisante. Pour surmonter cette limitation, on peut envisager :

    • L'augmentation des données pour accroître la diversité des échantillons d'entraînement (rotations, redimensionnements, transformations).
    • L'utilisation de l'apprentissage par transfert, où un réseau pré-entraîné sur un grand corpus de données est adopté à la tâche sismique, permettant d'apprendre des mappings non linéaires plus efficaces, surtout lorsque les données étiquetées sont rares.
  2. Importance de l'Information Basse Fréquence : Les expériences ont montré que la suppression de l'information basse fréquence dans les données sismiques dégrade la clarté des limites structurales et rend les variations de vitesse de fond moins distinctes. Cela souligne l'importance critique des basses fréquences pour la précision et la netteté des prédictions de modèles de vitesse. Cependant, l'acquisition fiable de ces informations basse fréquence reste un défi technique.

  3. Exploration des Réseaux Génératifs Antagonistes (GAN) : L'intégration des Réseaux Génératifs Antagonistes (GAN) représente une avenue prometteuse. Un GAN est constitué de deux composants en compétition :

    • Le Générateur : Tente de produire des échantillons synthétiques indifférenciables des vraies données.
    • Le Discriminateur : Un classifieur binaire dont le rôle est de distinguer les échantillons réels des échantillons générés par le générateur.

    Ce processus antagoniste pousse le générateur à produire des données de plus en plus réalistes. L'application des GAN (comme DCGAN) pourrait améliorer la qualité et le réalisme des modèles de vitesse générés, en particulier dans des contextes de données limitées ou complexes.

  4. Relation entre Inversion Traditionnelle et Réseaux de Neurones : Une piste de recherche fascinante consiste à explorer les liens et les synergies potentielles entre les méthodes d'inversion géophysique traditionnelles et les réseaux de neurones profonds. Comme le FCNVMB l'illustre, l'adaptation d'architectures existantes et la modification des entrées/sorties permettent des applications innovantes, suggérant une convergence future entre ces deux domaines.

Étiquettes: CNN VGG16 FCN U-Net FCNVMB

Publié le 21 juin à 02h30