Ver en TensorFlow.org | Ejecutar en Google Colab | Ver en GitHub | Descargar cuaderno |
Descripción general
El preprocesamiento de texto es la transformación de un extremo a otro de texto sin formato en entradas enteras de un modelo. Los modelos de PNL suelen ir acompañados de varios cientos (si no miles) de líneas de código Python para preprocesar texto. El preprocesamiento de texto suele ser un desafío para los modelos porque:
Sesgo entrenamiento-servicio. Se vuelve cada vez más difícil garantizar que la lógica de preprocesamiento de las entradas del modelo sea coherente en todas las etapas del desarrollo del modelo (por ejemplo, preentrenamiento, ajuste fino, evaluación, inferencia). El uso de diferentes hiperparámetros, tokenización, algoritmos de preprocesamiento de cadenas o simplemente empaquetar entradas del modelo de manera inconsistente en diferentes etapas podría producir efectos desastrosos y difíciles de depurar en el modelo.
Eficiencia y flexibilidad. Si bien el preprocesamiento se puede realizar fuera de línea (por ejemplo, escribiendo las salidas procesadas en archivos en el disco y luego volviendo a consumir dichos datos preprocesados en la canalización de entrada), este método incurre en un costo adicional de lectura y escritura de archivos. El preprocesamiento fuera de línea también es un inconveniente si hay decisiones de preprocesamiento que deben realizarse de forma dinámica. Experimentar con una opción diferente requeriría volver a regenerar el conjunto de datos.
Interfaz de modelo complejo. Los modelos de texto son mucho más comprensibles cuando sus entradas son texto puro. Es difícil entender un modelo cuando sus entradas requieren un paso de codificación indirecto adicional. Se agradece especialmente la reducción de la complejidad del preprocesamiento para la depuración, el servicio y la evaluación de modelos.
Además, las interfaces de modelo más simples también hacen que sea más conveniente probar el modelo (por ejemplo, inferencia o entrenamiento) en diferentes conjuntos de datos inexplorados.
Preprocesamiento de texto con TF.Text
Usando las API de preprocesamiento de texto de TF.Text, podemos construir una función de preprocesamiento que puede transformar el conjunto de datos de texto de un usuario en las entradas enteras del modelo. Los usuarios pueden empaquetar el preprocesamiento directamente como parte de su modelo para aliviar los problemas mencionados anteriormente.
Este tutorial le mostrará cómo utilizar TF.Text operaciones de pre-procesamiento para transformar datos de texto en las entradas para el modelo BERT y entradas para el lenguaje de enmascaramiento pre-entrenamiento tarea se describe en "enmascarado LM y procedimiento de enmascaramiento" de BERT: Pre-entrenamiento de profunda bidireccionales Transformadores para Idioma la comprensión . El proceso implica convertir el texto en fichas en unidades de subpalabras, combinar oraciones, recortar el contenido a un tamaño fijo y extraer etiquetas para la tarea de modelado del lenguaje enmascarado.
Configuración
Primero importemos los paquetes y las bibliotecas que necesitamos.
pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools
Nuestros datos contiene dos características del texto y podemos crear un ejemplo tf.data.Dataset
. Nuestro objetivo es crear una función que podemos suministrar Dataset.map()
con el que se utilizará en la formación.
examples = {
"text_a": [
b"Sponge bob Squarepants is an Avenger",
b"Marvel Avengers"
],
"text_b": [
b"Barack Obama is the President.",
b"President is the highest office"
],
}
dataset = tf.data.Dataset.from_tensor_slices(examples)
next(iter(dataset))
{'text_a': <tf.Tensor: shape=(), dtype=string, numpy=b'Sponge bob Squarepants is an Avenger'>, 'text_b': <tf.Tensor: shape=(), dtype=string, numpy=b'Barack Obama is the President.'>}
Tokenización
Nuestro primer paso es ejecutar cualquier preprocesamiento de cadenas y tokenizar nuestro conjunto de datos. Esto se puede hacer usando el text.BertTokenizer
, que es un text.Splitter
que pueden tokenize oraciones en palabras parciales o wordpieces para el modelo BERT dado un vocabulario generado a partir del algoritmo Wordpiece . Usted puede aprender más sobre otros tokenizers palabra parcial disponibles en TF.Text desde aquí .
El vocabulario puede provenir de un punto de control BERT generado previamente, o puede generar uno usted mismo con sus propios datos. Para los propósitos de este ejemplo, creemos un vocabulario de juguetes:
_VOCAB = [
# Special tokens
b"[UNK]", b"[MASK]", b"[RANDOM]", b"[CLS]", b"[SEP]",
# Suffixes
b"##ack", b"##ama", b"##ger", b"##gers", b"##onge", b"##pants", b"##uare",
b"##vel", b"##ven", b"an", b"A", b"Bar", b"Hates", b"Mar", b"Ob",
b"Patrick", b"President", b"Sp", b"Sq", b"bob", b"box", b"has", b"highest",
b"is", b"office", b"the",
]
_START_TOKEN = _VOCAB.index(b"[CLS]")
_END_TOKEN = _VOCAB.index(b"[SEP]")
_MASK_TOKEN = _VOCAB.index(b"[MASK]")
_RANDOM_TOKEN = _VOCAB.index(b"[RANDOM]")
_UNK_TOKEN = _VOCAB.index(b"[UNK]")
_MAX_SEQ_LEN = 8
_MAX_PREDICTIONS_PER_BATCH = 5
_VOCAB_SIZE = len(_VOCAB)
lookup_table = tf.lookup.StaticVocabularyTable(
tf.lookup.KeyValueTensorInitializer(
keys=_VOCAB,
key_dtype=tf.string,
values=tf.range(
tf.size(_VOCAB, out_type=tf.int64), dtype=tf.int64),
value_dtype=tf.int64),
num_oov_buckets=1
)
Constructo de dejar que un text.BertTokenizer
utilizando el vocabulario arriba y tokenize las entradas de texto en un RaggedTensor
.`.
bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.string)
bert_tokenizer.tokenize(examples["text_a"])
<tf.RaggedTensor [[[b'Sp', b'##onge'], [b'bob'], [b'Sq', b'##uare', b'##pants'], [b'is'], [b'an'], [b'A', b'##ven', b'##ger']], [[b'Mar', b'##vel'], [b'A', b'##ven', b'##gers']]]>
bert_tokenizer.tokenize(examples["text_b"])
<tf.RaggedTensor [[[b'Bar', b'##ack'], [b'Ob', b'##ama'], [b'is'], [b'the'], [b'President'], [b'[UNK]']], [[b'President'], [b'is'], [b'the'], [b'highest'], [b'office']]]>
Los resultados de texto de text.BertTokenizer
nos permite ver cómo se está tokenized el texto, pero el modelo requiere enteros identificaciones. Podemos establecer la token_out_type
PARAM a tf.int64
obtener número entero IDs (que son los índices en el vocabulario).
bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.int64)
segment_a = bert_tokenizer.tokenize(examples["text_a"])
segment_a
<tf.RaggedTensor [[[22, 9], [24], [23, 11, 10], [28], [14], [15, 13, 7]], [[18, 12], [15, 13, 8]]]>
segment_b = bert_tokenizer.tokenize(examples["text_b"])
segment_b
<tf.RaggedTensor [[[16, 5], [19, 6], [28], [30], [21], [0]], [[21], [28], [30], [27], [29]]]>
text.BertTokenizer
devuelve un RaggedTensor
con forma [batch, num_tokens, num_wordpieces]
. Debido a que no es necesario el extra num_tokens
dimensiones para nuestro caso de uso corriente, podemos combinar las dos últimas dimensiones para obtener una RaggedTensor
con forma [batch, num_wordpieces]
:
segment_a = segment_a.merge_dims(-2, -1)
segment_a
<tf.RaggedTensor [[22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7], [18, 12, 15, 13, 8]]>
segment_b = segment_b.merge_dims(-2, -1)
segment_b
<tf.RaggedTensor [[16, 5, 19, 6, 28, 30, 21, 0], [21, 28, 30, 27, 29]]>
Recorte de contenido
La entrada principal de BERT es una concatenación de dos oraciones. Sin embargo, BERT requiere que los insumos tengan un tamaño y una forma fijos y es posible que tengamos contenido que exceda nuestro presupuesto.
Podemos hacer frente a este mediante el uso de un text.Trimmer
para recortar nuestra abajo el contenido de un tamaño predeterminado (una vez concatenado a lo largo del último eje). Hay diferentes text.Trimmer
tipos de contenido que seleccionan para preservar el uso de diferentes algoritmos. text.RoundRobinTrimmer
por ejemplo asignará cuota igual para cada segmento, pero puede recortar los extremos de oraciones. text.WaterfallTrimmer
recortará a partir del final de la última frase.
Para nuestro ejemplo, vamos a utilizar RoundRobinTrimmer
que selecciona los artículos en cada segmento de forma de izquierda a derecha.
trimmer = text.RoundRobinTrimmer(max_seq_length=[_MAX_SEQ_LEN])
trimmed = trimmer.trim([segment_a, segment_b])
trimmed
[<tf.RaggedTensor [[22, 9, 24, 23], [18, 12, 15, 13]]>, <tf.RaggedTensor [[16, 5, 19, 6], [21, 28, 30, 27]]>]
trimmed
ahora contiene los segmentos en los que el número de elementos a través de un lote es de 8 elementos (cuando concatenado lo largo del eje = -1).
Combinar segmentos
Ahora que hemos recortado segmentos, podemos combinarlos para obtener una única RaggedTensor
. BERT utiliza fichas especiales para indicar el comienzo ( [CLS]
) y el final de un segmento ( [SEP]
). También necesitamos un RaggedTensor
indicando qué elementos en el combinado Tensor
pertenecen a cada segmento. Podemos utilizar text.combine_segments()
para conseguir ambas cosas Tensor
con fichas especiales insertados.
segments_combined, segments_ids = text.combine_segments(
[segment_a, segment_b],
start_of_sequence_id=_START_TOKEN, end_of_segment_id=_END_TOKEN)
segments_combined, segments_ids
(<tf.RaggedTensor [[3, 22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 8, 4, 21, 28, 30, 27, 29, 4]]>, <tf.RaggedTensor [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]]>)
Tarea de modelo de lenguaje enmascarado
Ahora que tenemos nuestros insumos básicos, podemos empezar a extraer los insumos necesarios para el "enmascarado LM y procedimiento de enmascaramiento" tarea describe en el BERT: Pre-entrenamiento de profunda bidireccionales Transformadores para la comprensión del lenguaje
La tarea del modelo de lenguaje enmascarado tiene dos subproblemas en los que debemos pensar: (1) qué elementos seleccionar para enmascarar y (2) ¿qué valores se les asignan?
Selección de artículos
Debido a que vamos a elegir para seleccionar elementos al azar para el enmascaramiento, vamos a utilizar una text.RandomItemSelector
. RandomItemSelector
selecciona aleatoriamente artículos en un lote sujeto a las restricciones dadas ( max_selections_per_batch
, selection_rate
y unselectable_ids
) y devuelve una máscara booleano que indica que se seleccionaron artículos.
random_selector = text.RandomItemSelector(
max_selections_per_batch=_MAX_PREDICTIONS_PER_BATCH,
selection_rate=0.2,
unselectable_ids=[_START_TOKEN, _END_TOKEN, _UNK_TOKEN]
)
selected = random_selector.get_selection_mask(
segments_combined, axis=1)
selected
<tf.RaggedTensor [[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, True, True, True, False, False], [False, False, False, False, False, True, False, False, False, False, False, True, False]]>
Elegir el valor enmascarado
La metodología descrita en el documento BERT original para elegir el valor de enmascaramiento es la siguiente:
Para mask_token_rate
del tiempo, sustituir el elemento con el [MASK]
token:
"my dog is hairy" -> "my dog is [MASK]"
Para random_token_rate
del tiempo, sustituir el artículo con una palabra al azar:
"my dog is hairy" -> "my dog is apple"
Para 1 - mask_token_rate - random_token_rate
del tiempo, mantener el tema sin cambios:
"my dog is hairy" -> "my dog is hairy."
text.MaskedValuesChooser
encapsula esta lógica y se puede utilizar para nuestra función de pre-procesamiento. He aquí un ejemplo de lo que MaskValuesChooser
vuelve dan un mask_token_rate
del 80% y por defecto random_token_rate
:
input_ids = tf.ragged.constant([[19, 7, 21, 20, 9, 8], [13, 4, 16, 5], [15, 10, 12, 11, 6]])
mask_values_chooser = text.MaskValuesChooser(_VOCAB_SIZE, _MASK_TOKEN, 0.8)
mask_values_chooser.get_mask_values(input_ids)
<tf.RaggedTensor [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1], [1, 10, 1, 1, 6]]>
Cuando se suministra con un RaggedTensor
de entrada, text.MaskValuesChooser
devuelve un RaggedTensor
de la misma forma, ya sea con _MASK_VALUE
(0), un ID aleatorio, o el mismo ID sin cambios.
Generación de entradas para la tarea del modelo de lenguaje enmascarado
Ahora que tenemos un RandomItemSelector
que ayuda a seleccionar artículos para el enmascaramiento y text.MaskValuesChooser
para asignar los valores, podemos utilizar text.mask_language_model()
para ensamblar todas las entradas de esta tarea para nuestro modelo BERT.
masked_token_ids, masked_pos, masked_lm_ids = text.mask_language_model(
segments_combined,
item_selector=random_selector, mask_values_chooser=mask_values_chooser)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/dispatch.py:206: batch_gather (from tensorflow.python.ops.array_ops) is deprecated and will be removed after 2017-10-25. Instructions for updating: `tf.batch_gather` is deprecated, please use `tf.gather` with `batch_dims=-1` instead.
Vamos inmersión más profunda y examinar las salidas de mask_language_model()
. La salida del masked_token_ids
es:
masked_token_ids
<tf.RaggedTensor [[3, 22, 1, 24, 23, 1, 10, 28, 1, 15, 1, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 1, 4, 21, 28, 30, 27, 1, 4]]>
Recuerde que nuestra entrada está codificada usando un vocabulario. Si desciframos masked_token_ids
utilizando nuestro vocabulario, obtenemos:
tf.gather(_VOCAB, masked_token_ids)
<tf.RaggedTensor [[b'[CLS]', b'Sp', b'[MASK]', b'bob', b'Sq', b'[MASK]', b'##pants', b'is', b'[MASK]', b'A', b'[MASK]', b'##ger', b'[SEP]', b'Bar', b'##ack', b'Ob', b'##ama', b'is', b'the', b'President', b'[UNK]', b'[SEP]'], [b'[CLS]', b'Mar', b'##vel', b'A', b'##ven', b'[MASK]', b'[SEP]', b'President', b'is', b'the', b'highest', b'[MASK]', b'[SEP]']]>
Tenga en cuenta que algunas fichas wordpiece han sido reemplazados con cualquiera de [MASK]
, [RANDOM]
o un valor de ID diferente. masked_pos
salida nos da los índices (en el respectivo lote) de las fichas que han sido reemplazados.
masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>
masked_lm_ids
nos da el valor original de la ficha.
masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>
Podemos decodificar nuevamente los ID aquí para obtener valores legibles por humanos.
tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>
Entradas del modelo de relleno
Ahora que tenemos todas las entradas para nuestro modelo, el último paso en nuestra pre-procesamiento es fijo empaquetarlos en 2 dimensiones Tensor
s con el acolchado y también generar una máscara Tensor
indicando los valores que son valores del relleno. Podemos utilizar text.pad_model_inputs()
para ayudarnos con esta tarea.
# Prepare and pad combined segment inputs
input_word_ids, input_mask = text.pad_model_inputs(
masked_token_ids, max_seq_length=_MAX_SEQ_LEN)
input_type_ids, _ = text.pad_model_inputs(
masked_token_ids, max_seq_length=_MAX_SEQ_LEN)
# Prepare and pad masking task inputs
masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
masked_token_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
masked_lm_ids, _ = text.pad_model_inputs(
masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
model_inputs = {
"input_word_ids": input_word_ids,
"input_mask": input_mask,
"input_type_ids": input_type_ids,
"masked_lm_ids": masked_lm_ids,
"masked_lm_positions": masked_lm_positions,
"masked_lm_weights": masked_lm_weights,
}
model_inputs
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 1, 24, 23, 1, 10, 28], [ 3, 18, 12, 15, 13, 1, 4, 21]])>, 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]])>, 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 1, 24, 23, 1, 10, 28], [ 3, 18, 12, 15, 13, 1, 4, 21]])>, 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[ 9, 11, 14, 13, 0], [ 8, 29, 0, 0, 0]])>, 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[ 3, 22, 1, 24, 23], [ 3, 18, 12, 15, 13]])>, 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]])>}
Revisar
Repasemos lo que tenemos hasta ahora y montemos nuestra función de preprocesamiento. Esto es lo que tenemos:
def bert_pretrain_preprocess(vocab_table, features):
# Input is a string Tensor of documents, shape [batch, 1].
text_a = features["text_a"]
text_b = features["text_b"]
# Tokenize segments to shape [num_sentences, (num_words)] each.
tokenizer = text.BertTokenizer(
vocab_table,
token_out_type=tf.int64)
segments = [tokenizer.tokenize(text).merge_dims(
1, -1) for text in (text_a, text_b)]
# Truncate inputs to a maximum length.
trimmer = text.RoundRobinTrimmer(max_seq_length=6)
trimmed_segments = trimmer.trim(segments)
# Combine segments, get segment ids and add special tokens.
segments_combined, segment_ids = text.combine_segments(
trimmed_segments,
start_of_sequence_id=_START_TOKEN,
end_of_segment_id=_END_TOKEN)
# Apply dynamic masking task.
masked_input_ids, masked_lm_positions, masked_lm_ids = (
text.mask_language_model(
segments_combined,
random_selector,
mask_values_chooser,
)
)
# Prepare and pad combined segment inputs
input_word_ids, input_mask = text.pad_model_inputs(
masked_input_ids, max_seq_length=_MAX_SEQ_LEN)
input_type_ids, _ = text.pad_model_inputs(
masked_input_ids, max_seq_length=_MAX_SEQ_LEN)
# Prepare and pad masking task inputs
masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
masked_input_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
masked_lm_ids, _ = text.pad_model_inputs(
masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
model_inputs = {
"input_word_ids": input_word_ids,
"input_mask": input_mask,
"input_type_ids": input_type_ids,
"masked_lm_ids": masked_lm_ids,
"masked_lm_positions": masked_lm_positions,
"masked_lm_weights": masked_lm_weights,
}
return model_inputs
Previamente se construyó una tf.data.Dataset
y ahora podemos utilizar nuestra función de pre-procesamiento montado bert_pretrain_preprocess()
en Dataset.map()
. Esto nos permite crear una canalización de entrada para transformar nuestros datos de cadena sin procesar en entradas enteras y alimentar directamente a nuestro modelo.
dataset = tf.data.Dataset.from_tensors(examples)
dataset = dataset.map(functools.partial(
bert_pretrain_preprocess, lookup_table))
next(iter(dataset))
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 9, 1, 4, 16, 5, 19], [ 3, 18, 1, 15, 4, 1, 28, 30]])>, 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]])>, 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy= array([[ 3, 22, 9, 1, 4, 16, 5, 19], [ 3, 18, 1, 15, 4, 1, 28, 30]])>, 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[24, 19, 0, 0, 0], [12, 21, 0, 0, 0]])>, 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[ 3, 22, 9, 1, 4], [ 3, 18, 1, 15, 4]])>, 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy= array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]])>}
Tutoriales relacionados
Clasifica texto con BERT - Un tutorial sobre cómo utilizar un modelo BERT pretrained al texto clasificar. Este es un buen seguimiento ahora que está familiarizado con cómo preprocesar las entradas utilizadas por el modelo BERT.
Tokenizar con texto TF - Tutorial que detalla los diferentes tipos de tokenizers que existen en TF.Text.
Manejo de Texto
RaggedTensor
- guía detallada sobre cómo crear, utilizar y manipularRaggedTensor
s.