Réponse courte
Entraînez un découpeur à codage par paires d’octets (BPE) : partez des 256 valeurs d’octets possibles, fusionnez sans cesse la paire voisine la plus fréquente en un nouveau jeton et arrêtez à la taille de vocabulaire choisie. Trente lignes de Python montrent l’idée, la bibliothèque tokenizers de Hugging Face le fait à vraie échelle, et le vôtre n’est nécessaire que si vous préentraînez à partir de zéro.
Question de suivi Devriez-vous entraîner votre propre découpeur ou en réutiliser un?
Un modèle de langage ne sait pas lire les lettres. Il lit des nombres, et le découpeur en jetons (tokenizer) est le petit programme qui convertit le texte en nombres, et l’inverse. Notre introduction aux jetons explique pourquoi les modèles lisent des morceaux de mots. Cet article porte sur la fabrication de ce programme, la première chose qu’on entraîne dans tout projet à partir de zéro, avant le modèle.
Contrairement à un modèle, un découpeur n’apprend aucun sens. Il apprend seulement quelles suites d’octets sont courantes dans votre texte. Son entraînement est donc rapide : celui de nanochat indique environ 34 secondes pour 2 milliards de caractères sur un MacBook Pro récent.
L’algorithme en mots simples
Le codage par paires d’octets (byte pair encoding, BPE) est la méthode derrière les découpeurs de GPT-2, de GPT-4, de Llama et de bien d’autres. Le texte est d’abord transformé en octets, les 256 valeurs de base que stocke un ordinateur. Ensuite :
- Comptez chaque paire de jetons voisins.
- Donnez un nouveau numéro à la paire la plus fréquente.
- Remplacez cette paire partout, puis recommencez.
Après des milliers de tours, les mots et morceaux de mots courants ont leur propre numéro, et les mots rares restent représentables, au pire octet par octet. Votre vocabulaire final compte les 256 octets, plus un jeton par fusion, plus les jetons spéciaux éventuels.
En bâtir un en trente lignes
Le projet éducatif minbpe de Karpathy est le meilleur code à lire ensuite. Cette version plus courte n’utilise que Python, sans rien à installer.
def pairs(ids):
counts = {}
for a, b in zip(ids, ids[1:]):
counts[(a, b)] = counts.get((a, b), 0) + 1
return counts
def merge(ids, pair, new_id):
out, i = [], 0
while i < len(ids):
if i < len(ids) - 1 and (ids[i], ids[i + 1]) == pair:
out.append(new_id)
i += 2
else:
out.append(ids[i])
i += 1
return out
text = "les élèves étudient; les élèves écrivent; les enseignantes étudient"
ids = list(text.encode("utf-8")) # partir des octets bruts : 256 jetons possibles
print(len(text), "caractères,", len(ids), "octets")
merges = {}
for step in range(8):
counts = pairs(ids)
best = max(counts, key=counts.get)
new_id = 256 + step
ids = merge(ids, best, new_id)
merges[best] = new_id
vocab = {i: bytes([i]) for i in range(256)}
for (a, b), new_id in merges.items():
vocab[new_id] = vocab[a] + vocab[b]
print(len(ids), "jetons après 8 fusions")
print(b"".join(vocab[i] for i in ids).decode("utf-8"))
print([vocab[i].decode("utf-8", "replace") for i in ids])
La sortie commence ainsi :
67 caractères, 74 octets
41 jetons après 8 fusions
les élèves étudient; les élèves écrivent; les enseignantes étudient
['les él', '�', '�', 'v', 'es é', 't', 'u', 'd', 'i', 'ent', ...
Il y a trois leçons ici. Les 7 octets de plus sont les accents, puisque « é » et « è » prennent chacun 2 octets en UTF-8, l’encodage de texte standard. La ligne reconstituée à partir des jetons correspond exactement à l’original, et c’est le test qu’un découpeur doit toujours réussir. Et les marques « � » montrent les deux octets de « è » dans des jetons séparés. C’est normal : un jeton peut finir au milieu d’un caractère, et le texte ne redevient entier que lorsque les octets sont rejoints. Imprimé seul, un jeton peut sembler brisé.
Remarquez aussi que « les él » est devenu un jeton, parce que ce tout petit texte le répète. Entraîné sur trop peu de texte, un découpeur mémorise des expressions. Utilisez un échantillon vaste et varié.
À vraie échelle
La version en Python ci-dessus serait beaucoup trop lente sur des milliards de caractères. La bibliothèque tokenizers de Hugging Face est écrite en Rust et les traite rapidement. Son guide de démarrage utilise une séparation aux espaces et un vocabulaire basé sur les caractères, ce qui change un émoji en jeton inconnu dans son propre exemple. Pour un modèle de langage, un montage au niveau des octets est plus sûr, puisque tout texte possible peut être représenté. Cette version demande pip install tokenizers et un fichier de texte à vous, ici corpus.txt :
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import ByteLevel
from tokenizers.decoders import ByteLevel as ByteLevelDecoder
tok = Tokenizer(BPE())
tok.pre_tokenizer = ByteLevel(add_prefix_space=False)
tok.decoder = ByteLevelDecoder()
trainer = BpeTrainer(
vocab_size=32768,
initial_alphabet=ByteLevel.alphabet(),
special_tokens=["<|endoftext|>", "<|user|>", "<|assistant|>"],
)
tok.train(["corpus.txt"], trainer)
tok.save("tokenizer.json")
sample = "Déjà, l’élève québécois écrit à l’hôpital."
out = tok.encode(sample)
print(out.tokens)
print(tok.decode(out.ids) == sample)
Les jetons imprimés semblent illisibles, par exemple é là où vous attendez é. C’est seulement la façon dont la bibliothèque affiche des octets bruts sous forme de texte. La dernière ligne devrait imprimer True.
Les jetons spéciaux marquent ce qui n’est pas du texte, comme la fin d’un document ou le début du tour de l’assistant. Ils s’ajoutent aux fusions. La documentation de minbpe met en garde contre le fait de transformer par défaut le texte saisi par l’utilisateur en jetons spéciaux, car quelqu’un pourrait alors simuler le tour de l’assistant. Encodez les entrées non fiables de façon que ces chaînes restent du texte ordinaire.
Choisir une taille de vocabulaire
| Découpeur | Vocabulaire |
|---|---|
| Notre jouet | 264 |
| nanochat | 32 768 |
| GPT-2 | 50 257 |
| Échelle de GPT-4, selon minbpe | environ 100 000 |
Un vocabulaire plus grand veut dire moins de jetons par phrase, donc plus de texte dans chaque lot d’entraînement. Il veut dire aussi une table plus grande à apprendre pour le modèle, et des jetons rares qu’il voit trop peu. Pour un petit modèle à partir de zéro, 32 768 entraîné sur environ 2 milliards de caractères est un point de départ éprouvé. Pour le français, incluez de vrai texte français : un vocabulaire entraîné sur l’anglais laisse les mots accentués en plusieurs petits morceaux, ce qui gruge votre contexte et votre budget.
À essayer : exécutez le script de trente lignes sur un paragraphe à vous, avec 8 fusions, puis avec 50. Imprimez les jetons chaque fois et repérez le moment où ils cessent de ressembler à des morceaux de mots pour ressembler à des expressions mémorisées.
Devriez-vous entraîner votre propre découpeur ou en réutiliser un?
Si vous faites un ajustement fin, réutilisez toujours. Les nombres d’un modèle à poids ouverts ont été appris pour son propre découpeur : le jeton 5 000 a pour lui un sens précis. Un autre découpeur brouille ses entrées. Il en va de même pour l’ajout de nombreux nouveaux jetons, qui devraient aussi être entraînés.
Si vous préentraînez à partir de zéro sur de l’anglais courant ou un mélange de langues communes, l’un ou l’autre choix se défend, puisque l’entraînement en prend quelques minutes et la réutilisation, aucune. Vérifiez la licence et testez l’aller-retour.
Entraînez le vôtre lorsque votre texte diffère de ce que les découpeurs existants ont vu : une langue peu présente en ligne, comme bien des langues autochtones, ou un domaine spécialisé comme le code ou la chimie. Comparez les candidats à l’aide d’un seul chiffre : le nombre de jetons que chacun exige pour un même échantillon de votre texte. Le script tok_eval de nanochat indique ce genre de taux de compression. Moins de jetons pour le même texte, c’est un entraînement moins cher et plus de place dans la fenêtre de contexte. Figez-le ensuite, car toutes les étapes suivantes dépendent de ce fichier précis.
Sources
- minbpe README — Andrej Karpathy (GitHub)
- Tokenization algorithms — Hugging Face
- Tokenizers quicktour — Hugging Face
Sources des articles liés dans le texte
Chaque article lié a sa propre page, avec ses sources et sa date.
- Qu’est-ce que GPT-2, et pourquoi OpenAI ne le publie-t-il pas en entier?
- Better Language Models and Their Implications — OpenAI, 14 février 2019
- Language Models are Unsupervised Multitask Learners — OpenAI (Radford, Wu, Child, Luan, Amodei, Sutskever), 14 février 2019
- Qu’est-ce que GPT-4, et que peut-il faire que GPT-3.5 ne peut pas faire?
- GPT-4 — OpenAI, 14 mars 2023
- Introducing ChatGPT Plus — OpenAI, 1 février 2023
Tiré du Guide de l’intelligence artificielle, par Robert Scales, publié par Holistic Hybrid inc. Partage et adaptation permis selon la licence CC BY-NC-SA 4.0 : citez le guide, mettez un lien vers https://guidetoartificialintelligence.com/fr/apprendre/construire-decoupeur-jetons-soi-meme/, pas d’usage commercial sans permission, et gardez la même licence.