Tokenizery podsłów

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten poradnik pokazuje, jak wygenerować słownictwa podsłowo z zestawu danych i użyć go do budowy text.BertTokenizer ze słownika.

Główną zaletą tokenizera podsłów jest to, że interpoluje między tokenizacją opartą na słowach i znakach. Popularne słowa zyskują miejsce w słowniku, ale tokenizer może wrócić do fragmentów słów i pojedynczych znaków dla nieznanych słów.

Przegląd

tensorflow_text Pakiet zawiera implementacje TensorFlow wielu wspólnych tokenizers. Obejmuje to trzy tokenizatory w stylu podsłów:

  • text.BertTokenizer - The BertTokenizer klasa jest interfejsem wyższy poziom. Obejmuje on symboliczny algorytm dzielenia Berta i WordPieceTokenizer . Zajmuje zdań na wejściu i zwraca token-identyfikatorów.
  • text.WordpieceTokenizer - The WordPieceTokenizer klasa jest interfejsem niższy poziom. Implementuje tylko algorytm WordPiece . Musisz ustandaryzować i podzielić tekst na słowa przed jego wywołaniem. Zajmuje słowa jako wejście i zwraca token-identyfikatorów.
  • text.SentencepieceTokenizer - The SentencepieceTokenizer wymaga bardziej złożonej konfiguracji. Jego inicjator wymaga wstępnie wytrenowanego modelu zdania. Zobacz repozytorium google / sentencepiece instrukcje, w jaki sposób zbudować jeden z tych modeli. Może on przyjąć zdań jako wejście podczas tokenizing.

Ten samouczek buduje słownictwo Wordpiece w sposób odgórny, zaczynając od istniejących słów. Ten proces nie działa w przypadku japońskiego, chińskiego ani koreańskiego, ponieważ te języki nie mają wyraźnych jednostek wieloznakowych. Do tokenize te języki rozważaniach pomocą text.SentencepieceTokenizer , text.UnicodeCharTokenizer czy to podejście .

Ustawiać

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

Pobierz zbiór danych

Pobrać portugalski / angielskie zestawu danych z 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']

Ten zbiór danych tworzy pary zdań portugalski/angielski:

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 .

Zwróć uwagę na kilka rzeczy o przykładowych zdaniach powyżej:

  • Są małymi literami.
  • Wokół znaków interpunkcyjnych znajdują się spacje.
  • Nie jest jasne, czy i jaka normalizacja Unicode jest używana.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

Wygeneruj słownictwo

Ta sekcja generuje słownictwo słówek z zestawu danych. Jeśli masz już plik słownictwo i po prostu chcą zobaczyć, jak zbudować text.BertTokenizer lub text.Wordpiece tokenizera ze to wtedy można przejść od razu do produkcji tokenizera przekroju.

Kod generacja słownictwo zawarte w tensorflow_text opakowaniu PIP. Nie jest domyślnie importowany, musisz go zaimportować ręcznie:

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

bert_vocab.bert_vocab_from_dataset funkcja wygeneruje słownictwo.

Istnieje wiele argumentów, które możesz ustawić, aby dostosować jego zachowanie. W tym samouczku użyjesz głównie ustawień domyślnych. Jeśli chcesz dowiedzieć się więcej o opcjach, należy najpierw przeczytać o algorytmie , a następnie spojrzeć na kodzie .

Zajmuje to około 2 minut.

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

Oto kilka fragmentów powstałego słownictwa.

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

Napisz plik słownika:

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)

Użyj tej funkcji, aby wygenerować słownictwo z danych w języku angielskim:

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

Oto dwa pliki słowników:

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

Zbuduj tokenizer

text.BertTokenizer mogą być inicjowane przez przechodząc ścieżkę pliku słownictwo jako pierwszy argument (patrz rozdział tf.lookup innych opcji):

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

Teraz możesz go użyć do zakodowania tekstu. Weź partię 3 przykładów z danych angielskich:

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

Uruchom go poprzez BertTokenizer.tokenize metody. Początkowo ta zwraca tf.RaggedTensor z osiami (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]

W przypadku wymiany tokena identyfikatory ze swoimi reprezentacjami tekstowych (używając tf.gather ) widać, że w pierwszym przykładzie słowa "searchability" i "serendipity" została rozłożona na "search ##ability" i "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)>

Aby ponownie zmontować słów wyodrębnionych żetonów użyć BertTokenizer.detokenize sposób:

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

Dostosowywanie i eksport

Ten poradnik buduje tokenizera tekstu i detokenizer używany przez Transformer tutoriala. Sekcja ta dodaje metody i etapy przetwarzania w celu uproszczenia tego samouczka i wywozi tokenizers użyciu tf.saved_model więc mogą być importowane przez innych samouczków.

Tokenizacja niestandardowa

Dalsi tutoriale oba oczekiwać tekst tokenized obejmować [START] i [END] żetony.

reserved_tokens miejsca rezerwowe na początku słownictwa, więc [START] i [END] mają takie same indeksy dla obu językach:

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

Detokenizacja niestandardowa

Przed wyeksportowaniem tokenizerów jest kilka rzeczy, które możesz wyczyścić w kolejnych samouczkach:

  1. Chcą, aby wygenerować czystego tekstu wyjściowego, tak jak kropla zastrzeżone znaki [START] , [END] i [PAD] .
  2. Są zainteresowani w kompletnych ciągów, więc stosuje się ciąg dołączyć wzdłuż words osi wyniku.
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)

Eksport

Poniższy blok kodu buduje CustomTokenizer klasę będzie zawierał text.BertTokenizer przypadkach logiki niestandardowej, a @tf.function owijarki wymaganych do eksportu.

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)

Budowanie CustomTokenizer dla każdego języka:

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

Wyeksportować tokenizers jak 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.

Przeładować saved_model i przetestowanie metod:

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 !

Zarchiwizować go do ćwiczeń tłumaczeniowych :

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

Opcjonalnie: algorytm

Warto tutaj zauważyć, że istnieją dwie wersje algorytmu WordPiece: oddolna i odgórna. W obu przypadkach cel jest taki sam: „Mając korpus treningowy i liczbę pożądanych tokenów D, problem optymalizacji polega na takim doborze słów D, aby wynikowy korpus zawierał minimalną liczbę słów w przypadku segmentacji zgodnie z wybranym modelem słów. "

Oryginalny algorytm WordPiece oddolne , opiera się na kodowaniu bajt pary . Podobnie jak BPE, zaczyna się od alfabetu i iteracyjnie łączy popularne bigramy, tworząc wyrazy i słowa.

Generator słownictwo TensorFlow tekstowego następuje wdrożenie odgórne z BERT . Zaczynając od słów i dzieląc je na mniejsze części, aż osiągną próg częstotliwości lub nie można ich dalej rozbić. W następnej sekcji szczegółowo to opisano. W przypadku języka japońskiego, chińskiego i koreańskiego to podejście odgórne nie działa, ponieważ nie ma wyraźnych jednostek wyrazów, od których można by zacząć. Dla tych, trzeba innego podejścia .

Wybór słownictwa

Algorytm odgórne WordPiece wytwarzanie odbywa się w zestawie (słowo, liczba) pary i wartości progowej T , i zwraca słowa V .

Algorytm jest iteracyjny. Jest prowadzony przez k iteracjach, gdzie zazwyczaj k = 4 , ale tylko dwa pierwsze są naprawdę ważne. Trzecia i czwarta (i kolejne) są identyczne z drugim. Należy pamiętać, że każdy etap poszukiwania binarnego działa algorytm od podstaw dla k iteracji.

Iteracje opisane poniżej:

Pierwsza iteracja

  1. Iteracyjnego każdego słowa i pary na wejściu licznika, oznaczoną jako (w, c) .
  2. Dla każdego słowa w , generowanie każdy podciąg, oznaczony jako s . Na przykład, na słowo human generujemy {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Utrzymanie podciągu, do zliczania mieszania mapę i zwiększać liczbę od siebie s o c . Na przykład, jeśli mamy (human, 113) i (humas, 3) w naszym wejściu, liczba s = huma będzie 113+3=116 .
  4. Po zebraliśmy hrabiów każdego fragmentu, iteracyjne nad (s, c) par zaczynając od najdłuższych s pierwszy.
  5. Zachować wszelkie s , który ma c > T . Na przykład, jeśli T = 100 i ma (pers, 231); (dogs, 259); (##rint; 76) , a następnie chcielibyśmy zachować pers i dogs .
  6. Gdy s jest trzymane, odjąć od swojej liczyć od wszystkich jego prefiksów. To jest powód do sortowania wszystkich s przez długość w kroku 4. Jest to kluczowa część algorytmu, bo inaczej słowa byłyby liczone podwójnie. Na przykład, powiedzmy, że mamy przechowywane human i dojeżdżamy do (huma, 116) . Wiemy, że 113 z tych 116 pochodzi od human , i 3 pochodzą z humas . Teraz jednak, że human jest w naszym słownictwie, wiemy, że nigdy segmentu human do huma ##n . Więc raz human został zachowany, a następnie huma ma tylko skuteczną liczbę 3 .

Ten algorytm wygeneruje zestaw kawałków słowne s (z których wiele będzie całe słowa w ), które moglibyśmy wykorzystać w naszym słownictwie WordPiece.

Istnieje jednak problem: ten algorytm poważnie nadgeneruje fragmenty słów. Powodem jest to, że odejmujemy tylko liczbę tokenów prefiksu. Dlatego, jeśli mamy zachować słowo human , będziemy odejmować od licznika do h, hu, hu, huma , ale nie dla ##u, ##um, ##uma, ##uman i tak dalej. Więc możemy generować zarówno human i ##uman jak kawałki słowem, chociaż ##uman nie będzie stosowana.

Więc dlaczego nie odejmować off liczniki dla każdego fragmentu, a nie tylko każdy prefiks? Ponieważ wtedy moglibyśmy wielokrotnie odejmować liczby. Załóżmy, że jesteśmy przetwarzanie s długości 5 i trzymamy zarówno (##denia, 129) i (##eniab, 137) , gdzie 65 z nich liczy dostarczonej od słowa undeniable . Jeśli odejmiemy od od każdego fragmentu, by odjąć 65 od podciągu ##enia dwukrotnie, choć powinniśmy odjąć tylko raz. Jeśli jednak odejmiemy tylko od przedrostków, zostanie on poprawnie odjęty tylko raz.

Druga (i trzecia...) iteracja

Aby rozwiązać wspomniany powyżej problem nadgeneracji, wykonujemy wielokrotne iteracje algorytmu.

Kolejne iteracje są identyczne z pierwszym, z jednym ważnym wyróżnieniem: W kroku 2, zamiast rozważa każdy podciąg, możemy zastosować algorytm WordPiece tokenizacja używając słownictwa z poprzedniej iteracji, a dopiero pod podciągi, które rozpoczynają się w punkcie podziału.

Na przykład, powiedzmy, że jesteśmy wykonując krok 2 algorytmu i spotkać słowo undeniable . W pierwszej iteracji, rozważymy każdy podciąg, np {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} .

Teraz, w drugiej iteracji, rozważymy tylko ich podzbiór. Załóżmy, że po pierwszej iteracji odpowiednie fragmenty słów to:

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

Segment wola algorytm WordPiece to pod un ##deni ##able (patrz punkt Stosowanie WordPiece uzyskać więcej informacji). W tym przypadku, będziemy brać pod uwagę tylko podciągi, które rozpoczynają się w punkcie segmentacji. Będziemy nadal rozważać każdą możliwą pozycję końcową. Więc podczas drugiej iteracji, zestaw s dla undeniable jest:

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

Poza tym algorytm jest identyczny. W tym przykładzie, w pierwszej iteracji, algorytm produkuje suprious tokeny ##ndeni i ##iable . Teraz te tokeny nigdy nie są brane pod uwagę, więc nie zostaną wygenerowane w drugiej iteracji. Wykonujemy kilka iteracji, aby upewnić się, że wyniki są zbieżne (chociaż nie ma gwarancji zbieżności dosłownej).

Stosowanie WordPiece

Po wygenerowaniu słownika WordPiece musimy mieć możliwość zastosowania go do nowych danych. Algorytm jest prostą, zachłanną aplikacją typu najdłuższy mecz.

Rozważmy na przykład segmentacji słowo undeniable .

My najpierw wyszukiwanie undeniable w naszym słowniku WordPiece, a jeśli jest obecny, skończymy. Jeśli nie, możemy zmniejszyć punkt końcowy o jeden znak i powtórzyć, na przykład undeniabl .

W końcu albo znajdziemy podtoken w naszym słowniku, albo przejdziemy do podtokenu pojedynczego znaku. (Na ogół zakładamy, że każda postać jest w naszym słownictwie, chociaż to może nie być w przypadku rzadkich znaków Unicode. Jeśli mamy do czynienia z rzadką znak Unicode, który nie jest w słownictwie po prostu odwzorować całe słowo <unk> ).

W tym przypadku możemy znaleźć un w naszym słownictwie. Więc to jest nasz pierwszy kawałek słowny. Potem skok do końca un i powtórzyć przetwarzanie, np spróbować znaleźć ##deniable , następnie ##deniabl , itd. To jest powtarzane, dopóki nie segmentowane cały wyraz.

Intuicja

Intuicyjnie tokenizacja WordPiece stara się spełnić dwa różne cele:

  1. Tokenize dane do najmniejszej liczby sztuk, jak to możliwe. Należy pamiętać, że algorytm WordPiece nie „chce” dzielić słów. W przeciwnym razie byłoby to po prostu podzielić każde słowo w jej znaków, np human -> {h, ##u, ##m, ##a, #n} . To jedna rzecz, która sprawia, że krytyczne WordPiece różni się od rozgałęźników morfologicznych, które będą podzielone morfemów językowych nawet dla zwykłych słów (np unwanted -> {un, want, ed} ).

  2. Kiedy słowo musi zostać podzielone na części, podziel je na części, które mają maksymalną liczbę w danych uczących. Na przykład, powód, dla którego słowo undeniable byłby podział na {un, ##deni, ##able} zamiast alternatywnych, takich jak {unde, ##niab, ##le} jest to, że liczy się dla un i ##able w szczególnie będą bardzo wysokie, ponieważ są to wspólne przedrostki i przyrostki. Nawet jeśli liczba dla ##le musi być wyższa niż ##able , niskie zliczenia unde i ##niab będzie to mniej „pożądane” tokenizacja algorytmu.

Opcjonalnie: tf.lookup

Jeśli potrzebujesz dostępu do lub większą kontrolę nad słownictwa Warto zauważyć, że można zbudować tabeli przeglądowej siebie i przekazać, że do BertTokenizer .

Po przejechaniu ciąg, BertTokenizer wykonuje następujące czynności:

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)

Teraz masz bezpośredni dostęp do tabeli przeglądowej używanej w tokenizerze.

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

Nie trzeba użyć pliku słownictwa, tf.lookup ma inne opcje initializer. Jeśli masz słownictwo w pamięci można użyć 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)