Esta página describe cómo TF2 SavedModels para tareas relacionadas con texto debe implementar la API de modelo guardado reutilizable . (Esto reemplaza y amplía las firmas comunes para texto para el formato TF1 Hub, ahora obsoleto).
Visión general
Hay varias API para calcular incrustaciones de texto (también conocidas como representaciones densas de texto o vectores de características de texto).
La API para incrustaciones de texto a partir de entradas de texto se implementa mediante un modelo guardado que asigna un lote de cadenas a un lote de vectores de incrustación. Esto es muy fácil de usar y muchos modelos en TF Hub lo han implementado. Sin embargo, esto no permite ajustar el modelo en TPU.
La API para incrustaciones de texto con entradas preprocesadas resuelve la misma tarea, pero se implementa mediante dos modelos guardados separados:
- un preprocesador que puede ejecutarse dentro de una canalización de entrada tf.data y convierte cadenas y otros datos de longitud variable en tensores numéricos,
- un codificador que acepta los resultados del preprocesador y realiza la parte entrenable del cálculo de incrustación.
Esta división permite preprocesar las entradas de forma asincrónica antes de introducirse en el ciclo de entrenamiento. En particular, permite crear codificadores que se pueden ejecutar y ajustar en TPU .
La API para incrustaciones de texto con codificadores Transformer extiende la API para incrustaciones de texto desde entradas preprocesadas hasta el caso particular de BERT y otros codificadores Transformer.
El preprocesador se amplía para crear entradas de codificador a partir de más de un segmento de texto de entrada.
El codificador Transformer expone las incrustaciones sensibles al contexto de tokens individuales.
En cada caso, las entradas de texto son cadenas codificadas en UTF-8, generalmente de texto sin formato, a menos que la documentación del modelo indique lo contrario.
Independientemente de la API, se han entrenado previamente diferentes modelos en texto de diferentes idiomas y dominios, y con diferentes tareas en mente. Por lo tanto, no todos los modelos de inserción de texto son adecuados para todos los problemas.
Incrustación de texto desde entradas de texto
Un modelo guardado para incrustaciones de texto a partir de entradas de texto acepta un lote de entradas en un tensor de cadena de forma [batch_size]
y las asigna a un tensor float32 de forma [batch_size, dim]
con representaciones densas (vectores de características) de las entradas.
Sinopsis de uso
obj = hub.load("path/to/model")
text_input = ["A long sentence.",
"single-word",
"http://example.com"]
embeddings = obj(text_input)
Recuerde de la API Reusable SavedModel que ejecutar el modelo en modo de entrenamiento (por ejemplo, para abandono) puede requerir un argumento de palabra clave obj(..., training=True)
, y que obj
proporciona atributos .variables
, .trainable_variables
y .regularization_losses
según corresponda .
En Keras, todo esto está a cargo de
embeddings = hub.KerasLayer("path/to/model", trainable=...)(text_input)
Entrenamiento distribuido
Si la inserción de texto se utiliza como parte de un modelo que se entrena con una estrategia de distribución, la llamada a hub.load("path/to/model")
o hub.KerasLayer("path/to/model", ...)
, respectivamente, debe suceder dentro del alcance de DistributionStrategy para poder crear las variables del modelo de forma distribuida. Por ejemplo
with strategy.scope():
...
model = hub.load("path/to/model")
...
Ejemplos de
- Tutorial de Colab Clasificación de texto con reseñas de películas .
Incrustaciones de texto con entradas preprocesadas
Dos modelos guardados independientes implementan una incrustación de texto con entradas preprocesadas :
- un preprocesador que asigna un tensor de cadena de forma
[batch_size]
a un dictado de tensores numéricos, - un codificador que acepta un dictado de tensores devuelto por el preprocesador, realiza la parte entrenable del cálculo de incrustación y devuelve un dictado de salidas. La salida bajo la clave
"default"
es un tensor float32 de forma[batch_size, dim]
.
Esto permite ejecutar el preprocesador en una canalización de entrada pero ajustar las incrustaciones calculadas por el codificador como parte de un modelo más grande. En particular, permite crear codificadores que se pueden ejecutar y ajustar en TPU .
Es un detalle de implementación qué tensores están contenidos en la salida del preprocesador y cuáles (si los hay) tensores adicionales además de "default"
están contenidos en la salida del codificador.
La documentación del codificador debe especificar qué preprocesador usar con él. Normalmente, hay exactamente una opción correcta.
Sinopsis de uso
text_input = tf.constant(["A long sentence.",
"single-word",
"http://example.com"])
preprocessor = hub.load("path/to/preprocessor") # Must match `encoder`.
encoder_inputs = preprocessor(text_input)
encoder = hub.load("path/to/encoder")
enocder_outputs = encoder(encoder_inputs)
embeddings = enocder_outputs["default"]
Recuerde de la API Reusable SavedModel que ejecutar el codificador en modo de entrenamiento (p. Ej., Para abandonar) puede requerir un encoder(..., training=True)
argumento de palabra clave encoder(..., training=True)
, y que el encoder
proporciona atributos .variables
, .trainable_variables
y .regularization_losses
según corresponda .
El modelo de preprocessor
puede tener .variables
pero no está destinado a ser entrenado más. El preprocesamiento no depende del modo: si preprocessor()
tiene un argumento training=...
, no tiene ningún efecto.
En Keras, todo esto está a cargo de
encoder_inputs = hub.KerasLayer("path/to/preprocessor")(text_input)
encoder_outputs = hub.KerasLayer("path/to/encoder", trainable=True)(encoder_inputs)
embeddings = encoder_outputs["default"]
Entrenamiento distribuido
Si el codificador se utiliza como parte de un modelo que se entrena con una estrategia de distribución, la llamada a hub.load("path/to/encoder")
o hub.KerasLayer("path/to/encoder", ...)
, resp., debe suceder dentro
with strategy.scope():
...
para recrear las variables del codificador de forma distribuida.
Del mismo modo, si el preprocesador es parte del modelo entrenado (como en el ejemplo simple anterior), también debe cargarse bajo el alcance de la estrategia de distribución. Sin embargo, si el preprocesador se usa en una canalización de entrada (por ejemplo, en un invocable pasado a tf.data.Dataset.map()
), su carga debe ocurrir fuera del alcance de la estrategia de distribución, para colocar sus variables (si las hay ) en la CPU del host.
Ejemplos de
- Tutorial de Colab Clasifica texto con BERT .
Incrustaciones de texto con Transformer Encoders
Los codificadores de transformadores para texto operan en un lote de secuencias de entrada, cada secuencia comprende n ≥ 1 segmentos de texto tokenizado, dentro de algún límite específico del modelo en n . Para BERT y muchas de sus extensiones, ese límite es 2, por lo que aceptan segmentos únicos y pares de segmentos.
La API para incrustaciones de texto con codificadores Transformer amplía la API para incrustaciones de texto con entradas preprocesadas para esta configuración.
Preprocesador
Un modelo guardado de preprocesador para incrustaciones de texto con codificadores Transformer implementa la API de un modelo guardado de preprocesador para incrustaciones de texto con entradas preprocesadas (ver arriba), que proporciona una forma de asignar entradas de texto de un solo segmento directamente a las entradas del codificador.
Además, el preprocesador SavedModel proporciona subobjetos invocables tokenize
para la tokenización (por separado por segmento) y bert_pack_inputs
para empaquetar n segmentos tokenizados en una secuencia de entrada para el codificador. Cada subobjeto sigue la API de modelo guardado reutilizable .
Sinopsis de uso
Como ejemplo concreto para dos segmentos de texto, veamos una tarea de implicación de oraciones que pregunta si una premisa (primer segmento) implica o no una hipótesis (segundo segmento).
preprocessor = hub.load("path/to/preprocessor")
# Tokenize batches of both text inputs.
text_premises = tf.constant(["The quick brown fox jumped over the lazy dog.",
"Good day."])
tokenized_premises = preprocessor.tokenize(text_premises)
text_hypotheses = tf.constant(["The dog was lazy.", # Implied.
"Axe handle!"]) # Not implied.
tokenized_hypotheses = preprocessor.tokenize(text_hypotheses)
# Pack input sequences for the Transformer encoder.
seq_length = 128
encoder_inputs = preprocessor.bert_pack_inputs(
[tokenized_premises, tokenized_hypotheses],
seq_length=seq_length) # Optional argument.
En Keras, este cálculo puede expresarse como
tokenize = hub.KerasLayer(preprocessor.tokenize)
tokenized_hypotheses = tokenize(text_hypotheses)
tokenized_premises = tokenize(text_premises)
bert_pack_inputs = hub.KerasLayer(
preprocessor.bert_pack_inputs,
arguments=dict(seq_length=seq_length)) # Optional argument.
encoder_inputs = bert_pack_inputs([tokenized_premises, tokenized_hypotheses])
Detalles de tokenize
Una llamada a preprocessor.tokenize()
acepta un Tensor de cadena de forma [batch_size]
y devuelve un RaggedTensor de forma [batch_size, ...]
cuyos valores son identificadores de token int32 que representan las cadenas de entrada. Puede haber r ≥ 1 dimensiones irregulares después de batch_size
pero ninguna otra dimensión uniforme.
- Si r = 1, la forma es
[batch_size, (tokens)]
, y cada entrada simplemente se tokeniza en una secuencia plana de tokens. - Si r > 1, hay r -1 niveles adicionales de agrupación. Por ejemplo, tensorflow_text.BertTokenizer usa r = 2 para agrupar tokens por palabras y da forma
[batch_size, (words), (tokens_per_word)]
. Depende del modelo en cuestión cuántos de estos niveles adicionales existen, si los hay, y qué agrupaciones representan.
El usuario puede (pero no es necesario) modificar las entradas tokenizadas, por ejemplo, para adaptarse al límite de seq_length que se aplicará en el empaquetado de las entradas del codificador. Las dimensiones adicionales en la salida del tokenizador pueden ayudar aquí (por ejemplo, para respetar los límites de las palabras) pero pierden sentido en el siguiente paso.
En términos de la API Reusable SavedModel , el objeto preprocessor.tokenize
puede tener .variables
pero no está destinado a ser entrenado más. La tokenización no depende del modo: si preprocessor.tokenize()
tiene un argumento training=...
, no tiene ningún efecto.
Detalles de bert_pack_inputs
Una llamada a preprocessor.bert_pack_inputs()
acepta una lista de Python de entradas tokenizadas (por lotes por separado para cada segmento de entrada) y devuelve un dictado de tensores que representan un lote de secuencias de entrada de longitud fija para el modelo de codificador Transformer.
Cada entrada tokenizada es un int32 RaggedTensor de shape [batch_size, ...]
, donde el número r de dimensiones irregulares después de batch_size es 1 o el mismo que en la salida de preprocessor.tokenize().
(Este último es solo por conveniencia; las dimensiones adicionales se aplanan antes de empacar).
El empaquetado agrega tokens especiales alrededor de los segmentos de entrada como esperaba el codificador. La llamada bert_pack_inputs()
implementa exactamente el esquema de empaquetado utilizado por los modelos BERT originales y muchas de sus extensiones: la secuencia empaquetada comienza con un token de inicio de secuencia, seguido de los segmentos tokenizados, cada uno terminado por un final de segmento simbólico. Las posiciones restantes hasta seq_length, si las hay, se llenan con tokens de relleno.
Si una secuencia empaquetada excedería seq_length, bert_pack_inputs()
trunca sus segmentos a prefijos de aproximadamente el mismo tamaño para que la secuencia empaquetada encaje exactamente dentro de seq_length.
El empaquetado no depende del modo: si preprocessor.bert_pack_inputs()
tiene un argumento training=...
, no tiene ningún efecto. Además, no se espera que preprocessor.bert_pack_inputs
tenga variables ni admita ajustes precisos.
Codificador
El codificador se llama en el dict de encoder_inputs
de la misma manera que en la API para incrustaciones de texto con entradas preprocesadas (ver arriba), incluidas las disposiciones de la API de modelo guardado reutilizable .
Sinposis de uso
enocder = hub.load("path/to/encoder")
enocder_outputs = encoder(encoder_inputs)
o equivalentemente en Keras:
encoder = hub.KerasLayer("path/to/encoder", trainable=True)
encoder_outputs = encoder(encoder_inputs)
Detalles
Los encoder_outputs
son un dictado de tensores con las siguientes claves.
-
"sequence_output"
: un tensor float32 de forma[batch_size, seq_length, dim]
con la incrustación sensible al contexto de cada token de cada secuencia de entrada empaquetada. -
"pooled_output"
: un tensor float32 de forma[batch_size, dim]
con la incrustación de cada secuencia de entrada como un todo, derivada de sequence_output de alguna manera entrenable. -
"default"
, como lo requiere la API para incrustaciones de texto con entradas preprocesadas: un tensor float32 de forma[batch_size, dim]
con la incrustación de cada secuencia de entrada. (Esto podría ser solo un alias de pooled_output).
El contenido de encoder_inputs
no es estrictamente requerido por esta definición de API. Sin embargo, para los codificadores que utilizan entradas de estilo BERT, se recomienda utilizar los siguientes nombres (del kit de herramientas de modelado de PNL de TensorFlow Model Garden ) para minimizar la fricción al intercambiar codificadores y reutilizar modelos de preprocesador:
-
"input_word_ids"
: un tensor int32 de forma[batch_size, seq_length]
con los identificadores de token de la secuencia de entrada empaquetada (es decir, incluido un token de inicio de secuencia, tokens de final de segmento y relleno). -
"input_mask"
: un tensor int32 de forma[batch_size, seq_length]
con valor 1 en la posición de todos los tokens de entrada presentes antes del relleno y valor 0 para los tokens de relleno. -
"input_type_ids"
: un tensor int32 de forma[batch_size, seq_length]
con el índice del segmento de entrada que dio lugar al token de entrada en la posición respectiva. El primer segmento de entrada (índice 0) incluye el token de inicio de secuencia y su token de final de segmento. El segundo y los últimos segmentos (si están presentes) incluyen su respectivo token de final de segmento. Los tokens de relleno obtienen el índice 0 nuevamente.
Entrenamiento distribuido
Para cargar los objetos de preprocesador y codificador dentro o fuera del alcance de una estrategia de distribución, se aplican las mismas reglas que en la API para incrustaciones de texto con entradas preprocesadas (ver más arriba).
Ejemplos de
- Tutorial de Colab Resuelva tareas GLUE usando BERT en TPU .