Menu iconMenu icon
NLP con Transformadores: Fundamentos y Aplicaciones Básicas

Capítulo 4: La arquitectura del transformador

4.2 Explicación del Marco Codificador-Decodificador

El marco codificador-decodificador se erige como la piedra angular de la arquitectura Transformer, representando un enfoque sofisticado para el procesamiento y generación de secuencias. En su núcleo, este marco consiste en dos componentes principales que trabajan en conjunto: el codificador, que procesa y contextualiza las secuencias de entrada, y el decodificador, que genera las salidas apropiadas basándose en la información codificada. Este diseño arquitectónico permite que el modelo maneje transformaciones complejas entre secuencias con notable precisión y eficiencia.

Lo que hace particularmente poderoso a este marco es su capacidad para mantener el contexto y el significado a lo largo de todo el proceso. El codificador primero transforma las secuencias de entrada en representaciones contextuales ricas, capturando no solo la información superficial sino también las relaciones intrincadas entre diferentes elementos. El decodificador luego aprovecha estas representaciones a través de mecanismos de atención para generar salidas que preservan el significado original mientras se adhieren al formato o lenguaje objetivo.

Esta versatilidad hace que el marco codificador-decodificador sea una opción ideal para una diversa gama de aplicaciones. En la traducción automática, puede capturar matices lingüísticos sutiles mientras convierte texto entre idiomas. Para el resumen de texto, destila eficazmente la información clave mientras mantiene la coherencia. En tareas de generación de texto, asegura que el contenido generado permanezca contextualmente relevante y semánticamente significativo.

En esta sección, realizaremos una exploración exhaustiva del marco codificador-decodificador, profundizando en los mecanismos intrincados que permiten que estos componentes trabajen juntos sin problemas. Examinaremos sus arquitecturas internas, enfocándonos particularmente en cómo los mecanismos de autoatención y atención cruzada facilitan el flujo de información entre el codificador y el decodificador, creando un sistema robusto para tareas de transformación de secuencias.

4.2.1 Descripción General del Marco Codificador-Decodificador

El marco codificador-decodificador opera en dos etapas:

Etapa de Codificación:

El codificador procesa la secuencia de entrada (por ejemplo, una oración en inglés) y genera una serie de incrustaciones contextualizadas. Estas incrustaciones son representaciones numéricas sofisticadas que van más allá de simples vectores de palabras al incorporar el contexto completo de la secuencia. Por ejemplo, en la oración "El banco está junto al río" versus "Necesito depositar el dinero en el banco", la incrustación para "banco" capturaría su significado distinto en cada contexto.

El codificador logra esto a través de múltiples capas de mecanismos de autoatención, donde la representación de cada token se refina continuamente al considerar sus relaciones con todos los demás tokens en la secuencia. Este proceso asegura que las incrustaciones finales contengan información semántica rica no solo sobre las palabras individuales, sino también sobre sus roles, relaciones y significados dentro del contexto más amplio.

Etapa de Decodificación:

El decodificador toma la salida del codificador y genera la secuencia objetivo (por ejemplo, la traducción en francés) a través de un proceso autorregresivo, produciendo un token a la vez. Durante la generación, cada nuevo token se crea considerando tanto los tokens previamente generados como la salida completa del codificador. El decodificador emplea dos tipos de mecanismos de atención:

  1. Autoatención para analizar relaciones entre tokens ya generados
  2. Atención cruzada para alinearse con la representación del codificador

Este proceso de atención dual asegura que cada token generado no solo sea coherente con la salida previa sino que también represente fielmente el contexto de entrada. Por ejemplo, al traducir "The cat sits" al francés, el decodificador:

  1. Genera "Le" mientras atiende a toda la oración en inglés
  2. Genera "chat" mientras considera tanto "Le" como el inglés original
  3. Genera "est assis" mientras mantiene la alineación con el contexto completo

Este proceso de generación paso a paso ayuda a mantener la precisión y la relevancia contextual a lo largo de toda la generación de la secuencia.

Ilustración del Marco

  • Entrada: "The cat sits on the mat."
  • Salida (Traducción): "Le chat est assis sur le tapis."

Veamos cómo funciona este proceso de traducción:

  1. Fase de Codificación:
    • El codificador primero convierte cada palabra en incrustaciones numéricas
    • Luego procesa "The cat sits on the mat" como una secuencia completa
    • A través de la autoatención, comprende las relaciones (por ejemplo, "sits" es la acción realizada por "cat")
  2. Creación de Contexto:
    • El codificador crea una representación contextual rica que captura el significado completo
    • La representación de cada palabra ahora contiene información sobre su rol en la oración
  3. Fase de Decodificación:
    • El decodificador comienza generando "Le" basándose en el contexto codificado
    • Luego produce "chat" mientras considera tanto "Le" como la oración original
    • El proceso continúa palabra por palabra, manteniendo la concordancia gramatical y el orden de las palabras según las reglas del francés

Este ejemplo demuestra cómo el marco codificador-decodificador mantiene el significado semántico mientras maneja las diferencias estructurales entre idiomas, como el orden de las palabras y las características gramaticales.

4.2.2 Componentes Detallados del Codificador

El codificador en el Transformer consiste en un conjunto de capas idénticas apiladas, cada una con los siguientes subcomponentes:

Capa de Autoatención Multi-Cabezal:

  • Captura las relaciones entre tokens en la secuencia de entrada permitiendo que cada token preste atención a todos los demás tokens simultáneamente. Por ejemplo, en la oración "El gato que perseguía al ratón era negro", el mecanismo de atención ayuda a conectar "era" con "gato" a pesar de su distancia.
  • Permite que el codificador cree incrustaciones contextualizadas para cada token procesando información de múltiples subespacios de representación en paralelo. Cada cabeza de atención puede enfocarse en diferentes aspectos de las relaciones, como la estructura sintáctica, el significado semántico o las dependencias de largo alcance.
  • El aspecto "multi-cabezal" divide el cálculo de la atención en varias cabezas paralelas, cada una aprendiendo diferentes tipos de relaciones. Por ejemplo, una cabeza podría enfocarse en palabras adyacentes, mientras que otra captura relaciones sujeto-verbo.
  • La capa combina estas diferentes perspectivas para crear representaciones ricas y conscientes del contexto que capturan tanto las dependencias locales como globales en la secuencia de entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Aplica una transformación no lineal a cada incrustación de token de manera independiente, típicamente consistiendo en dos transformaciones lineales con una función de activación ReLU entre ellas: FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
  • Mientras que las capas de atención capturan relaciones entre tokens, la FFN procesa cada token por separado, actuando como un potente extractor de características que puede identificar y mejorar patrones importantes dentro de las representaciones individuales de tokens
  • El ancho de la red (típicamente 4 veces la dimensión del modelo) proporciona capacidad para aprender funciones no lineales complejas, mientras que operar independientemente en cada posición ayuda a mantener la capacidad de procesamiento paralelo del modelo
  • Este componente es crucial para introducir no linealidad en el modelo, permitiéndole aproximar funciones complejas y aprender representaciones de características sofisticadas más allá de lo que las transformaciones lineales por sí solas podrían lograr

Capas de Suma y Normalización:

  • Las conexiones residuales (Suma) sirven como vías cruciales en la red al crear atajos directos entre capas. Estas conexiones permiten que los gradientes fluyan hacia atrás más efectivamente durante el entrenamiento, ayudando a prevenir el problema del desvanecimiento del gradiente que ocurre frecuentemente en redes profundas. Por ejemplo, si x es la entrada a una capa y F(x) es la transformación de la capa, la conexión residual calcula x + F(x), asegurando que la información de entrada original se preserve junto con la versión transformada.
  • La normalización de capa (Norm) juega un papel vital en la estabilización del proceso de entrenamiento al estandarizar las incrustaciones de tokens a través de la dimensión de características. Lo hace calculando la media y la varianza de las activaciones para cada posición de token, luego normalizando estos valores para tener media cero y varianza unitaria. Esta normalización ayuda a mantener escalas consistentes a través de la red, acelera el entrenamiento y hace que el modelo sea menos sensible a los parámetros de inicialización. Los valores normalizados luego son escalados y desplazados usando parámetros aprendidos, permitiendo que el modelo recupere la distribución original si es necesario.

4.2.3 Componentes Detallados del Decodificador

El decodificador también consiste en un conjunto de capas idénticas apiladas, con tres subcomponentes clave:

Capa de Autoatención Multi-Cabezal Enmascarada:

  • Evita que el decodificador vea tokens futuros en la secuencia objetivo durante el entrenamiento. Esto es crucial porque durante la inferencia, el modelo solo puede generar un token a la vez, por lo que no debería tener acceso a información futura durante el entrenamiento. Por ejemplo, al generar la palabra "gato" en una oración, el modelo no debería poder ver las palabras que vienen después.
  • Asegura que las predicciones dependan solo de tokens conocidos aplicando una máscara que establece los pesos de atención para posiciones futuras en infinito negativo. Esta técnica de enmascaramiento efectivamente anula la atención a tokens futuros en la operación softmax. Por ejemplo, al predecir la tercera palabra en una oración, el modelo solo puede prestar atención a la primera y segunda palabra, manteniendo la propiedad autorregresiva del proceso de generación.
  • El enmascaramiento se implementa a través de una matriz de máscara de atención, donde cada posición solo puede atender a posiciones anteriores y a sí misma. Esto crea un patrón de atención triangular que refuerza la naturaleza secuencial de la generación de texto mientras permite el entrenamiento paralelo.

Capa de Atención Codificador-Decodificador:

Atiende a las salidas del codificador, alineando los tokens generados con la secuencia de entrada. Este componente crucial permite que el decodificador acceda directamente y utilice la rica información contextual capturada por el codificador. Por ejemplo, al traducir "The red house" al español, esta capa ayuda al decodificador a determinar qué partes de la oración codificada en inglés son más relevantes al generar cada palabra en español ("La casa roja").

El mecanismo de atención calcula puntuaciones de relevancia entre el estado actual del decodificador y todas las salidas del codificador, permitiéndole enfocarse en diferentes partes de la entrada según sea necesario. Esta alineación dinámica es particularmente importante para manejar idiomas con diferentes órdenes de palabras o cuando se genera texto que requiere integrar información de múltiples partes de la entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Similar al codificador, la FFN del decodificador aplica transformaciones no lineales para mejorar las incrustaciones de tokens. Este componente desempeña varios roles cruciales:
    • Procesa cada posición independientemente, permitiendo el cómputo paralelo mientras mantiene la eficiencia del modelo
    • Introduce no linealidad a través de activaciones ReLU, permitiendo que el modelo aprenda patrones y relaciones complejas
    • Expande el espacio de representación a través de una capa intermedia más amplia (típicamente 4 veces la dimensión del modelo), dando a la red más capacidad para aprender características sofisticadas
    • Ayuda a transformar y refinar las representaciones de tokens después de que han sido procesadas por los mecanismos de atención, asegurando que la salida final capture tanto la información contextual como la específica de la posición

4.2.4 Interacción Entre Codificador y Decodificador

El codificador produce un rico conjunto de incrustaciones de salida que capturan el significado contextual de la secuencia de entrada, que el decodificador luego utiliza para generar la secuencia objetivo. Esta interacción crucial ocurre a través de la capa de atención codificador-decodificador, que actúa como puente entre los dos componentes. Así es como funciona:

  • Las consultas Q se derivan del estado actual del decodificador, representando qué información necesita para generar el siguiente token. Por ejemplo, al traducir "The red house" al español, el decodificador podría consultar información sobre "red" cuando decide si colocar el adjetivo antes o después de "casa".
  • Las claves K y valores V provienen de las salidas del codificador, conteniendo la información procesada de la secuencia de entrada. Las claves ayudan a determinar la relevancia, mientras que los valores contienen la información real que se utilizará. En nuestro ejemplo de traducción, las salidas del codificador contendrían tanto el significado semántico como la información estructural de la frase en inglés.

A través de este mecanismo de atención, el decodificador atiende de manera inteligente a las partes relevantes de la salida del codificador para cada token que genera. Esta atención selectiva permite que el modelo se enfoque en diferentes aspectos de la entrada según sea necesario - a veces atendiendo a palabras individuales, otras veces considerando el contexto más amplio o las relaciones estructurales. El proceso asegura que la secuencia generada mantenga fidelidad a la entrada mientras se adhiere a los requisitos del formato objetivo.

4.2.5 Representación Matemática

  1. Codificación:

    Para una secuencia de entrada X:

    H_{\text{encoder}} = \text{Encoder}(X)

    Aquí, H_{\text{encoder}} es el conjunto de incrustaciones contextualizadas.

  2. Decodificación:

    Para una secuencia parcialmente generada YY:

    H_{\text{decoder}} = \text{Decoder}(Y, H_{\text{encoder}})

    El decodificador combina su propia autoatención con la atención sobre las salidas del codificador.

  3. Salida Final:

    La salida final del decodificador pasa por una capa lineal y softmax para generar probabilidades para el siguiente token:

    P(y_t) = \text{softmax}(W_o \cdot H_{\text{decoder}})

Ejemplo Práctico: Construcción de un Modelo Codificador-Decodificador

Aquí se muestra cómo implementar un marco codificador-decodificador simplificado usando PyTorch.

Ejemplo de Código: Marco Codificador-Decodificador

import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):
    def __init__(self, hidden_dim, max_seq_length=5000):
        super().__init__()
        position = torch.arange(max_seq_length).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, hidden_dim, 2) * (-math.log(10000.0) / hidden_dim))
        pe = torch.zeros(max_seq_length, 1, hidden_dim)
        pe[:, 0, 0::2] = torch.sin(position * div_term)
        pe[:, 0, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe)

    def forward(self, x):
        return x + self.pe[:x.size(0)]

class EncoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Self-attention block
        attn_output, _ = self.attention(x, x, x, attn_mask=mask)
        x = self.norm1(x + self.dropout(attn_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_output))
        return x

class DecoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.self_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.enc_dec_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.norm3 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, encoder_output, tgt_mask=None, src_mask=None):
        # Self-attention block
        self_attn_output, _ = self.self_attention(x, x, x, attn_mask=tgt_mask)
        x = self.norm1(x + self.dropout(self_attn_output))
        
        # Encoder-decoder attention block
        enc_dec_output, _ = self.enc_dec_attention(x, encoder_output, encoder_output, attn_mask=src_mask)
        x = self.norm2(x + self.dropout(enc_dec_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm3(x + self.dropout(ffn_output))
        return x

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, hidden_dim, num_layers=6, num_heads=8, dropout=0.1):
        super().__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, hidden_dim)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, hidden_dim)
        self.positional_encoding = PositionalEncoding(hidden_dim)
        
        self.encoder_layers = nn.ModuleList([
            EncoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        self.decoder_layers = nn.ModuleList([
            DecoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        
        self.final_layer = nn.Linear(hidden_dim, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def create_mask(self, src, tgt):
        src_mask = None  # Allow attending to all source positions
        tgt_mask = nn.Transformer.generate_square_subsequent_mask(tgt.size(0))
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        # Create masks
        src_mask, tgt_mask = self.create_mask(src, tgt)
        
        # Embedding + Positional encoding
        src = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
        
        # Encoder
        enc_output = src
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)
        
        # Decoder
        dec_output = tgt
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, tgt_mask, src_mask)
        
        output = self.final_layer(dec_output)
        return output

# Example usage
def main():
    # Model parameters
    src_vocab_size = 10000
    tgt_vocab_size = 10000
    hidden_dim = 512
    num_layers = 6
    num_heads = 8
    dropout = 0.1

    # Create model
    model = Transformer(
        src_vocab_size=src_vocab_size,
        tgt_vocab_size=tgt_vocab_size,
        hidden_dim=hidden_dim,
        num_layers=num_layers,
        num_heads=num_heads,
        dropout=dropout
    )

    # Example input (batch_size=32, sequence_length=10)
    src = torch.randint(1, src_vocab_size, (10, 32))  # (seq_len, batch_size)
    tgt = torch.randint(1, tgt_vocab_size, (8, 32))   # (seq_len, batch_size)

    # Forward pass
    output = model(src, tgt)
    print("Output shape:", output.shape)

if __name__ == "__main__":
    main()

Desglose y Explicación del Código:

1. Clase PositionalEncoding:

  • Implementa codificaciones posicionales sinusoidales para proporcionar información de posición al modelo
  • Crea incrustaciones de posición únicas para cada posición en la secuencia
  • Añade estas codificaciones posicionales a las incrustaciones de entrada

2. Clase EncoderLayer:

  • Implementa una capa única del codificador con:
    • Mecanismo de auto-atención multi-cabezal
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Procesa secuencias de entrada mientras mantiene sus relaciones contextuales

3. Clase DecoderLayer:

  • Implementa una capa única del decodificador con:
    • Auto-atención multi-cabezal enmascarada
    • Atención codificador-decodificador
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Genera secuencias de salida atendiendo tanto a la salida del codificador como a los tokens previamente generados

4. Clase Transformer:

  • Combina todos los componentes en una arquitectura transformer completa:
    • Incrustaciones de entrada y codificación posicional
    • Pila de capas del codificador
    • Pila de capas del decodificador
    • Capa de proyección lineal final
  • Implementa la lógica principal del paso hacia adelante incluyendo la generación de máscaras

5. Características Principales:

  • Implementa máscaras de atención para la generación adecuada de secuencias
  • Utiliza dropout para regularización
  • Incluye conexiones residuales y normalización de capas
  • Admite número configurable de capas, cabezales y dimensiones del modelo

6. Ejemplo de Uso:

  • Demuestra cómo inicializar y utilizar el modelo transformer
  • Muestra el formato adecuado de entrada y paso hacia adelante
  • Incluye configuraciones típicas de hiperparámetros usadas en la práctica

4.2.6 Puntos Clave

  1. El marco codificador-decodificador sirve como la arquitectura fundamental del modelo Transformer, revolucionando cómo procesamos datos secuenciales. La eficiencia de este marco proviene de su capacidad para:
    • Procesar secuencias de entrada en paralelo en lugar de secuencialmente
    • Manejar entradas y salidas de longitud variable de manera natural
    • Mantener dependencias de largo alcance de manera efectiva
  2. La interacción entre el codificador y el decodificador es sofisticada y multicapa:
    • El codificador transforma las secuencias de entrada en incrustaciones contextualizadas ricas que capturan relaciones tanto locales como globales
    • El decodificador genera salidas a través de un mecanismo de doble atención: auto-atención para mantener la coherencia en la salida, y atención codificador-decodificador para extraer información relevante de la entrada
    • Múltiples cabezales de atención permiten al modelo enfocarse en diferentes aspectos de la entrada simultáneamente
  3. La arquitectura modular ofrece varias ventajas clave:
    • Fácil escalabilidad mediante la adición o eliminación de capas del codificador/decodificador
    • Flexibilidad para adaptarse a varias tareas mediante transferencia de aprendizaje
    • Capacidad para manejar múltiples idiomas, modalidades y tipos de datos
    • Integración simple de modificaciones específicas para tareas sin cambiar la arquitectura central

4.2 Explicación del Marco Codificador-Decodificador

El marco codificador-decodificador se erige como la piedra angular de la arquitectura Transformer, representando un enfoque sofisticado para el procesamiento y generación de secuencias. En su núcleo, este marco consiste en dos componentes principales que trabajan en conjunto: el codificador, que procesa y contextualiza las secuencias de entrada, y el decodificador, que genera las salidas apropiadas basándose en la información codificada. Este diseño arquitectónico permite que el modelo maneje transformaciones complejas entre secuencias con notable precisión y eficiencia.

Lo que hace particularmente poderoso a este marco es su capacidad para mantener el contexto y el significado a lo largo de todo el proceso. El codificador primero transforma las secuencias de entrada en representaciones contextuales ricas, capturando no solo la información superficial sino también las relaciones intrincadas entre diferentes elementos. El decodificador luego aprovecha estas representaciones a través de mecanismos de atención para generar salidas que preservan el significado original mientras se adhieren al formato o lenguaje objetivo.

Esta versatilidad hace que el marco codificador-decodificador sea una opción ideal para una diversa gama de aplicaciones. En la traducción automática, puede capturar matices lingüísticos sutiles mientras convierte texto entre idiomas. Para el resumen de texto, destila eficazmente la información clave mientras mantiene la coherencia. En tareas de generación de texto, asegura que el contenido generado permanezca contextualmente relevante y semánticamente significativo.

En esta sección, realizaremos una exploración exhaustiva del marco codificador-decodificador, profundizando en los mecanismos intrincados que permiten que estos componentes trabajen juntos sin problemas. Examinaremos sus arquitecturas internas, enfocándonos particularmente en cómo los mecanismos de autoatención y atención cruzada facilitan el flujo de información entre el codificador y el decodificador, creando un sistema robusto para tareas de transformación de secuencias.

4.2.1 Descripción General del Marco Codificador-Decodificador

El marco codificador-decodificador opera en dos etapas:

Etapa de Codificación:

El codificador procesa la secuencia de entrada (por ejemplo, una oración en inglés) y genera una serie de incrustaciones contextualizadas. Estas incrustaciones son representaciones numéricas sofisticadas que van más allá de simples vectores de palabras al incorporar el contexto completo de la secuencia. Por ejemplo, en la oración "El banco está junto al río" versus "Necesito depositar el dinero en el banco", la incrustación para "banco" capturaría su significado distinto en cada contexto.

El codificador logra esto a través de múltiples capas de mecanismos de autoatención, donde la representación de cada token se refina continuamente al considerar sus relaciones con todos los demás tokens en la secuencia. Este proceso asegura que las incrustaciones finales contengan información semántica rica no solo sobre las palabras individuales, sino también sobre sus roles, relaciones y significados dentro del contexto más amplio.

Etapa de Decodificación:

El decodificador toma la salida del codificador y genera la secuencia objetivo (por ejemplo, la traducción en francés) a través de un proceso autorregresivo, produciendo un token a la vez. Durante la generación, cada nuevo token se crea considerando tanto los tokens previamente generados como la salida completa del codificador. El decodificador emplea dos tipos de mecanismos de atención:

  1. Autoatención para analizar relaciones entre tokens ya generados
  2. Atención cruzada para alinearse con la representación del codificador

Este proceso de atención dual asegura que cada token generado no solo sea coherente con la salida previa sino que también represente fielmente el contexto de entrada. Por ejemplo, al traducir "The cat sits" al francés, el decodificador:

  1. Genera "Le" mientras atiende a toda la oración en inglés
  2. Genera "chat" mientras considera tanto "Le" como el inglés original
  3. Genera "est assis" mientras mantiene la alineación con el contexto completo

Este proceso de generación paso a paso ayuda a mantener la precisión y la relevancia contextual a lo largo de toda la generación de la secuencia.

Ilustración del Marco

  • Entrada: "The cat sits on the mat."
  • Salida (Traducción): "Le chat est assis sur le tapis."

Veamos cómo funciona este proceso de traducción:

  1. Fase de Codificación:
    • El codificador primero convierte cada palabra en incrustaciones numéricas
    • Luego procesa "The cat sits on the mat" como una secuencia completa
    • A través de la autoatención, comprende las relaciones (por ejemplo, "sits" es la acción realizada por "cat")
  2. Creación de Contexto:
    • El codificador crea una representación contextual rica que captura el significado completo
    • La representación de cada palabra ahora contiene información sobre su rol en la oración
  3. Fase de Decodificación:
    • El decodificador comienza generando "Le" basándose en el contexto codificado
    • Luego produce "chat" mientras considera tanto "Le" como la oración original
    • El proceso continúa palabra por palabra, manteniendo la concordancia gramatical y el orden de las palabras según las reglas del francés

Este ejemplo demuestra cómo el marco codificador-decodificador mantiene el significado semántico mientras maneja las diferencias estructurales entre idiomas, como el orden de las palabras y las características gramaticales.

4.2.2 Componentes Detallados del Codificador

El codificador en el Transformer consiste en un conjunto de capas idénticas apiladas, cada una con los siguientes subcomponentes:

Capa de Autoatención Multi-Cabezal:

  • Captura las relaciones entre tokens en la secuencia de entrada permitiendo que cada token preste atención a todos los demás tokens simultáneamente. Por ejemplo, en la oración "El gato que perseguía al ratón era negro", el mecanismo de atención ayuda a conectar "era" con "gato" a pesar de su distancia.
  • Permite que el codificador cree incrustaciones contextualizadas para cada token procesando información de múltiples subespacios de representación en paralelo. Cada cabeza de atención puede enfocarse en diferentes aspectos de las relaciones, como la estructura sintáctica, el significado semántico o las dependencias de largo alcance.
  • El aspecto "multi-cabezal" divide el cálculo de la atención en varias cabezas paralelas, cada una aprendiendo diferentes tipos de relaciones. Por ejemplo, una cabeza podría enfocarse en palabras adyacentes, mientras que otra captura relaciones sujeto-verbo.
  • La capa combina estas diferentes perspectivas para crear representaciones ricas y conscientes del contexto que capturan tanto las dependencias locales como globales en la secuencia de entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Aplica una transformación no lineal a cada incrustación de token de manera independiente, típicamente consistiendo en dos transformaciones lineales con una función de activación ReLU entre ellas: FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
  • Mientras que las capas de atención capturan relaciones entre tokens, la FFN procesa cada token por separado, actuando como un potente extractor de características que puede identificar y mejorar patrones importantes dentro de las representaciones individuales de tokens
  • El ancho de la red (típicamente 4 veces la dimensión del modelo) proporciona capacidad para aprender funciones no lineales complejas, mientras que operar independientemente en cada posición ayuda a mantener la capacidad de procesamiento paralelo del modelo
  • Este componente es crucial para introducir no linealidad en el modelo, permitiéndole aproximar funciones complejas y aprender representaciones de características sofisticadas más allá de lo que las transformaciones lineales por sí solas podrían lograr

Capas de Suma y Normalización:

  • Las conexiones residuales (Suma) sirven como vías cruciales en la red al crear atajos directos entre capas. Estas conexiones permiten que los gradientes fluyan hacia atrás más efectivamente durante el entrenamiento, ayudando a prevenir el problema del desvanecimiento del gradiente que ocurre frecuentemente en redes profundas. Por ejemplo, si x es la entrada a una capa y F(x) es la transformación de la capa, la conexión residual calcula x + F(x), asegurando que la información de entrada original se preserve junto con la versión transformada.
  • La normalización de capa (Norm) juega un papel vital en la estabilización del proceso de entrenamiento al estandarizar las incrustaciones de tokens a través de la dimensión de características. Lo hace calculando la media y la varianza de las activaciones para cada posición de token, luego normalizando estos valores para tener media cero y varianza unitaria. Esta normalización ayuda a mantener escalas consistentes a través de la red, acelera el entrenamiento y hace que el modelo sea menos sensible a los parámetros de inicialización. Los valores normalizados luego son escalados y desplazados usando parámetros aprendidos, permitiendo que el modelo recupere la distribución original si es necesario.

4.2.3 Componentes Detallados del Decodificador

El decodificador también consiste en un conjunto de capas idénticas apiladas, con tres subcomponentes clave:

Capa de Autoatención Multi-Cabezal Enmascarada:

  • Evita que el decodificador vea tokens futuros en la secuencia objetivo durante el entrenamiento. Esto es crucial porque durante la inferencia, el modelo solo puede generar un token a la vez, por lo que no debería tener acceso a información futura durante el entrenamiento. Por ejemplo, al generar la palabra "gato" en una oración, el modelo no debería poder ver las palabras que vienen después.
  • Asegura que las predicciones dependan solo de tokens conocidos aplicando una máscara que establece los pesos de atención para posiciones futuras en infinito negativo. Esta técnica de enmascaramiento efectivamente anula la atención a tokens futuros en la operación softmax. Por ejemplo, al predecir la tercera palabra en una oración, el modelo solo puede prestar atención a la primera y segunda palabra, manteniendo la propiedad autorregresiva del proceso de generación.
  • El enmascaramiento se implementa a través de una matriz de máscara de atención, donde cada posición solo puede atender a posiciones anteriores y a sí misma. Esto crea un patrón de atención triangular que refuerza la naturaleza secuencial de la generación de texto mientras permite el entrenamiento paralelo.

Capa de Atención Codificador-Decodificador:

Atiende a las salidas del codificador, alineando los tokens generados con la secuencia de entrada. Este componente crucial permite que el decodificador acceda directamente y utilice la rica información contextual capturada por el codificador. Por ejemplo, al traducir "The red house" al español, esta capa ayuda al decodificador a determinar qué partes de la oración codificada en inglés son más relevantes al generar cada palabra en español ("La casa roja").

El mecanismo de atención calcula puntuaciones de relevancia entre el estado actual del decodificador y todas las salidas del codificador, permitiéndole enfocarse en diferentes partes de la entrada según sea necesario. Esta alineación dinámica es particularmente importante para manejar idiomas con diferentes órdenes de palabras o cuando se genera texto que requiere integrar información de múltiples partes de la entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Similar al codificador, la FFN del decodificador aplica transformaciones no lineales para mejorar las incrustaciones de tokens. Este componente desempeña varios roles cruciales:
    • Procesa cada posición independientemente, permitiendo el cómputo paralelo mientras mantiene la eficiencia del modelo
    • Introduce no linealidad a través de activaciones ReLU, permitiendo que el modelo aprenda patrones y relaciones complejas
    • Expande el espacio de representación a través de una capa intermedia más amplia (típicamente 4 veces la dimensión del modelo), dando a la red más capacidad para aprender características sofisticadas
    • Ayuda a transformar y refinar las representaciones de tokens después de que han sido procesadas por los mecanismos de atención, asegurando que la salida final capture tanto la información contextual como la específica de la posición

4.2.4 Interacción Entre Codificador y Decodificador

El codificador produce un rico conjunto de incrustaciones de salida que capturan el significado contextual de la secuencia de entrada, que el decodificador luego utiliza para generar la secuencia objetivo. Esta interacción crucial ocurre a través de la capa de atención codificador-decodificador, que actúa como puente entre los dos componentes. Así es como funciona:

  • Las consultas Q se derivan del estado actual del decodificador, representando qué información necesita para generar el siguiente token. Por ejemplo, al traducir "The red house" al español, el decodificador podría consultar información sobre "red" cuando decide si colocar el adjetivo antes o después de "casa".
  • Las claves K y valores V provienen de las salidas del codificador, conteniendo la información procesada de la secuencia de entrada. Las claves ayudan a determinar la relevancia, mientras que los valores contienen la información real que se utilizará. En nuestro ejemplo de traducción, las salidas del codificador contendrían tanto el significado semántico como la información estructural de la frase en inglés.

A través de este mecanismo de atención, el decodificador atiende de manera inteligente a las partes relevantes de la salida del codificador para cada token que genera. Esta atención selectiva permite que el modelo se enfoque en diferentes aspectos de la entrada según sea necesario - a veces atendiendo a palabras individuales, otras veces considerando el contexto más amplio o las relaciones estructurales. El proceso asegura que la secuencia generada mantenga fidelidad a la entrada mientras se adhiere a los requisitos del formato objetivo.

4.2.5 Representación Matemática

  1. Codificación:

    Para una secuencia de entrada X:

    H_{\text{encoder}} = \text{Encoder}(X)

    Aquí, H_{\text{encoder}} es el conjunto de incrustaciones contextualizadas.

  2. Decodificación:

    Para una secuencia parcialmente generada YY:

    H_{\text{decoder}} = \text{Decoder}(Y, H_{\text{encoder}})

    El decodificador combina su propia autoatención con la atención sobre las salidas del codificador.

  3. Salida Final:

    La salida final del decodificador pasa por una capa lineal y softmax para generar probabilidades para el siguiente token:

    P(y_t) = \text{softmax}(W_o \cdot H_{\text{decoder}})

Ejemplo Práctico: Construcción de un Modelo Codificador-Decodificador

Aquí se muestra cómo implementar un marco codificador-decodificador simplificado usando PyTorch.

Ejemplo de Código: Marco Codificador-Decodificador

import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):
    def __init__(self, hidden_dim, max_seq_length=5000):
        super().__init__()
        position = torch.arange(max_seq_length).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, hidden_dim, 2) * (-math.log(10000.0) / hidden_dim))
        pe = torch.zeros(max_seq_length, 1, hidden_dim)
        pe[:, 0, 0::2] = torch.sin(position * div_term)
        pe[:, 0, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe)

    def forward(self, x):
        return x + self.pe[:x.size(0)]

class EncoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Self-attention block
        attn_output, _ = self.attention(x, x, x, attn_mask=mask)
        x = self.norm1(x + self.dropout(attn_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_output))
        return x

class DecoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.self_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.enc_dec_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.norm3 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, encoder_output, tgt_mask=None, src_mask=None):
        # Self-attention block
        self_attn_output, _ = self.self_attention(x, x, x, attn_mask=tgt_mask)
        x = self.norm1(x + self.dropout(self_attn_output))
        
        # Encoder-decoder attention block
        enc_dec_output, _ = self.enc_dec_attention(x, encoder_output, encoder_output, attn_mask=src_mask)
        x = self.norm2(x + self.dropout(enc_dec_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm3(x + self.dropout(ffn_output))
        return x

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, hidden_dim, num_layers=6, num_heads=8, dropout=0.1):
        super().__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, hidden_dim)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, hidden_dim)
        self.positional_encoding = PositionalEncoding(hidden_dim)
        
        self.encoder_layers = nn.ModuleList([
            EncoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        self.decoder_layers = nn.ModuleList([
            DecoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        
        self.final_layer = nn.Linear(hidden_dim, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def create_mask(self, src, tgt):
        src_mask = None  # Allow attending to all source positions
        tgt_mask = nn.Transformer.generate_square_subsequent_mask(tgt.size(0))
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        # Create masks
        src_mask, tgt_mask = self.create_mask(src, tgt)
        
        # Embedding + Positional encoding
        src = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
        
        # Encoder
        enc_output = src
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)
        
        # Decoder
        dec_output = tgt
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, tgt_mask, src_mask)
        
        output = self.final_layer(dec_output)
        return output

# Example usage
def main():
    # Model parameters
    src_vocab_size = 10000
    tgt_vocab_size = 10000
    hidden_dim = 512
    num_layers = 6
    num_heads = 8
    dropout = 0.1

    # Create model
    model = Transformer(
        src_vocab_size=src_vocab_size,
        tgt_vocab_size=tgt_vocab_size,
        hidden_dim=hidden_dim,
        num_layers=num_layers,
        num_heads=num_heads,
        dropout=dropout
    )

    # Example input (batch_size=32, sequence_length=10)
    src = torch.randint(1, src_vocab_size, (10, 32))  # (seq_len, batch_size)
    tgt = torch.randint(1, tgt_vocab_size, (8, 32))   # (seq_len, batch_size)

    # Forward pass
    output = model(src, tgt)
    print("Output shape:", output.shape)

if __name__ == "__main__":
    main()

Desglose y Explicación del Código:

1. Clase PositionalEncoding:

  • Implementa codificaciones posicionales sinusoidales para proporcionar información de posición al modelo
  • Crea incrustaciones de posición únicas para cada posición en la secuencia
  • Añade estas codificaciones posicionales a las incrustaciones de entrada

2. Clase EncoderLayer:

  • Implementa una capa única del codificador con:
    • Mecanismo de auto-atención multi-cabezal
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Procesa secuencias de entrada mientras mantiene sus relaciones contextuales

3. Clase DecoderLayer:

  • Implementa una capa única del decodificador con:
    • Auto-atención multi-cabezal enmascarada
    • Atención codificador-decodificador
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Genera secuencias de salida atendiendo tanto a la salida del codificador como a los tokens previamente generados

4. Clase Transformer:

  • Combina todos los componentes en una arquitectura transformer completa:
    • Incrustaciones de entrada y codificación posicional
    • Pila de capas del codificador
    • Pila de capas del decodificador
    • Capa de proyección lineal final
  • Implementa la lógica principal del paso hacia adelante incluyendo la generación de máscaras

5. Características Principales:

  • Implementa máscaras de atención para la generación adecuada de secuencias
  • Utiliza dropout para regularización
  • Incluye conexiones residuales y normalización de capas
  • Admite número configurable de capas, cabezales y dimensiones del modelo

6. Ejemplo de Uso:

  • Demuestra cómo inicializar y utilizar el modelo transformer
  • Muestra el formato adecuado de entrada y paso hacia adelante
  • Incluye configuraciones típicas de hiperparámetros usadas en la práctica

4.2.6 Puntos Clave

  1. El marco codificador-decodificador sirve como la arquitectura fundamental del modelo Transformer, revolucionando cómo procesamos datos secuenciales. La eficiencia de este marco proviene de su capacidad para:
    • Procesar secuencias de entrada en paralelo en lugar de secuencialmente
    • Manejar entradas y salidas de longitud variable de manera natural
    • Mantener dependencias de largo alcance de manera efectiva
  2. La interacción entre el codificador y el decodificador es sofisticada y multicapa:
    • El codificador transforma las secuencias de entrada en incrustaciones contextualizadas ricas que capturan relaciones tanto locales como globales
    • El decodificador genera salidas a través de un mecanismo de doble atención: auto-atención para mantener la coherencia en la salida, y atención codificador-decodificador para extraer información relevante de la entrada
    • Múltiples cabezales de atención permiten al modelo enfocarse en diferentes aspectos de la entrada simultáneamente
  3. La arquitectura modular ofrece varias ventajas clave:
    • Fácil escalabilidad mediante la adición o eliminación de capas del codificador/decodificador
    • Flexibilidad para adaptarse a varias tareas mediante transferencia de aprendizaje
    • Capacidad para manejar múltiples idiomas, modalidades y tipos de datos
    • Integración simple de modificaciones específicas para tareas sin cambiar la arquitectura central

4.2 Explicación del Marco Codificador-Decodificador

El marco codificador-decodificador se erige como la piedra angular de la arquitectura Transformer, representando un enfoque sofisticado para el procesamiento y generación de secuencias. En su núcleo, este marco consiste en dos componentes principales que trabajan en conjunto: el codificador, que procesa y contextualiza las secuencias de entrada, y el decodificador, que genera las salidas apropiadas basándose en la información codificada. Este diseño arquitectónico permite que el modelo maneje transformaciones complejas entre secuencias con notable precisión y eficiencia.

Lo que hace particularmente poderoso a este marco es su capacidad para mantener el contexto y el significado a lo largo de todo el proceso. El codificador primero transforma las secuencias de entrada en representaciones contextuales ricas, capturando no solo la información superficial sino también las relaciones intrincadas entre diferentes elementos. El decodificador luego aprovecha estas representaciones a través de mecanismos de atención para generar salidas que preservan el significado original mientras se adhieren al formato o lenguaje objetivo.

Esta versatilidad hace que el marco codificador-decodificador sea una opción ideal para una diversa gama de aplicaciones. En la traducción automática, puede capturar matices lingüísticos sutiles mientras convierte texto entre idiomas. Para el resumen de texto, destila eficazmente la información clave mientras mantiene la coherencia. En tareas de generación de texto, asegura que el contenido generado permanezca contextualmente relevante y semánticamente significativo.

En esta sección, realizaremos una exploración exhaustiva del marco codificador-decodificador, profundizando en los mecanismos intrincados que permiten que estos componentes trabajen juntos sin problemas. Examinaremos sus arquitecturas internas, enfocándonos particularmente en cómo los mecanismos de autoatención y atención cruzada facilitan el flujo de información entre el codificador y el decodificador, creando un sistema robusto para tareas de transformación de secuencias.

4.2.1 Descripción General del Marco Codificador-Decodificador

El marco codificador-decodificador opera en dos etapas:

Etapa de Codificación:

El codificador procesa la secuencia de entrada (por ejemplo, una oración en inglés) y genera una serie de incrustaciones contextualizadas. Estas incrustaciones son representaciones numéricas sofisticadas que van más allá de simples vectores de palabras al incorporar el contexto completo de la secuencia. Por ejemplo, en la oración "El banco está junto al río" versus "Necesito depositar el dinero en el banco", la incrustación para "banco" capturaría su significado distinto en cada contexto.

El codificador logra esto a través de múltiples capas de mecanismos de autoatención, donde la representación de cada token se refina continuamente al considerar sus relaciones con todos los demás tokens en la secuencia. Este proceso asegura que las incrustaciones finales contengan información semántica rica no solo sobre las palabras individuales, sino también sobre sus roles, relaciones y significados dentro del contexto más amplio.

Etapa de Decodificación:

El decodificador toma la salida del codificador y genera la secuencia objetivo (por ejemplo, la traducción en francés) a través de un proceso autorregresivo, produciendo un token a la vez. Durante la generación, cada nuevo token se crea considerando tanto los tokens previamente generados como la salida completa del codificador. El decodificador emplea dos tipos de mecanismos de atención:

  1. Autoatención para analizar relaciones entre tokens ya generados
  2. Atención cruzada para alinearse con la representación del codificador

Este proceso de atención dual asegura que cada token generado no solo sea coherente con la salida previa sino que también represente fielmente el contexto de entrada. Por ejemplo, al traducir "The cat sits" al francés, el decodificador:

  1. Genera "Le" mientras atiende a toda la oración en inglés
  2. Genera "chat" mientras considera tanto "Le" como el inglés original
  3. Genera "est assis" mientras mantiene la alineación con el contexto completo

Este proceso de generación paso a paso ayuda a mantener la precisión y la relevancia contextual a lo largo de toda la generación de la secuencia.

Ilustración del Marco

  • Entrada: "The cat sits on the mat."
  • Salida (Traducción): "Le chat est assis sur le tapis."

Veamos cómo funciona este proceso de traducción:

  1. Fase de Codificación:
    • El codificador primero convierte cada palabra en incrustaciones numéricas
    • Luego procesa "The cat sits on the mat" como una secuencia completa
    • A través de la autoatención, comprende las relaciones (por ejemplo, "sits" es la acción realizada por "cat")
  2. Creación de Contexto:
    • El codificador crea una representación contextual rica que captura el significado completo
    • La representación de cada palabra ahora contiene información sobre su rol en la oración
  3. Fase de Decodificación:
    • El decodificador comienza generando "Le" basándose en el contexto codificado
    • Luego produce "chat" mientras considera tanto "Le" como la oración original
    • El proceso continúa palabra por palabra, manteniendo la concordancia gramatical y el orden de las palabras según las reglas del francés

Este ejemplo demuestra cómo el marco codificador-decodificador mantiene el significado semántico mientras maneja las diferencias estructurales entre idiomas, como el orden de las palabras y las características gramaticales.

4.2.2 Componentes Detallados del Codificador

El codificador en el Transformer consiste en un conjunto de capas idénticas apiladas, cada una con los siguientes subcomponentes:

Capa de Autoatención Multi-Cabezal:

  • Captura las relaciones entre tokens en la secuencia de entrada permitiendo que cada token preste atención a todos los demás tokens simultáneamente. Por ejemplo, en la oración "El gato que perseguía al ratón era negro", el mecanismo de atención ayuda a conectar "era" con "gato" a pesar de su distancia.
  • Permite que el codificador cree incrustaciones contextualizadas para cada token procesando información de múltiples subespacios de representación en paralelo. Cada cabeza de atención puede enfocarse en diferentes aspectos de las relaciones, como la estructura sintáctica, el significado semántico o las dependencias de largo alcance.
  • El aspecto "multi-cabezal" divide el cálculo de la atención en varias cabezas paralelas, cada una aprendiendo diferentes tipos de relaciones. Por ejemplo, una cabeza podría enfocarse en palabras adyacentes, mientras que otra captura relaciones sujeto-verbo.
  • La capa combina estas diferentes perspectivas para crear representaciones ricas y conscientes del contexto que capturan tanto las dependencias locales como globales en la secuencia de entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Aplica una transformación no lineal a cada incrustación de token de manera independiente, típicamente consistiendo en dos transformaciones lineales con una función de activación ReLU entre ellas: FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
  • Mientras que las capas de atención capturan relaciones entre tokens, la FFN procesa cada token por separado, actuando como un potente extractor de características que puede identificar y mejorar patrones importantes dentro de las representaciones individuales de tokens
  • El ancho de la red (típicamente 4 veces la dimensión del modelo) proporciona capacidad para aprender funciones no lineales complejas, mientras que operar independientemente en cada posición ayuda a mantener la capacidad de procesamiento paralelo del modelo
  • Este componente es crucial para introducir no linealidad en el modelo, permitiéndole aproximar funciones complejas y aprender representaciones de características sofisticadas más allá de lo que las transformaciones lineales por sí solas podrían lograr

Capas de Suma y Normalización:

  • Las conexiones residuales (Suma) sirven como vías cruciales en la red al crear atajos directos entre capas. Estas conexiones permiten que los gradientes fluyan hacia atrás más efectivamente durante el entrenamiento, ayudando a prevenir el problema del desvanecimiento del gradiente que ocurre frecuentemente en redes profundas. Por ejemplo, si x es la entrada a una capa y F(x) es la transformación de la capa, la conexión residual calcula x + F(x), asegurando que la información de entrada original se preserve junto con la versión transformada.
  • La normalización de capa (Norm) juega un papel vital en la estabilización del proceso de entrenamiento al estandarizar las incrustaciones de tokens a través de la dimensión de características. Lo hace calculando la media y la varianza de las activaciones para cada posición de token, luego normalizando estos valores para tener media cero y varianza unitaria. Esta normalización ayuda a mantener escalas consistentes a través de la red, acelera el entrenamiento y hace que el modelo sea menos sensible a los parámetros de inicialización. Los valores normalizados luego son escalados y desplazados usando parámetros aprendidos, permitiendo que el modelo recupere la distribución original si es necesario.

4.2.3 Componentes Detallados del Decodificador

El decodificador también consiste en un conjunto de capas idénticas apiladas, con tres subcomponentes clave:

Capa de Autoatención Multi-Cabezal Enmascarada:

  • Evita que el decodificador vea tokens futuros en la secuencia objetivo durante el entrenamiento. Esto es crucial porque durante la inferencia, el modelo solo puede generar un token a la vez, por lo que no debería tener acceso a información futura durante el entrenamiento. Por ejemplo, al generar la palabra "gato" en una oración, el modelo no debería poder ver las palabras que vienen después.
  • Asegura que las predicciones dependan solo de tokens conocidos aplicando una máscara que establece los pesos de atención para posiciones futuras en infinito negativo. Esta técnica de enmascaramiento efectivamente anula la atención a tokens futuros en la operación softmax. Por ejemplo, al predecir la tercera palabra en una oración, el modelo solo puede prestar atención a la primera y segunda palabra, manteniendo la propiedad autorregresiva del proceso de generación.
  • El enmascaramiento se implementa a través de una matriz de máscara de atención, donde cada posición solo puede atender a posiciones anteriores y a sí misma. Esto crea un patrón de atención triangular que refuerza la naturaleza secuencial de la generación de texto mientras permite el entrenamiento paralelo.

Capa de Atención Codificador-Decodificador:

Atiende a las salidas del codificador, alineando los tokens generados con la secuencia de entrada. Este componente crucial permite que el decodificador acceda directamente y utilice la rica información contextual capturada por el codificador. Por ejemplo, al traducir "The red house" al español, esta capa ayuda al decodificador a determinar qué partes de la oración codificada en inglés son más relevantes al generar cada palabra en español ("La casa roja").

El mecanismo de atención calcula puntuaciones de relevancia entre el estado actual del decodificador y todas las salidas del codificador, permitiéndole enfocarse en diferentes partes de la entrada según sea necesario. Esta alineación dinámica es particularmente importante para manejar idiomas con diferentes órdenes de palabras o cuando se genera texto que requiere integrar información de múltiples partes de la entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Similar al codificador, la FFN del decodificador aplica transformaciones no lineales para mejorar las incrustaciones de tokens. Este componente desempeña varios roles cruciales:
    • Procesa cada posición independientemente, permitiendo el cómputo paralelo mientras mantiene la eficiencia del modelo
    • Introduce no linealidad a través de activaciones ReLU, permitiendo que el modelo aprenda patrones y relaciones complejas
    • Expande el espacio de representación a través de una capa intermedia más amplia (típicamente 4 veces la dimensión del modelo), dando a la red más capacidad para aprender características sofisticadas
    • Ayuda a transformar y refinar las representaciones de tokens después de que han sido procesadas por los mecanismos de atención, asegurando que la salida final capture tanto la información contextual como la específica de la posición

4.2.4 Interacción Entre Codificador y Decodificador

El codificador produce un rico conjunto de incrustaciones de salida que capturan el significado contextual de la secuencia de entrada, que el decodificador luego utiliza para generar la secuencia objetivo. Esta interacción crucial ocurre a través de la capa de atención codificador-decodificador, que actúa como puente entre los dos componentes. Así es como funciona:

  • Las consultas Q se derivan del estado actual del decodificador, representando qué información necesita para generar el siguiente token. Por ejemplo, al traducir "The red house" al español, el decodificador podría consultar información sobre "red" cuando decide si colocar el adjetivo antes o después de "casa".
  • Las claves K y valores V provienen de las salidas del codificador, conteniendo la información procesada de la secuencia de entrada. Las claves ayudan a determinar la relevancia, mientras que los valores contienen la información real que se utilizará. En nuestro ejemplo de traducción, las salidas del codificador contendrían tanto el significado semántico como la información estructural de la frase en inglés.

A través de este mecanismo de atención, el decodificador atiende de manera inteligente a las partes relevantes de la salida del codificador para cada token que genera. Esta atención selectiva permite que el modelo se enfoque en diferentes aspectos de la entrada según sea necesario - a veces atendiendo a palabras individuales, otras veces considerando el contexto más amplio o las relaciones estructurales. El proceso asegura que la secuencia generada mantenga fidelidad a la entrada mientras se adhiere a los requisitos del formato objetivo.

4.2.5 Representación Matemática

  1. Codificación:

    Para una secuencia de entrada X:

    H_{\text{encoder}} = \text{Encoder}(X)

    Aquí, H_{\text{encoder}} es el conjunto de incrustaciones contextualizadas.

  2. Decodificación:

    Para una secuencia parcialmente generada YY:

    H_{\text{decoder}} = \text{Decoder}(Y, H_{\text{encoder}})

    El decodificador combina su propia autoatención con la atención sobre las salidas del codificador.

  3. Salida Final:

    La salida final del decodificador pasa por una capa lineal y softmax para generar probabilidades para el siguiente token:

    P(y_t) = \text{softmax}(W_o \cdot H_{\text{decoder}})

Ejemplo Práctico: Construcción de un Modelo Codificador-Decodificador

Aquí se muestra cómo implementar un marco codificador-decodificador simplificado usando PyTorch.

Ejemplo de Código: Marco Codificador-Decodificador

import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):
    def __init__(self, hidden_dim, max_seq_length=5000):
        super().__init__()
        position = torch.arange(max_seq_length).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, hidden_dim, 2) * (-math.log(10000.0) / hidden_dim))
        pe = torch.zeros(max_seq_length, 1, hidden_dim)
        pe[:, 0, 0::2] = torch.sin(position * div_term)
        pe[:, 0, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe)

    def forward(self, x):
        return x + self.pe[:x.size(0)]

class EncoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Self-attention block
        attn_output, _ = self.attention(x, x, x, attn_mask=mask)
        x = self.norm1(x + self.dropout(attn_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_output))
        return x

class DecoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.self_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.enc_dec_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.norm3 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, encoder_output, tgt_mask=None, src_mask=None):
        # Self-attention block
        self_attn_output, _ = self.self_attention(x, x, x, attn_mask=tgt_mask)
        x = self.norm1(x + self.dropout(self_attn_output))
        
        # Encoder-decoder attention block
        enc_dec_output, _ = self.enc_dec_attention(x, encoder_output, encoder_output, attn_mask=src_mask)
        x = self.norm2(x + self.dropout(enc_dec_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm3(x + self.dropout(ffn_output))
        return x

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, hidden_dim, num_layers=6, num_heads=8, dropout=0.1):
        super().__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, hidden_dim)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, hidden_dim)
        self.positional_encoding = PositionalEncoding(hidden_dim)
        
        self.encoder_layers = nn.ModuleList([
            EncoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        self.decoder_layers = nn.ModuleList([
            DecoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        
        self.final_layer = nn.Linear(hidden_dim, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def create_mask(self, src, tgt):
        src_mask = None  # Allow attending to all source positions
        tgt_mask = nn.Transformer.generate_square_subsequent_mask(tgt.size(0))
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        # Create masks
        src_mask, tgt_mask = self.create_mask(src, tgt)
        
        # Embedding + Positional encoding
        src = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
        
        # Encoder
        enc_output = src
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)
        
        # Decoder
        dec_output = tgt
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, tgt_mask, src_mask)
        
        output = self.final_layer(dec_output)
        return output

# Example usage
def main():
    # Model parameters
    src_vocab_size = 10000
    tgt_vocab_size = 10000
    hidden_dim = 512
    num_layers = 6
    num_heads = 8
    dropout = 0.1

    # Create model
    model = Transformer(
        src_vocab_size=src_vocab_size,
        tgt_vocab_size=tgt_vocab_size,
        hidden_dim=hidden_dim,
        num_layers=num_layers,
        num_heads=num_heads,
        dropout=dropout
    )

    # Example input (batch_size=32, sequence_length=10)
    src = torch.randint(1, src_vocab_size, (10, 32))  # (seq_len, batch_size)
    tgt = torch.randint(1, tgt_vocab_size, (8, 32))   # (seq_len, batch_size)

    # Forward pass
    output = model(src, tgt)
    print("Output shape:", output.shape)

if __name__ == "__main__":
    main()

Desglose y Explicación del Código:

1. Clase PositionalEncoding:

  • Implementa codificaciones posicionales sinusoidales para proporcionar información de posición al modelo
  • Crea incrustaciones de posición únicas para cada posición en la secuencia
  • Añade estas codificaciones posicionales a las incrustaciones de entrada

2. Clase EncoderLayer:

  • Implementa una capa única del codificador con:
    • Mecanismo de auto-atención multi-cabezal
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Procesa secuencias de entrada mientras mantiene sus relaciones contextuales

3. Clase DecoderLayer:

  • Implementa una capa única del decodificador con:
    • Auto-atención multi-cabezal enmascarada
    • Atención codificador-decodificador
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Genera secuencias de salida atendiendo tanto a la salida del codificador como a los tokens previamente generados

4. Clase Transformer:

  • Combina todos los componentes en una arquitectura transformer completa:
    • Incrustaciones de entrada y codificación posicional
    • Pila de capas del codificador
    • Pila de capas del decodificador
    • Capa de proyección lineal final
  • Implementa la lógica principal del paso hacia adelante incluyendo la generación de máscaras

5. Características Principales:

  • Implementa máscaras de atención para la generación adecuada de secuencias
  • Utiliza dropout para regularización
  • Incluye conexiones residuales y normalización de capas
  • Admite número configurable de capas, cabezales y dimensiones del modelo

6. Ejemplo de Uso:

  • Demuestra cómo inicializar y utilizar el modelo transformer
  • Muestra el formato adecuado de entrada y paso hacia adelante
  • Incluye configuraciones típicas de hiperparámetros usadas en la práctica

4.2.6 Puntos Clave

  1. El marco codificador-decodificador sirve como la arquitectura fundamental del modelo Transformer, revolucionando cómo procesamos datos secuenciales. La eficiencia de este marco proviene de su capacidad para:
    • Procesar secuencias de entrada en paralelo en lugar de secuencialmente
    • Manejar entradas y salidas de longitud variable de manera natural
    • Mantener dependencias de largo alcance de manera efectiva
  2. La interacción entre el codificador y el decodificador es sofisticada y multicapa:
    • El codificador transforma las secuencias de entrada en incrustaciones contextualizadas ricas que capturan relaciones tanto locales como globales
    • El decodificador genera salidas a través de un mecanismo de doble atención: auto-atención para mantener la coherencia en la salida, y atención codificador-decodificador para extraer información relevante de la entrada
    • Múltiples cabezales de atención permiten al modelo enfocarse en diferentes aspectos de la entrada simultáneamente
  3. La arquitectura modular ofrece varias ventajas clave:
    • Fácil escalabilidad mediante la adición o eliminación de capas del codificador/decodificador
    • Flexibilidad para adaptarse a varias tareas mediante transferencia de aprendizaje
    • Capacidad para manejar múltiples idiomas, modalidades y tipos de datos
    • Integración simple de modificaciones específicas para tareas sin cambiar la arquitectura central

4.2 Explicación del Marco Codificador-Decodificador

El marco codificador-decodificador se erige como la piedra angular de la arquitectura Transformer, representando un enfoque sofisticado para el procesamiento y generación de secuencias. En su núcleo, este marco consiste en dos componentes principales que trabajan en conjunto: el codificador, que procesa y contextualiza las secuencias de entrada, y el decodificador, que genera las salidas apropiadas basándose en la información codificada. Este diseño arquitectónico permite que el modelo maneje transformaciones complejas entre secuencias con notable precisión y eficiencia.

Lo que hace particularmente poderoso a este marco es su capacidad para mantener el contexto y el significado a lo largo de todo el proceso. El codificador primero transforma las secuencias de entrada en representaciones contextuales ricas, capturando no solo la información superficial sino también las relaciones intrincadas entre diferentes elementos. El decodificador luego aprovecha estas representaciones a través de mecanismos de atención para generar salidas que preservan el significado original mientras se adhieren al formato o lenguaje objetivo.

Esta versatilidad hace que el marco codificador-decodificador sea una opción ideal para una diversa gama de aplicaciones. En la traducción automática, puede capturar matices lingüísticos sutiles mientras convierte texto entre idiomas. Para el resumen de texto, destila eficazmente la información clave mientras mantiene la coherencia. En tareas de generación de texto, asegura que el contenido generado permanezca contextualmente relevante y semánticamente significativo.

En esta sección, realizaremos una exploración exhaustiva del marco codificador-decodificador, profundizando en los mecanismos intrincados que permiten que estos componentes trabajen juntos sin problemas. Examinaremos sus arquitecturas internas, enfocándonos particularmente en cómo los mecanismos de autoatención y atención cruzada facilitan el flujo de información entre el codificador y el decodificador, creando un sistema robusto para tareas de transformación de secuencias.

4.2.1 Descripción General del Marco Codificador-Decodificador

El marco codificador-decodificador opera en dos etapas:

Etapa de Codificación:

El codificador procesa la secuencia de entrada (por ejemplo, una oración en inglés) y genera una serie de incrustaciones contextualizadas. Estas incrustaciones son representaciones numéricas sofisticadas que van más allá de simples vectores de palabras al incorporar el contexto completo de la secuencia. Por ejemplo, en la oración "El banco está junto al río" versus "Necesito depositar el dinero en el banco", la incrustación para "banco" capturaría su significado distinto en cada contexto.

El codificador logra esto a través de múltiples capas de mecanismos de autoatención, donde la representación de cada token se refina continuamente al considerar sus relaciones con todos los demás tokens en la secuencia. Este proceso asegura que las incrustaciones finales contengan información semántica rica no solo sobre las palabras individuales, sino también sobre sus roles, relaciones y significados dentro del contexto más amplio.

Etapa de Decodificación:

El decodificador toma la salida del codificador y genera la secuencia objetivo (por ejemplo, la traducción en francés) a través de un proceso autorregresivo, produciendo un token a la vez. Durante la generación, cada nuevo token se crea considerando tanto los tokens previamente generados como la salida completa del codificador. El decodificador emplea dos tipos de mecanismos de atención:

  1. Autoatención para analizar relaciones entre tokens ya generados
  2. Atención cruzada para alinearse con la representación del codificador

Este proceso de atención dual asegura que cada token generado no solo sea coherente con la salida previa sino que también represente fielmente el contexto de entrada. Por ejemplo, al traducir "The cat sits" al francés, el decodificador:

  1. Genera "Le" mientras atiende a toda la oración en inglés
  2. Genera "chat" mientras considera tanto "Le" como el inglés original
  3. Genera "est assis" mientras mantiene la alineación con el contexto completo

Este proceso de generación paso a paso ayuda a mantener la precisión y la relevancia contextual a lo largo de toda la generación de la secuencia.

Ilustración del Marco

  • Entrada: "The cat sits on the mat."
  • Salida (Traducción): "Le chat est assis sur le tapis."

Veamos cómo funciona este proceso de traducción:

  1. Fase de Codificación:
    • El codificador primero convierte cada palabra en incrustaciones numéricas
    • Luego procesa "The cat sits on the mat" como una secuencia completa
    • A través de la autoatención, comprende las relaciones (por ejemplo, "sits" es la acción realizada por "cat")
  2. Creación de Contexto:
    • El codificador crea una representación contextual rica que captura el significado completo
    • La representación de cada palabra ahora contiene información sobre su rol en la oración
  3. Fase de Decodificación:
    • El decodificador comienza generando "Le" basándose en el contexto codificado
    • Luego produce "chat" mientras considera tanto "Le" como la oración original
    • El proceso continúa palabra por palabra, manteniendo la concordancia gramatical y el orden de las palabras según las reglas del francés

Este ejemplo demuestra cómo el marco codificador-decodificador mantiene el significado semántico mientras maneja las diferencias estructurales entre idiomas, como el orden de las palabras y las características gramaticales.

4.2.2 Componentes Detallados del Codificador

El codificador en el Transformer consiste en un conjunto de capas idénticas apiladas, cada una con los siguientes subcomponentes:

Capa de Autoatención Multi-Cabezal:

  • Captura las relaciones entre tokens en la secuencia de entrada permitiendo que cada token preste atención a todos los demás tokens simultáneamente. Por ejemplo, en la oración "El gato que perseguía al ratón era negro", el mecanismo de atención ayuda a conectar "era" con "gato" a pesar de su distancia.
  • Permite que el codificador cree incrustaciones contextualizadas para cada token procesando información de múltiples subespacios de representación en paralelo. Cada cabeza de atención puede enfocarse en diferentes aspectos de las relaciones, como la estructura sintáctica, el significado semántico o las dependencias de largo alcance.
  • El aspecto "multi-cabezal" divide el cálculo de la atención en varias cabezas paralelas, cada una aprendiendo diferentes tipos de relaciones. Por ejemplo, una cabeza podría enfocarse en palabras adyacentes, mientras que otra captura relaciones sujeto-verbo.
  • La capa combina estas diferentes perspectivas para crear representaciones ricas y conscientes del contexto que capturan tanto las dependencias locales como globales en la secuencia de entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Aplica una transformación no lineal a cada incrustación de token de manera independiente, típicamente consistiendo en dos transformaciones lineales con una función de activación ReLU entre ellas: FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
  • Mientras que las capas de atención capturan relaciones entre tokens, la FFN procesa cada token por separado, actuando como un potente extractor de características que puede identificar y mejorar patrones importantes dentro de las representaciones individuales de tokens
  • El ancho de la red (típicamente 4 veces la dimensión del modelo) proporciona capacidad para aprender funciones no lineales complejas, mientras que operar independientemente en cada posición ayuda a mantener la capacidad de procesamiento paralelo del modelo
  • Este componente es crucial para introducir no linealidad en el modelo, permitiéndole aproximar funciones complejas y aprender representaciones de características sofisticadas más allá de lo que las transformaciones lineales por sí solas podrían lograr

Capas de Suma y Normalización:

  • Las conexiones residuales (Suma) sirven como vías cruciales en la red al crear atajos directos entre capas. Estas conexiones permiten que los gradientes fluyan hacia atrás más efectivamente durante el entrenamiento, ayudando a prevenir el problema del desvanecimiento del gradiente que ocurre frecuentemente en redes profundas. Por ejemplo, si x es la entrada a una capa y F(x) es la transformación de la capa, la conexión residual calcula x + F(x), asegurando que la información de entrada original se preserve junto con la versión transformada.
  • La normalización de capa (Norm) juega un papel vital en la estabilización del proceso de entrenamiento al estandarizar las incrustaciones de tokens a través de la dimensión de características. Lo hace calculando la media y la varianza de las activaciones para cada posición de token, luego normalizando estos valores para tener media cero y varianza unitaria. Esta normalización ayuda a mantener escalas consistentes a través de la red, acelera el entrenamiento y hace que el modelo sea menos sensible a los parámetros de inicialización. Los valores normalizados luego son escalados y desplazados usando parámetros aprendidos, permitiendo que el modelo recupere la distribución original si es necesario.

4.2.3 Componentes Detallados del Decodificador

El decodificador también consiste en un conjunto de capas idénticas apiladas, con tres subcomponentes clave:

Capa de Autoatención Multi-Cabezal Enmascarada:

  • Evita que el decodificador vea tokens futuros en la secuencia objetivo durante el entrenamiento. Esto es crucial porque durante la inferencia, el modelo solo puede generar un token a la vez, por lo que no debería tener acceso a información futura durante el entrenamiento. Por ejemplo, al generar la palabra "gato" en una oración, el modelo no debería poder ver las palabras que vienen después.
  • Asegura que las predicciones dependan solo de tokens conocidos aplicando una máscara que establece los pesos de atención para posiciones futuras en infinito negativo. Esta técnica de enmascaramiento efectivamente anula la atención a tokens futuros en la operación softmax. Por ejemplo, al predecir la tercera palabra en una oración, el modelo solo puede prestar atención a la primera y segunda palabra, manteniendo la propiedad autorregresiva del proceso de generación.
  • El enmascaramiento se implementa a través de una matriz de máscara de atención, donde cada posición solo puede atender a posiciones anteriores y a sí misma. Esto crea un patrón de atención triangular que refuerza la naturaleza secuencial de la generación de texto mientras permite el entrenamiento paralelo.

Capa de Atención Codificador-Decodificador:

Atiende a las salidas del codificador, alineando los tokens generados con la secuencia de entrada. Este componente crucial permite que el decodificador acceda directamente y utilice la rica información contextual capturada por el codificador. Por ejemplo, al traducir "The red house" al español, esta capa ayuda al decodificador a determinar qué partes de la oración codificada en inglés son más relevantes al generar cada palabra en español ("La casa roja").

El mecanismo de atención calcula puntuaciones de relevancia entre el estado actual del decodificador y todas las salidas del codificador, permitiéndole enfocarse en diferentes partes de la entrada según sea necesario. Esta alineación dinámica es particularmente importante para manejar idiomas con diferentes órdenes de palabras o cuando se genera texto que requiere integrar información de múltiples partes de la entrada.

Red Neuronal de Alimentación Hacia Adelante (FFN):

  • Similar al codificador, la FFN del decodificador aplica transformaciones no lineales para mejorar las incrustaciones de tokens. Este componente desempeña varios roles cruciales:
    • Procesa cada posición independientemente, permitiendo el cómputo paralelo mientras mantiene la eficiencia del modelo
    • Introduce no linealidad a través de activaciones ReLU, permitiendo que el modelo aprenda patrones y relaciones complejas
    • Expande el espacio de representación a través de una capa intermedia más amplia (típicamente 4 veces la dimensión del modelo), dando a la red más capacidad para aprender características sofisticadas
    • Ayuda a transformar y refinar las representaciones de tokens después de que han sido procesadas por los mecanismos de atención, asegurando que la salida final capture tanto la información contextual como la específica de la posición

4.2.4 Interacción Entre Codificador y Decodificador

El codificador produce un rico conjunto de incrustaciones de salida que capturan el significado contextual de la secuencia de entrada, que el decodificador luego utiliza para generar la secuencia objetivo. Esta interacción crucial ocurre a través de la capa de atención codificador-decodificador, que actúa como puente entre los dos componentes. Así es como funciona:

  • Las consultas Q se derivan del estado actual del decodificador, representando qué información necesita para generar el siguiente token. Por ejemplo, al traducir "The red house" al español, el decodificador podría consultar información sobre "red" cuando decide si colocar el adjetivo antes o después de "casa".
  • Las claves K y valores V provienen de las salidas del codificador, conteniendo la información procesada de la secuencia de entrada. Las claves ayudan a determinar la relevancia, mientras que los valores contienen la información real que se utilizará. En nuestro ejemplo de traducción, las salidas del codificador contendrían tanto el significado semántico como la información estructural de la frase en inglés.

A través de este mecanismo de atención, el decodificador atiende de manera inteligente a las partes relevantes de la salida del codificador para cada token que genera. Esta atención selectiva permite que el modelo se enfoque en diferentes aspectos de la entrada según sea necesario - a veces atendiendo a palabras individuales, otras veces considerando el contexto más amplio o las relaciones estructurales. El proceso asegura que la secuencia generada mantenga fidelidad a la entrada mientras se adhiere a los requisitos del formato objetivo.

4.2.5 Representación Matemática

  1. Codificación:

    Para una secuencia de entrada X:

    H_{\text{encoder}} = \text{Encoder}(X)

    Aquí, H_{\text{encoder}} es el conjunto de incrustaciones contextualizadas.

  2. Decodificación:

    Para una secuencia parcialmente generada YY:

    H_{\text{decoder}} = \text{Decoder}(Y, H_{\text{encoder}})

    El decodificador combina su propia autoatención con la atención sobre las salidas del codificador.

  3. Salida Final:

    La salida final del decodificador pasa por una capa lineal y softmax para generar probabilidades para el siguiente token:

    P(y_t) = \text{softmax}(W_o \cdot H_{\text{decoder}})

Ejemplo Práctico: Construcción de un Modelo Codificador-Decodificador

Aquí se muestra cómo implementar un marco codificador-decodificador simplificado usando PyTorch.

Ejemplo de Código: Marco Codificador-Decodificador

import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):
    def __init__(self, hidden_dim, max_seq_length=5000):
        super().__init__()
        position = torch.arange(max_seq_length).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, hidden_dim, 2) * (-math.log(10000.0) / hidden_dim))
        pe = torch.zeros(max_seq_length, 1, hidden_dim)
        pe[:, 0, 0::2] = torch.sin(position * div_term)
        pe[:, 0, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe)

    def forward(self, x):
        return x + self.pe[:x.size(0)]

class EncoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Self-attention block
        attn_output, _ = self.attention(x, x, x, attn_mask=mask)
        x = self.norm1(x + self.dropout(attn_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_output))
        return x

class DecoderLayer(nn.Module):
    def __init__(self, hidden_dim, num_heads=8, dropout=0.1):
        super().__init__()
        self.self_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.enc_dec_attention = nn.MultiheadAttention(hidden_dim, num_heads, dropout)
        self.ffn = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 4, hidden_dim)
        )
        self.norm1 = nn.LayerNorm(hidden_dim)
        self.norm2 = nn.LayerNorm(hidden_dim)
        self.norm3 = nn.LayerNorm(hidden_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, encoder_output, tgt_mask=None, src_mask=None):
        # Self-attention block
        self_attn_output, _ = self.self_attention(x, x, x, attn_mask=tgt_mask)
        x = self.norm1(x + self.dropout(self_attn_output))
        
        # Encoder-decoder attention block
        enc_dec_output, _ = self.enc_dec_attention(x, encoder_output, encoder_output, attn_mask=src_mask)
        x = self.norm2(x + self.dropout(enc_dec_output))
        
        # Feed-forward block
        ffn_output = self.ffn(x)
        x = self.norm3(x + self.dropout(ffn_output))
        return x

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, hidden_dim, num_layers=6, num_heads=8, dropout=0.1):
        super().__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, hidden_dim)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, hidden_dim)
        self.positional_encoding = PositionalEncoding(hidden_dim)
        
        self.encoder_layers = nn.ModuleList([
            EncoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        self.decoder_layers = nn.ModuleList([
            DecoderLayer(hidden_dim, num_heads, dropout) for _ in range(num_layers)
        ])
        
        self.final_layer = nn.Linear(hidden_dim, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def create_mask(self, src, tgt):
        src_mask = None  # Allow attending to all source positions
        tgt_mask = nn.Transformer.generate_square_subsequent_mask(tgt.size(0))
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        # Create masks
        src_mask, tgt_mask = self.create_mask(src, tgt)
        
        # Embedding + Positional encoding
        src = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
        
        # Encoder
        enc_output = src
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)
        
        # Decoder
        dec_output = tgt
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, tgt_mask, src_mask)
        
        output = self.final_layer(dec_output)
        return output

# Example usage
def main():
    # Model parameters
    src_vocab_size = 10000
    tgt_vocab_size = 10000
    hidden_dim = 512
    num_layers = 6
    num_heads = 8
    dropout = 0.1

    # Create model
    model = Transformer(
        src_vocab_size=src_vocab_size,
        tgt_vocab_size=tgt_vocab_size,
        hidden_dim=hidden_dim,
        num_layers=num_layers,
        num_heads=num_heads,
        dropout=dropout
    )

    # Example input (batch_size=32, sequence_length=10)
    src = torch.randint(1, src_vocab_size, (10, 32))  # (seq_len, batch_size)
    tgt = torch.randint(1, tgt_vocab_size, (8, 32))   # (seq_len, batch_size)

    # Forward pass
    output = model(src, tgt)
    print("Output shape:", output.shape)

if __name__ == "__main__":
    main()

Desglose y Explicación del Código:

1. Clase PositionalEncoding:

  • Implementa codificaciones posicionales sinusoidales para proporcionar información de posición al modelo
  • Crea incrustaciones de posición únicas para cada posición en la secuencia
  • Añade estas codificaciones posicionales a las incrustaciones de entrada

2. Clase EncoderLayer:

  • Implementa una capa única del codificador con:
    • Mecanismo de auto-atención multi-cabezal
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Procesa secuencias de entrada mientras mantiene sus relaciones contextuales

3. Clase DecoderLayer:

  • Implementa una capa única del decodificador con:
    • Auto-atención multi-cabezal enmascarada
    • Atención codificador-decodificador
    • Red feed-forward posicional
    • Normalización de capas y conexiones residuales
  • Genera secuencias de salida atendiendo tanto a la salida del codificador como a los tokens previamente generados

4. Clase Transformer:

  • Combina todos los componentes en una arquitectura transformer completa:
    • Incrustaciones de entrada y codificación posicional
    • Pila de capas del codificador
    • Pila de capas del decodificador
    • Capa de proyección lineal final
  • Implementa la lógica principal del paso hacia adelante incluyendo la generación de máscaras

5. Características Principales:

  • Implementa máscaras de atención para la generación adecuada de secuencias
  • Utiliza dropout para regularización
  • Incluye conexiones residuales y normalización de capas
  • Admite número configurable de capas, cabezales y dimensiones del modelo

6. Ejemplo de Uso:

  • Demuestra cómo inicializar y utilizar el modelo transformer
  • Muestra el formato adecuado de entrada y paso hacia adelante
  • Incluye configuraciones típicas de hiperparámetros usadas en la práctica

4.2.6 Puntos Clave

  1. El marco codificador-decodificador sirve como la arquitectura fundamental del modelo Transformer, revolucionando cómo procesamos datos secuenciales. La eficiencia de este marco proviene de su capacidad para:
    • Procesar secuencias de entrada en paralelo en lugar de secuencialmente
    • Manejar entradas y salidas de longitud variable de manera natural
    • Mantener dependencias de largo alcance de manera efectiva
  2. La interacción entre el codificador y el decodificador es sofisticada y multicapa:
    • El codificador transforma las secuencias de entrada en incrustaciones contextualizadas ricas que capturan relaciones tanto locales como globales
    • El decodificador genera salidas a través de un mecanismo de doble atención: auto-atención para mantener la coherencia en la salida, y atención codificador-decodificador para extraer información relevante de la entrada
    • Múltiples cabezales de atención permiten al modelo enfocarse en diferentes aspectos de la entrada simultáneamente
  3. La arquitectura modular ofrece varias ventajas clave:
    • Fácil escalabilidad mediante la adición o eliminación de capas del codificador/decodificador
    • Flexibilidad para adaptarse a varias tareas mediante transferencia de aprendizaje
    • Capacidad para manejar múltiples idiomas, modalidades y tipos de datos
    • Integración simple de modificaciones específicas para tareas sin cambiar la arquitectura central