Buckets:
| # Construction d'un <i>tokenizer</i>, bloc par bloc | |
| <CourseFloatingBanner chapter={6} | |
| classNames="absolute z-10 right-0 top-0" | |
| notebooks={[ | |
| {label: "English", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section8.ipynb"}, | |
| {label: "Français", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter6/section8.ipynb"}, | |
| {label: "English", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section8.ipynb"}, | |
| {label: "Français", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/fr/chapter6/section8.ipynb"}, | |
| ]} /> | |
| Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : | |
| - normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.), | |
| - prétokénisation (division de l'entrée en mots), | |
| - passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*), | |
| - post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). | |
| Pour mémoire, voici un autre aperçu du processus global : | |
| <div class="flex justify-center"> | |
| <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline."> | |
| <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline."> | |
| </div> | |
| La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! | |
| <Youtube id="MR8tZm5ViWU"/> | |
| Plus précisément, la bibliothèque est construite autour d'une classe centrale `Tokenizer` avec les blocs de construction regroupés en sous-modules : | |
| - `normalizers` contient tous les types de `Normalizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/normalizers)), | |
| - `pre_tokenizers` contient tous les types de `PreTokenizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)), | |
| - `models` contient les différents types de `Model` que vous pouvez utiliser, comme `BPE`, `WordPiece`, et `Unigram` (liste complète [ici](https://huggingface.co/docs/tokenizers/api/models)), | |
| - `trainers` contient tous les différents types de `Trainer` que vous pouvez utiliser pour entraîner votre modèle sur un corpus (un par type de modèle ; liste complète [ici](https://huggingface.co/docs/tokenizers/api/trainers)), | |
| - `post_processors` contient les différents types de `PostProcessor` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/post-processors)), | |
| - `decoders` contient les différents types de `Decoder` que vous pouvez utiliser pour décoder les sorties de tokenization (liste complète [ici](https://huggingface.co/docs/tokenizers/components#decoders)). | |
| Vous pouvez trouver la liste complète des blocs de construction [ici](https://huggingface.co/docs/tokenizers/components). | |
| ## Acquisition d'un corpus | |
| Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [WikiText-2](https://huggingface.co/datasets/wikitext) : | |
| ```python | |
| from datasets import load_dataset | |
| dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") | |
| def get_training_corpus(): | |
| for i in range(0, len(dataset), 1000): | |
| yield dataset[i : i + 1000]["text"] | |
| ``` | |
| La fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. | |
| 🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local : | |
| ```python | |
| with open("wikitext-2.txt", "w", encoding="utf-8") as f: | |
| for i in range(len(dataset)): | |
| f.write(dataset[i]["text"] + "\n") | |
| ``` | |
| Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! | |
| ## Construire un <i>tokenizer WordPiece</i> à partir de zéro | |
| Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons. | |
| Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : | |
| ```python | |
| from tokenizers import ( | |
| decoders, | |
| models, | |
| normalizers, | |
| pre_tokenizers, | |
| processors, | |
| trainers, | |
| Tokenizer, | |
| ) | |
| tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) | |
| ``` | |
| Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). | |
| La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : | |
| ```python | |
| tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) | |
| ``` | |
| Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` : | |
| ```python | |
| tokenizer.normalizer = normalizers.Sequence( | |
| [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] | |
| ) | |
| ``` | |
| Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. | |
| Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : | |
| ```python | |
| print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) | |
| ``` | |
| ```python out | |
| hello how are u? | |
| ``` | |
| > [!TIP] | |
| > **Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. | |
| > Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. | |
| L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : | |
| ```python | |
| tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() | |
| ``` | |
| Ou nous pouvons le construire à partir de zéro : | |
| ```python | |
| tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() | |
| ``` | |
| Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation : | |
| ```python | |
| tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") | |
| ``` | |
| ```python out | |
| [('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), | |
| ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] | |
| ``` | |
| Si vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place : | |
| ```python | |
| pre_tokenizer = pre_tokenizers.WhitespaceSplit() | |
| pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") | |
| ``` | |
| ```python out | |
| [("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] | |
| ``` | |
| Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers : | |
| ```python | |
| pre_tokenizer = pre_tokenizers.Sequence( | |
| [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] | |
| ) | |
| pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") | |
| ``` | |
| ```python out | |
| [('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), | |
| ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] | |
| ``` | |
| L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement : | |
| ```python | |
| special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] | |
| trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) | |
| ``` | |
| En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). | |
| Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : | |
| ```python | |
| tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) | |
| ``` | |
| Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : | |
| ```python | |
| tokenizer.model = models.WordPiece(unk_token="[UNK]") | |
| tokenizer.train(["wikitext-2.txt"], trainer=trainer) | |
| ``` | |
| Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : | |
| ```python | |
| encoding = tokenizer.encode("Let's test this tokenizer.") | |
| print(encoding.tokens) | |
| ``` | |
| ```python out | |
| ['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] | |
| ``` | |
| L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`. | |
| La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : | |
| ```python | |
| cls_token_id = tokenizer.token_to_id("[CLS]") | |
| sep_token_id = tokenizer.token_to_id("[SEP]") | |
| print(cls_token_id, sep_token_id) | |
| ``` | |
| ```python out | |
| (2, 3) | |
| ``` | |
| Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. | |
| Le gabarit classique de BERT est donc défini comme suit : | |
| ```python | |
| tokenizer.post_processor = processors.TemplateProcessing( | |
| single=f"[CLS]:0 $A:0 [SEP]:0", | |
| pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", | |
| special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], | |
| ) | |
| ``` | |
| Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement. | |
| Une fois cela ajouté, revenons à notre exemple précédent donnera : | |
| ```python | |
| encoding = tokenizer.encode("Let's test this tokenizer.") | |
| print(encoding.tokens) | |
| ``` | |
| ```python out | |
| ['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] | |
| ``` | |
| Et sur une paire de phrases, on obtient le bon résultat : | |
| ```python | |
| encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") | |
| print(encoding.tokens) | |
| print(encoding.type_ids) | |
| ``` | |
| ```python out | |
| ['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] | |
| [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] | |
| ``` | |
| Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : | |
| ```python | |
| tokenizer.decoder = decoders.WordPiece(prefix="##") | |
| ``` | |
| Testons-le sur notre précédent `encoding` : | |
| ```python | |
| tokenizer.decode(encoding.ids) | |
| ``` | |
| ```python out | |
| "let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. | |
| ``` | |
| Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : | |
| ```python | |
| tokenizer.save("tokenizer.json") | |
| ``` | |
| Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : | |
| ```python | |
| new_tokenizer = Tokenizer.from_file("tokenizer.json") | |
| ``` | |
| Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. | |
| Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc : | |
| ```python | |
| from transformers import PreTrainedTokenizerFast | |
| wrapped_tokenizer = PreTrainedTokenizerFast( | |
| tokenizer_object=tokenizer, | |
| # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement | |
| unk_token="[UNK]", | |
| pad_token="[PAD]", | |
| cls_token="[CLS]", | |
| sep_token="[SEP]", | |
| mask_token="[MASK]", | |
| ) | |
| ``` | |
| Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : | |
| ```python | |
| from transformers import BertTokenizerFast | |
| wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) | |
| ``` | |
| Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. | |
| Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences. | |
| ## Construire un <i>tokenizer</i> BPE à partir de zéro | |
| Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : | |
| ```python | |
| tokenizer = Tokenizer(models.BPE()) | |
| ``` | |
| Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet. | |
| GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation : | |
| ```python | |
| tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) | |
| ``` | |
| L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant : | |
| ```python | |
| tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") | |
| ``` | |
| ```python out | |
| [('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), | |
| ('tokenization', (15, 27)), ('!', (27, 28))] | |
| ``` | |
| Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte : | |
| ```python | |
| trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) | |
| tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) | |
| ``` | |
| Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme `</w>`), nous pouvons le définir avec `end_of_word_suffix`. | |
| Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : | |
| ```python | |
| tokenizer.model = models.BPE() | |
| tokenizer.train(["wikitext-2.txt"], trainer=trainer) | |
| ``` | |
| Regardons la tokenisation d'un exemple de texte : | |
| ```python | |
| encoding = tokenizer.encode("Let's test this tokenizer.") | |
| print(encoding.tokens) | |
| ``` | |
| ```python out | |
| ['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] | |
| ``` | |
| Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : | |
| ```python | |
| tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) | |
| ``` | |
| L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 : | |
| ```python | |
| sentence = "Let's test this tokenizer." | |
| encoding = tokenizer.encode(sentence) | |
| start, end = encoding.offsets[4] | |
| sentence[start:end] | |
| ``` | |
| ```python out | |
| ' test' | |
| ``` | |
| Enfin, nous ajoutons un décodeur au niveau de l'octet : | |
| ```python | |
| tokenizer.decoder = decoders.ByteLevel() | |
| ``` | |
| et nous pouvons vérifier qu'il fonctionne correctement : | |
| ```python | |
| tokenizer.decode(encoding.ids) | |
| ``` | |
| ```python out | |
| "Let's test this tokenizer." # Testons ce tokenizer | |
| ``` | |
| Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : | |
| ```python | |
| from transformers import PreTrainedTokenizerFast | |
| wrapped_tokenizer = PreTrainedTokenizerFast( | |
| tokenizer_object=tokenizer, | |
| bos_token="<|endoftext|>", | |
| eos_token="<|endoftext|>", | |
| ) | |
| ``` | |
| ou : | |
| ```python | |
| from transformers import GPT2TokenizerFast | |
| wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) | |
| ``` | |
| Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. | |
| ## Construire un <i>tokenizer Unigram</i> à partir de zéro | |
| Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : | |
| ```python | |
| tokenizer = Tokenizer(models.Unigram()) | |
| ``` | |
| Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. | |
| Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : | |
| ```python | |
| from tokenizers import Regex | |
| tokenizer.normalizer = normalizers.Sequence( | |
| [ | |
| normalizers.Replace("``", '"'), | |
| normalizers.Replace("''", '"'), | |
| normalizers.NFKD(), | |
| normalizers.StripAccents(), | |
| normalizers.Replace(Regex(" {2,}"), " "), | |
| ] | |
| ) | |
| ``` | |
| Il remplace <code>``</code> et <code>''</code> par <code>"</code> et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents. | |
| Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : | |
| ```python | |
| tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() | |
| ``` | |
| Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment : | |
| ```python | |
| tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") | |
| ``` | |
| ```python out | |
| [("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] | |
| ``` | |
| Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : | |
| ```python | |
| special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"] | |
| trainer = trainers.UnigramTrainer( | |
| vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>" | |
| ) | |
| tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) | |
| ``` | |
| Un argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16). | |
| Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : | |
| ```python | |
| tokenizer.model = models.Unigram() | |
| tokenizer.train(["wikitext-2.txt"], trainer=trainer) | |
| ``` | |
| Regardons la tokenisation de notre exemple : | |
| ```python | |
| encoding = tokenizer.encode("Let's test this tokenizer.") | |
| print(encoding.tokens) | |
| ``` | |
| ```python out | |
| ['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] | |
| ``` | |
| Une particularité de XLNet est qu'il place le *token* `<cls>` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `<cls>` et `<sep>` : | |
| ```python | |
| cls_token_id = tokenizer.token_to_id("<cls>") | |
| sep_token_id = tokenizer.token_to_id("<sep>") | |
| print(cls_token_id, sep_token_id) | |
| ``` | |
| ```python out | |
| 0 1 | |
| ``` | |
| Le modèle ressemble à ceci : | |
| ```python | |
| tokenizer.post_processor = processors.TemplateProcessing( | |
| single="$A:0 <sep>:0 <cls>:2", | |
| pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2", | |
| special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)], | |
| ) | |
| ``` | |
| Et nous pouvons tester son fonctionnement en codant une paire de phrases : | |
| ```python | |
| encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") | |
| print(encoding.tokens) | |
| print(encoding.type_ids) | |
| ``` | |
| ```python out | |
| ['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', | |
| '▁of', '▁sentence', 's', '!', '<sep>', '<cls>'] | |
| [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] | |
| ``` | |
| Enfin, nous ajoutons un décodeur `Metaspace` : | |
| ```python | |
| tokenizer.decoder = decoders.Metaspace() | |
| ``` | |
| et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche : | |
| ```python | |
| from transformers import PreTrainedTokenizerFast | |
| wrapped_tokenizer = PreTrainedTokenizerFast( | |
| tokenizer_object=tokenizer, | |
| bos_token="<s>", | |
| eos_token="</s>", | |
| unk_token="<unk>", | |
| pad_token="<pad>", | |
| cls_token="<cls>", | |
| sep_token="<sep>", | |
| mask_token="<mask>", | |
| padding_side="left", | |
| ) | |
| ``` | |
| Ou alternativement : | |
| ```python | |
| from transformers import XLNetTokenizerFast | |
| wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) | |
| ``` | |
| Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. | |
| <EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/fr/chapter6/8.mdx" /> |
Xet Storage Details
- Size:
- 26.5 kB
- Xet hash:
- 95021516e2c32ff0604285e80e755c0dc081d4f913cc504434a6426d0aa42f52
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.