Tokenizers de sous-mots

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Ce tutoriel montre comment générer un vocabulaire à partir d' un ensemble de données sous - mot, et l' utiliser pour construire un text.BertTokenizer du vocabulaire.

Le principal avantage d'un tokenizer de sous-mots est qu'il interpole entre la tokenisation basée sur les mots et celle basée sur les caractères. Les mots courants ont une place dans le vocabulaire, mais le tokenizer peut se rabattre sur des morceaux de mots et des caractères individuels pour les mots inconnus.

Aperçu

Le tensorflow_text package comprend les implémentations tensorflow de nombreux tokenizers communs. Cela comprend trois tokenizers de style sous-mot :

  • text.BertTokenizer - La BertTokenizer classe est une interface de niveau supérieur. Il comprend l' algorithme de séparation jeton BERT et un WordPieceTokenizer . Il faut des phrases en entrée et retourne jeton-ID.
  • text.WordpieceTokenizer - La WordPieceTokenizer classe est une interface de niveau inférieur. Il met en œuvre que l' algorithme de WordPiece . Vous devez normaliser et diviser le texte en mots avant de l'appeler. Il faut des mots en entrée et retourne jeton-ID.
  • text.SentencepieceTokenizer - Le SentencepieceTokenizer nécessite une configuration plus complexe. Son initialiseur nécessite un modèle de morceau de phrase pré-entraîné. Voir le référentiel google / sentencepiece pour obtenir des instructions sur la façon de construire un de ces modèles. Il peut accepter des phrases en entrée lorsque tokenizing.

Ce didacticiel construit un vocabulaire Wordpiece de manière descendante, à partir de mots existants. Ce processus ne fonctionne pas pour le japonais, le chinois ou le coréen, car ces langues n'ont pas d'unités multi-caractères claires. Pour tokenizer ces langues consi utilisant text.SentencepieceTokenizer , text.UnicodeCharTokenizer ou cette approche .

Installer

pip install -q -U tensorflow-text
pip install -q tensorflow_datasets
import collections
import os
import pathlib
import re
import string
import sys
import tempfile
import time

import numpy as np
import matplotlib.pyplot as plt

import tensorflow_datasets as tfds
import tensorflow_text as text
import tensorflow as tf
tf.get_logger().setLevel('ERROR')
pwd = pathlib.Path.cwd()

Télécharger le jeu de données

Fetch l'ensemble de données de traduction portugais / anglais de TFDS :

examples, metadata = tfds.load('ted_hrlr_translate/pt_to_en', with_info=True,
                               as_supervised=True)
train_examples, val_examples = examples['train'], examples['validation']

Cet ensemble de données produit des paires de phrases portugais/anglais :

for pt, en in train_examples.take(1):
  print("Portuguese: ", pt.numpy().decode('utf-8'))
  print("English:   ", en.numpy().decode('utf-8'))
Portuguese:  e quando melhoramos a procura , tiramos a única vantagem da impressão , que é a serendipidade .
English:    and when you improve searchability , you actually take away the one advantage of print , which is serendipity .

Notez quelques éléments concernant les exemples de phrases ci-dessus :

  • Ils sont en minuscules.
  • Il y a des espaces autour de la ponctuation.
  • Il n'est pas clair si ou quelle normalisation Unicode est utilisée.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

Générer le vocabulaire

Cette section génère un vocabulaire de mots à partir d'un ensemble de données. Si vous avez déjà un fichier de vocabulaire et que vous voulez juste voir comment construire un text.BertTokenizer ou text.Wordpiece tokenizer avec elle alors vous pouvez passer directement à la construction de la tokenizer section.

Le code de génération du vocabulaire est inclus dans le tensorflow_text paquet de pépin. Il n'est pas importé par défaut, vous devez l'importer manuellement :

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

La bert_vocab.bert_vocab_from_dataset fonction va générer le vocabulaire.

Il existe de nombreux arguments que vous pouvez définir pour ajuster son comportement. Pour ce tutoriel, vous utiliserez principalement les valeurs par défaut. Si vous voulez en savoir plus sur les options, d' abord en savoir plus sur l'algorithme , puis jeter un oeil à code .

Cela prend environ 2 minutes.

bert_tokenizer_params=dict(lower_case=True)
reserved_tokens=["[PAD]", "[UNK]", "[START]", "[END]"]

bert_vocab_args = dict(
    # The target vocabulary size
    vocab_size = 8000,
    # Reserved tokens that must be included in the vocabulary
    reserved_tokens=reserved_tokens,
    # Arguments for `text.BertTokenizer`
    bert_tokenizer_params=bert_tokenizer_params,
    # Arguments for `wordpiece_vocab.wordpiece_tokenizer_learner_lib.learn`
    learn_params={},
)
%%time
pt_vocab = bert_vocab.bert_vocab_from_dataset(
    train_pt.batch(1000).prefetch(2),
    **bert_vocab_args
)
CPU times: user 1min 30s, sys: 2.21 s, total: 1min 32s
Wall time: 1min 28s

Voici quelques tranches du vocabulaire obtenu.

print(pt_vocab[:10])
print(pt_vocab[100:110])
print(pt_vocab[1000:1010])
print(pt_vocab[-10:])
['[PAD]', '[UNK]', '[START]', '[END]', '!', '#', '$', '%', '&', "'"]
['no', 'por', 'mais', 'na', 'eu', 'esta', 'muito', 'isso', 'isto', 'sao']
['90', 'desse', 'efeito', 'malaria', 'normalmente', 'palestra', 'recentemente', '##nca', 'bons', 'chave']
['##–', '##—', '##‘', '##’', '##“', '##”', '##⁄', '##€', '##♪', '##♫']

Écrire un fichier de vocabulaire :

def write_vocab_file(filepath, vocab):
  with open(filepath, 'w') as f:
    for token in vocab:
      print(token, file=f)
write_vocab_file('pt_vocab.txt', pt_vocab)

Utilisez cette fonction pour générer un vocabulaire à partir des données anglaises :

%%time
en_vocab = bert_vocab.bert_vocab_from_dataset(
    train_en.batch(1000).prefetch(2),
    **bert_vocab_args
)
CPU times: user 1min 3s, sys: 2.21 s, total: 1min 6s
Wall time: 1min 2s
print(en_vocab[:10])
print(en_vocab[100:110])
print(en_vocab[1000:1010])
print(en_vocab[-10:])
['[PAD]', '[UNK]', '[START]', '[END]', '!', '#', '$', '%', '&', "'"]
['as', 'all', 'at', 'one', 'people', 're', 'like', 'if', 'our', 'from']
['choose', 'consider', 'extraordinary', 'focus', 'generation', 'killed', 'patterns', 'putting', 'scientific', 'wait']
['##_', '##`', '##ย', '##ร', '##อ', '##–', '##—', '##’', '##♪', '##♫']

Voici les deux fichiers de vocabulaire :

write_vocab_file('en_vocab.txt', en_vocab)
ls *.txt
en_vocab.txt  pt_vocab.txt

Construire le tokenizer

Le text.BertTokenizer peut être initialisé en passant le chemin du fichier de vocabulaire comme premier argument (voir la section sur tf.lookup pour d' autres options):

pt_tokenizer = text.BertTokenizer('pt_vocab.txt', **bert_tokenizer_params)
en_tokenizer = text.BertTokenizer('en_vocab.txt', **bert_tokenizer_params)

Vous pouvez maintenant l'utiliser pour encoder du texte. Prenez un lot de 3 exemples à partir des données anglaises :

for pt_examples, en_examples in train_examples.batch(3).take(1):
  for ex in en_examples:
    print(ex.numpy())
b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .'
b'but what if it were active ?'
b"but they did n't test for curiosity ."

Lancez - le par la BertTokenizer.tokenize méthode. Dans un premier temps , cela retourne un tf.RaggedTensor avec des axes (batch, word, word-piece) :

# Tokenize the examples -> (batch, word, word-piece)
token_batch = en_tokenizer.tokenize(en_examples)
# Merge the word and word-piece axes -> (batch, tokens)
token_batch = token_batch.merge_dims(-2,-1)

for ex in token_batch.to_list():
  print(ex)
[72, 117, 79, 1259, 1491, 2362, 13, 79, 150, 184, 311, 71, 103, 2308, 74, 2679, 13, 148, 80, 55, 4840, 1434, 2423, 540, 15]
[87, 90, 107, 76, 129, 1852, 30]
[87, 83, 149, 50, 9, 56, 664, 85, 2512, 15]

Si vous remplacez les ID de jeton avec leurs représentations textuelles ( en utilisant tf.gather ) vous pouvez voir que dans le premier exemple , les mots "searchability" facilité de "serendipity" "search ##ability" "s ##ere ##nd ##ip ##ity" "searchability" et "serendipity" ont été décomposé en "search ##ability" et "s ##ere ##nd ##ip ##ity" :

# Lookup each token id in the vocabulary.
txt_tokens = tf.gather(en_vocab, token_batch)
# Join with spaces.
tf.strings.reduce_join(txt_tokens, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'and when you improve search ##ability , you actually take away the one advantage of print , which is s ##ere ##nd ##ip ##ity .',
       b'but what if it were active ?',
       b"but they did n ' t test for curiosity ."], dtype=object)>

Pour ré-assembler les mots des jetons extraits, utilisez la BertTokenizer.detokenize méthode:

words = en_tokenizer.detokenize(token_batch)
tf.strings.reduce_join(words, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .',
       b'but what if it were active ?',
       b"but they did n ' t test for curiosity ."], dtype=object)>

Personnalisation et exportation

Ce tutoriel construit le texte tokenizer et detokenizer utilisé par le transformateur tutoriel. Cette section ajoute des méthodes et des étapes de traitement pour simplifier ce tutoriel, et exporte les tokenizers en utilisant tf.saved_model afin qu'ils puissent être importés par les autres tutoriels.

Tokenisation personnalisée

Les didacticiels en aval les deux attendent le texte sous forme de jeton d'inclure [START] et [END] jetons.

Le reserved_tokens espace de réserve au début du vocabulaire, donc [START] et [END] ont les mêmes indices pour les deux langues:

START = tf.argmax(tf.constant(reserved_tokens) == "[START]")
END = tf.argmax(tf.constant(reserved_tokens) == "[END]")

def add_start_end(ragged):
  count = ragged.bounding_shape()[0]
  starts = tf.fill([count,1], START)
  ends = tf.fill([count,1], END)
  return tf.concat([starts, ragged, ends], axis=1)
words = en_tokenizer.detokenize(add_start_end(token_batch))
tf.strings.reduce_join(words, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'[START] and when you improve searchability , you actually take away the one advantage of print , which is serendipity . [END]',
       b'[START] but what if it were active ? [END]',
       b"[START] but they did n ' t test for curiosity . [END]"],
      dtype=object)>

Détokenisation personnalisée

Avant d'exporter les tokenizers, vous pouvez effectuer quelques nettoyages pour les didacticiels en aval :

  1. Ils veulent générer une sortie de texte propre, donc jetons réservés drop comme [START] , [END] et [PAD] .
  2. Ils sont intéressés par des chaînes complètes, appliquez donc une chaîne rejoindre le long des words axe du résultat.
def cleanup_text(reserved_tokens, token_txt):
  # Drop the reserved tokens, except for "[UNK]".
  bad_tokens = [re.escape(tok) for tok in reserved_tokens if tok != "[UNK]"]
  bad_token_re = "|".join(bad_tokens)

  bad_cells = tf.strings.regex_full_match(token_txt, bad_token_re)
  result = tf.ragged.boolean_mask(token_txt, ~bad_cells)

  # Join them into strings.
  result = tf.strings.reduce_join(result, separator=' ', axis=-1)

  return result
en_examples.numpy()
array([b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .',
       b'but what if it were active ?',
       b"but they did n't test for curiosity ."], dtype=object)
token_batch = en_tokenizer.tokenize(en_examples).merge_dims(-2,-1)
words = en_tokenizer.detokenize(token_batch)
words
<tf.RaggedTensor [[b'and', b'when', b'you', b'improve', b'searchability', b',', b'you', b'actually', b'take', b'away', b'the', b'one', b'advantage', b'of', b'print', b',', b'which', b'is', b'serendipity', b'.'], [b'but', b'what', b'if', b'it', b'were', b'active', b'?'], [b'but', b'they', b'did', b'n', b"'", b't', b'test', b'for', b'curiosity', b'.']]>
cleanup_text(reserved_tokens, words).numpy()
array([b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .',
       b'but what if it were active ?',
       b"but they did n ' t test for curiosity ."], dtype=object)

Exportation

Le bloc de code suivant construit une CustomTokenizer classe pour contenir les text.BertTokenizer instances, la logique personnalisée, et les @tf.function emballages nécessaires à l' exportation.

class CustomTokenizer(tf.Module):
  def __init__(self, reserved_tokens, vocab_path):
    self.tokenizer = text.BertTokenizer(vocab_path, lower_case=True)
    self._reserved_tokens = reserved_tokens
    self._vocab_path = tf.saved_model.Asset(vocab_path)

    vocab = pathlib.Path(vocab_path).read_text().splitlines()
    self.vocab = tf.Variable(vocab)

    ## Create the signatures for export:   

    # Include a tokenize signature for a batch of strings. 
    self.tokenize.get_concrete_function(
        tf.TensorSpec(shape=[None], dtype=tf.string))

    # Include `detokenize` and `lookup` signatures for:
    #   * `Tensors` with shapes [tokens] and [batch, tokens]
    #   * `RaggedTensors` with shape [batch, tokens]
    self.detokenize.get_concrete_function(
        tf.TensorSpec(shape=[None, None], dtype=tf.int64))
    self.detokenize.get_concrete_function(
          tf.RaggedTensorSpec(shape=[None, None], dtype=tf.int64))

    self.lookup.get_concrete_function(
        tf.TensorSpec(shape=[None, None], dtype=tf.int64))
    self.lookup.get_concrete_function(
          tf.RaggedTensorSpec(shape=[None, None], dtype=tf.int64))

    # These `get_*` methods take no arguments
    self.get_vocab_size.get_concrete_function()
    self.get_vocab_path.get_concrete_function()
    self.get_reserved_tokens.get_concrete_function()

  @tf.function
  def tokenize(self, strings):
    enc = self.tokenizer.tokenize(strings)
    # Merge the `word` and `word-piece` axes.
    enc = enc.merge_dims(-2,-1)
    enc = add_start_end(enc)
    return enc

  @tf.function
  def detokenize(self, tokenized):
    words = self.tokenizer.detokenize(tokenized)
    return cleanup_text(self._reserved_tokens, words)

  @tf.function
  def lookup(self, token_ids):
    return tf.gather(self.vocab, token_ids)

  @tf.function
  def get_vocab_size(self):
    return tf.shape(self.vocab)[0]

  @tf.function
  def get_vocab_path(self):
    return self._vocab_path

  @tf.function
  def get_reserved_tokens(self):
    return tf.constant(self._reserved_tokens)

Construire un CustomTokenizer pour chaque langue:

tokenizers = tf.Module()
tokenizers.pt = CustomTokenizer(reserved_tokens, 'pt_vocab.txt')
tokenizers.en = CustomTokenizer(reserved_tokens, 'en_vocab.txt')

Exporter les tokenizers comme saved_model :

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.saved_model.save(tokenizers, model_name)
2021-11-02 15:20:31.762976: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.

Recharger la saved_model et tester les méthodes suivantes :

reloaded_tokenizers = tf.saved_model.load(model_name)
reloaded_tokenizers.en.get_vocab_size().numpy()
7010
tokens = reloaded_tokenizers.en.tokenize(['Hello TensorFlow!'])
tokens.numpy()
array([[   2, 4006, 2358,  687, 1192, 2365,    4,    3]])
text_tokens = reloaded_tokenizers.en.lookup(tokens)
text_tokens
<tf.RaggedTensor [[b'[START]', b'hello', b'tens', b'##or', b'##f', b'##low', b'!', b'[END]']]>
round_trip = reloaded_tokenizers.en.detokenize(tokens)

print(round_trip.numpy()[0].decode('utf-8'))
hello tensorflow !

Archiver pour les tutoriels de traduction :

zip -r {model_name}.zip {model_name}
adding: ted_hrlr_translate_pt_en_converter/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/saved_model.pb (deflated 91%)
  adding: ted_hrlr_translate_pt_en_converter/variables/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.data-00000-of-00001 (deflated 51%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.index (deflated 33%)
  adding: ted_hrlr_translate_pt_en_converter/assets/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/assets/pt_vocab.txt (deflated 57%)
  adding: ted_hrlr_translate_pt_en_converter/assets/en_vocab.txt (deflated 54%)
du -h *.zip
184K    ted_hrlr_translate_pt_en_converter.zip

Facultatif : L'algorithme

Il convient de noter ici qu'il existe deux versions de l'algorithme WordPiece : ascendante et descendante. Dans les deux cas, l'objectif est le même : « Compte tenu d'un corpus d'apprentissage et d'un nombre de jetons D souhaités, le problème d'optimisation est de sélectionner D fragments de mots de telle sorte que le corpus résultant soit minimal en nombre de fragments de mots lorsqu'il est segmenté selon le modèle de fragments de mots choisi. "

L'original algorithme de WordPiece bas vers le haut , est basé sur un codage octet pair . Comme BPE, il commence par l'alphabet et combine de manière itérative des bigrammes communs pour former des mots et des mots.

Générateur de vocabulaire tensorflow texte fait suite à la mise en œuvre de haut en bas de BERT . Commencer par des mots et les décomposer en composants plus petits jusqu'à ce qu'ils atteignent le seuil de fréquence ou qu'ils ne puissent plus être décomposés. La section suivante décrit cela en détail. Pour le japonais, le chinois et le coréen, cette approche descendante ne fonctionne pas car il n'y a pas d'unités de mots explicites pour commencer. Pour ceux dont vous avez besoin d' une approche différente .

Choisir le vocabulaire

Le top-down algorithme de génération WordPiece prend dans un ensemble de mots (comptage), des paires et un seuil T , et renvoie un vocabulaire V .

L'algorithme est itératif. Il est exécuté pour k itérations, où généralement k = 4 , mais seulement les deux premiers sont vraiment importantes. Le troisième et le quatrième (et au-delà) sont tout simplement identiques au second. Notez que chaque étape de la recherche binaire exécute l'algorithme à partir de zéro pour k itérations.

Les itérations décrites ci-dessous :

Première itération

  1. Itérer sur chaque mot et chaque paire de comptage à l'entrée, noté (w, c) .
  2. Pour chaque mot w , générer chaque sous - chaîne, notée s . Par exemple, pour le mot human , nous générons {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Maintenir une sous - chaîne à comptabiliser la carte de hachage, et incrémenter le compteur de chaque s par c . Par exemple, si nous avons (human, 113) et (humas, 3) dans notre entrée, le nombre de s = huma sera 113+3=116 .
  4. Une fois que nous avons recueilli les comptes de chaque sous - chaîne, itérer sur les (s, c) paires en commençant par la plus longue s premier.
  5. Conservez tous les s qui a un c > T . Par exemple, si T = 100 et nous avons (pers, 231); (dogs, 259); (##rint; 76) , nous garderions pers et les dogs .
  6. Lorsqu'une s est maintenue, soustraire de son compte de tous ses préfixes. Ceci est la raison de tri tous les s par longueur à l' étape 4. Ceci est une partie essentielle de l'algorithme, car sinon les mots seraient comptés deux fois. Par exemple, disons que nous avons gardé l' human et nous arrivons à (huma, 116) . Nous savons que 113 de ces 116 provenaient de l' human , et 3 venus de humas . Cependant, maintenant que l' human est dans notre vocabulaire, nous savons que nous ne le segment human dans huma ##n . Donc , une fois l' human a été maintenu, alors huma a seulement un nombre efficace de 3 .

Cet algorithme génère un ensemble de morceaux de mots s (dont le nombre sera de mots entiers w ), que nous pourrions utiliser comme notre vocabulaire WordPiece.

Cependant, il y a un problème : cet algorithme va fortement surgénérer des morceaux de mots. La raison en est que nous soustrayons uniquement le nombre de jetons de préfixe. Par conséquent, si nous gardons la parole human , nous soustraire hors du comptage pour h, hu, hu, huma , mais pas pour ##u, ##um, ##uma, ##uman et ainsi de suite. On peut donc générer à la fois human et ##uman que des morceaux de mots, même si ##uman ne sera jamais appliquée.

Alors pourquoi ne pas soustraire au large des comptes pour chaque sous - chaîne, non seulement chaque préfixe? Parce qu'alors, nous pourrions finir par soustraire les comptes plusieurs fois. Disons que nous traitiez s de longueur 5 et nous gardons les deux (##denia, 129) et (##eniab, 137) , où 65 de ces chefs d' accusation vient du mot undeniable . Si l' on retranche hors de tous les sous - chaîne, nous soustraire 65 de la sous - chaîne ##enia deux fois, même si nous ne devons soustraire une fois. Cependant, si nous ne soustrayons que des préfixes, il ne sera correctement soustrait qu'une seule fois.

Deuxième (et troisième ...) itération

Pour résoudre le problème de surgénération mentionné ci-dessus, nous effectuons plusieurs itérations de l'algorithme.

Les itérations suivantes sont identiques à la première, avec une distinction importante: l' étape 2, au lieu de considérer chaque sous - chaîne, on applique l'algorithme de tokens de WordPiece en utilisant le vocabulaire de l'itération précédente, et ne considère que les sous - chaînes qui commencent sur un point de partage.

Par exemple, disons que nous d' effectuer l' étape 2 de l'algorithme et de rencontre le mot undeniable . Dans la première itération, nous considérons tous les sous - chaîne, par exemple, {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} .

Maintenant, pour la deuxième itération, nous n'en considérerons qu'un sous-ensemble. Disons qu'après la première itération, les morceaux de mots pertinents sont :

un, ##deni, ##able, ##ndeni, ##iable

Le segment de l' algorithme de WordPiece de volonté dans ce un ##deni ##able (voir la section Application WordPiece pour plus d' informations). Dans ce cas, nous ne considérerons qui commencent à substrings un point de segmentation. Nous continuerons considérer chaque position finale possible. Ainsi , au cours de la deuxième itération, l'ensemble de s pour undeniable est:

{u, un, und, unden, undeni, undenia, undeniab, undeniabl, undeniable, ##d, ##de, ##den, ##deni, ##denia, ##deniab, ##deniabl , ##deniable, ##a, ##ab, ##abl, ##able}

L'algorithme est par ailleurs identique. Dans cet exemple, dans la première itération, l'algorithme produit les jetons suprious ##ndeni et ##iable . Désormais, ces jetons ne sont jamais pris en compte, ils ne seront donc pas générés par la deuxième itération. Nous effectuons plusieurs itérations juste pour nous assurer que les résultats convergent (bien qu'il n'y ait aucune garantie de convergence littérale).

Application de WordPiece

Une fois qu'un vocabulaire WordPiece a été généré, nous devons pouvoir l'appliquer à de nouvelles données. L'algorithme est une simple application avide de correspondance la plus longue en premier.

Par exemple, considérons segmentant le mot undeniable .

Nous avons d' abord recherche undeniable dans notre dictionnaire WordPiece, et si elle est présente, nous avons terminé. Sinon, nous décrémentons le point final d'un caractère, et répéter, par exemple, undeniabl .

Finalement, nous trouverons soit un sous-jeton dans notre vocabulaire, soit nous nous contenterons d'un sous-jeton à un seul caractère. (En général, nous partons du principe que chaque caractère est dans notre vocabulaire, bien que cela ne soit pas le cas pour les caractères rares Unicode. Si nous rencontrons un caractère rare Unicode qui n'est pas dans le vocabulaire nous suffit simplement de le mot entier à <unk> ).

Dans ce cas, nous trouvons un dans notre vocabulaire. C'est donc notre premier mot. Ensuite , nous sautons à la fin de un et répéter le traitement, par exemple, essayer de trouver ##deniable , puis ##deniabl , etc. Cette opération est répétée jusqu'à ce que nous avons segmenté le mot entier.

Intuition

Intuitivement, la tokenisation de WordPiece tente de satisfaire deux objectifs différents :

  1. Tokenize les données dans le moins de pièces possible. Il est important de garder à l'esprit que l'algorithme WordPiece ne « veut » pas diviser les mots. Dans le cas contraire, il serait tout simplement diviser chaque mot en caractères, par exemple, human -> {h, ##u, ##m, ##a, #n} . C'est une chose essentielle qui fait différent WordPiece de splitters morphologiques, qui se répartiront morphèmes linguistiques , même pour des mots communs (par exemple, unwanted -> {un, want, ed} ).

  2. Lorsqu'un mot doit être divisé en morceaux, divisez-le en morceaux qui ont un nombre maximal dans les données d'apprentissage. Par exemple, la raison pour laquelle le mot undeniable serait divisé en {un, ##deni, ##able} plutôt que des alternatives comme {unde, ##niab, ##le} est que les chefs d' accusation pour un et ##able en particulier sera très élevé, car ce sont des préfixes et des suffixes communs. Même si le nombre de ##le doit être supérieure à ##able , le faible nombre de unde et ##niab feront un moins « souhaitable » tokenization à l'algorithme.

Facultatif : tf.lookup

Si vous avez besoin d' accès ou plus de contrôle sur le vocabulaire , il est intéressant de noter que vous pouvez construire la table de recherche vous - même et passer que pour BertTokenizer .

Lorsque vous passez une chaîne, BertTokenizer effectue les opérations suivantes:

pt_lookup = tf.lookup.StaticVocabularyTable(
    num_oov_buckets=1,
    initializer=tf.lookup.TextFileInitializer(
        filename='pt_vocab.txt',
        key_dtype=tf.string,
        key_index = tf.lookup.TextFileIndex.WHOLE_LINE,
        value_dtype = tf.int64,
        value_index=tf.lookup.TextFileIndex.LINE_NUMBER)) 
pt_tokenizer = text.BertTokenizer(pt_lookup)

Vous avez maintenant un accès direct à la table de recherche utilisée dans le tokenizer.

pt_lookup.lookup(tf.constant(['é', 'um', 'uma', 'para', 'não']))
<tf.Tensor: shape=(5,), dtype=int64, numpy=array([7765,   85,   86,   87, 7765])>

Vous n'avez pas besoin d'utiliser un fichier de vocabulaire, tf.lookup a d' autres options de initialiseur. Si vous avez le vocabulaire en mémoire , vous pouvez utiliser lookup.KeyValueTensorInitializer :

pt_lookup = tf.lookup.StaticVocabularyTable(
    num_oov_buckets=1,
    initializer=tf.lookup.KeyValueTensorInitializer(
        keys=pt_vocab,
        values=tf.range(len(pt_vocab), dtype=tf.int64))) 
pt_tokenizer = text.BertTokenizer(pt_lookup)