День сообщества ML - 9 ноября! Присоединяйтесь к нам для обновления от TensorFlow, JAX, и многое другое Подробнее

Токенизаторы подслов

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

В этом учебнике показано , как создать подслово словарь из набора данных, и использовать его для создания text.BertTokenizer из словаря.

Основное преимущество токенизатора подсловов состоит в том, что он выполняет интерполяцию между токенизацией на основе слов и символов. Обычные слова получают место в словарном запасе, но токенизатор может вернуться к частям слова и отдельным символам для неизвестных слов.

Обзор

tensorflow_text пакет включает в себя реализацию TensorFlow многих распространенных tokenizers. Сюда входят три токенизатора в стиле подслова:

  • text.BertTokenizer - The BertTokenizer класс является высокоуровневым интерфейсом. Она включает в себя лексемы алгоритм расщепления Берта и WordPieceTokenizer . Он принимает предложения в качестве входных данных и возвращает токен-идентификаторы.
  • text.WordpeiceTokenizer - The WordPieceTokenizer класс является нижним интерфейсом уровня. Он только реализует алгоритм WordPiece . Вы должны стандартизировать и разбить текст на слова, прежде чем вызывать его. Он принимает слова в качестве входных данных и возвращает Token-идентификаторы.
  • text.SentencepieceTokenizer - The SentencepieceTokenizer требует более сложной настройки. Для его инициализатора требуется предварительно обученная модель предложения. Смотрите хранилище Google / sentencepiece для получения инструкций о том , как построить один из этих моделей. Он может принимать предложения в качестве входных данных при tokenizing.

В этом руководстве словарный запас Wordpiece создается сверху вниз, начиная с существующих слов. Этот процесс не работает для японского, китайского или корейского языков, поскольку в этих языках нет четких многосимвольных единиц. Чтобы разметить эти языки conside используя text.SentencepieceTokenizer , text.UnicodeCharTokenizer или этот подход .

Настраивать

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
2021-08-11 18:46:18.414452: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
tf.get_logger().setLevel('ERROR')
pwd = pathlib.Path.cwd()

Скачать набор данных

Позовите португальский / английский перевод набора данных из 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']
2021-08-11 18:46:23.603290: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcuda.so.1
2021-08-11 18:46:24.267575: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.268580: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1733] Found device 0 with properties: 
pciBusID: 0000:00:05.0 name: Tesla V100-SXM2-16GB computeCapability: 7.0
coreClock: 1.53GHz coreCount: 80 deviceMemorySize: 15.78GiB deviceMemoryBandwidth: 836.37GiB/s
2021-08-11 18:46:24.268617: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
2021-08-11 18:46:24.271953: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublas.so.11
2021-08-11 18:46:24.272053: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublasLt.so.11
2021-08-11 18:46:24.273192: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcufft.so.10
2021-08-11 18:46:24.273527: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcurand.so.10
2021-08-11 18:46:24.274580: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcusolver.so.11
2021-08-11 18:46:24.275559: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcusparse.so.11
2021-08-11 18:46:24.275779: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudnn.so.8
2021-08-11 18:46:24.275884: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.276973: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.277878: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
2021-08-11 18:46:24.278525: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-11 18:46:24.279136: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.280033: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1733] Found device 0 with properties: 
pciBusID: 0000:00:05.0 name: Tesla V100-SXM2-16GB computeCapability: 7.0
coreClock: 1.53GHz coreCount: 80 deviceMemorySize: 15.78GiB deviceMemoryBandwidth: 836.37GiB/s
2021-08-11 18:46:24.280116: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.281009: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.281856: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
2021-08-11 18:46:24.281894: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
2021-08-11 18:46:24.912068: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1258] Device interconnect StreamExecutor with strength 1 edge matrix:
2021-08-11 18:46:24.912106: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1264]      0 
2021-08-11 18:46:24.912115: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1277] 0:   N 
2021-08-11 18:46:24.912363: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.913392: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.914368: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-11 18:46:24.915336: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1418] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 14646 MB memory) -> physical GPU (device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0)

Этот набор данных создает пары предложений на португальском и английском языках:

for pt, en in train_examples.take(1):
  print("Portuguese: ", pt.numpy().decode('utf-8'))
  print("English:   ", en.numpy().decode('utf-8'))
2021-08-11 18:46:25.028577: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)
2021-08-11 18:46:25.029197: I tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2000165000 Hz
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 .

Обратите внимание на несколько моментов в приведенных выше примерах предложений:

  • Они строчные.
  • Вокруг знаков препинания есть пробелы.
  • Неясно, используется ли нормализация Unicode и какая.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

Создайте словарный запас

В этом разделе генерируется словарный запас из набора данных. Если у вас уже есть файл словаря и просто хочу , чтобы увидеть , как построить text.BertTokenizer или text.Wordpiece Tokenizer с ним , то вы можете пропустить вперед к строить токенизатор разделе.

Код словаря поколение входит в tensorflow_text пакет пип. По умолчанию он не импортируется, необходимо вручную импортировать:

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

bert_vocab.bert_vocab_from_dataset функция будет генерировать словарь.

Вы можете задать множество аргументов, чтобы изменить его поведение. В этом руководстве вы в основном будете использовать значения по умолчанию. Если вы хотите узнать больше о возможностях, сначала прочтите об алгоритме , а затем посмотреть на код .

Это займет около 2 минут.

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 36s, sys: 2.48 s, total: 1min 39s
Wall time: 1min 33s

Вот несколько кусочков получившейся лексики.

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

Напишите словарный файл:

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)

Используйте эту функцию для создания словаря из английских данных:

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

Вот два файла словаря:

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

Создайте токенизатор

text.BertTokenizer можно инициализировать, передавая путь словарного файла в качестве первого аргумента (смотрите раздел tf.lookup для других вариантов):

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

Теперь вы можете использовать его для кодирования текста. Возьмите партию из 3 примеров из английских данных:

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

Запустите его через BertTokenizer.tokenize метод. Первоначально это возвращает tf.RaggedTensor с осями (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]

Если заменить символические идентификаторы с текстовыми представлениями ( с помощью tf.gather ) вы можете увидеть , что в первом примере слова "searchability" возможность "serendipity" "search ##ability" "s ##ere ##nd ##ip ##ity" "searchability" и "serendipity" были разложенного на "search ##ability" и "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)>

Для того, чтобы повторно собрать слова из извлеченных маркеров, используйте 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)>

Настройка и экспорт

Этот учебник построен текст Tokenizer и detokenizer используемого трансформатора учебник. Этот раздел добавляет методы и этапы обработки , чтобы упростить этот учебник, а также экспорт в tokenizers используя tf.saved_model таким образом они могут быть импортированы в других учебниках.

Пользовательская токенизация

Вниз по течению учебников как ожидают токенизированный текст для включения [START] и [END] токены.

reserved_tokens запас пространства в начале словаря, так [START] и [END] имеют одинаковые индексы для обоих языков:

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

Индивидуальная детокенизация

Перед экспортом токенизаторов есть несколько вещей, которые вы можете очистить для последующих руководств:

  1. Они хотят , чтобы генерировать чистый вывод текста, так что падение зарезервированные лексемы , как [START] , [END] и [PAD] .
  2. Они заинтересованы в полных строк, поэтому применять строку присоединиться по words оси результата.
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)

Экспорт

Следующий блок кода создает CustomTokenizer класс , чтобы содержать text.BertTokenizer экземпляров, пользовательскую логику, и @tf.function оберток , необходимых для экспорта.

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)

Построить CustomTokenizer для каждого языка:

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

Экспорт tokenizers как saved_model :

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.saved_model.save(tokenizers, model_name)
2021-08-11 18:49:08.917974: 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.

Обновить saved_model и проверить методы:

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 !

Архивировать для учебников перевода :

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

Необязательно: алгоритм

Здесь стоит отметить, что существует две версии алгоритма WordPiece: снизу вверх и сверху вниз. В обоих случаях цель одна и та же: «Учитывая обучающий корпус и количество желаемых токенов D, задача оптимизации состоит в том, чтобы выбрать D словесных частей таким образом, чтобы результирующий корпус был минимальным по количеству частей слова при сегментировании в соответствии с выбранной моделью словаря. "

Оригинальный алгоритм WordPiece снизу вверх , основан на кодировании байт-пару . Как и BPE, он начинается с алфавита и итеративно комбинирует общие биграммы для образования частей и слов.

Словарь генератор TensorFlow Text следует за нисходящую реализацию из BERT . Начните со слов и разбейте их на более мелкие компоненты, пока они не достигнут порога частоты или не могут быть разбиты дальше. В следующем разделе это подробно описано. Для японского, китайского и корейского языков этот подход сверху вниз не работает, поскольку нет явных единиц слова, с которых можно было бы начать. Для тех , кто вам нужен другой подход .

Выбор словарного запаса

Сверху вниз WordPiece алгоритм генерации занимает в наборе (слово, количество) пары и порогом T и возвращает словарь , V .

Алгоритм итерационный. Это выполняется для k итераций, где обычно k = 4 , но только первые два действительно важны. Третий и четвертый (и последующие) просто идентичны второму. Обратите внимание , что каждый шаг двоичного поиска работает алгоритм с нуля для k итераций.

Итерации, описанные ниже:

Первая итерация

  1. Итерация над каждым словом и количеству пар на входе, обозначенный как (w, c) .
  2. Для каждого слова w , порождают все подстроки, обозначаемая s . Например, для слова human , мы генерируем {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Поддерживать подстроку-к-подсчета хэш - карту, и увеличивать количество каждого s помощью c . Например, если мы имеем (human, 113) и (humas, 3) в нашем входе, отсчет s = huma будет 113+3=116 .
  4. После того, как мы собрали отсчеты каждой подстроки, перебирайте (s, c) пар , начиная с самой длинной s первым.
  5. Храните s , что имеет c > T . Например, если T = 100 и мы имеем (pers, 231); (dogs, 259); (##rint; 76) , то мы будем держать pers и dogs .
  6. Когда s сохраняются, вычитать от его счетов от всех своих префиксов. Это является причиной для сортировки все s длиной в шаге 4. Это критическая часть алгоритма, поскольку в противном случае слова будут подсчитаны дважды. Например, допустим , что мы сохранили human и мы получаем (huma, 116) . Мы знаем , что 113 из этих 116 пришли от human , и 3 пришли humas . Однако теперь, когда human находится в нашем лексиконе, мы знаем , что мы никогда не сегментный human в huma ##n . Поэтому , как только human был сохранен, то huma имеет эффективную кол - 3 .

Этот алгоритм будет генерировать набор словесных Pièces s (многие из которых будет целыми словами w ), которые мы могли бы использовать в качестве нашего WordPiece словаря.

Однако есть проблема: этот алгоритм будет сильно генерировать фрагменты слов. Причина в том, что мы вычитаем только количество токенов префикса. Поэтому, если мы держим слово human , мы вычитаем от графа для h, hu, hu, huma , но не для ##u, ##um, ##uma, ##uman и так далее. Таким образом , мы могли бы генерировать как human и ##uman как куски слов, даже если ##uman никогда не будет применяться.

Так почему бы не вычитать от отсчетов для каждой подстроки, а не только каждый префикс? Потому что тогда мы могли бы вычесть количество раз несколько раз. Допустим , что мы обрабатываем s длины 5 , и мы продолжаем как (##denia, 129) и (##eniab, 137) , где 65 из этих подсчетов произошло от слова undeniable . Если вычесть из каждого от подстроки, мы вычитаем 65 из подстроки ##enia дважды, несмотря на то, что мы должны только вычесть один раз. Однако, если мы будем вычитать только из префиксов, оно будет правильно вычтено только один раз.

Вторая (и третья ...) итерация

Чтобы решить проблему избыточной генерации, упомянутую выше, мы выполняем несколько итераций алгоритма.

Последующие итерации идентичны первому, но с одним важным отличием: в шаге 2, вместо того , чтобы рассматривать каждую подстроку, мы применяем алгоритм WordPiece лексемизации с использованием словаря из предыдущей итерации, и рассматривать только подстроки , которые начинаются на точках разделения.

Например, допустим , что мы выполняем шаг 2 алгоритма и сталкиваются слово undeniable . В первой итерации, мы будем рассматривать каждую подстроку, например, {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} - {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} .

Теперь, для второй итерации, мы рассмотрим только их подмножество. Предположим, что после первой итерации соответствующие части слова:

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

Будет сегмент алгоритма WordPiece это в un ##deni ##able (смотрите раздел Применив WordPiece для получения дополнительной информации). В этом случае мы будем рассматривать только подстроки , которые начинаются в точке сегментации. Мы еще рассмотрим все возможное конечное положение. Таким образом , во второй итерации, множество s для undeniable является:

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

В остальном алгоритм идентичен. В этом примере, в первой итерации, алгоритм производит suprious маркеры ##ndeni и ##iable . Теперь эти токены никогда не учитываются, поэтому они не будут сгенерированы на второй итерации. Мы выполняем несколько итераций, чтобы убедиться, что результаты сходятся (хотя буквальной гарантии сходимости нет).

Применение WordPiece

После создания словаря WordPiece нам нужно иметь возможность применять его к новым данным. Алгоритм представляет собой простое жадное приложение с поиском наиболее длинного совпадения.

Например, рассмотрим сегментирование слово undeniable .

Мы первый поиск undeniable в нашем WordPiece словаре, и если он присутствует, мы сделали. Если нет, то мы уменьшаем конечную точку на один символ, и повторить, например, undeniabl .

В конце концов, мы либо найдем подтенк в нашем словаре, либо перейдем к подтену, состоящему из одного символа. (В целом, мы считаем , что каждый символ в нашем лексиконе, хотя это может быть не так для редких символов Unicode. Если мы встречаем редкий символ Unicode , что это не в словаре , мы просто отобразить все слова <unk> ).

В этом случае, мы находим un в нашем словаре. Итак, это наш первый отрывок из слов. Затем мы переходим к концу un и повторить обработку, например, попытаться найти ##deniable , то ##deniabl и т.д. Это повторяется до тех пор, пока сегментирование слова целиком.

Интуиция

Интуитивно понятно, что токенизация WordPiece пытается решить две разные задачи:

  1. Токенизировать данные в наименьшее число частей , как это возможно. Важно помнить, что алгоритм WordPiece не «хочет» разбивать слова. В противном случае, было бы просто разделить каждое слово в его символах, например, human -> {h, ##u, ##m, ##a, #n} . Это одна критической вещь , которая делает WordPiece отличается от морфологических разветвителей, которые разделят лингвистические морфемы даже для общих слов (например, unwanted -> {un, want, ed} ).

  2. Когда слово действительно нужно разделить на части, разбейте его на части, которые имеют максимальное количество в обучающих данных. Например, причина , почему слово undeniable будет разбит на {un, ##deni, ##able} в {unde, ##niab, ##le} un ##able {un, ##deni, ##able} , а не альтернативы , как {unde, ##niab, ##le} является то , что счетчики для un и ##able в Особенность будет очень высокой, так как это общие префиксы и суффиксы. Даже несмотря на то, счетчик для ##le должен быть выше , чем ##able в unde ##niab ##able , низкие отсчеты unde и ##niab сделают это менее «желательно» лексический анализ алгоритма.

Необязательно: tf.lookup

Если вам нужен доступ к или больше контроля над словарем стоит отметить , что вы можете создать таблицы перекодировки себя и передать в BertTokenizer .

Когда вы передаете строку, BertTokenizer выполняет следующие действия :

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)

Теперь у вас есть прямой доступ к таблице поиска, используемой в токенизаторе.

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

Вам не нужно использовать словарный файл, tf.lookup имеет другие параметры инициализатора. Если у вас есть словарный запас в памяти можно использовать 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)