¿Tengo una pregunta? Conéctese con la comunidad en el Foro de visita del foro de TensorFlow

Tokenizadores de subpalabras

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

Este tutorial demuestra cómo generar un vocabulario de subpalabras a partir de un conjunto de datos y usarlo para crear un texto. text.BertTokenizer partir 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.

Descripción general

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

  • text.BertTokenizer : la clase BertTokenizer es una interfaz de nivel superior. Incluye el algoritmo de división de tokens de BERT y un WordPieceTokenizer . Toma sentencias como entrada y devuelve ID de token .
  • text.WordpeiceTokenizer : la clase WordPieceTokenizer es una interfaz de nivel inferior. Solo implementa el algoritmo WordPiece . Debe estandarizar y dividir el texto en palabras antes de llamarlo. Toma palabras como entrada y devuelve ID de token.
  • text.SentencepieceTokenizer : el SentencepieceTokenizer requiere una configuración más compleja. Su inicializador requiere un modelo de pieza de oración previamente entrenado. Consulte el repositorio de google / oraciones para obtener instrucciones sobre cómo construir uno de estos modelos. Puede aceptar oraciones como entrada cuando se tokeniza.

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 tokenizar estos idiomas, considere usar text.SentencepieceTokenizer , text.UnicodeCharTokenizer o este enfoque .

Configuración

pip install -q tensorflow_datasets
# `BertTokenizer.detokenize` is not in `tf-text` stable yet (currently 2.4.3).
pip install -q tensorflow_text_nightly
# tf-text-nightly resquires tf-nightly
pip install -q tf-nightly
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()

Descarga el conjunto de datos

Obtenga el conjunto de datos de traducción portugués / inglés de tfds :

examples, metadata = tfds.load('ted_hrlr_translate/pt_to_en', with_info=True,
                               as_supervised=True)
train_examples, val_examples = examples['train'], examples['validation']

Este conjunto de 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'))
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 solo quiere ver cómo construir un text.BertTokenizer o text.Wordpiece tokenizer con él, entonces puede pasar a la sección Build the tokenizer .

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

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

La función bert_vocab.bert_vocab_from_dataset 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, primero lea sobre el algoritmo y luego eche 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 23s, sys: 3.67 s, total: 1min 27s
Wall time: 1min 19s

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, sys: 2.49 s, total: 1min 3s
Wall time: 54.8 s
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 se puede inicializar pasando la ruta del archivo de vocabulario como primer argumento (consulte la sección sobre tf.lookup para conocer 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 ."

Ejecútelo mediante el método BertTokenizer.tokenize . Inicialmente, esto devuelve untf.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 reemplaza los ID de token con sus representaciones de texto (usando tf.gather ), puede ver que en el primer ejemplo, las palabras "searchability" "search ##ability" "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 ensamblar palabras de los tokens extraídos, use el método 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)>

Personalización y exportación

Este tutorial crea el tokenizador y el destokenizador de texto utilizados por el tutorial de Transformer . Esta sección agrega métodos y pasos de procesamiento para simplificar ese tutorial y exporta los tokenizadores usando tf.saved_model para que puedan ser importados por otros tutoriales.

Tokenización personalizada

Los tutoriales posteriores esperan que el texto tokenizado incluya tokens [START] y [END] .

Los reserved_tokens reservan espacio al principio del vocabulario, por lo que [START] y [END] tienen los mismos índices para ambos 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. Quieren generar una salida de texto limpia, así que suelte tokens reservados como [START] , [END] y [PAD] .
  2. Están interesados ​​en cadenas completas, así que aplique una combinación de cadena a lo largo del eje de words 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 una clase CustomTokenizer para contener las instancias text.BertTokenizer , la lógica personalizada y los contenedores @tf.function necesarios 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)

Cree un CustomTokenizer para cada idioma:

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

Exporta los tokenizadores como saved_model :

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.saved_model.save(tokenizers, model_name)

saved_model cargar el saved_model y pruebe 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 !

Archívelo 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. "

Elalgoritmo originalde WordPiece ascendente se basa en la codificación de pares de bytes . Al igual que BPE, comienza con el alfabeto y combina iterativamente bigramas comunes para formar piezas de palabras y palabras.

El generador de vocabulario de TensorFlow Text sigue la implementació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, necesita un enfoque diferente .

Elegir el vocabulario

El algoritmo de generación de WordPiece de arriba hacia abajo toma un conjunto de pares (palabra, recuento) y un umbral T , y devuelve un vocabulario V

El algoritmo es iterativo. Se ejecuta para k iteraciones, donde normalmente k = 4 , pero solo las dos primeras 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 ejecuta el algoritmo desde cero para k iteraciones.

Las iteraciones que se describen a continuación:

Primera iteración

  1. Itere cada palabra y cuente el par en la entrada, denotado como (w, c) .
  2. Para cada palabra w , genere cada subcadena, denotada como s . Por ejemplo, para la palabra human , generamos {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n} .
  3. Mantenga un mapa hash de subcadena a conteo e incremente el conteo de cada s en 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 hayamos recopilado los recuentos de cada subcadena, iteramos sobre los pares (s, c) comenzando con la s más larga primero .
  5. Mantenga cualquier s que tenga una c > T Por ejemplo, si T = 100 y tenemos (pers, 231); (dogs, 259); (##rint; 76) , entonces mantendríamos pers y dogs .
  6. Cuando se mantiene una s , reste su recuento de todos sus prefijos. Esta es la razón para ordenar todos los s por longitud en el paso 4. Esta es una parte crítica del algoritmo, porque de lo contrario las palabras se contarían dos veces. Por ejemplo, digamos que nos hemos mantenido human y llegamos a (huma, 116) . Sabemos que 113 de esos 116 vinieron de human y 3 vinieron de humas . Sin embargo, ahora que human está en nuestro vocabulario, sabemos que nunca lo hará segmento human en huma ##n . Entonces, una vez que se ha mantenido al human , entonces huma solo tiene un recuento efectivo de 3 .

Este algoritmo generará un conjunto de piezas de palabras s (muchas de las cuales serán palabras completas w ), que podríamos usar como nuestro vocabulario de 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 , restaremos de la cuenta para h, hu, hu, huma , pero no para ##u, ##um, ##uma, ##uman y así sucesivamente. Por lo tanto, podríamos generar tanto human como ##uman como piezas de palabras, aunque ##uman nunca se aplicará.

Entonces, ¿por qué no restar los recuentos de cada subcadena , no solo de cada prefijo ? Porque entonces podríamos terminar restando los conteos varias veces. Digamos que estamos procesando s de longitud 5 y mantenemos ambos (##denia, 129) y (##eniab, 137) , donde 65 de esos conteos provienen de la palabra undeniable . Si restamos de cada subcadena, restamos 65 de la subcadena ##enia dos veces, aunque solo deberíamos 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.

Las iteraciones posteriores son idénticas a la primera, con una distinción importante: en el paso 2, en lugar de considerar cada subcadena, aplicamos el algoritmo de tokenización de WordPiece utilizando el vocabulario de la iteración anterior y solo consideramos las subcadenas que comienzan en un punto de división.

Por ejemplo, digamos que estamos realizando el paso 2 del algoritmo y encontramos la palabra undeniable . En la primera iteración, consideraríamos 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 algoritmo de WordPiece lo segmentará en un ##deni ##able (consulte la sección Aplicación de WordPiece para obtener más información). En este caso, solo consideraremos las subcadenas que comienzan en un punto de segmentación. Seguiremos considerando todas las posibles posiciones finales . Entonces, 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 los 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 segmentar la palabra undeniable .

Primero buscamos undeniable en nuestro diccionario de WordPiece, y si está presente, hemos terminado. Si no, disminuimos el punto final en un carácter y repetimos, 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, encontramos un en nuestro vocabulario. Así que esa es nuestra primera pieza de palabras. Luego saltamos al final de un y repetimos el procesamiento, por ejemplo, intentamos encontrar ##deniable , luego ##deniabl , etc. Esto se repite hasta que hayamos segmentado toda la palabra.

Intuición

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

  1. Tokenice los datos en la menor cantidad de piezas posible. Es importante tener en cuenta que el algoritmo de WordPiece no "quiere" dividir palabras. De lo contrario, simplemente dividiría cada palabra en sus caracteres, por ejemplo, human -> {h, ##u, ##m, ##a, #n} . Esta es una cosa fundamental que hace que WordPiece sea diferente de los divisores morfológicos, que dividirán los morfemas lingüísticos incluso para 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 que la palabra undeniable se dividiría en {un, ##deni, ##able} lugar de alternativas como {unde, ##niab, ##le} es que los recuentos de un y ##able en particular será muy alto, ya que son prefijos y sufijos comunes. Aunque el recuento de ##le debe ser mayor que ##able , los recuentos bajos de unde y ##niab harán que esta sea una tokenización menos "deseable" para el algoritmo.

Opcional: tf.lookup

Si necesita acceso o más control sobre el vocabulario, vale la pena señalar que puede crear la tabla de búsqueda usted mismo y pasarla a BertTokenizer .

Cuando 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 inicialización. Si tiene el vocabulario en la memoria, puede usar 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)