¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

Tokenizadores de subpalabras

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Este tutorial muestra cómo generar un vocabulario palabra parcial de un conjunto de datos, y lo utilizan para construir un text.BertTokenizer del vocabulario.

La principal ventaja de un tokenizador de subpalabras es que interpola entre la tokenización basada en palabras y en caracteres. Las palabras comunes obtienen un espacio en el vocabulario, pero el tokenizador puede recurrir a partes de palabras y caracteres individuales para palabras desconocidas.

Visión general

El tensorflow_text paquete incluye implementaciones de muchos TensorFlow tokenizers comunes. Esto incluye tres tokenizadores de estilo de subpalabras:

  • text.BertTokenizer - El BertTokenizer clase es una interfaz de nivel superior. Incluye algoritmo de división de contadores a BERT y una WordPieceTokenizer . Se necesita frases como entrada y devuelve token-IDs.
  • text.WordpeiceTokenizer - El WordPieceTokenizer clase es una interfaz de nivel inferior. Sólo se implementa el algoritmo de WordPiece . Debe estandarizar y dividir el texto en palabras antes de llamarlo. Se necesita palabras como entrada y devuelve token-IDs.
  • text.SentencepieceTokenizer - El SentencepieceTokenizer requiere una configuración más compleja. Su inicializador requiere un modelo de pieza de oración previamente entrenado. Ver el repositorio de Google / sentencepiece para obtener instrucciones sobre cómo construir uno de estos modelos. Se puede aceptar frases como entrada cuando tokenizar.

Este tutorial crea un vocabulario de Wordpiece de arriba hacia abajo, a partir de palabras existentes. Este proceso no funciona para japonés, chino o coreano, ya que estos idiomas no tienen unidades claras de varios caracteres. Para tokenize idiomas conside utilizando text.SentencepieceTokenizer , text.UnicodeCharTokenizer o este enfoque .

Configuración

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

Descarga el conjunto de datos

Buscar el conjunto de datos en portugués / Inglés de los TFD :

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)

Este conjunto de datos produce pares de oraciones en 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'))
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 .

Tenga en cuenta algunas cosas sobre las oraciones de ejemplo anteriores:

  • Son minúsculas.
  • Hay espacios alrededor de la puntuación.
  • No está claro si se está utilizando o qué normalización Unicode.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

Genera el vocabulario

Esta sección genera un vocabulario de palabras a partir de un conjunto de datos. Si ya tiene un archivo de vocabulario y sólo quiere ver cómo construir un text.BertTokenizer o text.Wordpiece tokenizer con él, entonces puede pasar directamente a la construcción del tokenizer sección.

El código de generación vocabulario está incluido en el tensorflow_text paquete PIP. No se importa de forma predeterminada, debe importarlo manualmente:

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

El bert_vocab.bert_vocab_from_dataset función generará el vocabulario.

Hay muchos argumentos que puede establecer para ajustar su comportamiento. Para este tutorial, utilizará principalmente los valores predeterminados. Si desea obtener más información sobre las opciones, leer primero sobre el algoritmo , y luego echar un vistazo al código .

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

A continuación se muestran algunos fragmentos del vocabulario 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']
['##–', '##—', '##‘', '##’', '##“', '##”', '##⁄', '##€', '##♪', '##♫']

Escribe un archivo de vocabulario:

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 esa función para generar un vocabulario a partir de los datos en inglés:

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

Aquí están los dos archivos de vocabulario:

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

Construye el tokenizador

El text.BertTokenizer puede ser inicializado por el que pasa la ruta del archivo de vocabulario como primer argumento (véase la sección sobre tf.lookup para otras opciones):

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

Ahora puedes usarlo para codificar texto. Tome un lote de 3 ejemplos de los datos en 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 ."

Ejecutar a través de la BertTokenizer.tokenize método. Inicialmente, este devuelve un tf.RaggedTensor con ejes (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]

Si sustituye los ID de testigo con sus representaciones de texto (usando tf.gather ) se puede ver que en el primer ejemplo de las palabras "searchability" y "serendipity" se han descompuesto en "search ##ability" y "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 volver a montar palabras de los tokens extraídos, utilice el 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)>

Personalización y exportación

Este tutorial se basa el texto y tokenizer detokenizer utilizado por el transformador tutorial. En esta sección se añade métodos y pasos de procesamiento para simplificar ese tutorial, y las exportaciones de los tokenizers usando tf.saved_model para que puedan ser importadas por los otros tutoriales.

Tokenización personalizada

Los tutoriales aguas abajo ambos esperan que el texto tokenized para incluir [START] y [END] fichas.

El reserved_tokens espacio de reserva al comienzo del vocabulario, por lo [START] y [END] tienen los mismos índices para los dos idiomas:

START = tf.argmax(tf.constant(reserved_tokens) == "[START]")
END = tf.argmax(tf.constant(reserved_tokens) == "[END]")

def add_start_end(ragged):
  count = ragged.bounding_shape()[0]
  starts = tf.fill([count,1], START)
  ends = tf.fill([count,1], END)
  return tf.concat([starts, ragged, ends], axis=1)
words = en_tokenizer.detokenize(add_start_end(token_batch))
tf.strings.reduce_join(words, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'[START] and when you improve searchability , you actually take away the one advantage of print , which is serendipity . [END]',
       b'[START] but what if it were active ? [END]',
       b"[START] but they did n ' t test for curiosity . [END]"],
      dtype=object)>

Destokenización personalizada

Antes de exportar los tokenizadores, hay un par de cosas que puede limpiar para los tutoriales posteriores:

  1. Ellos quieren generar la salida de texto limpio, así como dejar caer fichas reservados [START] , [END] y [PAD] .
  2. Están interesados en las cadenas completas, por lo que se aplica a lo largo de una cadena de unirse a las words eje del 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

El siguiente bloque de código crea un CustomTokenizer clase para contener los text.BertTokenizer casos, la lógica personalizada, y los @tf.function envoltorios requeridos para la exportación.

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 un CustomTokenizer para cada idioma:

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

Exportar los tokenizers como un 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.

Actualizar el saved_model y probar los 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 !

Archivarlo para los tutoriales de traducción :

zip -r {model_name}.zip {model_name}
adding: ted_hrlr_translate_pt_en_converter/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/variables/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.data-00000-of-00001 (deflated 51%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.index (deflated 33%)
  adding: ted_hrlr_translate_pt_en_converter/assets/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/assets/pt_vocab.txt (deflated 57%)
  adding: ted_hrlr_translate_pt_en_converter/assets/en_vocab.txt (deflated 54%)
  adding: ted_hrlr_translate_pt_en_converter/saved_model.pb (deflated 91%)
du -h *.zip
184K    ted_hrlr_translate_pt_en_converter.zip

Opcional: el algoritmo

Vale la pena señalar aquí que hay dos versiones del algoritmo de WordPiece: de abajo hacia arriba y de arriba hacia abajo. En ambos casos, el objetivo es el mismo: "Dado un corpus de entrenamiento y un número de tokens D deseados, el problema de optimización es seleccionar D wordpieces de manera que el corpus resultante sea mínimo en el número de wordpieces cuando se segmenta según el modelo de wordpiece elegido. "

El original algoritmo WordPiece de abajo hacia arriba , se basa en el byte de par de codificación . Al igual que BPE, comienza con el alfabeto y combina iterativamente bigramas comunes para formar piezas de palabras y palabras.

Generador de vocabulario de TensorFlow texto sigue la aplicación de arriba hacia abajo de BERT . Comenzar con palabras y dividirlas en componentes más pequeños hasta que alcancen el umbral de frecuencia o no se puedan dividir más. La siguiente sección describe esto en detalle. Para el japonés, el chino y el coreano, este enfoque de arriba hacia abajo no funciona, ya que no hay unidades de palabras explícitas con las que empezar. Para aquellos que se necesita un enfoque diferente .

Elegir el vocabulario

El algoritmo de generación de WordPiece de arriba hacia abajo realiza en un conjunto de (Word, recuento) pares y un umbral T , y devuelve un vocabulario V .

El algoritmo es iterativo. Está dirigido por k iteraciones, donde normalmente k = 4 , pero sólo los dos primeros son realmente importantes. El tercero y el cuarto (y más allá) son simplemente idénticos al segundo. Tenga en cuenta que cada paso de la búsqueda binaria se ejecuta el algoritmo de cero para k iteraciones.

Las iteraciones que se describen a continuación:

Primera iteración

  1. Iterar sobre cada palabra y par recuento en la entrada, indicada como (w, c) .
  2. Para cada palabra w , generar cada subcadena, que se denota como s . Por ejemplo, para la palabra human , que generan {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Mantener un mapa hash-subcadena a recuento, e incrementar el recuento de cada uno s de c . Por ejemplo, si tenemos (human, 113) y (humas, 3) en nuestra entrada la cuenta de s = huma será 113+3=116 .
  4. Una vez que hemos recogido los cargos de cada subcadena, iterar sobre las (s, c) pares empezando por el más largo s primero.
  5. Guarde todos los s que tiene un c > T . Por ejemplo, si T = 100 y tenemos (pers, 231); (dogs, 259); (##rint; 76) , entonces podríamos mantener pers y dogs .
  6. Cuando un s se mantiene, restar de su recuento de todos sus prefijos. Esta es la razón para la clasificación de todas las s por longitud en el paso 4. Esta es una parte crítica del algoritmo, porque de lo contrario palabras serían doble contados. Por ejemplo, digamos que hemos mantenido human y nos dan a (huma, 116) . Sabemos que 113 de los 116 procedían de human , y 3 venimos de humas . Sin embargo, ahora que human está en nuestro vocabulario, sabemos que nunca lo hará segmento human en huma ##n . Así que una vez human se ha mantenido, a continuación, huma sólo se tiene un recuento efectivo de 3 .

Este algoritmo genera un conjunto de piezas de palabras s (muchas de las cuales serán las palabras enteras w ), que podríamos utilizar como nuestro vocabulario WordPiece.

Sin embargo, hay un problema: este algoritmo sobregenerará en exceso las palabras. La razón es que solo restamos los recuentos de tokens de prefijo. Por lo tanto, si mantenemos la palabra human , vamos a restar de la cuenta para h, hu, hu, huma , pero no para ##u, ##um, ##uma, ##uman y así sucesivamente. Así que podríamos generar tanto human y ##uman como piezas de palabras, a pesar de que ##uman no se aplicará nunca.

¿Por qué no restar de los recuentos para cada subcadena, no sólo cada prefijo? Porque entonces podríamos terminar restando las cuentas varias veces. Digamos que estamos procesamiento s de longitud 5 y nos mantienen tanto (##denia, 129) y (##eniab, 137) , donde 65 de los recuentos de vino de la palabra undeniable . Si restamos fuera de cada subcadena, nos resta 65 de la subcadena ##enia dos veces, a pesar de que sólo debe restar una vez. Sin embargo, si solo restamos de los prefijos, correctamente solo se restará una vez.

Segunda (y tercera ...) iteración

Para resolver el problema de sobregeneración mencionado anteriormente, realizamos múltiples iteraciones del algoritmo.

Iteraciones subsiguientes son idénticas a la primera, con una diferencia importante: en el paso 2, en lugar de considerar cada subcadena, aplicamos el algoritmo WordPiece tokenización utilizando el vocabulario de la iteración anterior, y sólo consideramos subseries que comienzan en un punto de división.

Por ejemplo, digamos que estamos realizando el paso 2 del algoritmo y encontrar la palabra undeniable . En la primera iteración, que podríamos considerar cada subcadena, por ejemplo, {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...} .

Ahora, para la segunda iteración, solo consideraremos un subconjunto de estos. Digamos que después de la primera iteración, las piezas de palabras relevantes son:

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

El segmento voluntad algoritmo WordPiece esto en un ##deni ##able (véase la sección WordPiece aplicación para obtener más información). En este caso, sólo tendremos en cuenta subseries que se inician en un punto de segmentación. Todavía vamos a considerar cada posición final posible. Así que durante la segunda iteración, el conjunto de s para undeniable es:

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

Por lo demás, el algoritmo es idéntico. En este ejemplo, en la primera iteración, el algoritmo produce la suprious tokens ##ndeni y ##iable . Ahora, estos tokens nunca se consideran, por lo que no serán generados por la segunda iteración. Realizamos varias iteraciones solo para asegurarnos de que los resultados converjan (aunque no hay una garantía de convergencia literal).

Aplicar WordPiece

Una vez que se ha generado un vocabulario de WordPiece, necesitamos poder aplicarlo a nuevos datos. El algoritmo es una simple aplicación codiciosa de la coincidencia más larga.

Por ejemplo, considere la segmentación de la palabra undeniable .

En primer lugar, las operaciones de búsqueda undeniable en el diccionario WordPiece, y si está presente, hemos terminado. Si no, disminuir el punto final de un carácter, y repetir, por ejemplo, undeniabl .

Eventualmente, encontraremos un subtoken en nuestro vocabulario o bajaremos a un subtoken de un solo carácter. (En general, se supone que cada personaje está en nuestro vocabulario, aunque esto podría no ser el caso para los caracteres Unicode raras. Si nos encontramos con un personaje raro Unicode que no está en el vocabulario que sólo establecen la palabra completa a <unk> ).

En este caso, nos encontramos con un en nuestro vocabulario. Así que esa es nuestra primera pieza de palabras. Luego saltamos a finales de un y repetir el tratamiento, por ejemplo, tratar de encontrar ##deniable , a continuación, ##deniabl , etc. Esto se repite hasta que hemos segmentado la palabra completa.

Intuición

Intuitivamente, la tokenización de WordPiece intenta satisfacer dos objetivos diferentes:

  1. Tokenize los datos en el menor número de piezas como sea posible. Es importante tener en cuenta que el algoritmo de WordPiece no "quiere" dividir palabras. De lo contrario, sería simplemente dividir cada palabra en sus personajes, por ejemplo, human -> {h, ##u, ##m, ##a, #n} . Esto es una cosa fundamental que los hace diferentes de divisores WordPiece morfológicas, que se dividirán morfemas lingüísticas, incluso para las palabras comunes (por ejemplo, unwanted -> {un, want, ed} ).

  2. Cuando una palabra tenga que dividirse en partes, divídala en partes que tengan un recuento máximo en los datos de entrenamiento. Por ejemplo, la razón por la palabra undeniable sería dividido en {un, ##deni, ##able} en lugar de alternativas como {unde, ##niab, ##le} es que los recuentos de un y ##able de particular será muy alto, ya que son prefijos y sufijos comunes. A pesar de que el recuento de ##le debe ser superior a ##able , los recuentos bajos de unde y ##niab se hacen de este un tokenización menos "deseables" para el algoritmo.

Opcional: tf.lookup

Si necesita acceso a, o mayor control sobre el vocabulario Vale la pena señalar que se puede construir la tabla de búsqueda usted mismo y pasar a que BertTokenizer .

Cuando se pasa una cadena, BertTokenizer hace lo siguiente:

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)

Ahora tiene acceso directo a la tabla de búsqueda utilizada en el tokenizador.

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

No es necesario utilizar un archivo de vocabulario, tf.lookup tiene otras opciones de inicializador. Si usted tiene el vocabulario de memoria que puede 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)