Dans le monde de l'intelligence artificielle et des grands modèles de langage (LLMs) comme GPT-4, Llama ou Claude, on entend constamment parler de tokens. Mais qu'est-ce qu'un token exactement ? Comment passe-t-on d'un texte brut lisible par un humain à une suite de nombres exploitables par un réseau de neurones ?
Cet article vous explique en détail le principe de la tokenisation, comment elle fonctionne à partir de zéro, et vous propose une implémentation simple en C# basée sur l'algorithme standard BPE (Byte Pair Encoding).
1. Qu'est-ce qu'un Token et pourquoi en a-t-on besoin ?
Un modèle d'IA ne comprend pas le texte brut. Il ne manipule que des nombres (des vecteurs). La tokenisation est la première étape du pipeline de traitement du langage naturel (NLP). Elle consiste à découper un texte en morceaux appelés tokens, puis à associer chaque token à un identifiant numérique unique (un Token ID).
Les différentes approches de découpage :
- Par caractères : Découpage lettre par lettre.
- Avantage : Vocabulaire très petit (quelques dizaines/centaines de caractères). Pas de mots inconnus.
- Inconvénient : Les séquences deviennent extrêmement longues, ce qui sature la mémoire contextuelle de l'IA.
- Par mots : Découpage au niveau des espaces et de la ponctuation.
- Avantage : Séquences courtes et porteuses de sens direct.
- Inconvénient : Vocabulaire gigantesque (des millions de mots). Impossible de gérer les fautes d'orthographe, les néologismes ou les déclinaisons sans générer des tokens inconnus (
<UNK>).
- Par sous-mots (Subwords) : Le juste milieu utilisé par toutes les IA modernes (BPE, WordPiece, Unigram).
- Les mots fréquents restent entiers (ex:
chat), tandis que les mots rares ou complexes sont découpés en sous-unités fréquentes (ex:anticonstitutionnellement\(\rightarrow\)anti+constitutionnel+lement).
- Les mots fréquents restent entiers (ex:
2. Le pipeline global de la Tokenisation
Voici comment s'intègre le tokenizer dans l'architecture générale d'une IA :
flowchart TD
A[Texte Brut: 'Le chat mange'] --> B[Normalisation / Pré-segmentation]
B --> C[Tokenizer : Découpage en sous-mots]
C --> D[Mapping : Conversion en Token IDs]
D --> E["Embedding (Vecteurs de nombres réels)"]
E --> F[Modèle IA / Transformeur]
subgraph Vocabulaire [Table de correspondance / Dictionnaire]
V1["'Le' -> 102"]
V2["' chat' -> 4325"]
V3["' mange' -> 891"]
end
D -.-> Vocabulaire3. L'algorithme Byte Pair Encoding (BPE)
Le Byte Pair Encoding (BPE) est l'algorithme de tokenisation le plus populaire. Il commence au niveau le plus bas (les caractères ou octets individuels) et fusionne itérativement les paires de caractères les plus fréquentes pour construire des tokens de plus en plus grands.
Les étapes de l'apprentissage (Training) :
- Définir une taille de vocabulaire cible (ex: 50 000 tokens).
- Initialiser le vocabulaire avec tous les caractères de base (l'alphabet, la ponctuation, les chiffres).
- Découper le corpus de texte en caractères individuels.
- Compter toutes les paires adjacentes de tokens dans le corpus.
- Fusionner la paire la plus fréquente pour créer un nouveau token et l'ajouter au vocabulaire.
- Répéter les étapes 4 et 5 jusqu'à atteindre la taille de vocabulaire cible.
Schéma du processus d'apprentissage BPE :
stateDiagram-v2
[*] --> InitVocab : Initialiser le vocabulaire avec les caractères individuels
InitVocab --> TokeniserCaractere : Diviser le texte d'entraînement en lettres/symboles
state BoucleTraining {
TokeniserCaractere --> CompterPaires : Compter la frequence de chaque paire adjacente
CompterPaires --> TrouverMax : Identifier la paire la plus frequente comme e et s
TrouverMax --> FusionnerPaire : Remplacer la paire partout par le nouveau token fusionne
FusionnerPaire --> AjouterVocab : Ajouter le nouveau token au Vocabulaire
}
AjouterVocab --> VerifierTaille : Vocabulaire cible atteint ?
VerifierTaille --> CompterPaires : Non
VerifierTaille --> [*] : Oui apprentissage termine4. Implémentation en C# d'un BPE Tokenizer Simple
Voici un exemple d'implémentation propre, simple et éducatif en C# pour comprendre comment fonctionne l'apprentissage (Training) et l'encodage (Encoding) avec BPE.
using System;
using System.Collections.Generic;
using System.Linq;
namespace SimpleBpeTokenizer
{
public class BpeTokenizer
{
// Le vocabulaire associant un Token (chaîne) à son ID (entier)
private readonly Dictionary<string, int> _vocab = new();
// L'inverse pour le décodage rapide
private readonly Dictionary<int, string> _inverseVocab = new();
// Historique des fusions enregistrées (paire -> token fusionné)
private readonly Dictionary<(string, string), string> _merges = new();
private int _nextId = 0;
public IReadOnlyDictionary<string, int> Vocab => _vocab;
/// <summary>
/// Entraîne le tokenizer sur un corpus de texte pour atteindre une taille de vocabulaire cible.
/// </summary>
public void Train(string corpus, int targetVocabSize)
{
_vocab.Clear();
_inverseVocab.Clear();
_merges.Clear();
_nextId = 0;
// 1. Initialisation : Ajouter tous les caractères uniques du corpus au vocabulaire de base
var uniqueChars = corpus.Select(c => c.ToString()).Distinct().ToList();
foreach (var ch in uniqueChars)
{
AddToken(ch);
}
// Représentation du texte sous forme de liste de tokens modifiables au fil des fusions
// Exemple : "chat" -> [ "c", "h", "a", "t" ]
List<string> words = corpus.Select(c => c.ToString()).ToList();
// 2. Boucle de fusion itérative
while (_vocab.Count < targetVocabSize)
{
var pairs = GetPairFrequencies(words);
if (!pairs.Any())
{
break; // Plus aucune paire à fusionner
}
// Trouver la paire la plus fréquente
var bestPair = pairs.OrderByDescending(p => p.Value).First().Key;
string newSymbol = bestPair.Item1 + bestPair.Item2;
// Enregistrer la fusion et l'ajouter au vocabulaire
_merges[bestPair] = newSymbol;
AddToken(newSymbol);
// Appliquer la fusion dans notre liste de travail
words = MergePair(words, bestPair, newSymbol);
}
}
/// <summary>
/// Encode un nouveau texte en une liste d'IDs de tokens en appliquant les règles apprises.
/// </summary>
public List<int> Encode(string text)
{
if (string.IsNullOrEmpty(text))
{
return new List<int>();
}
// Commencer par découper en caractères individuels
var words = text.Select(c => c.ToString()).ToList();
// Appliquer successivement toutes les règles de fusion apprises dans l'ordre d'apprentissage
foreach (var mergeRule in _merges)
{
var pair = mergeRule.Key;
var newSymbol = mergeRule.Value;
words = MergePair(words, pair, newSymbol);
}
// Convertir les chaînes finales en IDs
return words.Select(w => _vocab.TryGetValue(w, out var id) ? id : -1).ToList();
}
/// <summary>
/// Décode une liste d'IDs de tokens pour reconstruire le texte original.
/// </summary>
public string Decode(List<int> ids)
{
var parts = ids.Select(id => _inverseVocab.TryGetValue(id, out var token) ? token : string.Empty);
return string.Concat(parts);
}
private void AddToken(string token)
{
if (!_vocab.ContainsKey(token))
{
_vocab[token] = _nextId;
_inverseVocab[_nextId] = token;
_nextId++;
}
}
private Dictionary<(string, string), int> GetPairFrequencies(List<string> words)
{
var pairs = new Dictionary<(string, string), int>();
for (int i = 0; i < words.Count - 1; i++)
{
var pair = (words[i], words[i + 1]);
if (pairs.ContainsKey(pair))
{
pairs[pair]++;
}
else
{
pairs[pair] = 1;
}
}
return pairs;
}
private List<string> MergePair(List<string> words, (string, string) pair, string newSymbol)
{
var merged = new List<string>();
int i = 0;
while (i < words.Count)
{
if (i < words.Count - 1 && words[i] == pair.Item1 && words[i + 1] == pair.Item2)
{
merged.Add(newSymbol);
i += 2; // Sauter la paire fusionnée
}
else
{
merged.Add(words[i]);
i++;
}
}
return merged;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("--- BPE Tokenizer éducatif ---");
var corpus = "le chat mange. un autre chat dort. le petit chat joue.";
var tokenizer = new BpeTokenizer();
// Entraînement du tokenizer avec une petite cible de vocabulaire pour l'exemple
Console.WriteLine("Entraînement en cours...");
tokenizer.Train(corpus, targetVocabSize: 50);
Console.WriteLine($"Taille finale du vocabulaire : {tokenizer.Vocab.Count}");
// Test de l'encodage et décodage
var phraseTest = "le chat joue.";
var encoded = tokenizer.Encode(phraseTest);
var decoded = tokenizer.Decode(encoded);
Console.WriteLine($"\nTexte original : '{phraseTest}'");
Console.WriteLine($"Tokens IDs : [{string.Join(", ", encoded)}]");
Console.WriteLine($"Texte décodé : '{decoded}'");
Console.WriteLine("\nQuelques tokens du vocabulaire :");
foreach (var token in tokenizer.Vocab.Take(15))
{
Console.WriteLine($"ID {token.Value} -> '{token.Key}'");
}
}
}
}
5. Comment optimiser l'implémentation pour la production ?
L'implémentation C# ci-dessus est optimisée pour la clarté et la compréhension du concept. En production, les tokenizers comme tiktoken (utilisé par OpenAI) ou Hugging Face Tokenizers appliquent plusieurs optimisations :
- Pré-tokenisation : Séparer le texte par des règles d'expressions régulières (Regex) complexes pour éviter que des fusions incongrues se fassent à cheval sur de la ponctuation ou des espaces importants.
- Byte-Level BPE : Au lieu de travailler sur des caractères Unicode, travailler directement sur les octets bruts (UTF-8 bytes). Cela garantit qu'aucun caractère inconnu ne puisse exister dans le monde, car n'importe quel caractère Unicode est une suite d'octets de 0 à 255.
- Structures de données avancées : Utilisation d'arbres de recherche (Tries) ou de files de priorité (Priority Queues/Heaps) pour identifier et fusionner très rapidement les paires les plus fréquentes sans reparcourir continuellement toute la liste de mots.
Conclusion
Générer des tokens à partir de zéro n'est pas magique : c'est un processus statistique itératif qui trouve des motifs de caractères récurrents dans la langue pour optimiser la transmission d'informations. Grâce à BPE, les LLMs réduisent la longueur de leur contexte d'apprentissage tout en maintenant une flexibilité totale face à n'importe quel mot de la langue française ou anglaise.
Aucun commentaire publié pour le moment.
Ajouter un commentaire