Aller au contenu principal

Module 2 — Tokenisation : mots, sous-mots, BPE, SentencePiece

Le texte nettoyé du module 1 est encore une chaîne de caractères. Aucun modèle ne consomme des caractères : tous voient une suite d'entiers, chacun désignant une entrée d'un vocabulaire fini. La tokenisation est le pont, et son choix pèse plus lourd sur les résultats qu'on ne l'imagine.

Découper par espaces marche jusqu'à ce que ça ne marche plus

La tokenisation la plus naïve sépare le texte aux espaces et à la ponctuation. Sur nos avis clients français, elle produit un vocabulaire immense — parce qu'une même racine engendre des dizaines de formes.

avis = "Nous avons adoré l'hôtel : chambres impeccables, hôtelières souriantes."
mots = avis.replace("'", "' ").split()
print(mots)
# ["Nous", "avons", "adoré", "l'", "hôtel", ":", "chambres", ...]

Sur cent mille avis, ce découpage produit couramment un vocabulaire de deux cent mille entrées. À l'inférence, chaque mot du texte à classer doit se retrouver dans ce vocabulaire ; sinon, il devient <UNK> — inconnu. Un mot inconnu, c'est un mot dont le modèle ne peut rien tirer, comme si un lecteur voyait un rectangle noir à sa place. « Hôteliers » a été vu 1 000 fois à l'entraînement ; « hôtelières » aussi ; « hôtelier » aussi ; le mot au singulier masculin qui apparaît dans un nouvel avis est cependant absent, et le lien avec les trois autres est perdu.

Le compromis fondamental : taille de vocabulaire contre mots inconnus

Un vocabulaire de mots pleins a deux ennemis :

  1. Il est grand. Chaque entrée coûte de la mémoire, chaque plongement associé pèse dans le modèle.
  2. Il rate les nouveaux mots. Néologismes, noms de marques, coquilles : tout ce qui n'était pas dans le corpus d'entraînement devient <UNK>.

Un vocabulaire de caractères (a, b, c, ç, é, …) supprime les mots inconnus mais rend les séquences très longues et diluées : le modèle doit apprendre lui-même à recomposer le sens du mot depuis ses caractères.

Les sous-mots cherchent l'équilibre. Ils gardent en un seul jeton les fragments fréquents (« hôtel »), coupent en morceaux les mots rares (« hôtelières » → hôtelier, ières) et se rabattent sur le caractère pour ce qui n'a jamais été vu. Le vocabulaire tient en trente ou soixante mille entrées, et le taux de <UNK> est nul.

BPE en dix passes sur vingt mots

L'algorithme Byte-Pair Encoding, sorti de la compression des années 1990 et réutilisé pour la traduction en 2016, se comprend en quelques passes. On part d'un vocabulaire de caractères et on fusionne, à chaque étape, la paire adjacente la plus fréquente dans le corpus.

Prenons un corpus minimal de trois mots répétés : « bas » (5 fois), « bat » (3 fois), « bar » (2 fois). Découpés en caractères avec un marqueur _ de fin de mot :

Étape 0 : b a s _ (5)   b a t _ (3)   b a r _ (2)
Étape 1 : paire la plus fréquente = (b, a), présente 10 fois → fusion en `ba`
Étape 2 : ba s _ (5) ba t _ (3) ba r _ (2) ; paire (s, _) fréquente 5 fois → `s_`
Étape 3 : ba s_ (5) ba t _ (3) ba r _ (2) ; paire (ba, s_) 5 fois → `bas_`

Trois fusions ont créé trois entrées de vocabulaire. Pour tokeniser un nouveau mot, on applique les fusions dans l'ordre appris. « basses » commence par bas_ puis coupe le reste en sous-mots. Un mot inédit tombe toujours, au pire, sur des caractères isolés.

WordPiece (BERT) est une variante qui choisit la fusion maximisant la vraisemblance du corpus, pas la simple fréquence. SentencePiece (utilisé par T5, mT5, LLaMA) traite le texte avant toute segmentation en mots — l'espace y est un caractère comme un autre, souvent noté . C'est le seul choix pratique pour les langues sans espaces (chinois, japonais) et pour les corpus multilingues.

from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace

tok = Tokenizer(BPE(unk_token="[UNK]"))
tok.pre_tokenizer = Whitespace()
tok.train(["corpus_avis.txt"],
trainer=BpeTrainer(vocab_size=8000,
special_tokens=["[UNK]", "[PAD]"]))
tok.save("bpe_avis.json")

sortie = tok.encode("L'hôtel était absolument impeccable")
print(sortie.tokens)

Le français coûte plus cher en jetons que l'anglais

C'est l'un des faits les plus contre-intuitifs du domaine, et il a des conséquences en euros. Un vocabulaire BPE appris majoritairement sur l'anglais découpe le français en jetons plus petits, donc plus nombreux. Le mot anglais « incredible » tient en un ou deux jetons ; « incroyablement » en cinq ou six.

Sur GPT-4, un même contenu occupe environ 1,5 à 2 fois plus de jetons en français qu'en anglais, et 3 à 4 fois plus en arabe. Or la facturation, la latence et la longueur de contexte se comptent en jetons. Le corollaire pratique : pour un service en français, un modèle multilingue avec un tokeniseur équilibré (mT5, XLM-RoBERTa) est souvent moins cher, à qualité comparable, qu'un modèle plus gros à tokeniseur anglocentré.

Le taux de jetons est un piège budgétaire

Estimer un coût sur un exemple anglais et facturer en français mène à des factures deux fois plus élevées que prévu. Toujours estimer sur un échantillon de la langue cible, avec le tokeniseur exact du modèle.

Aligner jetons et étiquettes, l'étape que tout le monde oublie

À partir du module 7 (entités nommées), les étiquettes portent sur des mots, pas sur des sous-mots. Un mot découpé en trois jetons doit être ré-agrégé, ou seul le premier sous-jeton doit porter l'étiquette. Les tokeniseurs de la bibliothèque transformers fournissent is_split_into_words=True et word_ids() précisément pour cela.

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("camembert-base")
mots = ["L'", "hôtelière", "était", "adorable"]
enc = tok(mots, is_split_into_words=True, return_tensors="pt")
print(enc.word_ids()) # [None, 0, 1, 1, 2, 3, None]

Chaque sous-jeton renvoie l'indice de son mot d'origine ; les jetons spéciaux (<s>, </s>) renvoient None. C'est cette table qui permet l'alignement du module 7.

En résumé

  • Les vocabulaires de mots pleins produisent trop de mots inconnus ; les vocabulaires de caractères produisent des séquences trop longues ; les sous-mots sont le compromis retenu partout.
  • BPE fusionne la paire la plus fréquente à chaque passe, WordPiece celle qui maximise la vraisemblance, SentencePiece traite l'espace comme un caractère et convient au multilingue.
  • Le français coûte 1,5 à 2 fois plus de jetons que l'anglais sur un tokeniseur anglocentré ; toujours estimer coût et latence sur la langue cible.
  • Un tokeniseur de sous-mots exige un alignement jeton-mot pour l'étiquetage ; word_ids() est le pont entre les deux niveaux.

Module suivant : à partir des jetons, deux représentations qui ignorent l'ordre mais suffisent souvent — le sac de mots et TF-IDF, la référence à battre.