Tokenizadores de subpalavra

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial demonstra como gerar um vocabulário de subpalavra a partir de um conjunto de dados e usá-lo para construir um text.BertTokenizer partir do vocabulário.

A principal vantagem de um tokenizer de subpalavra é que ele interpola entre a tokenização baseada em palavras e baseada em caracteres. Palavras comuns ocupam um espaço no vocabulário, mas o tokenizer pode recorrer a pedaços de palavras e caracteres individuais para palavras desconhecidas.

Visão geral

O pacote tensorflow_text inclui implementações do TensorFlow de muitos tokenizadores comuns. Isso inclui três tokenizadores de estilo de subpalavra:

  • text.BertTokenizer - A classe BertTokenizer é uma interface de nível superior. Inclui o algoritmo de divisão de tokens de BERT e um WordPieceTokenizer . Recebe frases como entrada e retorna IDs de token .
  • text.WordpeiceTokenizer - A classe WordPieceTokenizer é uma interface de nível inferior. Ele apenas implementa o algoritmo WordPiece . Você deve padronizar e dividir o texto em palavras antes de chamá-lo. Leva palavras como entrada e retorna token-IDs.
  • text.SentencepieceTokenizer - O SentencepieceTokenizer requer uma configuração mais complexa. Seu inicializador requer um modelo de frase pré-treinado. Consulte o repositório google / frase para obter instruções sobre como construir um desses modelos. Ele pode aceitar frases como entrada durante a tokenização.

Este tutorial constrói um vocabulário Wordpiece de cima para baixo, começando com palavras existentes. Esse processo não funciona para japonês, chinês ou coreano, pois esses idiomas não têm unidades claras de vários caracteres. Para tokenizar essas linguagens, considere usar text.SentencepieceTokenizer , text.UnicodeCharTokenizer ou esta abordagem .

Configurar

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()

Baixe o conjunto de dados

Obtenha o conjunto de dados de tradução português / inglês no 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']

Este conjunto de dados produz pares de frases em português / inglês:

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 .

Observe algumas coisas sobre as frases de exemplo acima:

  • Eles são minúsculos.
  • Existem espaços ao redor da pontuação.
  • Não está claro se ou qual normalização Unicode está sendo usada.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

Gere o vocabulário

Esta seção gera um vocabulário de palavras a partir de um conjunto de dados. Se você já possui um arquivo de vocabulário e deseja apenas ver como construir um text.BertTokenizer ou text.Wordpiece tokenizer com ele, você pode pular para a seção Construir o tokenizer .

O código de geração de vocabulário está incluído no pacote tensorflow_text pip. Não é importado por padrão, você precisa importá-lo manualmente:

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

A função bert_vocab.bert_vocab_from_dataset irá gerar o vocabulário.

Existem muitos argumentos que você pode definir para ajustar seu comportamento. Para este tutorial, você usará principalmente os padrões. Se você quiser saber mais sobre as opções, primeiro leia sobre o algoritmo e, em seguida, dê uma olhada no código .

Isso leva cerca de 2 minutos.

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 21s, sys: 2.76 s, total: 1min 23s
Wall time: 1min 17s

Aqui estão algumas fatias do vocabulário resultante.

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']
['##–', '##—', '##‘', '##’', '##“', '##”', '##⁄', '##€', '##♪', '##♫']

Escreva um arquivo de vocabulário:

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)

Use essa função para gerar um vocabulário a partir dos dados em inglês:

%%time
en_vocab = bert_vocab.bert_vocab_from_dataset(
    train_en.batch(1000).prefetch(2),
    **bert_vocab_args
)
CPU times: user 57.6 s, sys: 2.34 s, total: 59.9 s
Wall time: 54.2 s
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']
['##_', '##`', '##ย', '##ร', '##อ', '##–', '##—', '##’', '##♪', '##♫']

Aqui estão os dois arquivos de vocabulário:

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

Construir o tokenizer

O text.BertTokenizer pode ser inicializado passando o caminho do arquivo de vocabulário como o primeiro argumento (consulte a seção em tf.lookup para outras opções):

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

Agora você pode usá-lo para codificar algum texto. Pegue um lote de 3 exemplos dos dados em inglês:

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 ."

Execute-o por meio do método BertTokenizer.tokenize . Inicialmente, isso retorna umtf.RaggedTensor com eixos (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]

Se você substituir os IDs de token por suas representações de texto (usando tf.gather ), poderá ver que no primeiro exemplo as palavras "searchability" e "serendipity" foram decompostas em "search ##ability" e "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)>

Para remontar palavras dos tokens extraídos, use o método BertTokenizer.detokenize :

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)>

Personalização e exportação

Este tutorial cria o tokenizer e detokenizer de texto usado pelo tutorial do Transformer . Esta seção adiciona métodos e etapas de processamento para simplificar esse tutorial e exporta os tokenizers usando tf.saved_model para que possam ser importados por outros tutoriais.

Tokenização personalizada

Os tutoriais downstream esperam que o texto tokenizado inclua os tokens [START] e [END] .

Os reserved_tokens reservam espaço no início do vocabulário, então [START] e [END] têm os mesmos índices para os dois idiomas:

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)>

Detocagem personalizada

Antes de exportar os tokenizadores, há algumas coisas que você pode limpar para os tutoriais downstream:

  1. Eles querem gerar uma saída de texto limpo, então elimine tokens reservados como [START] , [END] e [PAD] .
  2. Eles estão interessados ​​em strings completas, portanto, aplique uma junção de string ao longo do eixo das words do resultado.
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)

Exportar

O bloco de código a seguir cria uma classe CustomTokenizer para conter as instâncias text.BertTokenizer , a lógica customizada e os wrappers @tf.function necessários para exportação.

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)

Crie um CustomTokenizer para cada idioma:

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

Exporte os tokenizadores como um modelo saved_model :

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.saved_model.save(tokenizers, model_name)

Recarregue o saved_model e teste os métodos:

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 !

Arquive para os tutoriais de tradução :

zip -r {model_name}.zip {model_name}
adding: ted_hrlr_translate_pt_en_converter/ (stored 0%)
  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%)
  adding: ted_hrlr_translate_pt_en_converter/saved_model.pb (deflated 91%)
du -h *.zip
184K    ted_hrlr_translate_pt_en_converter.zip

Opcional: o algoritmo

É importante notar aqui que existem duas versões do algoritmo WordPiece: de baixo para cima e de cima para baixo. Em ambos os casos, o objetivo é o mesmo: "Dado um corpus de treinamento e um número de tokens D desejados, o problema de otimização é selecionar peças de texto D de modo que o corpus resultante seja mínimo no número de peças de palavra quando segmentado de acordo com o modelo de peça de palavra escolhido. "

Oalgoritmo original debaixo para cima do WordPiece é baseado na codificação de pares de bytes . Como o BPE, começa com o alfabeto e combina iterativamente bigramas comuns para formar pedaços de palavras e palavras.

O gerador de vocabulário do TensorFlow Text segue a implementação de cima para baixo do BERT . Começando com palavras e dividindo-as em componentes menores até que atinjam o limite de frequência ou não possam ser mais quebradas. A próxima seção descreve isso em detalhes. Para japonês, chinês e coreano, essa abordagem de cima para baixo não funciona, pois não há unidades de palavras explícitas para começar. Para aqueles, você precisa de uma abordagem diferente .

Escolhendo o vocabulário

O algoritmo de geração de WordPiece de cima para baixo assume um conjunto de pares (palavra, contagem) e um limite T e retorna um vocabulário V

O algoritmo é iterativo. Ele é executado por k iterações, onde normalmente k = 4 , mas apenas as duas primeiras são realmente importantes. O terceiro e o quarto (e além) são idênticos ao segundo. Observe que cada etapa da pesquisa binária executa o algoritmo do zero para k iterações.

As iterações descritas abaixo:

Primeira iteração

  1. Repita cada palavra e conte o par na entrada, denotado como (w, c) .
  2. Para cada palavra w , gere cada substring, denotada como s . Por exemplo, para a palavra human , geramos {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Manter um hash map substring-to-count e incrementar a contagem de cada s em c . Por exemplo, se tivermos (human, 113) e (humas, 3) em nossa entrada, a contagem de s = huma será 113+3=116 .
  4. Depois de coletar as contagens de cada substring, itere sobre os pares (s, c) começando com o s mais longo primeiro .
  5. Mantenha qualquer s que tenha um c > T Por exemplo, se T = 100 e temos (pers, 231); (dogs, 259); (##rint; 76) , então (pers, 231); (dogs, 259); (##rint; 76) pers e dogs .
  6. Quando um s é mantido, subtraia sua contagem de todos os seus prefixos. Essa é a razão para classificar todos os s por comprimento na etapa 4. Essa é uma parte crítica do algoritmo, porque caso contrário, as palavras seriam contadas duas vezes. Por exemplo, digamos que mantivemos human e chegamos a (huma, 116) . Sabemos que 113 desses 116 vieram de human e 3 vieram de humas . No entanto, agora que human está em nosso vocabulário, sabemos que nunca segmentaremos human em huma ##n . Assim, uma vez que o human foi mantido, o huma só tem uma contagem efetiva de 3 .

Este algoritmo irá gerar um conjunto de peças palavra s (muitos dos quais será palavras inteiras w ), que poderíamos usar como nosso vocabulário WordPiece.

No entanto, há um problema: esse algoritmo irá gerar excessivamente trechos de palavras. O motivo é que apenas subtraímos as contagens de tokens de prefixo. Portanto, se mantivermos a palavra human , subtrairemos a contagem para h, hu, hu, huma , mas não para ##u, ##um, ##uma, ##uman e assim por diante. Portanto, podemos gerar human e ##uman como pedaços de palavras, embora ##uman nunca seja aplicado.

Então, por que não subtrair as contagens de cada substring , não apenas de cada prefixo ? Porque então poderíamos acabar subtraindo a contagem várias vezes. Digamos que estamos processando s de comprimento 5 e mantemos (##denia, 129) e (##eniab, 137) , onde 65 dessas contagens vêm da palavra undeniable . Se subtrairmos de cada substring, subtrairemos 65 da substring ##enia duas vezes, embora devamos subtrair apenas uma vez. No entanto, se apenas subtrairmos dos prefixos, ele será corretamente subtraído apenas uma vez.

Segunda (e terceira ...) iteração

Para resolver o problema de geração excessiva mencionado acima, realizamos várias iterações do algoritmo.

As iterações subsequentes são idênticas à primeira, com uma distinção importante: na etapa 2, em vez de considerar cada substring, aplicamos o algoritmo de tokenização WordPiece usando o vocabulário da iteração anterior e consideramos apenas substrings que começam em um ponto de divisão.

Por exemplo, digamos que estamos executando a etapa 2 do algoritmo e encontramos a palavra undeniable . Na primeira iteração, consideraríamos cada substring, por exemplo, {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} .

Agora, para a segunda iteração, consideraremos apenas um subconjunto deles. Digamos que, após a primeira iteração, as palavras relevantes sejam:

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

O algoritmo WordPiece irá segmentar isso em un ##deni ##able (consulte a seção Aplicando WordPiece para obter mais informações). Nesse caso, consideraremos apenas substrings que começam em um ponto de segmentação. Ainda consideraremos todas as posições finais possíveis. Portanto, durante a segunda iteração, o conjunto de s para undeniable é:

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

O algoritmo é idêntico. Neste exemplo, na primeira iteração, o algoritmo produz os tokens ##ndeni e ##iable . Agora, esses tokens nunca são considerados, portanto, eles não serão gerados pela segunda iteração. Realizamos várias iterações apenas para ter certeza de que os resultados convergem (embora não haja garantia de convergência literal).

Aplicando WordPiece

Uma vez que um vocabulário WordPiece tenha sido gerado, precisamos ser capazes de aplicá-lo a novos dados. O algoritmo é um aplicativo ganancioso de correspondência mais longa primeiro.

Por exemplo, considere segmentar a palavra undeniable .

Primeiro, procuramos undeniable em nosso dicionário WordPiece e, se estiver presente, terminamos. Caso contrário, diminuímos o ponto final em um caractere e repetimos, por exemplo, undeniabl .

Eventualmente, encontraremos um subtoken em nosso vocabulário ou chegaremos a um subtoken de um único caractere. (Em geral, assumimos que cada personagem está em nosso vocabulário, embora isto possa não ser o caso de caracteres Unicode raras. Se encontrarmos um caractere Unicode raro que não está no vocabulário nós simplesmente mapear toda a palavra para <unk> ).

Nesse caso, encontramos un em nosso vocabulário. Então essa é a nossa primeira palavra. Em seguida, saltamos para o final de un e repetimos o processamento, por exemplo, tentamos encontrar ##deniable , depois ##deniabl , etc. Isso é repetido até que tenhamos segmentado a palavra inteira.

Intuição

Intuitivamente, a tokenização do WordPiece tenta satisfazer dois objetivos diferentes:

  1. Tokenize os dados no menor número de partes possível. É importante ter em mente que o algoritmo WordPiece não "quer" dividir palavras. Caso contrário, ele iria apenas dividir cada palavra em seus caracteres, por exemplo, human -> {h, ##u, ##m, ##a, #n} . Esse é um fator crítico que torna o WordPiece diferente dos divisores morfológicos, que dividem os morfemas linguísticos até mesmo para palavras comuns (por exemplo, unwanted -> {un, want, ed} ).

  2. Quando uma palavra precisa ser dividida em partes, divida-a em partes que tenham contagens máximas nos dados de treinamento. Por exemplo, a razão pela qual a palavra undeniable seria dividida em {un, ##deni, ##able} vez de alternativas como {unde, ##niab, ##le} é que as contagens para un e ##able em particular será muito alto, uma vez que esses são prefixos e sufixos comuns. Mesmo que a contagem para ##le deve ser superior a ##able , as baixas contagens de unde e ##niab irá tornar este um tokenization menos "desejável" para o algoritmo.

Opcional: tf.lookup

Se você precisar acessar ou ter mais controle sobre o vocabulário, é importante notar que você mesmo pode construir a tabela de pesquisa e passá- BertTokenizer para BertTokenizer .

Quando você passa uma string, BertTokenizer faz o seguinte:

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)

Agora você tem acesso direto à tabela de pesquisa usada no tokenizer.

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

Você não precisa usar um arquivo de vocabulário, tf.lookup tem outras opções de inicializador. Se você tiver o vocabulário na memória, pode usar 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)