Criando um LLM do zero com Transformers

Introdução

Airton de Sousa Lira Junior
6 min readApr 27, 2024

Modelos de Linguagem de Grande Escala (LLMs) como GPT (Generative Pre-trained Transformer) têm revolucionado a interação entre humanos e máquinas, proporcionando avanços significativos em tarefas de processamento de linguagem natural (NLP). Este artigo fornece um guia detalhado sobre como construir um LLM do zero, utilizando a arquitetura Transformer, e exemplifica o processo usando um dataset público.

Uma rapida introdução sobre encode e decode (a base para LLM)

Muitos ja devem estar familiarizado com encode/decode dos famosos enconding de um arquivo de texto como UTF-8, ANSI, Windows-1258 etc… e é a mesma lógica aqui para se trabalhar com modelos LLM, usar encode e decode permite que os modelos sejam treinados de maneira eficiente em grandes corpora de texto e, posteriormente, gerem respostas precisas e contextualmente apropriadas. Esses processos são otimizados para trabalhar com a arquitetura de redes neurais, aproveitando técnicas como embeddings de palavras e mecanismos de atenção.

Um exemplo simples de encode e decode em python:

# Dicionário para mapear palavras para índices
word_to_index = {
'Olá': 1,
'mundo': 2,
'como': 3,
'você': 4,
'está': 5
}
#A função de encode vai receber uma lista de palavras e converterá cada palavra
#em seu índice correspondente usando o dicionário.
def encode(text):
return [word_to_index[word] for word in text.split() if word in word_to_index]

# Dicionário para mapear índices para palavras
index_to_word = {index: word for word, index in word_to_index.items()}

# A função de decode vai converter uma lista de índices de volta para uma
# string de palavras.
def decode(indices):
return ' '.join(index_to_word[index] for index in indices if index in index_to_word)


# Texto de exemplo
text = "Olá mundo como você está"

# Codificação
encoded_text = encode(text)
print("Encoded:", encoded_text)

# Decodificação
decoded_text = decode(encoded_text)
print("Decoded:", decoded_text)

O que é o processo de embedding de palavras?

É uma técnica essencial no processamento de linguagem natural (NLP) que envolve converter palavras em vetores de números densos e contínuos. Esses vetores são projetados para representar semanticamente as palavras de forma que as distâncias entre eles no espaço vetorial reflitam as semelhanças semânticas entre as palavras. Aqui estão os pontos principais sobre o processo de embedding de palavras:

Representação Densa

Ao contrário de representações mais antigas como “one-hot encoding”, onde cada palavra é representada por um vetor esparso (composta principalmente de zeros), os embeddings de palavras são vetores densos (onde todos os valores são números reais). Isso reduz a dimensionalidade e captura mais informações em menos espaço.

Captura de Contexto e Semântica

Os vetores resultantes de um processo de embedding capturam relações semânticas e sintáticas entre as palavras. Por exemplo, palavras com significados semelhantes ou usadas em contextos similares tendem a estar mais próximas no espaço vetorial. Isso permite que modelos de NLP realizem tarefas como análise de sentimento, tradução automática e reconhecimento de entidades nomeadas de forma mais eficaz.

Imagem do livro Processamento de Linguagem Natural (Helena de Medeiros Caseli)

Aprendizado dos Vetores

Os embeddings de palavras são geralmente aprendidos de grandes corpora de texto usando modelos específicos. Os mais comuns incluem:

  • Word2Vec: Um dos modelos mais populares, desenvolvido pelo Google, que aprende embeddings treinando uma rede neural para prever palavras a partir de seu contexto (CBOW) ou prever o contexto a partir de uma palavra (Skip-gram).
  • GloVe (Global Vectors for Word Representation): Desenvolvido pela Stanford, este modelo aprende embeddings ao modelar estatísticas globais de co-ocorrência de palavras em um corpus, tentando derivar relações em um espaço de menor dimensão.
  • FastText: Desenvolvido pelo Facebook, este modelo estende o Word2Vec para considerar subunidades de palavras (como n-gramas), permitindo que capture melhor o significado de palavras desconhecidas ou mal escritas.

Aplicações

Embeddings de palavras são utilizados em uma ampla variedade de tarefas de NLP, tais como:

  • Melhoria da performance de modelos de linguagem.
  • Busca semântica, onde termos de pesquisa que têm significado similar aos termos do documento podem ser encontrados mesmo que não sejam textualmente idênticos.
  • Análise de sentimentos, onde o modelo precisa entender o significado das palavras para determinar a polaridade (positiva, negativa, neutra) de um texto.
  • Sistemas de recomendação e personalização de conteúdo.

Esses vetores são fundamentais para permitir que os modelos de processamento de linguagem natural entendam e manipulem a linguagem humana de maneira eficaz, capturando nuances e variações linguísticas complexas.

Iniciando nosso projeto na prática com passo a passo explicativo.

Bom após essa introdução sobre basicamente o que rodeia esse mundo de LLM, NLP. Vamos criar nosso modelo do zero utilizando o modelo Transformer, ele é uma arquitetura de rede neural amplamente utilizada no processamento de linguagem natural (NLP), especialmente em modelos de linguagem de grande escala (LLM), como o GPT (Generative Pre-trained Transformer). Desenvolvido por Vaswani et al. em 2017, o Transformer introduziu várias inovações que o tornaram particularmente eficaz para tarefas de NLP, não vou entrar em detalhes que invoações são essas mas a titulo de curiosidade podem pesquisar sobre arquitetura baseada em atenção multi-cabeça.

Para começar, é necessário instalar algumas bibliotecas essenciais:

pip install torch torchvision transformers datasetssAgora vamos preparar os dados dividindo em partes menores

Estas bibliotecas incluem:

  • Torch: Framework de deep learning que oferece ampla flexibilidade e velocidade durante o treinamento de modelos.
  • Transformers e Datasets: Oferecidas pela Hugging Face, estas bibliotecas facilitam o carregamento de modelos pré-treinados e datasets de maneira eficiente.

Preparação dos Dados

Carregamento do Dataset

Usaremos o dataset wikitext-103, um conjunto de textos da Wikipedia usado frequentemente para treinar modelos de linguagem. Para carregar este dataset:

from datasets import load_dataset

dataset = load_dataset("wikitext", "wikitext-103-v1")
train_data = dataset['train']['text']

Tokenização

A tokenização é o processo de conversão de texto em tokens que o modelo pode entender. Usamos o tokenizador do GPT-2 para isso:

from transformers import GPT2Tokenizer

tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
train_encodings = tokenizer(train_data, return_tensors='pt', max_length=512, truncation=True, padding="max_length")

Resultado esperado:

{
'input_ids': tensor([...]),
'attention_mask': tensor([...])
}

Construção do Modelo

Definindo a Configuração do Modelo

O Transformer requer uma configuração específica que define as capacidades do modelo:

from transformers import GPT2Config, GPT2LMHeadModel

config = GPT2Config(
vocab_size=tokenizer.vocab_size,
n_positions=512,
n_ctx=512,
n_embd=768,
n_layer=12,
n_head=12
)
model = GPT2LMHeadModel(config)

Treinamento do Modelo

O treinamento é um processo crucial que adapta o modelo ao nosso dataset específico:

from torch.optim import AdamW
from torch.utils.data import DataLoader

optimizer = AdamW(model.parameters(), lr=5e-5)
train_loader = DataLoader(train_encodings, batch_size=8, shuffle=True)

model.train()
for epoch in range(5):
for batch in train_loader:
inputs, labels = batch['input_ids'], batch['input_ids']
outputs = model(inputs, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(f"Epoch {epoch}: Loss {loss.item()}")

Resultado esperado:

GPT2LMHeadModel(
(transformer): GPT2Model(
...
(wte): Embedding(50257, 768)
...
)
)

Treinamento do Modelo

O treinamento é um processo crucial que adapta o modelo ao nosso dataset específico:

from torch.optim import AdamW
from torch.utils.data import DataLoader

optimizer = AdamW(model.parameters(), lr=5e-5)
train_loader = DataLoader(train_encodings, batch_size=8, shuffle=True)

model.train()
for epoch in range(5):
for batch in train_loader:
inputs, labels = batch['input_ids'], batch['input_ids']
outputs = model(inputs, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(f"Epoch {epoch}: Loss {loss.item()}")

Resultado esperado a cada época:

Epoch 0: Loss 3.762
Epoch 1: Loss 3.158
Epoch 2: Loss 2.943
Epoch 3: Loss 2.712
Epoch 4: Loss 2.487

Salvando e Carregando o Modelo

Após o treinamento, salve o modelo para usá-lo posteriormente:

model.save_pretrained('./meuModeloGPT2')

Para carregar e usar o modelo:

from transformers import pipeline

generator = pipeline('text-generation', model='./meuModeloGPT2', tokenizer='gpt2')

Testando o Modelo

Para verificar a eficácia do modelo, podemos utilizar uma prompt de pergunta:

prompt = "Qual é o significado da vida, do universo e tudo mais?"
response = generator(prompt, max_length=50, num_return_sequences=1)
print(response[0]['generated_text'])

Resultado esperado:

"Qual é o significado da vida, do universo e tudo mais? A vida é um jogo de aprendizado e descobertas."

Conclusão

Este guia detalhado fornece as ferramentas e conhecimentos necessários para construir um LLM usando a arquitetura Transformer. Desde a preparação e tokenização dos dados até o treinamento e a aplicação do modelo, seguimos passos que são essenciais para desenvolver um modelo de linguagem eficiente e robusto.

--

--