Chapitre 3 : Encodage positionnel et embeddings
Introduction
Contrairement aux RNNs qui traitent les séquences token par token, les Transformers traitent toutes les positions simultanément. Cela crée un défi fondamental : comment le modèle connaît-il l'ordre des tokens ? Ce chapitre explique l'encodage positionnel et le processus plus large d'embedding des tokens.
Le problème de la position
Pourquoi la position est importante
Considérez ces phrases :
- "Le chien a mordu l'homme"
- "L'homme a mordu le chien"
Mêmes tokens, significations complètement différentes. La position est cruciale pour comprendre le langage.
Transformers et invariance par permutation
Sans information positionnelle, l'auto-attention est invariante par permutation :
Où est n'importe quelle permutation. Le modèle traite "chien mordu homme" et "homme mordu chien" de manière identique !
# Démonstration : les scores d'attention sont indépendants de la position
import torch
def attention_scores(Q, K):
return torch.softmax(Q @ K.T, dim=-1)
# Séquence originale
x1 = torch.tensor([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
# Séquence permutée (échanger les positions 0 et 1)
x2 = torch.tensor([[0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
# Même motif d'attention (juste permuté) !
print(attention_scores(x1, x1))
print(attention_scores(x2, x2))
Embeddings de tokens
Des tokens aux vecteurs
Avant d'ajouter l'information positionnelle, nous convertissons d'abord les tokens en vecteurs denses :
Implémentation
import torch
import torch.nn as nn
class TokenEmbedding(nn.Module):
"""
Convertir les IDs de tokens en vecteurs denses.
"""
def __init__(self, vocab_size, d_model):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
def forward(self, token_ids):
"""
token_ids : (batch, seq_len) d'IDs de tokens entiers
Retourne : (batch, seq_len, d_model) embeddings
"""
# Échelle des embeddings (à partir de l'article original sur les Transformers)
return self.embedding(token_ids) * (self.d_model ** 0.5)
# Exemple
vocab_size = 50000
d_model = 512
embedding_layer = TokenEmbedding(vocab_size, d_model)
tokens = torch.tensor([[101, 7592, 2088, 102]]) # [CLS] Bonjour Monde [SEP]
embeddings = embedding_layer(tokens)
print(f"Shape: {embeddings.shape}") # (1, 4, 512)
Encodage positionnel sinusoïdal
L'approche originale
L'article original sur les Transformers utilisait des fonctions sinusoïdales fixes :
Où :
- = position dans la séquence
- = indice de dimension
- = dimension du modèle
Intuition
Chaque dimension oscille à une fréquence différente :
- Basses dimensions : oscillation lente (capture les positions à longue portée)
- Hautes dimensions : oscillation rapide (capture les positions fines)
Implémentation
import numpy as np
def get_sinusoidal_encoding(max_len, d_model):
"""
Générer des encodages positionnels sinusoïdaux.
Args:
max_len: Longueur maximale de la séquence
d_model: Dimension du modèle
Returns:
Matrice d'encodage positionnel (max_len, d_model)
"""
position = np.arange(max_len)[:, np.newaxis]
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
pe = np.zeros((max_len, d_model))
pe[:, 0::2] = np.sin(position * div_term) # Indices pairs
pe[:, 1::2] = np.cos(position * div_term) # Indices impairs
return pe
# Visualiser
import matplotlib.pyplot as plt
pe = get_sinusoidal_encoding(100, 512)
plt.figure(figsize=(12, 4))
plt.imshow(pe.T, aspect='auto', cmap='RdBu')
plt.xlabel('Position')
plt.ylabel('Dimension')
plt.colorbar(label='Valeur')
plt.title('Encodage positionnel sinusoïdal')
plt.show()
Propriétés de l'encodage sinusoïdal
- Unique : Chaque position a un encodage unique
- Borné : Valeurs toujours dans [-1, 1]
- Positions relatives : La relation linéaire permet d'apprendre les positions relatives
- Extrapolation : Peut se généraliser à des séquences plus longues que celles vues lors de l'entraînement
Implémentation PyTorch
class SinusoidalPositionalEncoding(nn.Module):
"""
Encodage positionnel sinusoïdal de 'Attention Is All You Need'.
"""
def __init__(self, d_model, max_len=5000, dropout=0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
# Créer la matrice d'encodage positionnel
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(
torch.arange(0, d_model, 2).float() * (-np.log(10000.0) / d_model)
)
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0) # Ajouter la dimension batch
# Enregistrer comme tampon (pas un paramètre, mais sauvegardé avec le modèle)
self.register_buffer('pe', pe)
def forward(self, x):
"""
x : (batch, seq_len, d_model)
"""
x = x + self.pe[:, :x.size(1)]
return self.dropout(x)
Embeddings positionnels appris
Une approche alternative
De nombreux LLM modernes utilisent des embeddings de position appris :
class LearnedPositionalEmbedding(nn.Module):
"""
Embeddings positionnels appris (par exemple, utilisés dans GPT-2).
"""
def __init__(self, max_len, d_model):
super().__init__()
self.position_embedding = nn.Embedding(max_len, d_model)
def forward(self, x):
"""
x : (batch, seq_len, d_model)
"""
positions = torch.arange(x.size(1), device=x.device)
return x + self.position_embedding(positions)
Comparaison
| Aspect | Sinusoïdal | Appris |
|---|---|---|
| Paramètres | Aucun | max_len × d_model |
| Extrapolation | Meilleure | Limitée à max_len |
| Performance | Comparable | Comparable |
| Flexibilité | Fixe | S'adapte aux données |
Embedding de position rotatif (RoPE)
Innovation moderne
RoPE, utilisé dans LLaMA, Mistral, et de nombreux modèles modernes, encode la position par rotation :
Principaux avantages
- Positions relatives : Capture naturellement l'information de position relative
- Extrapolation : Meilleure gestion des séquences plus longues
- Efficacité : Appliqué directement à Q, K sans paramètres supplémentaires
Implémentation simplifiée
def rotary_embedding(x, seq_len, d_model):
"""
Implémentation simplifiée de RoPE.
x : (batch, seq_len, d_model)
"""
# Générer les angles de rotation
position = torch.arange(seq_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
angles = position * div_term
# Appliquer la rotation aux paires de dimensions
x_rotated = x.clone()
x_rotated[..., 0::2] = x[..., 0::2] * torch.cos(angles) - x[..., 1::2] * torch.sin(angles)
x_rotated[..., 1::2] = x[..., 0::2] * torch.sin(angles) + x[..., 1::2] * torch.cos(angles)
return x_rotated
ALiBi : Attention avec biais linéaires
Une autre approche
ALiBi (Attention with Linear Biases) ajoute une pénalité linéaire basée sur la distance :
Où est une pente spécifique à la tête.
Avantages
- Aucun paramètre supplémentaire
- Excellente extrapolation de longueur
- Implémentation simple
Couche d'embedding complète
Tout mettre ensemble
class TransformerEmbedding(nn.Module):
"""
Couche d'embedding complète : token + position.
"""
def __init__(self, vocab_size, d_model, max_len=5000,
pos_encoding='sinusoidal', dropout=0.1):
super().__init__()
self.token_embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
self.dropout = nn.Dropout(dropout)
if pos_encoding == 'sinusoidal':
self.pos_encoding = SinusoidalPositionalEncoding(d_model, max_len)
elif pos_encoding == 'learned':
self.pos_encoding = LearnedPositionalEmbedding(max_len, d_model)
else:
raise ValueError(f"Unknown pos_encoding: {pos_encoding}")
def forward(self, token_ids):
"""
token_ids : (batch, seq_len)
Retourne : (batch, seq_len, d_model)
"""
# Embeddings de tokens (échelonnés)
x = self.token_embedding(token_ids) * (self.d_model ** 0.5)
# Ajouter l'encodage positionnel
x = self.pos_encoding(x)
return self.dropout(x)
# Utilisation
embedding = TransformerEmbedding(
vocab_size=50000,
d_model=512,
max_len=2048,
pos_encoding='sinusoidal'
)
tokens = torch.randint(0, 50000, (32, 100)) # Batch de 32, seq_len 100
embedded = embedding(tokens)
print(f"Output shape: {embedded.shape}") # (32, 100, 512)
La vue d'ensemble
Voici comment les tokens passent des embeddings à l'attention :
IDs de tokens : [101, 7592, 2088, 102]
↓
Embedding de tokens : Recherche dans la table d'embedding
↓
[0.23, -0.45, ...] [0.89, 0.12, ...] [0.45, -0.23, ...] [0.67, 0.34, ...]
↓
Ajouter l'encodage positionnel
↓
[0.23+PE_0, -0.45+PE_0, ...] [0.89+PE_1, 0.12+PE_1, ...] ...
↓
Attention multi-tête
↓
Sortie avec contexte conscient de la position
Points clés à retenir
- L'information de position est essentielle—les Transformers sont autrement invariants par permutation
- Les embeddings de tokens convertissent les tokens discrets en vecteurs continus
- L'encodage sinusoïdal utilise des motifs de fréquence fixes (Transformer original)
- Les embeddings appris s'adaptent aux données mais ont une longueur maximale fixe
- RoPE encode les positions relatives par rotation (LLMs modernes)
- ALiBi utilise des biais d'attention basés sur la distance
À venir
Maintenant que nous comprenons l'architecture complète du Transformer—attention, attention multi-tête, et encodage positionnel—nous sommes prêts à passer au Module 3, où nous explorerons comment ces modèles massifs sont entraînés.