Projeto 05 – A implementação do “mecanismo de atenção” de uma arquitetura Transformer: IAG (Inteligência Artificial Generativa)

Nesse post, será uma complementação prática do post “O mecanismo de atenção em uma IA Generativa“, em que foi explanado, em linhas gerais, o que é o mecanismo de atenção de uma arquitetura transformer, que é a base das IAGs.

Nesse recorte do post anterior vimos os seguintes conceitos:

Vetores de Consulta, Chave e Valor: Para cada palavra na sequência, o Transformer gera três vetores: uma consulta (query – Q), uma chave (key – K) e um valor (value – V). Esses vetores são criados a partir de multiplicações matriciais com os pesos aprendidos durante o treinamento.

Esses elementos serão utilizados para o cálculo do mecanismo de atenção. Assim, abaixo, temos sua forma matemática:

Attention(Q,K,V) = softmax(\frac{QK^{T}}{\sqrt{d_{k}}})V

Primeiramente, vou fazer uma breve explicação de algo bastante relevante para o contexto. Quem já trabalhou ou brincou com as api (chamada de software) da OpenAi já se familiarizou com o termo token. Mas afinal, …

O que é um token?

Os tokens em uma rede Transformer são as unidades básicas de entrada e processamento em modelos de linguagem baseados em Transformers, como o GPT (Generative Pre-trained Transformer) e o BERT (Bidirectional Encoder Representations from Transformers).

Vou mostrar um exemplo:

Temos a seguinte frase: “Vamos aprender!”. Ao efetuar a tokenização ela pode ser dividida em [“Vamos”, “aprender”, “!”] (lembrando que os tokens também poderiam ser as sílabas ou mesmo os caracteres). Agora, cada token é convertido em um número através de um vocabulário predefinido. Por exemplo, “Vamos” pode ser representado pelo número 345, “aprender” pelo 789 e “!” pelo 56. Então, esses números são então transformados em vetores (embeddings), que são representações numéricas de alta dimensão (aquelas matrizes que aprendemos nas aulas de matemática do ensino médio). Esses vetores capturam informações semânticas sobre os tokens (ex: posição de entrada, relacionamento com termo anterior e posterior, etc.). Depois os vetores são inseridos no modelo Transformer.

Feito tudo isso, o mecanismos de atenção entra em cena (junto com outras operações) e o modelo processa esses vetores para realizar tarefas como tradução, resumo ou geração de texto.

Implementando o Mecanismo de Atenção

1 – Definindo os hiperparâmetros

O primeiro aspecto ao qual devemos prestar atenção é a definição dos hiperparâmetros. O primeiro deles será o tamanho da sequência de saída, pois, ao contrário dos seres humanos, que podem produzir texto quase infinitamente, as LLMs possuem um limite fixo (64 KB, 128 KB e assim por diante). No nosso exemplo, vamos utilizar uma saída de apenas 8 tokens

Outras variáveis que serão utilizadas são o tamanho do vocabulário, que definiremos com o valor 80, e dimensão do modelo, com magnitude igual 64.

vocab_size = 80 
dim_model = 64
2 – Embedding

O embedding é utilizada para converter entradas sequenciais em vetores de tamanho fixo. Nesses vetores serão armazenados informações do texto e semânticas, como já falado anteriormente, e os hiperparâmetros definidos.

def embedding(input, vocab_size, dim_model):

3 – Softmax

A função softamx é um transformador de valores brutos de saída em probabilidades de somatório total igual a 1. Assim, podemos dizer que as saídas das LLMs Bert, GPT e demais podem ter uma probabilidade de acertar e errar (as alucinações).

def softmax(x):

   ex = np.exp(x - np.max(x))

4 – Scale Dot Product Attention

Como o próprio nome revela, essa função calcula o produto escalar da atenção utilizando o conjunto das queries (Q), keys (K) e values (V).

É essa função que permitirá ao modelo dispensar uma maior atenção a diferentes partes do texto de entrada.

def scaled_dot_product_attention(Q, K, V):
    
    # Calcula o produto escalar entre Q e a transposta de K
    matmul_qk = np.dot(Q, K.T)
5 – Modelo transformer

O transformer é o responsável por juntar todas as partes, que vão da entrada do texto, transformação na matriz de embeddings, depois alimenta a camada de atenção, gerando as saídas as saídas probabilísticas. Feito isso, filtramos as maiores probabilidades e teremos as saídas do modelo ou os resultados.

def transformer_model(input):

         embedded_input;
         output_attention(scaled_dot_product_attention);
         softmax;
         max(output_probalities);
         output_indices; 
6 – Exemplificando
# Gerando dados aleatórios para a entrada do modelo
input_sequence = np.random.randint(0, vocab_size, seq_length)

print("Sequência de Entrada:", input_sequence)

Essa sequência aleatória de dados (um texto, uma série temporal, um texto) seria o texto de entrada transformado na matriz de embeddings.

Sequência de Entrada: [ 8 68 93 50 71 77 16 88]

Alimentando o modelo a sequência de dados criados.

# Fazendo previsões com o modelo
output = transformer_model(input_sequence)

rint("Saída do Modelo:", output)
Saída do Modelo: [94 97 19 81 45 64 51  1]

Conclusão

O ideal seria que a saída imitasse a entrada, mas apesar da saída gerada não ser “compreensível”, podemos evidenciar que:

  1. Obviamente um maior volume de dados para treinamento do modelo;
  2. Precisaríamos de mais cabeças de atenção, mais épocas de treino e a rotina de backpropagation implementada;
  3. O mecanismo de atenção funcionou, pois conseguimos aplicar as operações matemáticas e gerar uma saída!!!

Espero que tenham uma leitura proveitosa!

Grande abraço.