Redes Neurais Feedforward em Transformers: Arquitetura e Implementação

As Redes Neurais Feedforward (FFN), frequentemente chamadas de Módulos Feedforward ou Camadas de Transformação Posição-a-Posição, são um componente fundamental na arquitetura dos Transformers. Apesar de sua aparente simplicidade, elas desempenham um papel crucial ao permitir que o modelo processe e transforme as representações de tokens de forma indepandente em cada posição da sequência. A FFN atua sobre a saída do mecanismo de autoatenção, adicionando capacidade de modelagem não linear.

Princípios Fundamentais

Uma FFN típica dentro de um Transformer consiste em duas transformações lineares intercaladas por uma função de ativação não linear e, geralmente, uma camada de dropout. A sequência de operações é a seguinte:

  1. Uma camada linear projeta o vetor de entrada para um espaço de dimensão superior.
  2. Uma função de ativação, como ReLU (Recitfied Linear Unit), introduz não-linearidade, permitindo que a rede aprenda padrões mais complexos.
  3. Uma camada de dropout é aplicada para regularização, ajudando a prevenir o overfitting.
  4. Uma segunda camada linear projeta o vetor de volta para a dimensão original do modelo.

Isso pode ser visualizado como:


Entrada (batch_size, seq_len, dim_modelo)
    ↓ Projeção de Expansão (Linear)
(batch_size, seq_len, dim_oculta)
    ↓ Função de Ativação (ReLU)
(batch_size, seq_len, dim_oculta)
    ↓ Camada de Regularização (Dropout)
(batch_size, seq_len, dim_oculta)
    ↓ Projeção de Compressão (Linear)
(batch_size, seq_len, dim_modelo)

Onde dim_modelo é a dimensão do espaço de embeddings (e.g., 512) e dim_oculta (e.g., 2048) é uma dimensão interna expadnida, geralmente um múltiplo da dim_modelo (comumente 4x).

Implementação em PyTorch

A seguir, apresentamos uma implementação em PyTorch de um módulo Feedforward, encapsulando as operações descritas:


import torch
import torch.nn as nn
import torch.nn.functional as F

class ModuloFeedForward(nn.Module):
    """
    Implementação de uma Rede Neural Feedforward (FFN) para arquiteturas Transformer.
    
    Estrutura:
    - Camada Linear de expansão (dim_modelo -> dim_oculta)
    - Função de ativação ReLU
    - Camada de Dropout
    - Camada Linear de compressão (dim_oculta -> dim_modelo)
    """
    def __init__(self, dim_modelo, dim_oculta, taxa_dropout=0.1):
        super().__init__()
        
        # Primeira transformação linear: expande a dimensão
        self.proj_expansao = nn.Linear(dim_modelo, dim_oculta)
        
        # Função de ativação para introduzir não-linearidade
        self.ativacao_relu = nn.ReLU()
        
        # Camada de dropout para regularização
        self.camada_dropout = nn.Dropout(taxa_dropout)
        
        # Segunda transformação linear: retorna à dimensão original
        self.proj_compressao = nn.Linear(dim_oculta, dim_modelo)
    
    def forward(self, x_input):
        """
        Executa a passagem forward do ModuloFeedForward.
        
        Args:
            x_input: Tensor de entrada (batch_size, seq_len, dim_modelo)
        
        Returns:
            Tensor de saída (batch_size, seq_len, dim_modelo)
        """
        # Aplica a primeira camada linear
        x_processed = self.proj_expansao(x_input)
        
        # Aplica a função de ativação ReLU
        x_processed = self.ativacao_relu(x_processed)
        
        # Aplica dropout
        x_processed = self.camada_dropout(x_processed)
        
        # Aplica a segunda camada linear
        output = self.proj_compressao(x_processed)
        
        return output

def demonstrar_modulo_feedforward():
    """
    Função para demonstrar passo a passo o funcionamento do ModuloFeedForward.
    """
    print("\n=== Demonstração do Módulo FeedForward para Transformers ===\n")
    
    # Parâmetros de exemplo
    tamanho_batch = 2
    comprimento_sequencia = 5
    dimensao_modelo = 512
    dimensao_oculta = 2048 # Geralmente 4 * dimensao_modelo
    taxa_dropout = 0.1
    
    # Cria dados de entrada aleatórios
    dados_entrada = torch.randn(tamanho_batch, comprimento_sequencia, dimensao_modelo)
    print(f"1. Dados de entrada:")
    print(f"   Formato: {dados_entrada.shape}")
    print(f"   Exemplo (primeiro elemento do primeiro batch):")
    print(f"   {dados_entrada[0, 0, :10].tolist()}...\n") # Usar .tolist() para uma saída mais limpa
    
    # Instancia o módulo FFN
    modulo_ffn = ModuloFeedForward(dimensao_modelo, dimensao_oculta, taxa_dropout)
    print(f"2. Módulo FFN instanciado:")
    print(f"   Dimensão do Modelo (d_model): {dimensao_modelo}")
    print(f"   Dimensão Oculta (d_ff): {dimensao_oculta}")
    print(f"   Taxa de Dropout: {taxa_dropout}\n")
    
    # Define o módulo para modo de avaliação (dropout desativado)
    modulo_ffn.eval()
    
    # Demonstração passo a passo
    print(f"3. Projeção de Expansão (proj_expansao):")
    interm_expansao = modulo_ffn.proj_expansao(dados_entrada)
    print(f"   Formato de entrada: {dados_entrada.shape}")
    print(f"   Formato de pesos (proj_expansao.weight): {modulo_ffn.proj_expansao.weight.shape}")
    print(f"   Formato de bias (proj_expansao.bias): {modulo_ffn.proj_expansao.bias.shape}")
    print(f"   Formato de saída: {interm_expansao.shape}")
    print(f"   Exemplo (primeiro elemento do primeiro batch):")
    print(f"   {interm_expansao[0, 0, :10].tolist()}...\n")
    
    print(f"4. Ativação ReLU (ativacao_relu):")
    antes_relu = interm_expansao.clone()
    apos_relu = modulo_ffn.ativacao_relu(interm_expansao)
    print(f"   Formato de entrada: {antes_relu.shape}")
    print(f"   Formato de saída: {apos_relu.shape}")
    print(f"   Exemplo (primeiro elemento do primeiro batch):")
    print(f"   Antes ReLU: {antes_relu[0, 0, :10].tolist()}...")
    print(f"   Após ReLU: {apos_relu[0, 0, :10].tolist()}...")
    print(f"   Observação: Valores negativos são substituídos por zero.\n")
    
    print(f"5. Camada de Dropout (camada_dropout) - Modo de Avaliação:")
    antes_dropout = apos_relu.clone()
    apos_dropout = modulo_ffn.camada_dropout(apos_relu)
    print(f"   Formato de entrada: {antes_dropout.shape}")
    print(f"   Formato de saída: {apos_dropout.shape}")
    print(f"   Dados alterados? {not torch.equal(antes_dropout, apos_dropout)}")
    print(f"   Observação: No modo 'eval', dropout não desativa neurônios.\n")
    
    print(f"6. Projeção de Compressão (proj_compressao):")
    antes_compressao = apos_dropout.clone()
    saida_final = modulo_ffn.proj_compressao(apos_dropout)
    print(f"   Formato de entrada: {antes_compressao.shape}")
    print(f"   Formato de pesos (proj_compressao.weight): {modulo_ffn.proj_compressao.weight.shape}")
    print(f"   Formato de bias (proj_compressao.bias): {modulo_ffn.proj_compressao.bias.shape}")
    print(f"   Formato de saída: {saida_final.shape}")
    print(f"   Exemplo (primeiro elemento do primeiro batch):")
    print(f"   {saida_final[0, 0, :10].tolist()}...\n")
    
    print(f"7. Verificação de Formato Final:")
    print(f"   Formato de entrada original: {dados_entrada.shape}")
    print(f"   Formato de saída final: {saida_final.shape}")
    print(f"   Formato preservado: {dados_entrada.shape == saida_final.shape}\n")
    
    # Demonstração de dropout em modo de treinamento
    print(f"8. Dropout em Modo de Treinamento:")
    modulo_ffn.train() # Ativa o modo de treinamento
    saida_treino = modulo_ffn.proj_expansao(dados_entrada)
    saida_treino = modulo_ffn.ativacao_relu(saida_treino)
    antes_dropout_treino = saida_treino.clone()
    saida_treino = modulo_ffn.camada_dropout(saida_treino)
    print(f"   Formato de entrada: {antes_dropout_treino.shape}")
    print(f"   Formato de saída: {saida_treino.shape}")
    print(f"   Dados alterados? {not torch.equal(antes_dropout_treino, saida_treino)}")
    print(f"   Observação: No modo 'train', dropout zera aleatoriamente alguns neurônios.\n")
    
    # Contagem de parâmetros
    print(f"9. Estatísticas de Parâmetros:")
    params_proj1 = dimensao_oculta * dimensao_modelo + dimensao_oculta
    params_proj2 = dimensao_modelo * dimensao_oculta + dimensao_modelo
    total_params = params_proj1 + params_proj2
    print(f"   Parâmetros da Projeção de Expansão: {params_proj1:,}")
    print(f"   Parâmetros da Projeção de Compressão: {params_proj2:,}")
    print(f"   Total de Parâmetros: {total_params:,}")

def demonstrar_ffn_no_contexto_transformer():
    """
    Demonstra a posição do ModuloFeedForward dentro de uma camada de encoder Transformer.
    """
    print("\n=== Posição do Módulo FeedForward em uma Camada Encoder Transformer ===\n")
    
    tamanho_batch = 2
    comprimento_sequencia = 5
    dimensao_modelo = 512
    num_cabecas_atencao = 8
    dimensao_oculta_ffn = 2048
    
    # Entrada inicial
    tensor_inicial = torch.randn(tamanho_batch, comprimento_sequencia, dimensao_modelo)
    print(f"1. Tensor de Entrada Inicial:")
    print(f"   Formato: {tensor_inicial.shape}")
    
    # Camada de Multi-Head Attention
    print(f"\n2. Camada de Autoatenção Multi-Cabeça (Multi-Head Attention):")
    mha_camada = nn.MultiheadAttention(dimensao_modelo, num_cabecas_atencao, batch_first=True)
    saida_atencao, _ = mha_camada(tensor_inicial, tensor_inicial, tensor_inicial)
    print(f"   Formato da saída da atenção: {saida_atencao.shape}")
    
    # Conexão Residual e Normalização de Camada (LayerNorm) após MHA
    print(f"\n3. Conexão Residual + Normalização de Camada (pós-MHA):")
    norm_camada1 = nn.LayerNorm(dimensao_modelo)
    processado_apos_mha = norm_camada1(tensor_inicial + saida_atencao)
    print(f"   Formato do tensor processado: {processado_apos_mha.shape}")
    
    # O Módulo FeedForward entra aqui
    print(f"\n4. Módulo FeedForward (FFN):")
    ffn_camada = ModuloFeedForward(dimensao_modelo, dimensao_oculta_ffn)
    ffn_camada.eval() # Modo de avaliação
    saida_ffn = ffn_camada(processado_apos_mha)
    print(f"   Formato de entrada do FFN: {processado_apos_mha.shape}")
    print(f"   Formato de saída do FFN: {saida_ffn.shape}")
    
    # Conexão Residual e Normalização de Camada (LayerNorm) após FFN
    print(f"\n5. Conexão Residual + Normalização de Camada (pós-FFN):")
    norm_camada2 = nn.LayerNorm(dimensao_modelo)
    saida_final_encoder = norm_camada2(processado_apos_mha + saida_ffn)
    print(f"   Formato da saída final do encoder: {saida_final_encoder.shape}")
    
    print(f"\n6. Visão Geral da Saída do Bloco Encoder:")
    print(f"   Formato: {saida_final_encoder.shape}")

if __name__ == "__main__":
    demonstrar_modulo_feedforward()
    demonstrar_ffn_no_contexto_transformer()

Pontos Chave

  • Duas Camadas Lineares: A primeira camada linear expande a dimensionalidade dos dados (e.g., de 512 para 2048), e a segunda a comprime de volta à dimensão original (e.g., de 2048 para 512). Isso permite uma transformação rica e detalhada dos recursos.
  • Ativação Não-linear (ReLU): A função ReLU introduz a capacidade de modelar relações não-lineares nos dados, convertendo valores negativos em zero e mantendo os positivos, o que é essencial para a expressividade da rede.
  • Regularização com Dropout: Durante o treinamento, a camada de Dropout zera aleatoriamente uma fração dos neurônios, forçando a rede a aprender representações mais robustas e menos dependentes de neurônios específicos, prevenindo o overfitting. No modo de avaliação, o Dropout é desativado.
  • Significativo Número de Parâmetros: A FFN possui uma quantidade substancial de parâmetros, contribuindo significativamente para a capacidade de aprendizado do Transformer. Sua complexidade é geralmente maior que a do mecanismo de autoatenção em termos de contagem de parâmetros.
  • Função Principal: A FFN oferece uma camada adicional de transformação e refino para as representações contextuais geradas pela atenção, permitindo que o modelo extraia e incorpore informações mais complexas em cada token.

Tags: Pytorch Redes Neurais Transformers Aprendizado Profundo Processamento de Linguagem Natural

Publicado em 6-11 18:21 por Thomas