L'architecture Transformer, introduite initialement par Vaswani et al. dans l'article "Attention is All You Need", a radicalement transformé le traitement automatique du langage naturel (NLP). Contrairement aux architectures récurrentes (RNN) ou convolutionnelles (CNN), le Transformer repose entièrement sur des mécanismes d'attention pour modéliser les dépendances globales au sein d'une séquence, indépendamment de la distance entre les éléments.
Structure globale de l'architecture
Le modèle suit une structure de type encodeur-décodeur :
- L'Encodeur : Son rôle est de transformer une séquence d'entrée en une représentation vectorielle continue. Il est composé de plusieurs couches identiques, chacune intégrant un mécanisme d'attention multi-têtes suivi d'un réseau de neurones à propagation avant (Feed-Forward).
- Le Décodeur : Il génère la séquence de sortie étape par étape. En plus des composants présents dans l'encodeur, il insère une troisième sous-couche d'attention qui se focalise sur les sorties de l'encodeur, permettant ainsi de lier les informatoins d'entrée et de sortie.
Le mécanisme d'Auto-Attention (Self-Attention)
Le cœur du Transformer est l'auto-attention. Pour chaque élément de la séquence, le modèle calcule trois vecteurs : Requête (Query), Clé (Key) et Valeur (Value).
Le score d'attention est déterminé par le produit scalaire entre la Requête d'un mot et les Clés de tous les autres mots de la séquence. Ces scores sont ensuite normalisés via une fonction Softmax pour obtenir des poids, qui sont finalement appliqués aux vecteurs Valeurs. Mathématiquement, cela se traduit par :
Attention(Q, K, V) = softmax(QKᵀ / √dₖ)V
Attention Multi-Têtes
Plutôt que d'effectuer un seul calcul d'attention, le modèle projette les vecteurs dans plusieurs sous-espaces de représentation. Chaque "tête" peut ainsi apprendre des relations différentes (syntaxiques, sémantiques, temporelles). Les résultats sont ensuite concaténés et projetés à nouveau pour retrouver la dimension d'origine.
Composants techniques essentiels
Encodage Positionnel
Comme le Transformer ne traite pas les données de manière séquentielle, il n'a aucune connaissance intrinsèque de l'ordre des mots. Pour pallier cela, on ajoute des vecteurs d'encodage positionnel aux plongements (embeddings) d'entrée. Ces vecteurs utilisent des fonctions sinusoïdales de fréquences variées pour coder la position relative ou absolue des jetons.
Réseau Feed-Forward (FFN)
Chaque couche d'attention est suivie d'un réseau de neurones totalement connecté apliqué de manière indépendante à chaque position. Il se compose généralement de deux transformations linéaires séparées par une activation non linéaire (souvent ReLU ou GELU).
Implémenattion avec PyTorch
Voici une approche simplifiée pour construire les briques fondamentales d'un Transformer.
1. Mécanisme d'Attention Multi-Têtes
import torch
import torch.nn as nn
import math
class AttentionMultiTetes(nn.Module):
def __init__(self, d_modele, nb_tetes):
super(AttentionMultiTetes, self).__init__()
self.d_modele = d_modele
self.nb_tetes = nb_tetes
self.d_tete = d_modele // nb_tetes
assert self.d_tete * nb_tetes == d_modele, "La dimension doit être divisible par le nombre de têtes"
self.projection_q = nn.Linear(d_modele, d_modele)
self.projection_k = nn.Linear(d_modele, d_modele)
self.projection_v = nn.Linear(d_modele, d_modele)
self.sortie_lineaire = nn.Linear(d_modele, d_modele)
def forward(self, q, k, v, masque=None):
taille_batch = q.size(0)
# Transformation et division en têtes
q = self.projection_q(q).view(taille_batch, -1, self.nb_tetes, self.d_tete).transpose(1, 2)
k = self.projection_k(k).view(taille_batch, -1, self.nb_tetes, self.d_tete).transpose(1, 2)
v = self.projection_v(v).view(taille_batch, -1, self.nb_tetes, self.d_tete).transpose(1, 2)
# Calcul du produit scalaire entrelacé
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_tete)
if masque is not None:
scores = scores.masked_fill(masque == 0, -1e9)
poids_attention = torch.softmax(scores, dim=-1)
contexte = torch.matmul(poids_attention, v)
# Recombinaison des têtes
contexte = contexte.transpose(1, 2).contiguous().view(taille_batch, -1, self.d_modele)
return self.sortie_lineaire(contexte)
2. Couche d'Encodage
class BlocEncodeur(nn.Module):
def __init__(self, d_modele, nb_tetes, d_ff, dropout=0.1):
super(BlocEncodeur, self).__init__()
self.attention = AttentionMultiTetes(d_modele, nb_tetes)
self.norm1 = nn.LayerNorm(d_modele)
self.norm2 = nn.LayerNorm(d_modele)
self.feed_forward = nn.Sequential(
nn.Linear(d_modele, d_ff),
nn.ReLU(),
nn.Linear(d_ff, d_modele)
)
self.dropout = nn.Dropout(dropout)
def forward(self, x, masque):
# Sous-couche d'attention avec connexion résiduelle
attn_res = self.attention(x, x, x, masque)
x = self.norm1(x + self.dropout(attn_res))
# Sous-couche Feed-Forward avec connexion résiduelle
ff_res = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_res))
return x
Évolutions et cas d'usage
L'efficacité du Transformer repose sur sa capacité de parallélisation massive, contrairement aux RNN qui sont intrinsèquement séquentiels. Cette propriété a permis l'entraînement de modèles géants sur des corpus de données colossaux.
Aujourd'hui, cette architecture ne se limite plus au texte. Elle est déclinée dans le domaine de la vision par ordinateur (Vision Transformers - ViT), de la reconnaissance vocale et même de la génération de protéines. Des modèles célèbres comme GPT (basé sur le décodeur) et BERT (basé sur l'encodeur) illustrent la polyvalence de ce paradigme.