Du texte aux représentations numériques et aux premiers modèles séquentiels
Cette séance construit le socle conceptuel des LLM. On suit une progression continue : texte brut vers tokens, tokens vers vecteurs, vecteurs vers transformations matricielles, puis modèles séquentiels (RNN et LSTM) et leurs limites. Cette trajectoire prépare la séance suivante consacrée au Transformer et à la self-attention.
Ce que construit cette séance
Le cours avance par couches. Il part du langage comme signal textuel, passe par la tokenisation, transforme les tokens en vecteurs, puis montre pourquoi les RNN et les LSTM ont constitué une étape clé avant l’attention et le Transformer.
La logique reste cumulative. Chaque bloc répond à une question précise de modélisation et prépare la suivante, sans rupture artificielle entre mathématiques, intuition et lecture critique.
Résumé exécutif et hypothèses
Cette séance construit le socle conceptuel des LLM. On suit une progression continue : texte brut vers tokens, tokens vers vecteurs, vecteurs vers transformations matricielles, puis modèles séquentiels (RNN et LSTM) et leurs limites. Cette trajectoire prépare la séance suivante consacrée au Transformer et à la self-attention.
Le fil pédagogique est volontairement cumulatif : chaque brique répond à une question précise de modélisation du langage et éclaire la suivante.
Objectifs pédagogiques et prérequis
- Expliquer ce qu'est le NLP et situer les LLM dans une trajectoire réaliste, sans récit linéaire simplifié.
- Définir token et tokenisation, et justifier clairement pourquoi token ne signifie pas mot.
- Distinguer embedding d'un token, table d'embedding du vocabulaire et matrice d'embeddings d'une séquence.
- Interpréter une transformation matricielle apprise et relier cette opération au calcul neuronal.
- Comprendre la malédiction de la dimension et expliquer le rôle des embeddings dans la généralisation.
- Décrire le fonctionnement d'une RNN et d'une LSTM, leurs apports et leurs limites structurelles.
- Distinguer attention, logits, softmax et décodage pour éviter les confusions classiques sur la prédiction du token suivant.
Du NLP aux tokens : tokenisation, sous-mots et représentation discrète
Commençons par une question volontairement simple : que signifie "traiter automatiquement une langue" ? Le NLP (Natural Language Processing) vise à faire exécuter par des machines des tâches impliquant du langage : analyser, traduire, résumer, extraire des informations, répondre à des questions, etc. Dans les manuels de référence, le NLP est présenté comme un champ à l'intersection de la linguistique, de l'informatique et de l'apprentissage automatique, avec une grande diversité de modèles selon les époques et les tâches (Jurafsky & Martin, 2024).
Historiquement, il n'y a pas eu une marche triomphale "n-gram → Transformer → LLM" comme si tout était écrit d'avance. Il y a eu des périodes où certaines approches dominaient parce qu'elles étaient calculables et performantes (par exemple les modèles n-gram en modélisation du langage), d'autres idées existaient mais étaient limitées par le calcul, l'accès aux données ou la maturité des algorithmes d'apprentissage, puis des ruptures ont émergé quand une combinaison "architecture + données + calcul" s'est alignée.
Une façon fiable de raconter cette histoire est de suivre un fil conducteur : comment représenter le texte pour qu'un algorithme apprenne ? C'est là que naissent nos trois briques fondamentales : tokenisation, embeddings, et modèles séquentiels / contextuels.
Qu'est-ce qu'un token ?
Un ordinateur ne "mange" pas des phrases : il ingère des symboles discrets. La tokenisation transforme une chaîne de caractères (texte brut) en une suite d'unités appelées tokens. Dans les systèmes modernes, les tokens sont très souvent des sous-mots (subwords), pour gérer la diversité des langues, les mots rares et les néologismes (Sennrich et al., 2016).
Définition simple
Un token est une brique de texte utilisée par le modèle : un morceau de mot, un mot entier, de la ponctuation, etc.
Explication rigoureuse
La plupart des LLM utilisent une segmentation en sous-unités (famille BPE/unigram). L'idée, popularisée en traduction neuronale puis en tokenisation moderne, est de représenter des mots rares comme des séquences de sous-unités fréquentes, ce qui réduit les problèmes de vocabulaire fixe (Sennrich et al., 2016 ; Kudo & Richardson, 2018).
Imaginez un jeu de LEGO : si vous n'avez que des pièces de mots entiers, un mot rare devient introuvable. Avec des pièces de sous-mots, vous reconstruisez plus facilement des mots nouveaux.
J'aime l'IA.
Point clé : les tokens ne correspondent pas toujours à des mots. La segmentation dépend d'un vocabulaire appris, de type BPE ou unigram, et des conventions d'espaces et de ponctuation.
Deux tokenizers différents peuvent tokeniser différemment la même phrase. La longueur "en tokens" n'est pas la longueur "en mots". Pour des raisons d'ingénierie (coûts, latence, fenêtre de contexte), on compte très souvent en tokens (Kudo & Richardson, 2018).
# Objectif : rendre concret texte -> tokens -> IDs import tiktoken texte = "Bonjour, monde ! Une tokenisation n'est pas une découpe en mots." enc = tiktoken.get_encoding("o200k_base") ids = enc.encode(texte) texte_reconstruit = enc.decode(ids) print("Texte original :", texte) print("IDs :", ids) print("Nombre de tokens :", len(ids)) print("Texte reconstruit :", texte_reconstruit)
Lecture vulgarisée Tokenisation
Le modèle ne voit pas des mots, il voit des IDs. Le comptage en tokens sert ensuite aux coûts, à la latence et à la fenêtre de contexte.
Embeddings, matrices, multiplications matricielles et malédiction de la dimension
La tokenisation transforme le texte en IDs discrets. L'embedding transforme ensuite ces IDs en vecteurs continus. Le contexte n'est pas fabriqué par la tokenisation, il émerge des transformations du réseau.
Une suite de tokens, ce sont des identifiants (0, 1, 2, …). Mais un réseau neuronal apprend mieux quand les objets sont dans un espace continu où la similarité a un sens. C'est l'idée des représentations distribuées : un mot (ou token) est associé à un vecteur réel (Bengio et al., 2003).
Définition simple
Un embedding est une liste de nombres (un vecteur) qui représente un token (ou une phrase) de manière exploitable par le modèle.
Explication rigoureuse
Les réseaux de langage neuronaux ont mis en avant l'idée d'apprendre simultanément : (i) des vecteurs pour les mots/tokens, (ii) une fonction de probabilité sur les séquences, afin de mieux généraliser malgré la malédiction de la dimension des distributions discrètes (Bengio et al., 2003).
Quand la dimension augmente, les exemples deviennent statistiquement plus dispersés. Les distances utiles deviennent moins discriminantes, et il faut davantage de données pour estimer correctement les régularités. Les embeddings aident justement à compresser de l'information utile dans un espace continu appris, plus exploitable qu'une représentation purement discrète.
La dimension élevée n'est pas un défaut en soi. Le problème apparaît quand le nombre d'exemples et la qualité de l'espace appris ne suffisent pas pour structurer correctement cette grande dimension.
Si les tokens étaient des noms de villes et les embeddings des coordonnées sur une carte, deux mots proches en sens se retrouveraient proches dans cette carte. Le modèle peut alors généraliser plus facilement.
embedding("chat") = [ 0.2, 0.8, -0.1] embedding("chien") = [ 0.1, 0.7, -0.2] embedding("banane") = [-0.6, 0.0, 0.9]
Lecture vulgarisée Espace vectoriel
Chaque token est représenté par un point dans un espace à haute dimension. La distance entre deux points reflète leur similarité sémantique.
- "chat" et "chien" partagent des contextes similaires (animaux domestiques) → leurs vecteurs sont proches.
- "banane" apparaît dans des contextes très différents → son vecteur est éloigné.
- En pratique, on utilise des centaines ou milliers de dimensions, pas 3.
- La sémantique est distribuée sur toutes les dimensions : aucune dimension ne correspond à un trait linguistique précis.
- C'est un encodage émergent appris par l'optimisation, pas une table de traits écrite à la main.
Vecteur = [dimension1, dimension2, dimension3, ...] [ 0.12 , -0.03 , 0.77 , ...]
Lecture vulgarisée Représentation distribuée
Contrairement à une table de traits (ex. [+animé, +domestique, …]), un embedding ne possède pas de dimensions interprétables directement. La signification émerge de la structure globale de l'espace.
- Chaque dimension est un nombre réel appris par rétropropagation.
- Aucune dimension isolée ne correspond à un trait linguistique précis.
- C'est la combinaison de toutes les dimensions qui encode le sens.
- Cela permet une généralisation bien meilleure que les représentations one-hot.
Vue compacte pour repérer des groupes proches sur deux axes principaux.
Vue plus fidèle quand la séparation nécessite une troisième composante principale. Un point supplémentaire apparaît ici et n'était pas distinguable en projection 2D.
À ce stade, un piège classique apparaît : croire qu'un mot a "un sens" fixe. Les embeddings statiques (type Word2Vec, GloVe) associent en gros un vecteur par mot (ou par token), ce qui marche bien pour une sémantique moyenne.
Mais la langue est pleine de polysémie. Exemple : "banc" (banc de poissons vs banc pour s'asseoir). Un embedding statique doit "moyenner" ces usages en un seul point.
Les représentations contextualisées (ELMo, BERT, Transformers) produisent une représentation du token conditionnée par le contexte : le vecteur pour "banc" n'est pas le même dans "banc de poissons" et "banc en bois".
Le terme "embedding" peut désigner trois choses distinctes selon le contexte technique : (1) un vecteur appris en entrée (statique, type Word2Vec) ; (2) une représentation interne d'un token à une couche donnée (contextualisée) ; (3) un embedding de phrase (agrégation) utilisé pour la recherche ou la similarité. Le sens du mot "embedding" dépend donc du contexte.
| Élément | Idée centrale | Avantage | Limite typique | Source pivot |
|---|---|---|---|---|
| Embeddings statiques Word2Vec / GloVe |
Un vecteur par mot/token | Simple, efficace, capture des régularités | Polysémie mal gérée (un seul vecteur) | Bengio et al., 2003 |
| RNN / LSTM | Lire séquentiellement, état caché | Naturel pour séquences, historique | Longues dépendances + faible parallélisme | Elman, 1990 |
| Représentations contextualisées ELMo / Transformers |
Vecteur dépend du contexte | Polysémie mieux capturée, transfert | Complexité, besoin de données/calcul | Jurafsky & Martin, 2024 |
Cette partie regroupe les trois notions qui créent le plus de confusion. Le but est simple : savoir exactement quel objet on manipule à chaque étape.
1) Embedding d'un token, table du vocabulaire, matrice de séquence
C'est la fiche d'identité numérique d'un seul token. On peut l'imaginer comme une ligne de nombres qui décrit ce token pour le modèle.
C'est le grand classeur de toutes les fiches d'identité possibles. Une ligne par token du vocabulaire, toujours la même largeur \(d\).
Pour une phrase de longueur \(n\), on empile \(n\) fiches du classeur. La phrase devient une matrice.
2) Formule de lookup
Le modèle va dans le classeur \(E\), prend la ligne numéro \(i\), et c'est tout. Le lookup est un accès direct à la bonne ligne.
Cette écriture dit la même chose autrement. Le vecteur one-hot \(o_i\) agit comme un curseur qui sélectionne exactement une ligne.
3) Transformation matricielle et généralisation
Le réseau prend une représentation \(x\), la transforme avec \(W\), et produit une nouvelle vue \(y\). C'est la brique de base de presque tous les calculs internes.
Le nombre de phrases possibles explose très vite. Les embeddings et les transformations matricielles aident le modèle à généraliser sans avoir vu toutes les phrases exactes.
import numpy as np vocab = {"chat": 0, "chien": 1, "banane": 2, ".": 3} V = len(vocab) d = 4 np.random.seed(0) E = np.random.randn(V, d) sequence = ["chat", "chien", ".", "banane"] ids = [vocab[t] for t in sequence] X = E[ids, :] print("E shape:", E.shape) print("X shape:", X.shape) print("embedding('chat'):", X[0])
Phrase A : "Il s'assoit sur le banc."
Phrase B : "Un banc de poissons traverse la baie."
Un embedding statique pour "banc" est unique : il représente une moyenne de ses usages. Une représentation contextualisée, elle, tient compte du contexte "s'assoit" ou "poissons" et se positionne différemment dans l'espace interne.
Pour réfléchir : d'autres mots comme « avocat » (fruit vs métier) ou « charge » (électrique vs responsabilité) posent le même problème. Un embedding statique doit moyenner ces significations, tandis qu'une représentation contextualisée peut distinguer les sens selon la phrase.
RNN et LSTM : architecture, équations, forces, limites, et lien vers l attention
Un RNN ne traite pas les mots indépendamment. À chaque pas, il combine le token courant \(x_t\) avec l'état caché précédent \(h_{t-1}\). Le contexte est donc intégré pendant le calcul.
Avant les Transformers, les réseaux récurrents (RNN) ont structuré une grande partie du NLP neuronal, notamment parce qu'ils modélisent naturellement des séquences : à chaque pas, l'état caché accumule l'information du passé (Elman, 1990).
Définition simple
Une RNN lit une phrase mot par mot et conserve un état interne.
Explication rigoureuse
Les modèles de langage récurrents (RNNLM) ont montré qu'on pouvait améliorer des métriques de modélisation du langage (perplexité) en exploitant un historique potentiellement long via la récurrence (Elman, 1990).
Imaginez un lecteur qui avance ligne par ligne et garde un carnet. Il ne relit pas tout le livre à chaque ligne, il s'appuie sur ce carnet.
Chaque neurone est connecté à tous les neurones de la couche suivante. Le réseau est donc pleinement connecté entre couches adjacentes.
Dans un réseau de neurones dense, chaque couche reçoit les activations de la couche précédente. Elle effectue d'abord une transformation affine, puis applique une fonction d'activation non linéaire.
Ces équations ne décrivent pas une probabilité de passer d'une couche à l'autre. Elles décrivent le calcul réalisé par la couche \(\ell\) à partir des activations de la couche précédente.
Dans le cas particulier d'une classification binaire, la dernière couche contient un seul neurone de sortie. On applique alors une sigmoïde pour obtenir une valeur comprise entre 0 et 1.
Les couches internes ne calculent pas des probabilités de transition. Elles transforment progressivement l'information. La dimension probabiliste apparaît à la fin, au niveau de la sortie. En binaire, si la sortie vaut 0,82, le modèle attribue une probabilité estimée de 0,82 à la classe positive.
Signification des symboles : \(a^{(\ell-1)}\) représente les activations de la couche précédente. \(W^{(\ell)}\) et \(b^{(\ell)}\) sont les paramètres appris. \(z^{(\ell)}\) est le signal avant activation. \(\phi\) est la fonction d'activation non linéaire. \(\hat{p}\) est la probabilité estimée de la classe positive.
1. Dépendances longues et gradients
Apprendre à conserver une information très ancienne est difficile : les gradients qui se propagent dans le temps ont tendance à s'évanouir ou à exploser. Les LSTM (Long Short-Term Memory) ont été proposées pour mieux gérer ces dépendances via des mécanismes de "gates" (Hochreiter & Schmidhuber, 1997).
2. Parallélisation
Une RNN traite intrinsèquement la séquence pas-à-pas. Même si on optimise l'implémentation, il existe une dépendance séquentielle difficile à éliminer : le calcul de ht requiert ht-1, ce qui empêche le traitement en parallèle de tous les tokens simultanément.
3. Goulot d'étranglement de compression
Dans certaines architectures encodeur-décodeur, tout le sens devait passer par un vecteur fixe (le "vecteur de contexte"), ce qui a motivé l'apparition du mécanisme d'attention dans la traduction neuronale.
Cette cellule sépare la mémoire longue \(c_t\) et la sortie instantanée \(h_t\). Les portes contrôlent ce qui est oublié, ajouté et transmis. C'est précisément ce mécanisme qui stabilise l'apprentissage sur des dépendances longues.
x1 -> [RNN] -> h1 -> y1 x2 -> [RNN] -> h2 -> y2 x3 -> [RNN] -> h3 -> y3 ... Relation récurrente : • xt = embedding du token t • ht = état caché (résume le passé) • yt = sortie (ex. probas du prochain token)
Lecture vulgarisée Architecture RNN
La RNN traite la séquence pas-à-pas. À chaque étape t, elle combine l'embedding du token courant xt avec l'état caché précédent ht-1 pour produire le nouvel état ht et une sortie yt.
- xt : le token t est converti en vecteur (embedding).
- ht-1 : l'état caché du pas précédent résume tout le passé.
- La fonction récurrente combine l'entrée courante et l'état précédent.
- yt : la sortie (ex. distribution de probabilité sur le prochain token).
- La dépendance séquentielle ht -> ht+1 rend la parallélisation difficile.
Version canonique : la non-linéarité \(\tanh\) transforme le mélange du présent et du passé.
import numpy as np def tanh(x): return np.tanh(x) def sigmoid(x): return 1 / (1 + np.exp(-x)) def rnn_step(x_t, h_prev, Wx=0.8, Wh=0.4, b=0.0): preact = Wx * x_t + Wh * h_prev + b h_t = tanh(preact) return h_t def lstm_step(c_prev, i_preact=1.0, f_preact=-1.0, o_preact=0.5, g_preact=0.2): i_t, f_t, o_t = sigmoid(i_preact), sigmoid(f_preact), sigmoid(o_preact) g_t = tanh(g_preact) c_t = f_t * c_prev + i_t * g_t h_t = o_t * tanh(c_t) return h_t, c_t print("rnn h_t =", rnn_step(1.0, 0.5)) print("lstm (h_t, c_t) =", lstm_step(0.1))
Aperçu du Transformer : attention, Q/K/V, softmax, logits et token suivant
Le mécanisme d'attention est au cœur de l'architecture Transformer. Pour chaque token, trois vecteurs sont calculés à partir de son embedding : la requête (Query, q), la clé (Key, k) et la valeur (Value, v). Le diagramme ci-dessous illustre ce mécanisme sur la phrase "A cute teddy bear is reading."
Le diagramme illustre le mécanisme d'attention pour le token "teddy bear" dans la phrase "A cute teddy bear is reading." La rangée supérieure montre les tokens de la phrase tels qu'ils arrivent en entrée. La rangée centrale montre les mêmes tokens avec leurs vecteurs Value (v, en vert, au-dessus) et leurs vecteurs Key transposés (kT, en bleu, en dessous). Les lignes convergent toutes vers la Query qteddy bear (en rouge), qui représente "ce que le token teddy bear cherche" dans le contexte. La rangée inférieure montre les tokens de sortie, avec "teddy bear" mis en évidence comme token dont la représentation contextualisée est calculée.
Formellement, le score d'attention entre le token requêteur \(i\) et le token clé \(j\) est un produit scalaire entre \(q_i\) et \(k_j\), suivi d'une normalisation.
Produit scalaire
Scaled dot-product attention
Softmax
Même fonction mathématique, deux usages différents : distribution d'attention sur le contexte, puis distribution de sortie sur le vocabulaire.
Point clé : l'attention construit une représentation contextualisée. La sélection du token suivant se fait ensuite via les logits et le décodage.
Le modèle calcule des scores \(z_t\), puis les convertit en probabilités avant le choix du token suivant.
| Critère | RNN / LSTM | Transformer (aperçu) |
|---|---|---|
| Parallélisation | Limitée par la dépendance séquentielle | Plus forte grâce aux opérations matricielles sur la séquence |
| Dépendances longues | Difficiles malgré les portes LSTM | Mieux gérées via les interactions attentionnelles directes |
| Coût en longueur | Séquentiel, peu parallélisable | Attention quadratique en version standard, mais parallélisable |
| Mécanisme de mémoire | Etat caché et cellule mémoire | Représentation distribuée par attention |
Atelier Python : température, top-k, top-p et impact en décodage
import math import random def softmax(logits): m = max(logits) exps = [math.exp(x - m) for x in logits] s = sum(exps) return [e / s for e in exps] def apply_temperature(logits, T): return [x / T for x in logits] def top_k_filter(tokens, probs, k): pairs = sorted(zip(tokens, probs), key=lambda x: x[1], reverse=True) kept = pairs[:k] total = sum(p for _, p in kept) return [(t, p/total) for t, p in kept] def top_p_filter(tokens, probs, p): pairs = sorted(zip(tokens, probs), key=lambda x: x[1], reverse=True) kept, cum = [], 0.0 for t, pr in pairs: kept.append((t, pr)) cum += pr if cum >= p: break total = sum(pr for _, pr in kept) return [(t, pr/total) for t, pr in kept] tokens = ["lait", "eau", "moteur", "chaise"] logits = [2.0, 1.0, 0.1, -0.5] probs = softmax(logits) print("T=1:", list(zip(tokens, probs))) print("top-k=2:", top_k_filter(tokens, probs, k=2)) print("top-p=0.8:", top_p_filter(tokens, probs, p=0.8))
Fenêtre glissante, erreurs fréquentes et bibliographie
La fenêtre glissante est une bonne intuition pédagogique pour comprendre le décalage des cibles. Le principe reste toujours le même : à partir d'un préfixe de tokens, le modèle apprend à prédire le token suivant.
Dans une implémentation explicite, on décale la fenêtre d'un seul token à chaque nouvel exemple. Dans les LLM modernes, on traite plutôt une séquence complète de longueur de contexte, puis on apprend en une passe plusieurs prédictions décalées. Le cadre reste auto-régressif et correspond à la next-token prediction.
fenêtre 1 -> [t1, t2, t3] pour prédire t4 fenêtre 2 -> [t2, t3, t4] pour prédire t5 fenêtre 3 -> [t3, t4, t5] pour prédire t6
| Erreur fréquente | Pourquoi c'est incorrect |
|---|---|
| Confondre token et mot | Un token est souvent un sous-mot ou un signe de ponctuation selon le tokenizer. |
| Confondre tokenisation et embedding | La tokenisation produit des IDs discrets, l'embedding produit des vecteurs continus. |
| Confondre table d'embedding et matrice de séquence | \(E\) couvre tout le vocabulaire, \(X\) ne contient que les lignes sélectionnées pour la séquence. |
| Dire attention = choix du prochain token | L'attention contextualise ; logits, softmax et décodage réalisent le choix de sortie. |
| Dire top-p ou température modifient l'apprentissage | Ces réglages interviennent à l'inférence, pas dans les poids appris. |
Speech and Language Processing - Jurafsky, D., Martin, J. H. Manuel de référence pour NLP et LLM.
A Neural Probabilistic Language Model - Bengio et al., 2003. Représentations distribuées et généralisation.
Efficient Estimation of Word Representations in Vector Space - Mikolov et al., 2013.
Subword Units for Rare Words - Sennrich et al., 2016.
SentencePiece - Kudo, Richardson, 2018.
Finding Structure in Time - Elman, 1990.
Learning Long-Term Dependencies with Gradient Descent is Difficult - Bengio et al., 1994.
On the difficulty of training RNN - Pascanu et al., 2012.
Long Short-Term Memory - Hochreiter, Schmidhuber, 1997.
Attention Is All You Need - Vaswani et al., 2017.
The Curious Case of Neural Text Degeneration - Holtzman et al., 2019.
Documentation API OpenAI - Paramètres de sampling et manipulation des logits.