Hemos visto cómo entrenar el modelo de Transformer en un conjunto de datos de pares de oraciones en inglés y alemán, así como también cómo trazar las curvas de pérdida de entrenamiento y validación para diagnosticar el rendimiento de aprendizaje del modelo y decidir en qué época inferir el modelo entrenado. Ahora estamos listos para inferir el modelo de Transformador entrenado con el propósito de traducir una oración de entrada.
En este tutorial, descubrirá cómo inferir el modelo de Transformer entrenado para la traducción automática neuronal.
Después de completar este tutorial, sabrás:
- Cómo inferir el modelo de Transformer entrenado.
- Cómo generar traducciones de texto.
Empecemos.
Inferencia del modelo de transformador
Foto de Karsten Würth, algunos derechos reservados.
Descripción general del tutorial
Este tutorial se divide en tres partes; están:
- Resumen de la arquitectura del transformador
- Inferencia del modelo de transformador
- Probando el código
requisitos previos
Para este tutorial, asumimos que ya está familiarizado con:
- La teoría detrás del modelo Transformer
- Una implementación del modelo Transformer
- Entrenando el modelo de Transformador
- Trazado de las curvas de pérdida de entrenamiento y validación para el modelo Transformer
Resumen de la arquitectura del transformador
Recuerde haber visto que la arquitectura de Transformer sigue una estructura de codificador-decodificador: el codificador, en el lado izquierdo, tiene la tarea de mapear una secuencia de entrada a una secuencia de representaciones continuas; el decodificador, en el lado derecho, recibe la salida del codificador junto con la salida del decodificador en el paso de tiempo anterior, para generar una secuencia de salida.
La estructura del codificador-decodificador de la arquitectura del transformador
Tomado de “La atención es todo lo que necesitas“
Al generar una secuencia de salida, el Transformador no se basa en recurrencias ni circunvoluciones.
Hemos visto cómo implementar el modelo completo de Transformer y, posteriormente, entrenarlo en un conjunto de datos de pares de oraciones en inglés y alemán. Ahora procederemos a inferir el modelo entrenado para la traducción automática neuronal.
Inferencia del modelo de transformador
Comencemos primero por crear una nueva instancia de la TransformerModel
clase que hemos implementado previamente en este tutorial.
Introduciremos en él los argumentos de entrada relevantes como se especifica en el artículo de Vaswani et al. (2017), así como la información relevante sobre el conjunto de datos en uso:
# Define the model parameters
h = 8 # Number of self-attention heads
d_k = 64 # Dimensionality of the linearly projected queries and keys
d_v = 64 # Dimensionality of the linearly projected values
d_model = 512 # Dimensionality of model layers’ outputs
d_ff = 2048 # Dimensionality of the inner fully connected layer
n = 6 # Number of layers in the encoder stack
# Define the dataset parameters
enc_seq_length = 7 # Encoder sequence length
dec_seq_length = 12 # Decoder sequence length
enc_vocab_size = 2405 # Encoder vocabulary size
dec_vocab_size = 3858 # Decoder vocabulary size
# Create model
inferencing_model = TransformerModel(enc_vocab_size, dec_vocab_size, enc_seq_length, dec_seq_length, h, d_k, d_v, d_model, d_ff, n, 0)
Aquí tenga en cuenta que la última entrada que se introduce en el TransformerModel
corresponde a la tasa de deserción para cada uno de los Dropout
capas en el modelo de Transformador. Estas Dropout
las capas no se usarán durante la inferencia del modelo (eventualmente estableceremos el training
argumento a False
) y, por lo tanto, podemos establecer con seguridad la tasa de abandono en 0.
Además, también había guardado el TransformerModel
clase en un script separado, que había nombrado, model.py
. Por lo tanto, para poder utilizar el TransformerModel
clase, necesito incluir, from model import TransformerModel
.
A continuación, crearemos una clase, Translate
que hereda del Module
clase base en Keras, y asigne el modelo de inferencia inicializado a la variable, transformer
:
class Translate(Module):
def __init__(self, inferencing_model, **kwargs):
super(Translate, self).__init__(**kwargs)
self.transformer = inferencing_model
…
Cuando entrenamos el modelo de Transformer, vimos que primero necesitábamos tokenizar las secuencias de texto que se iban a introducir tanto en el codificador como en el decodificador. Lo habíamos logrado creando un vocabulario de palabras y reemplazando cada palabra por su índice de vocabulario correspondiente.
Necesitamos pasar por un proceso similar durante la etapa de inferencia, antes de introducir la secuencia de texto que se traducirá al modelo de Transformer.
Para ello, estaremos incluyendo dentro de la clase los siguientes load_tokenizer
método, que servirá para cargar los tokenizadores codificadores y decodificadores que hubiéramos generado y guardado durante la etapa de entrenamiento:
def load_tokenizer(self, name):
with open(name, ‘rb’) as handle:
return load(handle)
Es importante que tokenicemos el texto de entrada en la etapa de inferencia utilizando los mismos tokenizadores generados en la etapa de entrenamiento del modelo Transformer, ya que estos tokenizadores ya se habrán entrenado en secuencias de texto que son similares a nuestros datos de prueba.
El siguiente paso es crear el método de clase, call()
que se encargará de:
- Agregue tokens de inicio (
) y fin de cadena ( ) a la oración de entrada:
def __call__(self, sentence):
sentence[0] = «<START> » + sentence[0] + » <EOS>»
- Cargue los tokenizadores del codificador y del decodificador (en este caso, guardados en el
enc_tokenizer.pkl
ydec_tokenizer.pkl
pickle archivos, respectivamente):
enc_tokenizer = self.load_tokenizer(‘enc_tokenizer.pkl’)
dec_tokenizer = self.load_tokenizer(‘dec_tokenizer.pkl’)
- Prepare la oración de entrada tokenizándola primero, luego rellenándola hasta la longitud máxima de la frase y luego convirtiéndola en un tensor:
encoder_input = enc_tokenizer.texts_to_sequences(sentence)
encoder_input = pad_sequences(encoder_input, maxlen=enc_seq_length, padding=’post’)
encoder_input = convert_to_tensor(encoder_input, dtype=int64)
- Repita un procedimiento similar de tokenización y conversión de tensor para los tokens
y en la salida:
output_start = dec_tokenizer.texts_to_sequences([«<START>»])
output_start = convert_to_tensor(output_start[0], dtype=int64)
output_end = dec_tokenizer.texts_to_sequences([«<EOS>»])
output_end = convert_to_tensor(output_end[0], dtype=int64)
- Prepare la matriz de salida que contendrá el texto traducido. Dado que no conocemos la longitud de la oración traducida de antemano, inicializaremos el tamaño de la matriz de salida en 0, pero estableceremos su
dynamic_size
parámetro aTrue
para que crezca más allá de su tamaño inicial. Luego estableceremos el primer valor en esta matriz de salida en el token:
decoder_output = TensorArray(dtype=int64, size=0, dynamic_size=True)
decoder_output = decoder_output.write(0, output_start)
- Iterar, hasta la longitud de la secuencia del decodificador, llamando cada vez al modelo de Transformador para predecir un token de salida. Aquí el
training
entrada, que luego se pasa a cada uno de los transformadoresDropout
capas, se establece enFalse
para que no se eliminen valores durante la inferencia. A continuación, se selecciona la predicción con la puntuación más alta y se escribe en el siguiente índice disponible de la matriz de salida. losfor
bucle se termina con unbreak
tan pronto como se prediga un token:
for i in range(dec_seq_length):
prediction = self.transformer(encoder_input, transpose(decoder_output.stack()), training=False)
prediction = prediction[:, -1, :]
predicted_id = argmax(prediction, axis=-1)
predicted_id = predicted_id[0][newaxis]
decoder_output = decoder_output.write(i + 1, predicted_id)
if predicted_id == output_end:
break
- Decodifique los tokens predichos en una lista de salida y devuélvalos:
output = transpose(decoder_output.stack())[0] output = output.numpy()
output_str = []
# Decode the predicted tokens into an output list
for i in range(output.shape[0]):
key = output[i] translation = dec_tokenizer.index_word[key] output_str.append(translation)
return output_str
La lista completa de códigos, hasta ahora, es la siguiente:
from pickle import load
from tensorflow import Module
from keras.preprocessing.sequence import pad_sequences
from tensorflow import convert_to_tensor, int64, TensorArray, argmax, newaxis, transpose
from model import TransformerModel
# Define the model parameters
h = 8 # Number of self-attention heads
d_k = 64 # Dimensionality of the linearly projected queries and keys
d_v = 64 # Dimensionality of the linearly projected values
d_model = 512 # Dimensionality of model layers’ outputs
d_ff = 2048 # Dimensionality of the inner fully connected layer
n = 6 # Number of layers in the encoder stack
# Define the dataset parameters
enc_seq_length = 7 # Encoder sequence length
dec_seq_length = 12 # Decoder sequence length
enc_vocab_size = 2405 # Encoder vocabulary size
dec_vocab_size = 3858 # Decoder vocabulary size
# Create model
inferencing_model = TransformerModel(enc_vocab_size, dec_vocab_size, enc_seq_length, dec_seq_length, h, d_k, d_v, d_model, d_ff, n, 0)
class Translate(Module):
def __init__(self, inferencing_model, **kwargs):
super(Translate, self).__init__(**kwargs)
self.transformer = inferencing_model
def load_tokenizer(self, name):
with open(name, ‘rb’) as handle:
return load(handle)
def __call__(self, sentence):
# Append start and end of string tokens to the input sentence
sentence[0] = «<START> » + sentence[0] + » <EOS>»
# Load encoder and decoder tokenizers
enc_tokenizer = self.load_tokenizer(‘enc_tokenizer.pkl’)
dec_tokenizer = self.load_tokenizer(‘dec_tokenizer.pkl’)
# Prepare the input sentence by tokenizing, padding and converting to tensor
encoder_input = enc_tokenizer.texts_to_sequences(sentence)
encoder_input = pad_sequences(encoder_input, maxlen=enc_seq_length, padding=’post’)
encoder_input = convert_to_tensor(encoder_input, dtype=int64)
# Prepare the output <START> token by tokenizing, and converting to tensor
output_start = dec_tokenizer.texts_to_sequences([«<START>»])
output_start = convert_to_tensor(output_start[0], dtype=int64)
# Prepare the output <EOS> token by tokenizing, and converting to tensor
output_end = dec_tokenizer.texts_to_sequences([«<EOS>»])
output_end = convert_to_tensor(output_end[0], dtype=int64)
# Prepare the output array of dynamic size
decoder_output = TensorArray(dtype=int64, size=0, dynamic_size=True)
decoder_output = decoder_output.write(0, output_start)
for i in range(dec_seq_length):
# Predict an output token
prediction = self.transformer(encoder_input, transpose(decoder_output.stack()), training=False)
prediction = prediction[:, -1, :]
# Select the prediction with the highest score
predicted_id = argmax(prediction, axis=-1)
predicted_id = predicted_id[0][newaxis]
# Write the selected prediction to the output array at the next available index
decoder_output = decoder_output.write(i + 1, predicted_id)
# Break if an <EOS> token is predicted
if predicted_id == output_end:
break
output = transpose(decoder_output.stack())[0] output = output.numpy()
output_str = []
# Decode the predicted tokens into an output string
for i in range(output.shape[0]):
key = output[i] print(dec_tokenizer.index_word[key])
return output_str
Probando el código
Para probar el código, echemos un vistazo a la test_dataset.txt
archivo que habríamos guardado al preparar el conjunto de datos para el entrenamiento. Este archivo de texto contiene un conjunto de pares de oraciones en inglés y alemán que hemos reservado para probar, de las cuales podemos seleccionar un par de oraciones para probar.
Comencemos con la primera oración:
# Sentence to translate
sentence = [‘im thirsty’]
La traducción de verdad básica correspondiente en alemán para esta oración, incluidos los tokens decodificadores <START> ich bin durstig <EOS>
.
Si echamos un vistazo a las curvas de pérdida de validación y entrenamiento trazadas para este modelo (aquí estamos entrenando durante 20 épocas), podemos notar que la curva de pérdida de validación se ralentiza considerablemente y comienza a estabilizarse alrededor de la época 16.
Entonces, procedamos a cargar los pesos del modelo guardado en la época 16 y verifiquemos la predicción que genera el modelo:
# Load the trained model’s weights at the specified epoch
inferencing_model.load_weights(‘weights/wghts16.ckpt’)
# Create a new instance of the ‘Translate’ class
translator = Translate(inferencing_model)
# Translate the input sentence
print(translator(sentence))
Ejecutar las líneas de código anteriores produce la siguiente lista traducida de palabras:
[‘start’, ‘ich’, ‘bin’, ‘durstig’, ‘eos’]Lo cual es equivalente a la oración alemana de verdad básica que esperábamos (siempre tenga en cuenta que, dado que estamos entrenando el modelo de Transformer desde cero, puede llegar a diferentes resultados dependiendo de la inicialización aleatoria de los pesos del modelo).
Veamos qué hubiera pasado si, en cambio, hubiéramos cargado un conjunto de pesos correspondientes a una época mucho más anterior, como la 4ª época. En nuestro caso, la traducción generada es la siguiente:
[‘start’, ‘ich’, ‘bin’, ‘nicht’, ‘nicht’, ‘eos’]En inglés, esto se traduce como: yo no noque está claramente lejos de la oración de entrada en inglés, pero que se espera ya que, en esta época, el proceso de aprendizaje del modelo de Transformador aún se encuentra en etapas muy tempranas.
Intentemos nuevamente con una segunda oración del conjunto de datos de prueba:
# Sentence to translate
sentence = [‘are we done’]
La traducción de verdad básica correspondiente en alemán para esta oración, incluidos los tokens decodificadores <START> sind wir dann durch <EOS>
.
Encontramos que la traducción del modelo para esta oración, usando los pesos guardados en la época 16, es:
[‘start’, ‘ich’, ‘war’, ‘fertig’, ‘eos’]Lo que, más bien, se traduce en: estaba listo. Si bien esto tampoco es igual a la verdad básica, al menos es cerca a ella en significado.
Sin embargo, lo que sugiere la última prueba es que el modelo de Transformer podría haber requerido muchas más muestras de datos para entrenar de manera efectiva. Esto también se corrobora por el hecho de que la pérdida de validación en la que se estabiliza la curva de pérdida de validación sigue siendo relativamente alta.
De hecho, los modelos Transformer son conocidos por tener mucha necesidad de datos. Vaswani et al. (2017), por ejemplo, había entrenado su modelo de traducción de inglés a alemán utilizando un conjunto de datos que contenía alrededor de 4,5 millones de pares de oraciones.
Nos entrenamos en el conjunto de datos estándar WMT 2014 inglés-alemán que consta de aproximadamente 4,5 millones de pares de oraciones… Para inglés-francés, usamos el conjunto de datos WMT 2014 inglés-francés significativamente más grande que consta de 36 millones de oraciones…
– La atención es todo lo que necesitas, 2017.
Informaron que les llevó 3,5 días en 8 GPU P100 entrenar el modelo de traducción de inglés a alemán.
En comparación, aquí solo hemos entrenado en un conjunto de datos que comprende 10,000 muestras de datos, divididos entre conjuntos de entrenamiento, validación y prueba.
Por lo tanto, la siguiente tarea es para ti. Si tiene los recursos computacionales disponibles, intente entrenar el modelo de Transformador en un conjunto mucho más grande de pares de oraciones y vea si puede obtener mejores resultados que las traducciones que hemos obtenido aquí con una cantidad limitada de datos.
Otras lecturas
Esta sección proporciona más recursos sobre el tema si desea profundizar más.
Libros
- Aprendizaje profundo avanzado con Python, 2019.
- Transformadores para el procesamiento del lenguaje natural, 2021.
Documentos
- La atención es todo lo que necesitas, 2017.
Resumen
En este tutorial, descubrió cómo inferir el modelo de Transformer entrenado para la traducción automática neuronal.
Específicamente, aprendiste:
- Cómo inferir el modelo de Transformer entrenado.
- Cómo generar traducciones de texto.
¿Tiene usted alguna pregunta?
Haga sus preguntas en los comentarios a continuación y haré todo lo posible para responder.
La publicación Inferencia del modelo de transformador apareció primero en Machine Learning Mastery.