Ver no TensorFlow.org | Executar no Google Colab | Ver fonte no GitHub | Baixar caderno |
Este tutorial demonstra como gerar um vocabulário subword a partir de um conjunto de dados, e usá-lo para construir uma text.BertTokenizer
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 tensorflow_text
pacote inclui implementações TensorFlow de muitos tokenizers comuns. Isso inclui três tokenizadores de estilo de subpalavra:
-
text.BertTokenizer
- OBertTokenizer
classe é uma interface de nível superior. Ele inclui algoritmo de divisão simbólica do BERT e umaWordPieceTokenizer
. Leva frases como entrada e retorna token-IDs. -
text.WordpieceTokenizer
- OWordPieceTokenizer
classe é uma interface de nível inferior. Ele só implementa o algoritmo WordPiece . Você deve padronizar e dividir o texto em palavras antes de chamá-lo. É preciso palavras como entrada e retorna token-IDs. -
text.SentencepieceTokenizer
- OSentencepieceTokenizer
requer uma configuração mais complexa. Seu inicializador requer um modelo de frase pré-treinado. Veja o repositório google / sentencepiece para obter instruções sobre como construir um destes modelos. Ele pode aceitar frases como entrada quando tokenizing.
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 línguas conside usando 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
Buscar o Português / Inglês dataset tradução do 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 peças de um conjunto de dados. Se você já tem um arquivo de vocabulário e só quero ver como construir um text.BertTokenizer
ou text.Wordpiece
tokenizer com ele, então você pode pular para a Criar o tokenizer seção.
O código de geração de vocabulário está incluído no tensorflow_text
pacote 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
O bert_vocab.bert_vocab_from_dataset
função 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ê quer aprender mais sobre as opções, primeiro ler sobre o algoritmo , em seguida, ter um olhar para o 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 30s, sys: 2.21 s, total: 1min 32s Wall time: 1min 28s
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 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'] ['##_', '##`', '##ย', '##ร', '##อ', '##–', '##—', '##’', '##♪', '##♫']
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
podem ser inicializados passando o caminho do arquivo de vocabulário como o primeiro argumento (veja a seção sobre 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 ."
Executá-lo através do BertTokenizer.tokenize
método. Inicialmente, isso retorna um tf.RaggedTensor
com machados (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 token com suas representações de texto (usando tf.gather
) você pode ver que no primeiro exemplo as palavras "searchability"
e "serendipity"
foram decompostos 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 re-montar palavras a partir das fichas extraídos, utilizar o BertTokenizer.detokenize
método:
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 constrói o tokenizer texto e detokenizer usado pelo Transformer tutorial. Esta seção acrescenta métodos e etapas de processamento para simplificar esse tutorial, e exporta o tokenizers usando tf.saved_model
para que possam ser importados por outros tutoriais.
Tokenização personalizada
Os tutoriais jusante ambos espera que o texto tokenized para incluir [START]
e [END]
fichas.
O reserved_tokens
espaço reserva no início do vocabulário, então [START]
e [END]
têm os mesmos índices para ambas as línguas:
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:
- Eles querem gerar saída de texto limpo, por isso cair fichas reservados como
[START]
,[END]
e[PAD]
. - Eles estão interessados em cordas completas, por isso, aplicar uma seqüência de juntar-se ao longo das
words
eixo 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 constrói um CustomTokenizer
classe para conter os text.BertTokenizer
casos, a lógica personalizada, e os @tf.function
wrappers 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)
Construir uma CustomTokenizer
para cada idioma:
tokenizers = tf.Module()
tokenizers.pt = CustomTokenizer(reserved_tokens, 'pt_vocab.txt')
tokenizers.en = CustomTokenizer(reserved_tokens, 'en_vocab.txt')
Exportar os tokenizers como 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.
Recarregar o saved_model
e testar 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 !
Arquivá-lo 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/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
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. "
O original algoritmo WordPiece de baixo para cima , é baseado em codificação byte de par . Como o BPE, ele começa com o alfabeto e combina iterativamente bigramas comuns para formar pedaços de palavras e palavras.
Gerador de vocabulário de TensorFlow texto segue a implementação de cima para baixo a partir 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 que você precisa de uma abordagem diferente .
Escolhendo o vocabulário
O algoritmo de geração de WordPiece de cima para baixo leva em um conjunto de (palavra, contagem) pares e um limiar T
, e retorna um vocabulário V
.
O algoritmo é iterativo. É gerido por k
iterações, onde normalmente k = 4
, mas apenas os dois primeiros são realmente importantes. O terceiro e o quarto (e além) são idênticos ao segundo. Note-se que cada etapa da busca binária é executado o algoritmo a partir do zero para k
iterações.
As iterações descritas abaixo:
Primeira iteração
- Itera sobre cada palavra e par contagem na entrada, indicado como
(w, c)
. - Para cada palavra
w
, gerar cada substring, denotado comos
. Por exemplo, para a palavrahuman
, geramos{h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n}
. - Manter um mapa de hash subsequência-a-count, e incrementa a contagem de cada um
s
porc
. Por exemplo, se tivermos(human, 113)
e(humas, 3)
na nossa entrada, a contagem des = huma
será113+3=116
. - Assim que tiver recolhido as contagens de cada substring, iterar sobre os
(s, c)
pares começando com a mais longas
primeiro. - Mantenha todas as
s
que tem umc > T
. Por exemplo, seT = 100
e temos(pers, 231); (dogs, 259); (##rint; 76)
, então poderíamos manterpers
edogs
. - Quando um
s
é mantido, subtrair fora de sua contagem de todos os seus prefixos. Esta é a razão para a triagem de todos oss
por tamanho no passo 4. Esta é uma parte crítica do algoritmo, porque de outro modo seria palavras dupla contados. Por exemplo, digamos que mantivemoshuman
e nós conseguimos(huma, 116)
. Sabemos que113
dos116
veio dohuman
, e3
veio dehumas
. No entanto, agora quehuman
está em nosso vocabulário, sabemos que nunca será segmentohuman
emhuma ##n
. Assim, uma vezhuman
tem sido mantido, em seguida,huma
só tem uma contagem efetiva de3
.
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: este 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
, vamos subtrair off a contagem para h, hu, hu, huma
, mas não para ##u, ##um, ##uma, ##uman
e assim por diante. Assim, poderíamos gerar tanto human
e ##uman
como peças palavra, embora ##uman
nunca vai ser aplicado.
Então por que não subtrair off a contagem para cada substring, não apenas cada prefixo? Porque então poderíamos acabar subtraindo da contagem várias vezes. Digamos que estamos processando s
de comprimento 5 e nós manter tanto (##denia, 129)
e (##eniab, 137)
, em que 65
dessas contagens veio da palavra undeniable
. Se subtrairmos fora de todos os substring, nós subtrair 65
do substring ##enia
duas vezes, embora nós só deve subtrair 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.
Iterações subseqüentes são idênticos ao primeiro, com uma diferença importante: Na etapa 2, em vez de considerar cada substring, aplicamos o algoritmo WordPiece tokenization usando o vocabulário da iteração anterior, e considerar apenas substrings que começam em um ponto de divisão.
Por exemplo, digamos que estamos realizando passo 2 do algoritmo e encontrar a palavra undeniable
. Na primeira iteração, poderíamos considerar 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 segmento de vontade algoritmo WordPiece isso em un ##deni ##able
(consulte a seção WordPiece Aplicando para mais informações). Neste caso, só vamos considerar substrings que começam em um ponto de segmentação. Nós ainda vamos considerar cada posição final possível. Assim, 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 de outra forma. Neste exemplo, na primeira iteração, o algoritmo produz o suprious fichas ##ndeni
e ##iable
. Agora, esses tokens nunca são considerados, portanto, 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
.
Nós primeira pesquisa undeniable
em nosso dicionário WordPiece, e se ele está presente, estamos a fazer. Se não, vamos diminuir o ponto final de um caractere, e repita, 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>
).
Neste caso, encontramos un
em nosso vocabulário. Então essa é a nossa primeira palavra. Em seguida, saltar para o fim de un
e repita o processamento, por exemplo, tentar encontrar ##deniable
, em seguida, ##deniabl
, etc. Isto é repetido até que tenhamos segmentado a palavra inteira.
Intuição
Intuitivamente, a tokenização do WordPiece tenta satisfazer dois objetivos diferentes:
Tokenizar os dados para o menor número de peças possível. É importante ter em mente que o algoritmo WordPiece não "quer" dividir palavras. Caso contrário, seria apenas dividir cada palavra em seus personagens, por exemplo,
human -> {h, ##u, ##m, ##a, #n}
. Isso é uma coisa crítica que faz WordPiece diferente de divisores morfológicas, que irá dividir morfemas linguísticas mesmo para palavras comuns (por exemplo,unwanted -> {un, want, ed}
).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 dividido em{un, ##deni, ##able}
ao invés de alternativas como{unde, ##niab, ##le}
é que as contagens paraun
e##able
de 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 deunde
e##niab
irá tornar este um tokenization menos "desejável" para o algoritmo.
Opcional: tf.lookup
Se você precisa de acesso ou mais controle sobre o vocabulário é importante notar que você pode construir a tabela de referência a si mesmo e passar isso para BertTokenizer
.
Quando você passar 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 inicializador. Se você tem o vocabulário de memória que pode utilizar 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)