Tokenizzatori di sottoparole

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino

Questo tutorial mostra come generare un vocabolario sottoparola da un set di dati, e utilizzarlo per costruire un text.BertTokenizer dal vocabolario.

Il vantaggio principale di un tokenizer di sottoparole è che interpola tra tokenizzazione basata su parole e basata su caratteri. Le parole comuni ottengono uno spazio nel vocabolario, ma il tokenizzatore può ricorrere a pezzi di parole e singoli caratteri per parole sconosciute.

Panoramica

La tensorflow_text pacchetto comprende tensorflow implementazioni di molti tokenizers comuni. Ciò include tre tokenizer in stile sottoparola:

  • text.BertTokenizer - Il BertTokenizer classe è un'interfaccia di alto livello. Esso comprende l'algoritmo di token di scissione BERT e un WordPieceTokenizer . Prende frasi come input e restituisce token-ID.
  • text.WordpieceTokenizer - Il WordPieceTokenizer classe è un'interfaccia di livello inferiore. Esso implementa solo l' algoritmo di WordPiece . Devi standardizzare e dividere il testo in parole prima di chiamarlo. Prende le parole come input e restituisce token-ID.
  • text.SentencepieceTokenizer - Il SentencepieceTokenizer richiede una configurazione più complessa. Il suo inizializzatore richiede un modello di frase pre-addestrato. Vedere la repository di Google / sentencepiece per le istruzioni su come costruire uno di questi modelli. Si può accettare frasi come input quando creazione di token.

Questo tutorial costruisce un vocabolario di Wordpiece in modo dall'alto verso il basso, partendo da parole esistenti. Questo processo non funziona per il giapponese, il cinese o il coreano poiché queste lingue non hanno unità chiare a più caratteri. Per tokenize nelle lingue conside utilizzando text.SentencepieceTokenizer , text.UnicodeCharTokenizer o questo approccio .

Impostare

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

Scarica il set di dati

Fetch la traduzione dataset portoghese / inglese da 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']

Questo set di dati produce coppie di frasi portoghese/inglese:

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 .

Nota alcune cose sulle frasi di esempio sopra:

  • Sono minuscole.
  • Ci sono spazi intorno alla punteggiatura.
  • Non è chiaro se o quale normalizzazione unicode venga utilizzata.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

Genera il vocabolario

Questa sezione genera un vocabolario di parole da un set di dati. Se si dispone già di un file di vocabolario e desidera solo per vedere come costruire un text.BertTokenizer o text.Wordpiece tokenizer con esso poi si può passare direttamente alla Costruire il tokenizzatore sezione.

Il codice vocabolario generazione è incluso nel tensorflow_text pacchetto pip. Non è importato per impostazione predefinita, è necessario importarlo manualmente:

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

La bert_vocab.bert_vocab_from_dataset funzione genera il vocabolario.

Ci sono molti argomenti che puoi impostare per regolare il suo comportamento. Per questo tutorial, utilizzerai principalmente le impostazioni predefinite. Se volete saperne di più sulle opzioni, prima leggere l'algoritmo , e poi dare un'occhiata al codice .

Questo richiede circa 2 minuti.

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

Ecco alcune sezioni del vocabolario risultante.

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

Scrivi un file di vocabolario:

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)

Usa quella funzione per generare un vocabolario dai dati in inglese:

%%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']
['##_', '##`', '##ย', '##ร', '##อ', '##–', '##—', '##’', '##♪', '##♫']

Ecco i due file di vocabolario:

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

Costruisci il tokenizzatore

Il text.BertTokenizer può essere inizializzato passando il percorso del file di vocabolario come primo argomento (si veda la sezione tf.lookup per altre opzioni):

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

Ora puoi usarlo per codificare del testo. Prendi una serie di 3 esempi dai dati inglesi:

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

Eseguire attraverso il BertTokenizer.tokenize metodo. Inizialmente, questo restituisce un tf.RaggedTensor ad assi (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 si sostituiscono gli ID token con le loro rappresentazioni di testo (utilizzando tf.gather ) si può vedere che nel primo esempio le parole "searchability" e "serendipity" sono state decomposto in "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)>

Per riassemblare parole dai token estratti, utilizzare il BertTokenizer.detokenize metodo:

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

Personalizzazione ed esportazione

Questa esercitazione si basa il tokenizzatore testo e detokenizer utilizzato dal trasformatore tutorial. Questa sezione aggiunge metodi e fasi di lavorazione per semplificare che un'esercitazione, ed esporta il tokenizers utilizzando tf.saved_model in modo che possano essere importati dagli altri tutorial.

Tokenizzazione personalizzata

I tutorial valle entrambi si aspettano che il testo in formato token di includere [START] e [END] gettoni.

Il reserved_tokens spazio riserva all'inizio del vocabolario, in modo [START] e [END] hanno gli stessi indici per entrambe le lingue:

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

Detokenizzazione personalizzata

Prima di esportare i tokenizer ci sono un paio di cose che puoi pulire per i tutorial downstream:

  1. Vogliono di generare output di testo pulito, in modo da eliminare i token riservati come [START] , [END] e [PAD] .
  2. Sono interessati a stringhe complete, in modo da applicare una stringa unirsi a lungo le words asse del risultato.
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)

Esportare

Il seguente blocco di codice crea un CustomTokenizer classe per contenere i text.BertTokenizer casi, la logica personalizzata, e le @tf.function involucri richieste per l'esportazione.

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)

Costruire un CustomTokenizer per ogni lingua:

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

Esportare le tokenizers come 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.

Ricaricare il saved_model e testare i metodi:

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 !

Archiviare per le esercitazioni di traduzione :

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

Opzionale: l'algoritmo

Vale la pena notare qui che ci sono due versioni dell'algoritmo WordPiece: Bottom-up e top-down. In entrambi i casi l'obiettivo è lo stesso: "Dato un corpus di addestramento e un numero di token desiderati D, il problema di ottimizzazione è selezionare D wordpiece in modo tale che il corpus risultante sia minimo nel numero di wordpiece quando segmentato secondo il modello di wordpiece scelto. "

L'originale algoritmo WordPiece bottom-up , si riferiscono al codifica byte-pair . Come BPE, inizia con l'alfabeto e combina in modo iterativo bigrammi comuni per formare frammenti di parole e parole.

Generatore di vocabolario di tensorflow testo segue l'implementazione top-down dal BERT . Iniziando con le parole e scomponendole in componenti più piccoli finché non raggiungono la soglia di frequenza, o non possono essere ulteriormente scomposte. La sezione successiva lo descrive in dettaglio. Per il giapponese, il cinese e il coreano questo approccio dall'alto verso il basso non funziona poiché non ci sono unità di parole esplicite con cui iniziare. Per coloro che è necessario un approccio diverso .

La scelta del vocabolario

L'algoritmo di generazione WordPiece top-down prende in un insieme di parole, (valore) coppie e una soglia T , e ritorna un vocabolario V .

L'algoritmo è iterativo. È gestito per k iterazioni, dove tipicamente k = 4 , ma solo i primi due sono molto importanti. Il terzo e il quarto (e oltre) sono semplicemente identici al secondo. Nota che ogni passo della ricerca binaria viene eseguito l'algoritmo da zero per k iterazioni.

Le iterazioni descritte di seguito:

Prima iterazione

  1. Iterare su ogni parola e coppia conteggio nel ingresso, indicato come (w, c) .
  2. Per ogni parola w , generare ogni sottostringa, indicato come s . Ad esempio, per la parola human , abbiamo generare {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Mantenere una mappa hash stringa-to-count, e incrementare il conteggio di ogni s dal c . Ad esempio, se abbiamo (human, 113) e (humas, 3) nel nostro input, il conte di s = huma sarà 113+3=116 .
  4. Una volta che abbiamo raccolto i conti di ogni stringa, iterate nel corso degli (s, c) coppie di partire con la più lunga s prima.
  5. Tenere tutti s che ha un c > T . Ad esempio, se T = 100 e abbiamo (pers, 231); (dogs, 259); (##rint; 76) , allora avremmo tenere pers e dogs .
  6. Quando un s è tenuto, sottrarre fuori il suo numero da tutte le sue prefissi. Questo è il motivo per ordinamento tutti i s per lunghezza nel passo 4. Questa è una parte critica dell'algoritmo, perché altrimenti parole sarebbero doppio contati. Per esempio, diciamo che abbiamo mantenuto human e si arriva a (huma, 116) . Sappiamo che 113 di quei 116 provenivano da human , e 3 venuti humas . Tuttavia, ora che human è nel nostro vocabolario, sappiamo che non sarà mai segmento human in huma ##n . Quindi, una volta human è stato mantenuto, quindi huma ha solo un conteggio effettivo del 3 .

Questo algoritmo genera una serie di pezzi di parole s (molti dei quali saranno parole intere w ), che potremmo usare come nostro vocabolario WordPiece.

Tuttavia, c'è un problema: questo algoritmo genererà in modo eccessivo i pezzi di parole. Il motivo è che sottraiamo solo i conteggi dei token del prefisso. Pertanto, se osserviamo la parola human , noi sottrarre fuori il conteggio per h, hu, hu, huma , ma non per ##u, ##um, ##uma, ##uman e così via. Così potremmo generare sia human e ##uman come pezzi di parola, anche se ##uman non sarà mai applicata.

Allora perché non sottrarre fuori i conteggi per ogni stringa, non solo ogni prefisso? Perché così potremmo finire per sottrarre dai conteggi più volte. Diciamo che siamo l'elaborazione s di lunghezza di 5 e ci tengono entrambi (##denia, 129) e (##eniab, 137) , dove 65 di questi conteggi è venuto dalla parola undeniable . Se sottraiamo fuori da ogni stringa, vorremmo sottrarre 65 dalla stringa ##enia due volte, anche se dovremmo sottrarre solo una volta. Tuttavia, se sottraiamo solo dai prefissi, verrà sottratto correttamente solo una volta.

Seconda (e terza...) iterazione

Per risolvere il problema di sovragenerazione sopra menzionato, eseguiamo più iterazioni dell'algoritmo.

Iterazioni successive sono identiche alla prima, con una distinzione importante: Nel passo 2, invece di considerare ogni stringa, si applica l'algoritmo WordPiece tokenization utilizzando il vocabolario iterazione precedente e consideriamo solo sottostringhe che si aprono su un punto di divisione.

Per esempio, diciamo che stiamo eseguito il punto 2 della algoritmo e incontrare la parola undeniable . Nella prima iterazione, potremmo considerare ogni stringa, ad esempio, {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} .

Ora, per la seconda iterazione, considereremo solo un sottoinsieme di questi. Diciamo che dopo la prima iterazione, i pezzi di parole rilevanti sono:

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

Il segmento volontà algoritmo WordPiece questo in un ##deni ##able (vedi la sezione Applicazione WordPiece per maggiori informazioni). In questo caso, prenderemo in considerazione solo stringhe che partono da un punto di segmentazione. Ci sarà ancora in considerazione ogni possibile posizione finale. Così durante la seconda iterazione, l'insieme di s per undeniable è:

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

L'algoritmo è per il resto identico. In questo esempio, nella prima iterazione, l'algoritmo produce il suprious gettoni ##ndeni e ##iable . Ora, questi token non vengono mai considerati, quindi non verranno generati dalla seconda iterazione. Eseguiamo diverse iterazioni solo per assicurarci che i risultati convergano (sebbene non vi sia alcuna garanzia di convergenza letterale).

Applicazione di WordPiece

Una volta che un vocabolario WordPiece è stato generato, dobbiamo essere in grado di applicarlo a nuovi dati. L'algoritmo è una semplice applicazione avida di corrispondenza più lunga.

Si consideri ad esempio la segmentazione della parola undeniable .

In primo luogo abbiamo lookup undeniable nel nostro dizionario WordPiece, e se è presente, abbiamo finito. In caso contrario, si decrementa il punto finale di un carattere, e ripetere, ad esempio, undeniabl .

Alla fine, troveremo un sottotoken nel nostro vocabolario, o scenderemo a un sottotoken di un singolo carattere. (In generale, si assume che ogni personaggio è nel nostro vocabolario, anche se questo potrebbe non essere il caso per i caratteri Unicode rare. Se incontriamo un personaggio rara Unicode che non è nel vocabolario abbiamo semplicemente mappare l'intera parola a <unk> ).

In questo caso, troviamo un nel nostro vocabolario. Quindi questo è il nostro primo pezzo di parola. Poi saltiamo alla fine di un e ripetere il trattamento, ad esempio, cercare di trovare ##deniable , poi ##deniabl , ecc Questo si ripete finché non avremo segmentato l'intera parola.

Intuizione

Intuitivamente, la tokenizzazione di WordPiece sta cercando di soddisfare due diversi obiettivi:

  1. Tokenize i dati nel minor numero di pezzi possibile. È importante tenere presente che l'algoritmo WordPiece non "vuole" dividere le parole. In caso contrario, sarebbe solo dividere ogni parola nel suo personaggi, ad esempio, human -> {h, ##u, ##m, ##a, #n} . Questa è una cosa fondamentale che rende WordPiece diverso da splitter morfologiche, che si dividerà morfemi linguistiche, anche per le parole comuni (ad esempio, unwanted -> {un, want, ed} ).

  2. Quando una parola deve essere divisa in pezzi, suddividila in pezzi che hanno un numero massimo di conteggi nei dati di addestramento. Ad esempio, il motivo per cui la parola undeniable sarebbe diviso in {un, ##deni, ##able} piuttosto che alternative come {unde, ##niab, ##le} è che i conteggi per un e ##able di particolare sarà molto alto, poiché si tratta di prefissi e suffissi comuni. Anche se il conteggio per ##le deve essere superiore a ##able , le basse conti di unde e ##niab faranno di questo un meno tokenizzazione "desiderabile" per l'algoritmo.

Opzionale: tf.lookup

Se è necessario accedere a, o un maggiore controllo sul vocabolario vale la pena notare che si può costruire la tabella di ricerca stessi e passare che a BertTokenizer .

Quando si passa una stringa, BertTokenizer fa il seguente:

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)

Ora hai accesso diretto alla tabella di ricerca utilizzata nel tokenizer.

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

Non è necessario utilizzare un file di vocabolario, tf.lookup ha altre opzioni di inizializzazione. Se avete il vocabolario in memoria è possibile utilizzare 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)