Saltar al contenido

Implementación del codificador de transformador desde cero en TensorFlow y Keras

7 de octubre de 2022


Habiendo visto cómo implementar la atención del producto punto escalado e integrarla dentro de la atención de múltiples cabezas del modelo de Transformador, podemos avanzar un paso más hacia la implementación de un modelo de Transformador completo implementando su codificador. Nuestro objetivo final sigue siendo la aplicación del modelo completo al procesamiento del lenguaje natural (NLP).

En este tutorial, descubrirás cómo implementar el codificador Transformer desde cero en TensorFlow y Keras.

Después de completar este tutorial, sabrás:

  • Las capas que forman parte del codificador Transformer.
  • Cómo implementar el codificador Transformer desde cero.

Empecemos.


Recomendado: ¿Qué es el Big data?.


Implementación del codificador de transformador desde cero en TensorFlow y Keras
Foto de ian dooley, algunos derechos reservados.

Descripción general del tutorial

Este tutorial se divide en tres partes; están:

  • Resumen de la arquitectura del transformador
    • El codificador de transformador
  • Implementando el codificador de transformador desde cero
    • La red neuronal feed-forward totalmente conectada y la normalización de capas
    • La capa del codificador
    • El codificador de transformador
  • Probando el código

requisitos previos

Para este tutorial, asumimos que ya está familiarizado con:

  • El modelo del transformador
  • La atención del producto punto escalado
  • La atención de múltiples cabezas
  • La codificación posicional del transformador

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.

Habíamos visto que la parte del decodificador del Transformador comparte muchas similitudes en su arquitectura con el codificador. En este tutorial, nos centraremos en los componentes que forman parte del codificador Transformer.

El codificador de transformador

El codificador Transformer consta de una pila de $N$ capas idénticas, donde cada capa consta además de dos subcapas principales:

  • La primera subcapa comprende un mecanismo de atención de múltiples cabezas que recibe las consultas, claves y valores como entradas.
  • Una segunda subcapa que comprende una red feed-forward totalmente conectada.

El bloque codificador de la arquitectura del transformador
Tomado de “La atención es todo lo que necesitas“

Después de cada una de estas dos subcapas está la normalización de capas, en la que se alimentan la entrada de la subcapa (a través de una conexión residual) y la salida. El resultado de cada paso de normalización de capa es el siguiente:

LayerNorm (entrada de subcapa + salida de subcapa)

Para facilitar tal operación, que implica una adición entre la entrada y la salida de la subcapa, Vaswani et al. diseñó todas las subcapas y capas incrustadas en el modelo para producir salidas de dimensión, $d_{text{modelo}}$ = 512.

Recupere también las consultas, claves y valores como entradas al codificador Transformer.

Aquí, las consultas, las claves y los valores llevan la misma secuencia de entrada después de que se haya incrustado y aumentado con información posicional, donde las consultas y las claves son de dimensionalidad, $d_k$, mientras que la dimensionalidad de los valores es $d_v$.

Además, Vaswani et al. también introduzca la regularización en el modelo aplicando abandono a la salida de cada subcapa (antes del paso de normalización de la capa), así como a las codificaciones posicionales antes de que se introduzcan en el codificador.

Veamos ahora cómo implementar el codificador Transformer desde cero en TensorFlow y Keras.

Implementando el codificador de transformador desde cero

La red neuronal feed-forward totalmente conectada y la normalización de capas

Comenzaremos creando clases para el Avance hacia adelante y Agregar y norma capas que se muestran en el diagrama de arriba.

Vaswani et al. díganos que la red feed-forward completamente conectada consta de dos transformaciones lineales con una activación ReLU en el medio. La primera transformación lineal produce una salida de dimensionalidad, $d_{ff}$ = 2048, mientras que la segunda transformación lineal produce una salida de dimensionalidad, $d_{text{modelo}}$ = 512.

Para este propósito, primero vamos a crear la clase, FeedForward que hereda de la Layer clase base en Keras, e inicializa las capas densas y la activación de ReLU:

class FeedForward(Layer):
def __init__(self, d_ff, d_model, **kwargs):
super(FeedForward, self).__init__(**kwargs)
self.fully_connected1 = Dense(d_ff) # First fully connected layer
self.fully_connected2 = Dense(d_model) # Second fully connected layer
self.activation = ReLU() # ReLU activation layer

Le añadiremos el método de clase, call()que recibe una entrada y la pasa a través de las dos capas totalmente conectadas con activación ReLU, devolviendo una salida de dimensionalidad igual a 512:


def call(self, x):
# The input is passed into the two fully-connected layers, with a ReLU in between
x_fc1 = self.fully_connected1(x)

return self.fully_connected2(self.activation(x_fc1))

El siguiente paso es crear otra clase, AddNormalizationque también hereda de la Layer clase base en Keras e inicialice una capa de normalización de capa:

class AddNormalization(Layer):
def __init__(self, **kwargs):
super(AddNormalization, self).__init__(**kwargs)
self.layer_norm = LayerNormalization() # Layer normalization layer

En él, incluiremos el siguiente método de clase que suma la entrada y la salida de su subcapa, que recibe como entradas, y aplica la normalización de capa al resultado:


def call(self, x, sublayer_x):
# The sublayer input and output need to be of the same shape to be summed
add = x + sublayer_x

# Apply layer normalization to the sum
return self.layer_norm(add)

La capa del codificador

A continuación, implementaremos la capa del codificador, que el codificador de Transformer replicará idénticamente $N$ veces.

Para este propósito, vamos a crear la clase, EncoderLayere inicialice todas las subcapas de las que consta:

class EncoderLayer(Layer):
def __init__(self, h, d_k, d_v, d_model, d_ff, rate, **kwargs):
super(EncoderLayer, self).__init__(**kwargs)
self.multihead_attention = MultiHeadAttention(h, d_k, d_v, d_model)
self.dropout1 = Dropout(rate)
self.add_norm1 = AddNormalization()
self.feed_forward = FeedForward(d_ff, d_model)
self.dropout2 = Dropout(rate)
self.add_norm2 = AddNormalization()

Aquí puede notar que hemos inicializado instancias del FeedForward y AddNormalization clases, que acabamos de crear en la sección anterior, y asignamos su salida a las respectivas variables, feed_forward y add_norm (1 y 2). los Dropout La capa se explica por sí misma, donde rate define la frecuencia en la que las unidades de entrada se establecen en 0. Habíamos creado el MultiHeadAttention clase en un tutorial anterior, y si ha guardado el código en un script de Python separado, no olvide import eso. Guardé el mío en un script de Python llamado, multihead_atencion.pyy por esta razón necesito incluir la línea de código, from multihead_attention import MultiHeadAttention.

Procedamos ahora a crear el método de clase, call()que implementa todas las subcapas del codificador:


def call(self, x, padding_mask, training):
# Multi-head attention layer
multihead_output = self.multihead_attention(x, x, x, padding_mask)
# Expected output shape = (batch_size, sequence_length, d_model)

# Add in a dropout layer
multihead_output = self.dropout1(multihead_output, training=training)

# Followed by an Add & Norm layer
addnorm_output = self.add_norm1(x, multihead_output)
# Expected output shape = (batch_size, sequence_length, d_model)

# Followed by a fully connected layer
feedforward_output = self.feed_forward(addnorm_output)
# Expected output shape = (batch_size, sequence_length, d_model)

# Add in another dropout layer
feedforward_output = self.dropout2(feedforward_output, training=training)

# Followed by another Add & Norm layer
return self.add_norm2(addnorm_output, feedforward_output)

Además de los datos de entrada, el call() El método también puede recibir una máscara de relleno. Como un breve recordatorio de lo que habíamos dicho en un tutorial anterior, el relleno La máscara es necesaria para evitar que el relleno de ceros en la secuencia de entrada se procese junto con los valores de entrada reales.

El mismo método de clase puede recibir un training indicador que, cuando se establece en Truesolo aplicará las capas de Dropout durante el entrenamiento.

El codificador de transformador

El último paso es crear una clase para el codificador Transformer, que nombraremos Encoder:

class Encoder(Layer):
def __init__(self, vocab_size, sequence_length, h, d_k, d_v, d_model, d_ff, n, rate, **kwargs):
super(Encoder, self).__init__(**kwargs)
self.pos_encoding = PositionEmbeddingFixedWeights(sequence_length, vocab_size, d_model)
self.dropout = Dropout(rate)
self.encoder_layer = [EncoderLayer(h, d_k, d_v, d_model, d_ff, rate) for _ in range(n)] …

El codificador Transformer recibe una secuencia de entrada después de que esta haya pasado por un proceso de incrustación de palabras y codificación posicional. Para calcular la codificación posicional, haremos uso de la PositionEmbeddingFixedWeights clase descrita por Mehreen Saeed en este tutorial.

Como hemos hecho de manera similar en las secciones anteriores, aquí también crearemos un método de clase, call()que aplica incrustación de palabras y codificación posicional a la secuencia de entrada, y envía el resultado a las capas del codificador $N$:


def call(self, input_sentence, padding_mask, training):
# Generate the positional encoding
pos_encoding_output = self.pos_encoding(input_sentence)
# Expected output shape = (batch_size, sequence_length, d_model)

# Add in a dropout layer
x = self.dropout(pos_encoding_output, training=training)

# Pass on the positional encoded values to each encoder layer
for i, layer in enumerate(self.encoder_layer):
x = layer(x, padding_mask, training)

return x

La lista de códigos para el codificador Transformer completo es la siguiente:

from tensorflow.keras.layers import LayerNormalization, Layer, Dense, ReLU, Dropout
from multihead_attention import MultiHeadAttention
from positional_encoding import PositionEmbeddingFixedWeights

# Implementing the Add & Norm Layer
class AddNormalization(Layer):
def __init__(self, **kwargs):
super(AddNormalization, self).__init__(**kwargs)
self.layer_norm = LayerNormalization() # Layer normalization layer

def call(self, x, sublayer_x):
# The sublayer input and output need to be of the same shape to be summed
add = x + sublayer_x

# Apply layer normalization to the sum
return self.layer_norm(add)

# Implementing the Feed-Forward Layer
class FeedForward(Layer):
def __init__(self, d_ff, d_model, **kwargs):
super(FeedForward, self).__init__(**kwargs)
self.fully_connected1 = Dense(d_ff) # First fully connected layer
self.fully_connected2 = Dense(d_model) # Second fully connected layer
self.activation = ReLU() # ReLU activation layer

def call(self, x):
# The input is passed into the two fully-connected layers, with a ReLU in between
x_fc1 = self.fully_connected1(x)

return self.fully_connected2(self.activation(x_fc1))

# Implementing the Encoder Layer
class EncoderLayer(Layer):
def __init__(self, h, d_k, d_v, d_model, d_ff, rate, **kwargs):
super(EncoderLayer, self).__init__(**kwargs)
self.multihead_attention = MultiHeadAttention(h, d_k, d_v, d_model)
self.dropout1 = Dropout(rate)
self.add_norm1 = AddNormalization()
self.feed_forward = FeedForward(d_ff, d_model)
self.dropout2 = Dropout(rate)
self.add_norm2 = AddNormalization()

def call(self, x, padding_mask, training):
# Multi-head attention layer
multihead_output = self.multihead_attention(x, x, x, padding_mask)
# Expected output shape = (batch_size, sequence_length, d_model)

# Add in a dropout layer
multihead_output = self.dropout1(multihead_output, training=training)

# Followed by an Add & Norm layer
addnorm_output = self.add_norm1(x, multihead_output)
# Expected output shape = (batch_size, sequence_length, d_model)

# Followed by a fully connected layer
feedforward_output = self.feed_forward(addnorm_output)
# Expected output shape = (batch_size, sequence_length, d_model)

# Add in another dropout layer
feedforward_output = self.dropout2(feedforward_output, training=training)

# Followed by another Add & Norm layer
return self.add_norm2(addnorm_output, feedforward_output)

# Implementing the Encoder
class Encoder(Layer):
def __init__(self, vocab_size, sequence_length, h, d_k, d_v, d_model, d_ff, n, rate, **kwargs):
super(Encoder, self).__init__(**kwargs)
self.pos_encoding = PositionEmbeddingFixedWeights(sequence_length, vocab_size, d_model)
self.dropout = Dropout(rate)
self.encoder_layer = [EncoderLayer(h, d_k, d_v, d_model, d_ff, rate) for _ in range(n)]

def call(self, input_sentence, padding_mask, training):
# Generate the positional encoding
pos_encoding_output = self.pos_encoding(input_sentence)
# Expected output shape = (batch_size, sequence_length, d_model)

# Add in a dropout layer
x = self.dropout(pos_encoding_output, training=training)

# Pass on the positional encoded values to each encoder layer
for i, layer in enumerate(self.encoder_layer):
x = layer(x, padding_mask, training)

return x

Probando el código

Trabajaremos con los valores de los parámetros especificados en el artículo Attention Is All You Need, de Vaswani et al. (2017):

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_ff = 2048 # Dimensionality of the inner fully connected layer
d_model = 512 # Dimensionality of the model sub-layers’ outputs
n = 6 # Number of layers in the encoder stack

batch_size = 64 # Batch size from the training process
dropout_rate = 0.1 # Frequency of dropping the input units in the dropout layers

En cuanto a la secuencia de entrada, por el momento trabajaremos con datos ficticios hasta que lleguemos a la etapa de entrenamiento del modelo completo de Transformer en un tutorial separado, momento en el cual usaremos oraciones reales:


enc_vocab_size = 20 # Vocabulary size for the encoder
input_seq_length = 5 # Maximum length of the input sequence

input_seq = random.random((batch_size, input_seq_length))

A continuación, crearemos una nueva instancia del Encoder clase, asignando su salida a la encoder variable, y posteriormente alimentando los argumentos de entrada e imprimiendo el resultado. Estableceremos el argumento de la máscara de relleno en None por el momento, pero volveremos a esto cuando implementemos el modelo completo de Transformer:


encoder = Encoder(enc_vocab_size, input_seq_length, h, d_k, d_v, d_model, d_ff, n, dropout_rate)
print(encoder(input_seq, None, True))

Unir todo produce la siguiente lista de códigos:

from numpy import random

enc_vocab_size = 20 # Vocabulary size for the encoder
input_seq_length = 5 # Maximum length of the input sequence
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_ff = 2048 # Dimensionality of the inner fully connected layer
d_model = 512 # Dimensionality of the model sub-layers’ outputs
n = 6 # Number of layers in the encoder stack

batch_size = 64 # Batch size from the training process
dropout_rate = 0.1 # Frequency of dropping the input units in the dropout layers

input_seq = random.random((batch_size, input_seq_length))

encoder = Encoder(enc_vocab_size, input_seq_length, h, d_k, d_v, d_model, d_ff, n, dropout_rate)
print(encoder(input_seq, None, True))

Ejecutar este código produce una salida de forma, (tamaño del lote, longitud de la secuencia, dimensionalidad del modelo). Tenga en cuenta que probablemente verá una salida diferente debido a la inicialización aleatoria de la secuencia de entrada y los valores de los parámetros de las capas densas.

tf.Tensor(
[[[-0.4214715 -1.1246173 -0.8444572 … 1.6388322 -0.1890367
1.0173352 ] [ 0.21662089 -0.61147404 -1.0946581 … 1.4627445 -0.6000164
-0.64127874] [ 0.46674493 -1.4155326 -0.5686513 … 1.1790234 -0.94788337
0.1331717 ] [-0.30638126 -1.9047263 -1.8556844 … 0.9130118 -0.47863355
0.00976158] [-0.22600567 -0.9702025 -0.91090447 … 1.7457147 -0.139926
-0.07021569]] …

[[-0.48047638 -1.1034104 -0.16164204 … 1.5588069 0.08743562
-0.08847156] [-0.61683714 -0.8403657 -1.0450369 … 2.3587787 -0.76091915
-0.02891812] [-0.34268388 -0.65042275 -0.6715749 … 2.8530657 -0.33631966
0.5215888 ] [-0.6288677 -1.0030932 -0.9749813 … 2.1386387 0.0640307
-0.69504136] [-1.33254 -1.2524267 -0.230098 … 2.515467 -0.04207756
-0.3395423 ]]], shape=(64, 5, 512), dtype=float32)

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 implementar el codificador Transformer desde cero en TensorFlow y Keras.

Específicamente, aprendiste:

  • Las capas que forman parte del codificador Transformer.
  • Cómo implementar el codificador Transformer desde cero.

¿Tiene usted alguna pregunta?
Haga sus preguntas en los comentarios a continuación y haré todo lo posible para responder.



La publicación Implementing the Transformer Encoder From Scratch in TensorFlow and Keras apareció primero en Machine Learning Mastery.