Vision Transformer (ViT) : Guide Complet — Transformer Appliqué à l’Image

Vision Transformer (ViT) : Guide Complet — Transformer Appliqué à l'Image

Vision Transformer (ViT)

Résumé

Le Vision Transformer (ViT), introduit par Dosovitskiy et son équipe chez Google Brain en 2020, représente une avancée majeure en vision par ordinateur. Pour la première fois, une architecture reposant exclusivement sur des mécanismes d’attention multi-têtes — sans aucune opération de convolution — dépasse les réseaux convolutifs classiques sur des tâches de classification d’images à grande échelle.

L’idée principale du Vision Transformer est particulièrement élégante : au lieu de traiter une image pixel par pixel avec des filtres de convolution, on la découpe en patches rectangulaires, on projette chaque patch dans un espace vectoriel de dimension fixe, et on alimente cette séquence de vecteurs dans un encodeur Transformer standard. Ce changement de paradigme ouvre la porte à une compréhension globale des relations spatiales au sein de l’image, là où les CNN se limitent traditionnellement à un voisinage local.

Dans ce guide complet, nous allons explorer en détail le principe mathématique du Vision Transformer, son intuition fondamentale, son implémentation pas à pas en PyTorch, ses hyperparamètres clés, ainsi que ses avantages, ses limites et ses cas d’usage concrets.

Principe mathématique du Vision Transformer

Le principe de fonctionnement du Vision Transformer repose sur une succession d’étapes mathématiques précises, chacune jouant un rôle indispensable dans la transformation d’une image brute en une représentation sémantique exploitable pour la classification ou d’autres tâches de vision.

Étape 1 — Découpage de l’image en patches

Considérons une image d’entrée ( x \in \mathbb{R}^{H \times W \times C} ), où ( H ) est la hauteur en pixels, ( W ) la largeur, et ( C ) le nombre de canaux de couleur (généralement 3 pour RVB).

On choisit une taille de patch fixe ( P \times P ) pixels. L’image est alors découpée en une grille non chevauchante de patches. Le nombre total de patches s’obtient par la formule :

$$
N = \frac{H \times W}{P^2}
$$

Par exemple, une image de ( 224 \times 224 ) pixels découpée en patches de ( 16 \times 16 ) donne ( N = \frac{224 \times 224}{16^2} = 196 ) patches. Chaque patch est un tenseur de dimensions ( P \times P \times C ), que l’on « aplatie » en un vecteur de dimension ( P^2 \cdot C ).

Étape 2 — Projection linéaire (Patch Embedding)

Chaque patch aplati est projeté dans un espace de représentation de dimension ( d_{\text{model}} ) à l’aide d’une transformation linéaire (une couche dense sans non-linéarité) :

$$
z_0^{(p)} = x_p \cdot E \quad \text{où} \quad E \in \mathbb{R}^{(P^2 \cdot C) \times d_{\text{model}}}
$$

Ici, ( x_p \in \mathbb{R}^{P^2 \cdot C} ) représente le ( p )-ème patch aplati, et ( z_0^{(p)} \in \mathbb{R}^{d_{\text{model}}} ) est son embedding après projection. La matrice ( E ) est apprise pendant l’entraînement et constitue le cœur de l’étape d’embedding. En pratique, cette projection linéaire est souvent implémentée de manière équivalente à l’aide d’une convolution 2D de taille de noyau ( P \times P ), de pas ( P ) (stride égal à la taille du patch), et de ( d_{\text{model}} ) filtres en sortie.

Étape 3 — Ajout du token de classification [CLS]

À l’instar du Transformer originel utilisé en traitement du langage naturel, on insère un vecteur spécial ( z_{\text{cls}} ) au début de la séquence d’embeddings. Ce token, appelé [CLS] token, est un vecteur apprenable de dimension ( d_{\text{model}} ). Son rôle est d’agréger l’information globale de toute l’image au fil des couches d’attention.

$$
z_0 = [z_{\text{cls}}; z_0^{(1)}; z_0^{(2)}; \dots; z_0^{(N)}] \in \mathbb{R}^{(N+1) \times d_{\text{model}}}
$$

La séquence complète comporte donc ( N + 1 ) vecteurs : le token de classification suivi des ( N ) embeddings de patches.

Étape 4 — Positional Embeddings

Contrairement aux réseaux convolutifs, le mécanisme d’attention est intrinsèquement agnostique à la position : il ne possède aucune notion inhérente d’ordre spatial. Pour remédier à cette limitation, on ajoute à chaque vecteur de la séquence un embedding positionnel ( E_{\text{pos}} ) :

$$
z_0 \leftarrow z_0 + E_{\text{pos}} \quad \text{où} \quad E_{\text{pos}} \in \mathbb{R}^{(N+1) \times d_{\text{model}}}
$$

Les embeddings positionnels sont typiquement appris pendant l’entraînement (embeddings positionnels apprenables), bien que certaines variantes utilisent des embeddings sinusoïdaux fixes inspirés du Transformer originel de Vaswani et al.

Étape 5 — Passage à travers l’encodeur Transformer

La séquence ( z_0 ) traverse ensuite ( L ) couches d’encodeur Transformer identiques. Chaque couche ( \ell ) est composée de deux sous-couches fondamentales :

Attention multi-têtes (MHA) :
$$
z_\ell’ = \text{MHA}(\text{LayerNorm}(z_{\ell-1})) + z_{\ell-1}
$$

Flux feed-forward (MLP) :
$$
z_\ell = \text{MLP}(\text{LayerNorm}(z_\ell’)) + z_\ell’
$$

Chaque sous-couche est précédée d’une normalisation par couche (LayerNorm) et suivie d’une connexion résiduelle. Le mécanisme d’attention multi-têtes calcule, pour chaque paire de positions ( (i, j) ) dans la séquence, un score d’attention qui mesure la pertinence relative du token ( j ) pour le token ( i ). Grâce à cette attention globale, chaque patch peut interagir avec tous les autres patches — y compris ceux situés aux extrémités opposées de l’image.

Étape 6 — Tête de classification (MLP Head)

Après les ( L ) couches d’encodage, on extrait l’état final du token [CLS], noté ( z_L^{(\text{cls})} ), et on le fait passer à travers une tête de classification constituée généralement d’une couche de normalisation (LayerNorm) suivie d’une couche linéaire projetant vers le nombre de classes ( K ) :

$$
y = \text{MLP}(z_L^{(\text{cls})}) \in \mathbb{R}^K
$$

En classification, on applique ensuite une fonction softmax pour obtenir une distribution de probabilité sur les ( K ) classes possibles :

$$
\hat{y} = \text{softmax}(y)
$$

Intuition : un puzzle analysé globalement

Pour bien comprendre l’intuition derrière le Vision Transformer, comparons-le à l’approche traditionnelle des réseaux convolutifs (CNN).

Un CNN parcourt l’image avec des filtres de convolution de taille fixe (par exemple ( 3 \times 3 ) ou ( 5 \times 5 )). Chaque filtre examine un petit voisinage local de pixels, détecte des motifs simples (bords, textures), puis les couches plus profondes combinent ces motifs en caractéristiques plus complexes. Cependant, pour qu’un CNN capture des relations entre régions éloignées de l’image, il faut empiler de nombreuses couches : le champ récepteur effectif ne croît que progressivement avec la profondeur du réseau.

Le Vision Transformer, quant à lui, adopte une stratégie radicalement différente. Imaginez que vous prenez une image et que vous la découpez en morceaux comme un puzzle. Chaque morceau (patch) est envoyé à un « analyseur » Transformer. La magie opère grâce au mécanisme d’attention : chaque fragment peut communiquer directement avec tous les autres fragments, quelle que soit leur distance spatiale dans l’image.

En une seule couche d’attention, un patch situé dans le coin supérieur gauche peut échanger de l’information avec un patch situé dans le coin inférieur droit. Cette capacité à modéliser des relations globales dès la première couche est l’avantage fondamental du ViT. Là où un CNN doit « voir » progressivement de plus en plus loin à travers l’empilement de couches, le Transformer voit tout dès le départ.

Bien entendu, cette puissance a un prix : la complexité computationnelle de l’attention multi-têtes croît quadratiquement avec le nombre de patches ( O(N^2) ), ce qui devient rapidement coûteux pour des images haute résolution.

Implémentation Python avec PyTorch

Voici une implémentation complète et pédagogique du Vision Transformer en PyTorch, étape par étape.

Imports et configuration de base

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

class PatchEmbedding(nn.Module):
    """Transforme l'image en patches et projette chaque patch
    dans l'espace d_model."""

    def __init__(self, img_size=224, patch_size=16, in_channels=3, d_model=768):
        super().__init__()
        self.img_size = img_size
        self.patch_size = patch_size
        self.n_patches = (img_size // patch_size) ** 2
        # Conv2D équivalente à la projection linéaire par patch
        self.projection = nn.Conv2d(
            in_channels, d_model,
            kernel_size=patch_size, stride=patch_size
        )

    def forward(self, x):
        # x: (batch, channels, height, width)
        x = self.projection(x)          # (batch, d_model, n_h, n_w)
        x = x.flatten(2)                 # (batch, d_model, n_patches)
        x = x.transpose(1, 2)            # (batch, n_patches, d_model)
        return x

Token de classification et embeddings positionnels

class VisionTransformer(nn.Module):
    def __init__(
        self, img_size=224, patch_size=16, in_channels=3,
        d_model=768, n_heads=12, n_layers=12,
        mlp_ratio=4, dropout=0.1, n_classes=1000
    ):
        super().__init__()
        self.d_model = d_model
        self.patch_embed = PatchEmbedding(
            img_size, patch_size, in_channels, d_model
        )
        n_patches = self.patch_embed.n_patches

        # Token [CLS] apprenable
        self.cls_token = nn.Parameter(torch.zeros(1, 1, d_model))

        # Embeddings positionnels apprenables
        self.pos_embed = nn.Parameter(
            torch.zeros(1, n_patches + 1, d_model)
        )
        self.dropout = nn.Dropout(dropout)

        # Encodeur Transformer (L couches)
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model,
            nhead=n_heads,
            dim_feedforward=d_model * mlp_ratio,
            dropout=dropout,
            batch_first=True,
            activation='gelu'
        )
        self.transformer_encoder = nn.TransformerEncoder(
            encoder_layer, num_layers=n_layers
        )

        # Tête de classification
        self.norm = nn.LayerNorm(d_model)
        self.head = nn.Linear(d_model, n_classes)

        # Initialisation des paramètres
        self._init_weights()

    def _init_weights(self):
        nn.init.trunc_normal_(self.cls_token, std=0.02)
        nn.init.trunc_normal_(self.pos_embed, std=0.02)

    def forward(self, x):
        batch_size = x.shape[0]

        # 1. Embedding des patches
        x = self.patch_embed(x)  # (batch, n_patches, d_model)

        # 2. Ajout du token CLS
        cls_tokens = self.cls_token.expand(batch_size, -1, -1)
        x = torch.cat([cls_tokens, x], dim=1)  # (batch, n_patches+1, d_model)

        # 3. Ajout des embeddings positionnels
        x = x + self.pos_embed
        x = self.dropout(x)

        # 4. Passage dans l'encodeur Transformer
        x = self.transformer_encoder(x)  # (batch, n_patches+1, d_model)

        # 5. Extraction du token CLS et classification
        cls_output = x[:, 0]  # Premier token = CLS
        cls_output = self.norm(cls_output)
        logits = self.head(cls_output)

        return logits

Fonction d’entraînement minimale

def train_vit(model, dataloader, criterion, optimizer, device):
    """Boucle d'entraînement pour une époque."""
    model.train()
    total_loss = 0
    correct = 0
    total = 0

    for images, labels in dataloader:
        images, labels = images.to(device), labels.to(device)

        optimizer.zero_grad()
        logits = model(images)
        loss = criterion(logits, labels)
        loss.backward()
        optimizer.step()

        total_loss += loss.item() * images.size(0)
        _, predicted = logits.max(1)
        total += labels.size(0)
        correct += predicted.eq(labels).sum().item()

    avg_loss = total_loss / total
    accuracy = 100.0 * correct / total
    return avg_loss, accuracy

Comparaison ResNet vs Vision Transformer

Aspect ResNet-50 (CNN) ViT-B/16 (Transformer)
Opération fondamentale Convolution locale Attention globale
Champ récepteur Progressif (augmente avec la profondeur) Global dès la 1ère couche
Biais inductif Fort (localité, translation) Faible (peu d’hypothèses a priori)
Paramètres ~25 millions ~86 millions (Base)
FLOPs ~4,1 G ~17,6 G
Données nécessaires Modérées (ImageNet suffit) Énormes (JFT-300M, ImageNet-21K)
Parallélisation Bonne (convolutions localisées) Excellente (matrices denses)
Interprétabilité Filtres visualisables Cartes d’attention explicites

Cette comparaison révèle un compromis fondamental : le ResNet, grâce à son fort biais inductif (l’hypothèse que les pixels voisins sont corrélés), généralise bien avec peu de données. Le ViT, avec son biais inductif plus faible, a besoin de beaucoup plus de données pour apprendre ces régularités par lui-même, mais en contrepartie, il atteint des performances supérieures une fois entraîné à grande échelle.

Hyperparamètres clés du Vision Transformer

Le réglage des hyperparamètres est crucial pour obtenir de bonnes performances avec une architecture de type Vision Transformer. Voici les principaux leviers à ajuster.

Taille de patch (patch_size)

La taille de patch ( P ) détermine le nombre de patches et donc la résolution de la séquence d’entrée. Les valeurs les plus courantes sont 8, 14 et 16.

  • Patch plus petit (P = 8) : plus de patches, donc plus de détails capturés, mais complexité quadratique en ( O(N^2) ) et risque de surapprentissage.
  • Patch plus grand (P = 16 ou 32) : moins de patches, donc calcul plus rapide et moindre besoin de données, mais perte de détails fins.

En pratique, P = 16 constitue un excellent compromis pour la plupart des applications. Le modèle ViT-B/16 (Base, patch 16) est la variante la plus utilisée dans la littérature et dans les applications industrielles.

Dimension cachée (hidden_size / d_model)

La dimension de l’espace de représentation, notée ( d_{\text{model}} ), contrôle la capacité d’expression du modèle. Les configurations standards sont :

  • ViT-Tiny : ( d_{\text{model}} = 192 ), ~5 millions de paramètres
  • ViT-Small : ( d_{\text{model}} = 384 ), ~22 millions de paramètres
  • ViT-Base : ( d_{\text{model}} = 768 ), ~86 millions de paramètres
  • ViT-Large : ( d_{\text{model}} = 1024 ), ~307 millions de paramètres
  • ViT-Huge : ( d_{\text{model}} = 1280 ), ~632 millions de paramètres

Augmenter ( d_{\text{model}} ) améliore généralement les performances au prix d’un coût computationnel et mémoriel significativement plus élevé.

Nombre de couches (n_layers)

Le nombre de couches d’encodeur ( L ) détermine la profondeur du modèle. Les valeurs usuelles sont :

  • ViT-Tiny : 12 couches
  • ViT-Small : 12 couches
  • ViT-Base : 12 couches
  • ViT-Large : 24 couches
  • ViT-Huge : 32 couches

Un nombre plus élevé de couches permet au modèle de construire des représentations de plus en plus abstraites, à l’instar de la profondeur dans les CNN. Toutefois, au-delà d’un certain seuil, le rendement marginal diminue et le risque de vanishing gradients augmente (même si les connexions résiduelles atténuent ce problème).

Nombre de têtes d’attention (n_heads)

Le nombre de têtes divise l’espace ( d_{\text{model}} ) en sous-espaces indépendants, chacun apprenant un type différent de relation attentionnelle. Typiquement :

$$
d_{\text{head}} = \frac{d_{\text{model}}}{n_{\text{heads}}}
$$

Par exemple, avec ( d_{\text{model}} = 768 ) et ( n_{\text{heads}} = 12 ), chaque tête opère dans un sous-espace de dimension 64. Un trop petit nombre de têtes limite la diversité des relations apprises, tandis qu’un trop grand nombre dilue la capacité de chaque tête.

Taux de dropout (dropout)

La régularisation par dropout (abandon) est essentielle pour éviter le surapprentissage, en particulier lorsque le ViT est entraîné à partir de zéro. Les valeurs recommandées sont :

  • Entraînement à partir de zéro : dropout = 0,1 à 0,3
  • Fine-tuning : dropout = 0,0 à 0,1 (moins nécessaire car le modèle est déjà pré-entraîné)

On utilise souvent aussi du stochastic depth (abandon aléatoire de couches entières) comme technique de régularisation complémentaire, particulièrement utile pour les modèles très profonds.

Avantages et limites du Vision Transformer

Avantages

  1. Modélisation globale des relations spatiales : Contrairement au CNN dont le champ récepteur est local et ne s’élargit que progressivement, le mécanisme d’attention du ViT capture des dépendances entre toutes les paires de régions de l’image, dès la première couche. Cette vision d’ensemble est particulièrement puissante pour comprendre la composition globale d’une scène.
  2. Excellente scalabilité : Les performances du Vision Transformer s’améliorent de manière continue avec la taille des données d’entraînement et du modèle. Des études montrent que sur des datasets comportant des centaines de millions d’images (comme JFT-300M), le ViT dépasse systématiquement les architectures convolutives les plus performantes.
  3. Interprétabilité via les cartes d’attention : Les poids d’attention entre les patches fournissent naturellement une visualisation intuitive de ce que le modèle « regarde » pour prendre sa décision. Chaque tête d’attention peut être interprétée comme se concentrant sur un aspect différent de l’image.
  4. Unification architecturelle : Le ViT utilise la même brique fondamentale (l’encodeur Transformer) que les modèles de langage. Cette unification permet de concevoir des architectures multi-modales combinant texte et image de façon naturelle, comme CLIP, Flamingo ou les modèles de génération d’images basés sur des Transformers.
  5. Transfert learning performant : Une fois pré-entraîné sur un large corpus d’images, le ViT se transfère remarquablement bien vers des tâches cibles, même avec peu de données annotées disponibles.

Limites

  1. Besoin massif de données : Sans pré-entraînement sur des corpus gigantesques (JFT-300M, ImageNet-21K), le ViT peine à généraliser correctement. Son faible biais inductif est un handicap lorsqu’on dispose de peu de données : il ne « sait pas » a priori que les pixels voisins sont corrélés ou que la translation préserve la sémantique.
  2. Complexité computationnelle quadratique : La matrice d’attention calcule des scores pour toutes les paires de patches, ce qui implique un coût en ( O(N^2 \cdot d_{\text{model}}) ). Pour une image ( 224 \times 224 ) avec ( P = 16 ), cela représente ( 196^2 \approx 38\,000 ) paires à évaluer. Pour des images haute résolution, ce coût devient prohibitif.
  3. Moins performant sur les tâches denses : Pour des tâches nécessitant des prédictions pixel-à-pixel comme la segmentation sémantique ou la détection d’objets, les architectures convolutives conservent souvent un avantage en termes de précision et de finesse des contours, bien que des adaptations spécifiques du ViT (comme Segmenter ou MaskFormer) comblent progressivement cet écart.
  4. Sensibilité aux perturbations adversielles : Des études ont montré que les transformers visuels peuvent être plus vulnérables aux attaques adversielles que les CNN dans certains scénarios, notamment lorsque les perturbations exploitent la structure globale de l’attention.

4 cas d’usage concrets du Vision Transformer

Cas d’usage 1 : Classification d’images médicales

Le Vision Transformer excelle dans la classification d’images radiographiques, de coupes IRM et de photographies dermatologiques. Sa capacité à capturer des relations globales est particulièrement pertinente : dans une radiographie pulmonaire, une anomalie peut être corrélée avec une autre région éloignée du poumon, une relation qu’un CNN classique pourrait manquer si son champ récepteur n’est pas suffisamment large. Des équipes de recherche ont démontré des résultats supérieurs aux CNN sur des benchmarks tels que CheXpert et MIMIC-CXR pour la détection de pneumonies et d’autres pathologies thoraciques.

Cas d’usage 2 : Classification de scènes satellites

Les images satellitaires couvrent souvent de vastes zones géographiques où les objets d’intérêt sont dispersés sur l’ensemble de la scène. Le Vision Transformer, avec son attention globale, est naturellement adapté à cette configuration : il peut établir des corrélations entre des régions distantes pour classifier le type de scène (zone urbaine, forêt, zone agricole, plan d’eau). Des variantes spécialisées comme SatMAIntègrent également des données multi-temporelles et multi-spectrales pour l’analyse environnementale.

Cas d’usage 3 : Reconnaissance de documents et OCR intelligent

Pour la compréhension de documents scannés (factures, formulaires, contrats), le ViT permet d’analyser simultanément la structure visuelle globale (mise en page, colonnes, tableaux) et les caractéristiques locales (polices, logos, signatures). Couplé à un modèle de langage pour le traitement du texte extrait, le Vision Transformer forme la base de systèmes de traitement de documents end-to-end qui surpassent les pipelines traditionnels à base de règles.

Cas d’usage 4 : Vérification de contenu multimédia et détection de deepfakes

La détection de deepfakes et de manipulations d’images est un domaine où la vision globale du Transformer offre des avantages significatifs. Les artefacts de génération laissés par les modèles GAN ou par diffusion sont souvent subtils et répartis sur l’ensemble de l’image. En analysant les corrélations globales entre patches, un Vision Transformer peut détecter des incohérences qui échappent à un CNN focalisé sur des motifs locaux. Des recherches récentes ont montré que les ViT atteignent des taux de détection supérieurs à 95 % sur des benchmarks de détection de deepfakes.

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.