Cours 3 sur 7 · AI COURSE

NLP

Le langage pour une machine : représenter le texte, modéliser des séquences, résoudre des tâches (classification, étiquetage, recherche) et les évaluer, y compris en plusieurs langues.

10 chapitres14 blocs5 nouveaux
Un chapitre, plusieurs niveauxChaque chapitre empile les blocs du même sujet, du plus accessible au plus rigoureux.
BasesIntuition, calcul détaillé, premier TP.
Orienté LLMLe même sujet vu depuis les grands modèles.
RigueurÉnoncés, preuves, niveau master.
NouveauBlocs écrits pour combler ce cours.
Le Carnet décideLe planning indique, séance par séance, quels blocs lire.

Partie 1

Le texte comme donnée

Transformer des mots en vecteurs, du sac de mots aux embeddings appris.

Chapitre 1

Du sac de mots à word2vec

1 bloc · Nouveau

1.1Nouveaunouveau
1.1

Du sac de mots à word2vec

Prérequis : MATH 2.1 (similarité cosinus), DL 5.1 (régression logistique), MATH 13.1 (entropie croisée) · Notions : sac de mots, n-grammes, TF-IDF, hypothèse distributionnelle, matrice de cooccurrence, word2vec (skip-gram, CBOW), échantillonnage négatif, analogies, GloVe, limites des embeddings statiques

Cours
Le sac de mots

Avant les réseaux, on représentait un document par le sac de mots (bag of words) : un vecteur de la taille du vocabulaire V, où la case w compte les occurrences du mot w. L'ordre est perdu : « le chien mord l'homme » et « l'homme mord le chien » ont le même vecteur. On récupère un peu d'ordre avec les n-grammes (paires, triplets de mots consécutifs), au prix d'un vocabulaire qui explose.

TF-IDF : pondérer par la rareté

Les mots fréquents partout (« le », « de ») dominent les comptes sans rien dire du sujet. TF-IDF (term frequency – inverse document frequency) les écrase :

tfidf(w,d)=nw,d∑w′nw′,d⏟fréquence dans d×ln⁡Ndf(w)⏟rareté

où N est le nombre de documents et df(w) le nombre de documents contenant w. Un mot présent dans tous les documents a un idf nul. On compare ensuite les documents par similarité cosinus. TF-IDF reste une base de référence redoutable pour la recherche documentaire ; son cousin BM25 est encore au cœur de nombreux systèmes RAG en « recherche hybride » (NLP 9.1).

Limite fondamentale : « voiture » et « automobile » sont deux cases différentes, orthogonales. Le sac de mots ne sait rien du sens.

L'hypothèse distributionnelle

« On connaît un mot à ses fréquentations » (J. R. Firth, 1957). Deux mots qui apparaissent dans les mêmes contextes ont un sens proche : « guitare » et « basse » apparaissent tous deux après « joue de la ». On peut donc apprendre un vecteur dense par mot (dimension 100 à 300) tel que les mots de contextes similaires aient des vecteurs proches : un embedding de mot.

word2vec, skip-gram

Mikolov et al. (2013). On parcourt le texte ; pour chaque mot central t et chaque mot c de sa fenêtre (par exemple ±2), on veut prédire c à partir de t. Chaque mot a deux vecteurs : wt (comme centre) et cc (comme contexte). La version naïve fait une softmax sur tout le vocabulaire, p(c∣t)=exp⁡(cc⊤wt)∑vexp⁡(cv⊤wt), trop chère quand V=106.

L'échantillonnage négatif (negative sampling, SGNS) remplace la softmax par k+1 classifications binaires : la vraie paire (t,c) doit avoir un score élevé, et k mots tirés au hasard (les négatifs) un score faible :

ℓ(t,c)=−ln⁡σ(cc⊤wt)−∑j=1kln⁡σ(−cnj⊤wt),nj∼Pn(w)∝freq(w)3/4

C'est une régression logistique (DL 5.1) dont on apprend aussi les entrées. Le gradient sur chaque score est « prédiction moins cible », σ(s)−y. L'exposant 3/4 sur-échantillonne un peu les mots rares. CBOW fait l'inverse : prédire le mot central à partir de la moyenne de son contexte.

Levy et Goldberg (2014) ont montré que SGNS factorise implicitement une matrice de cooccurrence décalée (PMI − ln k) : les deux familles, comptage et prédiction, apprennent au fond la même chose. GloVe (2014) factorise explicitement le logarithme des cooccurrences.

Analogies et limites

Les embeddings appris capturent des régularités linéaires : vroi−vhomme+vfemme≈vreine. Ils capturent aussi les biais du corpus (associations métier–genre), ce qui a lancé la recherche sur l'équité des représentations (PONT 19.1).

Limite qui a mené aux Transformers : un mot a un seul vecteur, quel que soit le contexte. « La batterie du téléphone » et « la batterie du groupe » partagent le même embedding. Les modèles contextuels (ELMo 2018, puis BERT, NLP 6.1) produisent un vecteur par occurrence, qui dépend de la phrase entière.

Schéma du coursSkip-gram avec échantillonnage négatif : la vraie paire (vert) est rapprochée, les négatifs tirés au hasard (orange) sont éloignés.
« le musicien joue de la guitare sur scène » w(guitare) c(la) c(sur) c(four) c(bourse) σ(c·w) → 1 σ(c·w) → 0 (k négatifs)
Mon schémaLe plan de table d'un mariage : ceux qui ont les mêmes amis finissent assis côte à côte, sans qu'on ait jamais décrit qui ils sont.
guitare basse batterie tomates carottes pommes contexte : « joue de la … » contexte : « fait cuire les … »
Exemple du cours

Quatre documents : (1) « le chat dort sur le canapé », (2) « le chien dort sur le tapis », (3) « la bourse chute et le dollar monte », (4) « le dollar chute à la bourse ». « le » figure dans les quatre : idf =ln⁡(4/4)=0, il disparaît. « dort » et « sur » figurent dans 2 documents : idf =ln⁡2≈0,69. « chat » dans un seul : ln⁡4≈1,39. Après normalisation, la similarité cosinus vaut ≈ 0,20 entre (1) et (2) (ils partagent « dort sur »), ≈ 0,41 entre (3) et (4), et 0 entre les deux groupes. Mais « chat » et « chien » restent orthogonaux : TF-IDF ne voit pas qu'ils se ressemblent.

Exemple inventé

Les mots d'un corpus de l'OIF. Sur des rapports de la Francophonie, un word2vec placera « coopération », « partenariat » et « appui » côte à côte, et « Sénégal », « Côte d'Ivoire », « Bénin » dans un même amas. Mais « Conseil » (le Conseil permanent, le conseil municipal, un conseil pratique) n'aura qu'un vecteur, moyenne de tous ses sens. Pour un moteur de recherche thématique, c'est rédhibitoire : il faudra des embeddings contextuels de phrases (NLP 9.1), et un modèle multilingue si le corpus mélange français, anglais et arabe (NLP 10.1).

TP · TF-IDF puis word2vec en NumPy
  1. Calcule la matrice TF-IDF des quatre documents de l'exemple et vérifie les similarités.
  2. Implémente skip-gram avec échantillonnage négatif sur un corpus jouet qui mélange deux thèmes (musique, cuisine) avec la même syntaxe.
  3. Vérifie que « guitare » est plus proche de « basse » que de « tomates », et affiche les plus proches voisins.
Solution commentée
import numpy as np, math
docs = ["le chat dort sur le canapé", "le chien dort sur le tapis",
        "la bourse chute et le dollar monte", "le dollar chute à la bourse"]
toks = [d.split() for d in docs]
vocab = sorted({w for t in toks for w in t}); idx = {w: i for i, w in enumerate(vocab)}
tf = np.zeros((len(docs), len(vocab)))
for i, t in enumerate(toks):
    for w in t: tf[i, idx[w]] += 1
tf /= tf.sum(1, keepdims=True)                         # fréquence relative dans le document
df = (tf > 0).sum(0)                                   # nombre de documents contenant le mot
idf = np.log(len(docs) / df)                            # « le » apparaît partout : idf = 0
X = tf * idf
X /= np.linalg.norm(X, axis=1, keepdims=True)
S = X @ X.T
print(np.round(S, 2))
assert idf[idx["le"]] == 0
assert S[0, 1] > S[0, 2] and S[2, 3] > S[1, 3]       # chat~chien, finance~finance
import numpy as np, re, collections
rng = np.random.default_rng(0)
# corpus jouet : deux « mondes » (musique, cuisine) qui partagent la syntaxe
gabarits = ["le {a} joue de la {i} sur scène", "la {a} accorde sa {i} avant le concert",
            "le {c} coupe les {l} dans la cuisine", "la {c} fait cuire les {l} au four"]
mots = {"a": ["musicien", "pianiste", "guitariste"], "i": ["guitare", "basse", "batterie"],
        "c": ["cuisinier", "chef", "pâtissier"], "l": ["carottes", "pommes", "tomates"]}
phrases = []
for _ in range(3000):
    g = gabarits[rng.integers(4)]
    phrases.append(g.format(**{k: v[rng.integers(3)] for k, v in mots.items()}).split())
vocab = sorted({w for p in phrases for w in p}); idx = {w: i for i, w in enumerate(vocab)}; V = len(vocab)
freq = np.bincount([idx[w] for p in phrases for w in p], minlength=V).astype(float)
pneg = freq ** 0.75; pneg /= pneg.sum()                  # loi de bruit unigramme^0.75
paires = [(idx[p[t]], idx[p[c]]) for p in phrases for t in range(len(p))
          for c in range(max(0, t - 2), min(len(p), t + 3)) if c != t]
paires = np.array(paires)
D, k, lr = 16, 5, 0.05
W = rng.normal(0, 0.1, (V, D)); C = np.zeros((V, D))      # embeddings centre / contexte
sig = lambda x: 1 / (1 + np.exp(-x))
for ep in range(3):
    rng.shuffle(paires)
    for t, c in paires:
        negs = rng.choice(V, k, p=pneg)
        ctx = np.concatenate([[c], negs]); lab = np.r_[1.0, np.zeros(k)]
        s = sig(C[ctx] @ W[t])                             # (k+1,)
        g = s - lab                                        # gradient de la BCE sur les scores
        gw = g @ C[ctx]
        C[ctx] -= lr * np.outer(g, W[t]); W[t] -= lr * gw
E = W / np.linalg.norm(W, axis=1, keepdims=True)
def voisins(w, n=3):
    s = E @ E[idx[w]]; o = np.argsort(-s)
    return [vocab[i] for i in o if vocab[i] != w][:n]
for w in ["guitare", "chef", "pommes"]: print(w, "→", voisins(w))
def cos(a, b): return float(E[idx[a]] @ E[idx[b]])
print("guitare~basse", round(cos("guitare", "basse"), 2), " guitare~tomates", round(cos("guitare", "tomates"), 2))
assert cos("guitare", "basse") > cos("guitare", "tomates") + 0.3

En 3 époques (une dizaine de secondes), les instruments se regroupent entre eux, les aliments aussi. Les deux thèmes ont exactement la même syntaxe : seul le contexte de cooccurrence les sépare, c'est l'hypothèse distributionnelle à l'œuvre. Essaie une fenêtre de ±1 au lieu de ±2 et regarde ce qui change.

À retenir

  • Sac de mots et TF-IDF : simples, efficaces pour la recherche, aveugles au sens.
  • Hypothèse distributionnelle : le sens d'un mot vient de ses contextes.
  • Skip-gram + échantillonnage négatif = régression logistique sur des paires (mot, contexte), dont on apprend les vecteurs.
  • Un vecteur par mot, pas par occurrence : la limite que lèvent les modèles contextuels.

Chapitre 2

Tokenisation et BPE

2 blocs · Bases → Orienté LLM

2.1Basesex-DLZ 32
2.1

Tokenisation et embeddings

Prérequis : blocs MATH 2.1, MATH 10.1 et DL 5.2 · Notions : jeton (token), découpage par caractères, mots et sous-mots, BPE, vocabulaire, BPE sur octets, jetons spéciaux, embedding comme table de correspondance, équivalence one-hot × matrice, géométrie des embeddings, partage des poids d'entrée et de sortie

Cours
Du texte aux nombres

Un réseau ne lit que des nombres. Il faut découper le texte en unités, les jetons (tokens), et associer à chacun un entier, son identifiant dans un vocabulaire de taille V.

  • Caractères : vocabulaire minuscule (une centaine), mais séquences très longues et chaque unité porte peu de sens.
  • Mots : séquences courtes, mais vocabulaire gigantesque, et tout mot inconnu (« chatonnerie », une faute de frappe) devient un jeton <inconnu>.
  • Sous-mots : le compromis utilisé par tous les LLM. Les mots fréquents sont un seul jeton (« le », « chat ») ; les mots rares sont découpés en morceaux (« chat|onn|erie »). Plus aucun mot n'est inconnu.
L'algorithme BPE (Byte Pair Encoding)
  1. Partir d'un vocabulaire de symboles de base (caractères, ou les 256 octets possibles en BPE « sur octets », qui couvre n'importe quel texte, emoji et code compris).
  2. Compter toutes les paires de symboles adjacents dans le corpus d'entraînement.
  3. Fusionner la paire la plus fréquente en un nouveau symbole et l'ajouter au vocabulaire.
  4. Recommencer jusqu'à la taille de vocabulaire voulue (32 000 pour Llama 2, environ 100 000 pour GPT-4, 128 000 pour Llama 3, 200 000 pour GPT-4o).

Pour encoder un nouveau texte, on applique les fusions apprises dans le même ordre. Des variantes existent (WordPiece pour BERT, Unigram/SentencePiece), même principe. On ajoute des jetons spéciaux : début et fin de séquence, remplissage, et pour les assistants des marqueurs de rôle (utilisateur, assistant).

Conséquences pratiques : un jeton ≈ 0,75 mot anglais, un peu moins en français ; les langues peu présentes dans le corpus du tokeniseur coûtent plus de jetons ; le modèle ne « voit » pas les lettres d'un mot tokenisé d'un bloc, d'où ses difficultés à compter les « r » de « strawberry ».

Les embeddings

Un identifiant entier n'a pas de géométrie (le jeton 512 n'est pas « proche » du 513). On associe à chaque jeton un vecteur appris de dimension D, rangé dans une matrice E∈RV×D. L'embedding du jeton i est simplement la ligne i :

emb(i)=Ei,:=ei⊤E(ei=vecteur one-hot)

Une couche d'embedding est donc une couche linéaire appliquée à un one-hot, mais implémentée comme une lecture de table (beaucoup plus rapide). Ses gradients ne touchent que les lignes des jetons présents dans le lot.

Géométrie apprise : à l'entraînement, des jetons qui apparaissent dans des contextes similaires obtiennent des vecteurs proches (similarité cosinus, bloc MATH 2.1). Des directions prennent un sens : l'exemple célèbre de word2vec, roi→−homme→+femme→≈reine→.

Partage des poids : la dernière couche d'un modèle de langage projette l'état caché de dimension D vers V logits, avec une matrice D×V. On peut réutiliser E⊤ (weight tying) : on économise V⋅D paramètres (GPT-2 le fait).

Fin de l'aperçu

Débloquer la suite