Le NLP de ses débuts à maintenant : la Tokenization

Après ces premières semaines de rentrée, je vous souhaite la bienvenue dans ce troisième article de notre série sur le traitement du langage naturel après avoir vu les n-grams et les embeddings. Aujourd’hui nous allons traiter un des aspects les plus sous estimés dans la conception d’un modèle de langage : la tokenization.
Pour le moment à chaque fois que l’on a utilisé le terme token on s’est limité à des cas très simples où les tokens sont des lettres ou des mots. Malheureusement pour nous, une bonne tokenization nécessite d’être un peu plus subtile. Nous reviendront là dessus par la suite.


Les objectifs de cet article sont de comprendre :

  • les enjeux de la tokenization
  • les éléments de conception de base d’un tokenizer en particulier appréhender l’équilibre délicat entre la longueur des tokens et la taille du vocabulaire associé, sachant que, suite à la tokenization, le modèle associe à chaque token un embedding correspondant (une bonne occasion de relire notre article sur les embeddings !)

Complexité de la Tokenization

Granularité

La tokenization constitue la pierre angulaire de la préparation des données dans le traitement du langage naturel (NLP). Son rôle est de décomposer les chaînes de caractères en unités plus petites, appelées tokens, pour permettre leur traitement par un modèle de langage. Cette opération soulève la question essentielle : quel est le niveau optimal de décomposition ? Doit-on se baser sur les caractères individuels, les mots complets, les bouts de mots, les phrases ?

Faisons un petit exercice. Imaginons que l’on prenne des caractères comme tokens. Alors on aurait un vocabulaire relativement petit ce qui est plutôt un avantage. Par contre on heurterait très vite à plusieurs problèmes :

  • Longueur des séquences : les modèles traitant les caractères comme tokens génèrent des séquences beaucoup plus longues que ceux utilisant des mots ou des sous-mots, or la longueur de la séquence impacte directement la complexité computationnelle. Traiter de longues séquences peut limiter la capacité des modèles à traiter efficacement de grands contextes.
  • Richesse sémantique : les caractères individuels portent généralement moins d’informations sémantiques que les mots ou les sous-mots. Par conséquent, un modèle basé sur des caractères doit apprendre des associations et des structures linguistiques complexes à partir de composants de base relativement dénués de sens. Cela demanderait notamment d’avoir un embedding de tokens très puissant afin d’insuffler à ces tokens le sens qu’ils ne porteraient pas par eux-même.
  • Défis de contextualisation : les grands modèles de langage (LLM) excellent en assimilant le sens à partir de larges contextes. Lorsque les tokens sont des caractères, le modèle doit traiter beaucoup plus d’informations intermédiaires pour extraire le même contexte sémantique, ce qui peut diluer l’attention du modèle sur les détails fins nécessaires pour une compréhension précise ou la génération de texte naturel.

A l’opposé, que se passerait-il si les tokens étaient des mots. L’avantage évident serait que les tokens seraient naturellement porteur de sens au niveau de granularité auquel nous utilisons le langage. Malheureusement nous serions aussi confrontés à un certain nombre de problèmes :

  • Vocabulaire étendu : l’un des principaux défis de l’utilisation de mots comme tokens réside dans la taille potentiellement immense du vocabulaire. Toutes les conjugaisons et variations des mots d’une langue, sans parler des néologismes, argots, noms propres, et jargons techniques. Tout ça crée un vocabulaire immense et peu maniable.
  • Flexibilité Linguistique : un modèle dont le tokenizer serait au niveau du mot manquerait de flexibilité pour traiter des langues très différentes. Certaines langues, comme le chinois, utilisent des caractères qui représentent des concepts entiers ou des parties de mots. D’autres, comme l’allemand, peuvent former des mots extrêmement longs par composition.
  • Réutilisation des connaissances : l’emploi de mots complets comme token compromet la généralisation en limitant les capacités à réutiliser la connaissance acquise d’une partie du langage (comme une racine ou une préfixe) dans des contextes variés. Cela bride donc son efficience et sa capacité d’adaptation.

On comprend bien à la lumière de ces deux exemples extrêmes que la granularité optimale pour un modèle de langage se trouve quelque part entre le caractère et le mot complet, l’objectif étant d’obtenir un bon compromis entre taille du vocabulaire et efficacité de la représentation.

Mécanismes fondamentaux de la Tokenization

Afin de concevoir un tokenizer, il faut pouvoir prendre du texte en entrée et le découper d’une certaine manière. Il faut de plus pouvoir prendre en entrée une très grande variété de caractères : les lettres de toutes les langues, les chiffres ou encore les caractères spéciaux… Heureusement pour nous une partie du travail existe déjà au travers de la norme Unicode qui définit un code unique pour chaque caractère (environ 150 000 caractères représentés). La représentation la plus efficace et la plus répandue des encodages Unicode est UTF-8.
On a maintenant une manière de prendre du texte en entrée sous forme d’octets, il ne nous reste plus qu’à le découper. Pour cela nous allons utiliser le Byte Pair Encoding.

Byte Pair Encoding algorithm

Quand on veut découper du texte, une idée naturelle est de repérer les séquences de caractères fréquentes (syllabes, morceaux de mots…). C’est exactement ce que fait BPE. Le Byte Pair Encoding (BPE) est une technique de tokenization utilisée en traitement du langage naturel (NLP) qui vise à encoder efficacement un texte en identifiant et en fusionnant les paires de caractères (ou de bytes) les plus fréquemment observées dans un corpus. À l’origine, BPE a été conçu comme une méthode de compression de données, mais il a trouvé une nouvelle utilité dans le domaine du NLP, en particulier pour la préparation des données en vue de leur traitement par des modèles de langage.

L’algorithme de BPE commence par diviser le texte en caractères individuels et traite chaque caractère comme un token initial. Il procède ensuite en comptant la fréquence de toutes les paires adjacentes de tokens dans le corpus. À chaque itération, la paire la plus fréquente est fusionnée pour former un nouveau token. Ce processus itératif se poursuit selon un nombre prédéfini d’étapes ou jusqu’à ce qu’un critère spécifique soit atteint, comme la taille désirée du vocabulaire.

L’un des principaux avantages du Byte Pair Encoding est sa capacité à équilibrer efficacement la granularité des mots et celle des caractères, permettant un niveau de décomposition qui capture des morphèmes ou des sous-unités de mots significatives. Par exemple, dans le mot « incompréhensible », BPE peut identifier des sous-unités fréquentes comme « in », « compr », et « ensible » comme des tokens distincts, facilitant ainsi le traitement du mot en préservant une partie de sa structure sémantique sans nécessiter un token unique pour le mot entier.

En conclusion, le Byte Pair Encoding est devenu une composante essentielle des pipelines de traitement de texte moderne en NLP, s’adaptant particulièrement bien aux besoins des modèles de langage en raison de sa flexibilité, de son efficacité, et de sa capacité à traiter de manière dynamique un large éventail de vocabulaire.

Décodage et Encodage

La tokenisation ne se limite pas à la simple décomposition du texte en tokens. Il est également crucial de pouvoir décoder ces tokens, c’est-à-dire de reconstruire le texte original à partir des tokens. Cette opération de décodage inclut des subtilités importantes, comme la gestion des fusions de tokens lors de l’utilisation de la technique BPE (Byte Pair Encoding). En effet, il peut être nécessaire de reconnaître et de réutiliser des fusions réalisées au cours des itérations précédentes.

Une autre subtilité importante est la non-bijectivité de l’encodage. Cela signifie que les tokens décodés peuvent ne pas correspondre exactement à l’encodage UTF-8 d’origine, notamment parce que l’UTF-8 est un encodage à taille variable. Toutefois, cette non-bijectivité ne pose pas de problème dans l’immense majorité des cas, car les divergences sont généralement mineures et ne compromettent pas la compréhension ou l’intégrité du texte reconstruit.

Quelques précisions sur le BPE

Enfin, il est important de noter que la sortie du Byte Pair Encoding est la liste des fusions de paires occurrentes en nouveaux tokens qui ont eu lieu au cours de l’entraînement dans leur ordre d’occurrence. Il est aussi intéressant de noter que de part sa nature itérative l’algorithme peut utiliser un token produit lors d’une itération précédente pour une nouvelle fusion. Par conséquent la sortie de l’algorithme est en fait une forêt de tokens. Si l’on appliquait l’algorithme sans condition d’arrêt la sortie serait un arbre avec à sa racine un unique token encodant l’ensemble du texte et à ses feuilles les tokens initiaux avant augmentation de la taille du vocabulaire.

Nous allons implémenter ici une version simple de cette technique pour mieux illustrer son fonctionnement.

Implémentation

Byte Pair Encoding algorithm

Le Byte Pair Encoding est un algorithme assez simple à implémenter qui comporte 2 étapes principales :

  • Comptage des paires de tokens et identification de la paire la plus fréquente
  • Remplacement de la paire la plus fréquente par un nouveau token

Comptage des tokens

Cette fonction nous retourne un dictionnaire de la forme :

Dans cet exemple de sortie, chaque tuple représente une paire et la valeur associée représente l’occurrence de cette paire.

Encodage de la paire la plus fréquente

Les commentaires dans le code décrivent la mécanique précise de l’implémentation. D’un point de vue haut niveau cette opération consiste à remplacer la paire la plus fréquente par un nouveau token.

Algorithme complet

L’algorithme consiste à itérativement compresser le texte avec de nouveaux tokens à la place des paires de tokens les plus fréquentes jusqu’à obtention de la taille de vocabulaire souhaitée.Le fait que la condition d’arrêt de l’algorithme soit la taille finale du vocabulaire permet de régler de manière plutôt précise la granularité des tokens.

L’implémentation proposée est à visée pédagogique, il manque de nombreux détails techniques et la nécessaire optimisation pour en faire un code tokenizer utilisable.

Tokenizer du code

La tokenization de code source est un cas particulier qui présente des défis uniques en raison de la structure syntaxique et sémantique intrinsèque au langage de programmation. En cela, elle diffère considérablement des langues naturelles. GPT, dans ses premières versions, a illustré ces difficultés, en particulier par une gestion inadéquate des espaces et des indentations, éléments cruciaux dans la syntaxe de nombreux langages de programmation. Dans de tels langages, l’espacement n’est pas seulement un séparateur de tokens mais joue un rôle structurant (par exemple, l’indentation en Python détermine les blocs de code), ce qui accentue l’importance d’une tokenization précise.

Ces lacunes étaient partiellement dues à la tokenization qui traitait les espaces de manière trop uniforme, sans prendre en compte leur signification contextuelle dans le code. Pour être précis, le fait de de prendre les espaces de manière uniforme dans la tokenization signifie que chaque indentation va générer 4 tokens. Dans ce cas la représentation tokenizée du code sera très peu efficace et redondante et va limiter la capacité du modèle de langage à contextualiser les tokens significatifs car ceux-ci seront dilués par une grande quantité de tokens inutiles.

Pour illustrer le point précédent, utilisons tiktokenizer, un site qui permet de tester différent tokenizers et de les visualiser.

On voit bien qu’un token est utilisé à chaque espace.

Avec GPT-4, des améliorations notables ont été apportées pour résoudre ces défis. Le tokenizer de GPT-4 bénéficie de techniques d’entraînement et d’optimisation plus sophistiquées, y compris une meilleure reconnaissance des structures programmatiques et de leur importance. Cela a été réalisé grâce à l’incorporation de grands volumes de texte de programmation dans le corpus d’entraînement et en affinant les mécanismes de tokenization pour mieux traiter les spécificités syntaxiques, comme les espacements significatifs.

Pour le même code on voit tout d’abord que la tokenization est bien plus efficace avec 250 tokens pour le tokenizer de gpt-4 contre 483 pour le tokenizer de gpt-2. On peut aussi observer que chaque indentation ou double indentation est un token. Si l’on compare les 2 tokenizations sur le reste du code on sent bien que la tokenization de gpt-4 est plus « naturelle » que celle de gpt-2.

L’évolution du tokenizer de gpt-2 vers le tokenizer de gpt-4 souligne l’importance de concevoir des tokenizers qui tiennent compte des exigences syntaxiques et sémantiques spécifiques de leurs domaines d’application. Pour le code, cela implique une sensibilité aux conventions stylistiques et structurelles qui régissent la clarté et la fonctionnalité du code source. Grâce à ces avancées, GPT-4 marque une étape significative vers une meilleure compréhension et génération de code, offrant ainsi aux développeurs et aux chercheurs des outils plus puissants pour l’automatisation de la programmation, le débogage et même l’enseignement des concepts de programmation.

Conclusion

La tokenization n’est pas seulement une étape préliminaire dans le traitement du langage naturel ; elle est un pilier essentiel permettant aux modèles de langage d’appréhender et de générer le langage humain. Sa complexité réside dans le soin à déterminer la « granularité » de tokenization idéale, équilibrant longueur des tokens et taille du vocabulaire, tout en naviguant dans les nuances propres à chaque langue et format de données. Avec des avancées telles que celles observées dans GPT-4, la tokenization continue d’évoluer, promettant une compréhension encore plus affinée et des performances améliorées pour les futures générations de modèles de langage.

Fediverse Reactions

Publié

dans

par

En savoir plus sur enioka

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture