mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/AI/AI-llm-architecture/1.-tokenizing.md', 'src/AI/AI-ll
This commit is contained in:
parent
4d56ad7ca3
commit
ace696f6ba
95
src/AI/AI-llm-architecture/1.-tokenizing.md
Normal file
95
src/AI/AI-llm-architecture/1.-tokenizing.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 1. Tokenização
|
||||
|
||||
## Tokenização
|
||||
|
||||
**Tokenização** é o processo de dividir dados, como texto, em partes menores e gerenciáveis chamadas _tokens_. Cada token é então atribuído a um identificador numérico único (ID). Este é um passo fundamental na preparação do texto para processamento por modelos de aprendizado de máquina, especialmente em processamento de linguagem natural (NLP).
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta fase inicial é muito simples: **Dividir a entrada em tokens (ids) de uma maneira que faça sentido**.
|
||||
|
||||
### **Como a Tokenização Funciona**
|
||||
|
||||
1. **Dividindo o Texto:**
|
||||
- **Tokenizador Básico:** Um tokenizador simples pode dividir o texto em palavras individuais e sinais de pontuação, removendo espaços.
|
||||
- _Exemplo:_\
|
||||
Texto: `"Olá, mundo!"`\
|
||||
Tokens: `["Olá", ",", "mundo", "!"]`
|
||||
2. **Criando um Vocabulário:**
|
||||
- Para converter tokens em IDs numéricos, um **vocabulário** é criado. Este vocabulário lista todos os tokens únicos (palavras e símbolos) e atribui a cada um um ID específico.
|
||||
- **Tokens Especiais:** Estes são símbolos especiais adicionados ao vocabulário para lidar com vários cenários:
|
||||
- `[BOS]` (Início da Sequência): Indica o início de um texto.
|
||||
- `[EOS]` (Fim da Sequência): Indica o fim de um texto.
|
||||
- `[PAD]` (Preenchimento): Usado para fazer todas as sequências em um lote terem o mesmo comprimento.
|
||||
- `[UNK]` (Desconhecido): Representa tokens que não estão no vocabulário.
|
||||
- _Exemplo:_\
|
||||
Se `"Olá"` é atribuído ao ID `64`, `","` é `455`, `"mundo"` é `78`, e `"!"` é `467`, então:\
|
||||
`"Olá, mundo!"` → `[64, 455, 78, 467]`
|
||||
- **Tratando Palavras Desconhecidas:**\
|
||||
Se uma palavra como `"Tchau"` não está no vocabulário, ela é substituída por `[UNK]`.\
|
||||
`"Tchau, mundo!"` → `["[UNK]", ",", "mundo", "!"]` → `[987, 455, 78, 467]`\
|
||||
_(Assumindo que `[UNK]` tem ID `987`)_
|
||||
|
||||
### **Métodos Avançados de Tokenização**
|
||||
|
||||
Enquanto o tokenizador básico funciona bem para textos simples, ele tem limitações, especialmente com vocabulários grandes e ao lidar com palavras novas ou raras. Métodos avançados de tokenização abordam essas questões dividindo o texto em subunidades menores ou otimizando o processo de tokenização.
|
||||
|
||||
1. **Codificação de Par de Bytes (BPE):**
|
||||
- **Propósito:** Reduz o tamanho do vocabulário e lida com palavras raras ou desconhecidas, dividindo-as em pares de bytes que ocorrem com frequência.
|
||||
- **Como Funciona:**
|
||||
- Começa com caracteres individuais como tokens.
|
||||
- Mescla iterativamente os pares de tokens mais frequentes em um único token.
|
||||
- Continua até que não haja mais pares frequentes que possam ser mesclados.
|
||||
- **Benefícios:**
|
||||
- Elimina a necessidade de um token `[UNK]`, uma vez que todas as palavras podem ser representadas combinando tokens de subpalavras existentes.
|
||||
- Vocabulário mais eficiente e flexível.
|
||||
- _Exemplo:_\
|
||||
`"jogando"` pode ser tokenizado como `["jogar", "ndo"]` se `"jogar"` e `"ndo"` forem subpalavras frequentes.
|
||||
2. **WordPiece:**
|
||||
- **Usado Por:** Modelos como BERT.
|
||||
- **Propósito:** Semelhante ao BPE, divide palavras em unidades de subpalavras para lidar com palavras desconhecidas e reduzir o tamanho do vocabulário.
|
||||
- **Como Funciona:**
|
||||
- Começa com um vocabulário base de caracteres individuais.
|
||||
- Adiciona iterativamente a subpalavra mais frequente que maximiza a probabilidade dos dados de treinamento.
|
||||
- Usa um modelo probabilístico para decidir quais subpalavras mesclar.
|
||||
- **Benefícios:**
|
||||
- Equilibra entre ter um tamanho de vocabulário gerenciável e representar palavras de forma eficaz.
|
||||
- Lida eficientemente com palavras raras e compostas.
|
||||
- _Exemplo:_\
|
||||
`"infelicidade"` pode ser tokenizado como `["in", "felicidade"]` ou `["in", "feliz", "dade"]` dependendo do vocabulário.
|
||||
3. **Modelo de Linguagem Unigram:**
|
||||
- **Usado Por:** Modelos como SentencePiece.
|
||||
- **Propósito:** Usa um modelo probabilístico para determinar o conjunto mais provável de tokens de subpalavras.
|
||||
- **Como Funciona:**
|
||||
- Começa com um grande conjunto de tokens potenciais.
|
||||
- Remove iterativamente tokens que menos melhoram a probabilidade do modelo em relação aos dados de treinamento.
|
||||
- Finaliza um vocabulário onde cada palavra é representada pelas unidades de subpalavras mais prováveis.
|
||||
- **Benefícios:**
|
||||
- Flexível e pode modelar a linguagem de forma mais natural.
|
||||
- Muitas vezes resulta em tokenizações mais eficientes e compactas.
|
||||
- _Exemplo:_\
|
||||
`"internacionalização"` pode ser tokenizado em subpalavras menores e significativas como `["internacional", "ização"]`.
|
||||
|
||||
## Exemplo de Código
|
||||
|
||||
Vamos entender isso melhor a partir de um exemplo de código de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
|
||||
```python
|
||||
# Download a text to pre-train the model
|
||||
import urllib.request
|
||||
url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt")
|
||||
file_path = "the-verdict.txt"
|
||||
urllib.request.urlretrieve(url, file_path)
|
||||
|
||||
with open("the-verdict.txt", "r", encoding="utf-8") as f:
|
||||
raw_text = f.read()
|
||||
|
||||
# Tokenize the code using GPT2 tokenizer version
|
||||
import tiktoken
|
||||
token_ids = tiktoken.get_encoding("gpt2").encode(txt, allowed_special={"[EOS]"}) # Allow the user of the tag "[EOS]"
|
||||
|
||||
# Print first 50 tokens
|
||||
print(token_ids[:50])
|
||||
#[40, 367, 2885, 1464, 1807, 3619, 402, 271, 10899, 2138, 257, 7026, 15632, 438, 2016, 257, 922, 5891, 1576, 438, 568, 340, 373, 645, 1049, 5975, 284, 502, 284, 3285, 326, 11, 287, 262, 6001, 286, 465, 13476, 11, 339, 550, 5710, 465, 12036, 11, 6405, 257, 5527, 27075, 11]
|
||||
```
|
||||
## Referências
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
203
src/AI/AI-llm-architecture/3.-token-embeddings.md
Normal file
203
src/AI/AI-llm-architecture/3.-token-embeddings.md
Normal file
@ -0,0 +1,203 @@
|
||||
# 3. Token Embeddings
|
||||
|
||||
## Token Embeddings
|
||||
|
||||
Após a tokenização dos dados textuais, o próximo passo crítico na preparação dos dados para treinar grandes modelos de linguagem (LLMs) como o GPT é criar **token embeddings**. Token embeddings transformam tokens discretos (como palavras ou subpalavras) em vetores numéricos contínuos que o modelo pode processar e aprender. Esta explicação detalha os token embeddings, sua inicialização, uso e o papel dos embeddings posicionais em melhorar a compreensão do modelo sobre sequências de tokens.
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta terceira fase é muito simples: **Atribuir a cada um dos tokens anteriores no vocabulário um vetor das dimensões desejadas para treinar o modelo.** Cada palavra no vocabulário será um ponto em um espaço de X dimensões.\
|
||||
> Note que inicialmente a posição de cada palavra no espaço é apenas inicializada "aleatoriamente" e essas posições são parâmetros treináveis (serão melhorados durante o treinamento).
|
||||
>
|
||||
> Além disso, durante o embedding de tokens **outra camada de embeddings é criada** que representa (neste caso) a **posição absoluta da palavra na frase de treinamento**. Dessa forma, uma palavra em diferentes posições na frase terá uma representação (significado) diferente.
|
||||
|
||||
### **O Que São Token Embeddings?**
|
||||
|
||||
**Token Embeddings** são representações numéricas de tokens em um espaço vetorial contínuo. Cada token no vocabulário está associado a um vetor único de dimensões fixas. Esses vetores capturam informações semânticas e sintáticas sobre os tokens, permitindo que o modelo entenda relacionamentos e padrões nos dados.
|
||||
|
||||
- **Tamanho do Vocabulário:** O número total de tokens únicos (por exemplo, palavras, subpalavras) no vocabulário do modelo.
|
||||
- **Dimensões do Embedding:** O número de valores numéricos (dimensões) no vetor de cada token. Dimensões mais altas podem capturar informações mais sutis, mas requerem mais recursos computacionais.
|
||||
|
||||
**Exemplo:**
|
||||
|
||||
- **Tamanho do Vocabulário:** 6 tokens \[1, 2, 3, 4, 5, 6]
|
||||
- **Dimensões do Embedding:** 3 (x, y, z)
|
||||
|
||||
### **Inicializando Token Embeddings**
|
||||
|
||||
No início do treinamento, os token embeddings são tipicamente inicializados com pequenos valores aleatórios. Esses valores iniciais são ajustados (afinados) durante o treinamento para representar melhor os significados dos tokens com base nos dados de treinamento.
|
||||
|
||||
**Exemplo PyTorch:**
|
||||
```python
|
||||
import torch
|
||||
|
||||
# Set a random seed for reproducibility
|
||||
torch.manual_seed(123)
|
||||
|
||||
# Create an embedding layer with 6 tokens and 3 dimensions
|
||||
embedding_layer = torch.nn.Embedding(6, 3)
|
||||
|
||||
# Display the initial weights (embeddings)
|
||||
print(embedding_layer.weight)
|
||||
```
|
||||
**Saída:**
|
||||
```lua
|
||||
luaCopy codeParameter containing:
|
||||
tensor([[ 0.3374, -0.1778, -0.1690],
|
||||
[ 0.9178, 1.5810, 1.3010],
|
||||
[ 1.2753, -0.2010, -0.1606],
|
||||
[-0.4015, 0.9666, -1.1481],
|
||||
[-1.1589, 0.3255, -0.6315],
|
||||
[-2.8400, -0.7849, -1.4096]], requires_grad=True)
|
||||
```
|
||||
**Explicação:**
|
||||
|
||||
- Cada linha corresponde a um token no vocabulário.
|
||||
- Cada coluna representa uma dimensão no vetor de incorporação.
|
||||
- Por exemplo, o token no índice `3` tem um vetor de incorporação `[-0.4015, 0.9666, -1.1481]`.
|
||||
|
||||
**Acessando a Incorporação de um Token:**
|
||||
```python
|
||||
# Retrieve the embedding for the token at index 3
|
||||
token_index = torch.tensor([3])
|
||||
print(embedding_layer(token_index))
|
||||
```
|
||||
**Saída:**
|
||||
```lua
|
||||
tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=<EmbeddingBackward0>)
|
||||
```
|
||||
**Interpretação:**
|
||||
|
||||
- O token no índice `3` é representado pelo vetor `[-0.4015, 0.9666, -1.1481]`.
|
||||
- Esses valores são parâmetros treináveis que o modelo ajustará durante o treinamento para representar melhor o contexto e o significado do token.
|
||||
|
||||
### **Como os Embeddings de Token Funcionam Durante o Treinamento**
|
||||
|
||||
Durante o treinamento, cada token nos dados de entrada é convertido em seu vetor de embedding correspondente. Esses vetores são então usados em vários cálculos dentro do modelo, como mecanismos de atenção e camadas de rede neural.
|
||||
|
||||
**Cenário de Exemplo:**
|
||||
|
||||
- **Tamanho do Lote:** 8 (número de amostras processadas simultaneamente)
|
||||
- **Comprimento Máximo da Sequência:** 4 (número de tokens por amostra)
|
||||
- **Dimensões do Embedding:** 256
|
||||
|
||||
**Estrutura de Dados:**
|
||||
|
||||
- Cada lote é representado como um tensor 3D com forma `(batch_size, max_length, embedding_dim)`.
|
||||
- Para nosso exemplo, a forma seria `(8, 4, 256)`.
|
||||
|
||||
**Visualização:**
|
||||
```css
|
||||
cssCopy codeBatch
|
||||
┌─────────────┐
|
||||
│ Sample 1 │
|
||||
│ ┌─────┐ │
|
||||
│ │Token│ → [x₁₁, x₁₂, ..., x₁₂₅₆]
|
||||
│ │ 1 │ │
|
||||
│ │... │ │
|
||||
│ │Token│ │
|
||||
│ │ 4 │ │
|
||||
│ └─────┘ │
|
||||
│ Sample 2 │
|
||||
│ ┌─────┐ │
|
||||
│ │Token│ → [x₂₁, x₂₂, ..., x₂₂₅₆]
|
||||
│ │ 1 │ │
|
||||
│ │... │ │
|
||||
│ │Token│ │
|
||||
│ │ 4 │ │
|
||||
│ └─────┘ │
|
||||
│ ... │
|
||||
│ Sample 8 │
|
||||
│ ┌─────┐ │
|
||||
│ │Token│ → [x₈₁, x₈₂, ..., x₈₂₅₆]
|
||||
│ │ 1 │ │
|
||||
│ │... │ │
|
||||
│ │Token│ │
|
||||
│ │ 4 │ │
|
||||
│ └─────┘ │
|
||||
└─────────────┘
|
||||
```
|
||||
**Explicação:**
|
||||
|
||||
- Cada token na sequência é representado por um vetor de 256 dimensões.
|
||||
- O modelo processa essas embeddings para aprender padrões de linguagem e gerar previsões.
|
||||
|
||||
## **Embeddings Posicionais: Adicionando Contexto às Embeddings de Token**
|
||||
|
||||
Enquanto as embeddings de token capturam o significado de tokens individuais, elas não codificam inherentemente a posição dos tokens dentro de uma sequência. Compreender a ordem dos tokens é crucial para a compreensão da linguagem. É aqui que as **embeddings posicionais** entram em cena.
|
||||
|
||||
### **Por que as Embeddings Posicionais São Necessárias:**
|
||||
|
||||
- **A Ordem dos Tokens Importa:** Em frases, o significado muitas vezes depende da ordem das palavras. Por exemplo, "O gato sentou no tapete" vs. "O tapete sentou no gato."
|
||||
- **Limitação da Embedding:** Sem informações posicionais, o modelo trata os tokens como um "saco de palavras", ignorando sua sequência.
|
||||
|
||||
### **Tipos de Embeddings Posicionais:**
|
||||
|
||||
1. **Embeddings Posicionais Absolutos:**
|
||||
- Atribuem um vetor de posição único a cada posição na sequência.
|
||||
- **Exemplo:** O primeiro token em qualquer sequência tem a mesma embedding posicional, o segundo token tem outra, e assim por diante.
|
||||
- **Usado Por:** Modelos GPT da OpenAI.
|
||||
2. **Embeddings Posicionais Relativos:**
|
||||
- Codificam a distância relativa entre tokens em vez de suas posições absolutas.
|
||||
- **Exemplo:** Indicam quão distantes dois tokens estão, independentemente de suas posições absolutas na sequência.
|
||||
- **Usado Por:** Modelos como Transformer-XL e algumas variantes do BERT.
|
||||
|
||||
### **Como as Embeddings Posicionais São Integradas:**
|
||||
|
||||
- **Mesmas Dimensões:** As embeddings posicionais têm a mesma dimensionalidade que as embeddings de token.
|
||||
- **Adição:** Elas são adicionadas às embeddings de token, combinando a identidade do token com informações posicionais sem aumentar a dimensionalidade geral.
|
||||
|
||||
**Exemplo de Adição de Embeddings Posicionais:**
|
||||
|
||||
Suponha que um vetor de embedding de token seja `[0.5, -0.2, 0.1]` e seu vetor de embedding posicional seja `[0.1, 0.3, -0.1]`. A embedding combinada usada pelo modelo seria:
|
||||
```css
|
||||
Combined Embedding = Token Embedding + Positional Embedding
|
||||
= [0.5 + 0.1, -0.2 + 0.3, 0.1 + (-0.1)]
|
||||
= [0.6, 0.1, 0.0]
|
||||
```
|
||||
**Benefícios das Embeddings Posicionais:**
|
||||
|
||||
- **Consciência Contextual:** O modelo pode diferenciar entre tokens com base em suas posições.
|
||||
- **Compreensão de Sequência:** Permite que o modelo entenda gramática, sintaxe e significados dependentes do contexto.
|
||||
|
||||
## Exemplo de Código
|
||||
|
||||
Seguindo com o exemplo de código de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
|
||||
```python
|
||||
# Use previous code...
|
||||
|
||||
# Create dimensional emdeddings
|
||||
"""
|
||||
BPE uses a vocabulary of 50257 words
|
||||
Let's supose we want to use 256 dimensions (instead of the millions used by LLMs)
|
||||
"""
|
||||
|
||||
vocab_size = 50257
|
||||
output_dim = 256
|
||||
token_embedding_layer = torch.nn.Embedding(vocab_size, output_dim)
|
||||
|
||||
## Generate the dataloader like before
|
||||
max_length = 4
|
||||
dataloader = create_dataloader_v1(
|
||||
raw_text, batch_size=8, max_length=max_length,
|
||||
stride=max_length, shuffle=False
|
||||
)
|
||||
data_iter = iter(dataloader)
|
||||
inputs, targets = next(data_iter)
|
||||
|
||||
# Apply embeddings
|
||||
token_embeddings = token_embedding_layer(inputs)
|
||||
print(token_embeddings.shape)
|
||||
torch.Size([8, 4, 256]) # 8 x 4 x 256
|
||||
|
||||
# Generate absolute embeddings
|
||||
context_length = max_length
|
||||
pos_embedding_layer = torch.nn.Embedding(context_length, output_dim)
|
||||
|
||||
pos_embeddings = pos_embedding_layer(torch.arange(max_length))
|
||||
|
||||
input_embeddings = token_embeddings + pos_embeddings
|
||||
print(input_embeddings.shape) # torch.Size([8, 4, 256])
|
||||
```
|
||||
## Referências
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
416
src/AI/AI-llm-architecture/4.-attention-mechanisms.md
Normal file
416
src/AI/AI-llm-architecture/4.-attention-mechanisms.md
Normal file
@ -0,0 +1,416 @@
|
||||
# 4. Mecanismos de Atenção
|
||||
|
||||
## Mecanismos de Atenção e Auto-Atenção em Redes Neurais
|
||||
|
||||
Os mecanismos de atenção permitem que redes neurais **focalizem partes específicas da entrada ao gerar cada parte da saída**. Eles atribuem pesos diferentes a diferentes entradas, ajudando o modelo a decidir quais entradas são mais relevantes para a tarefa em questão. Isso é crucial em tarefas como tradução automática, onde entender o contexto de toda a frase é necessário para uma tradução precisa.
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta quarta fase é muito simples: **Aplicar alguns mecanismos de atenção**. Estes serão muitas **camadas repetidas** que vão **capturar a relação de uma palavra no vocabulário com seus vizinhos na frase atual sendo usada para treinar o LLM**.\
|
||||
> Muitas camadas são usadas para isso, então muitos parâmetros treináveis vão capturar essa informação.
|
||||
|
||||
### Entendendo os Mecanismos de Atenção
|
||||
|
||||
Em modelos tradicionais de sequência para sequência usados para tradução de linguagem, o modelo codifica uma sequência de entrada em um vetor de contexto de tamanho fixo. No entanto, essa abordagem tem dificuldades com frases longas porque o vetor de contexto de tamanho fixo pode não capturar todas as informações necessárias. Os mecanismos de atenção abordam essa limitação permitindo que o modelo considere todos os tokens de entrada ao gerar cada token de saída.
|
||||
|
||||
#### Exemplo: Tradução Automática
|
||||
|
||||
Considere traduzir a frase em alemão "Kannst du mir helfen diesen Satz zu übersetzen" para o inglês. Uma tradução palavra por palavra não produziria uma frase em inglês gramaticalmente correta devido a diferenças nas estruturas gramaticais entre as línguas. Um mecanismo de atenção permite que o modelo se concentre nas partes relevantes da frase de entrada ao gerar cada palavra da frase de saída, levando a uma tradução mais precisa e coerente.
|
||||
|
||||
### Introdução à Auto-Atenção
|
||||
|
||||
A auto-atensão, ou intra-atensão, é um mecanismo onde a atenção é aplicada dentro de uma única sequência para calcular uma representação dessa sequência. Isso permite que cada token na sequência atenda a todos os outros tokens, ajudando o modelo a capturar dependências entre tokens, independentemente da distância entre eles na sequência.
|
||||
|
||||
#### Conceitos Chave
|
||||
|
||||
- **Tokens**: Elementos individuais da sequência de entrada (por exemplo, palavras em uma frase).
|
||||
- **Embeddings**: Representações vetoriais de tokens, capturando informações semânticas.
|
||||
- **Pesos de Atenção**: Valores que determinam a importância de cada token em relação aos outros.
|
||||
|
||||
### Calculando Pesos de Atenção: Um Exemplo Passo a Passo
|
||||
|
||||
Vamos considerar a frase **"Hello shiny sun!"** e representar cada palavra com um embedding de 3 dimensões:
|
||||
|
||||
- **Hello**: `[0.34, 0.22, 0.54]`
|
||||
- **shiny**: `[0.53, 0.34, 0.98]`
|
||||
- **sun**: `[0.29, 0.54, 0.93]`
|
||||
|
||||
Nosso objetivo é calcular o **vetor de contexto** para a palavra **"shiny"** usando auto-atensão.
|
||||
|
||||
#### Passo 1: Calcular Pontuações de Atenção
|
||||
|
||||
> [!TIP]
|
||||
> Basta multiplicar cada valor de dimensão da consulta pelo relevante de cada token e somar os resultados. Você obtém 1 valor por par de tokens.
|
||||
|
||||
Para cada palavra na frase, calcule a **pontuação de atenção** em relação a "shiny" calculando o produto escalar de seus embeddings.
|
||||
|
||||
**Pontuação de Atenção entre "Hello" e "shiny"**
|
||||
|
||||
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
**Pontuação de Atenção entre "shiny" e "shiny"**
|
||||
|
||||
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
**Pontuação de Atenção entre "sun" e "shiny"**
|
||||
|
||||
<figure><img src="../../images/image (2) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
#### Passo 2: Normalizar Pontuações de Atenção para Obter Pesos de Atenção
|
||||
|
||||
> [!TIP]
|
||||
> Não se perca nos termos matemáticos, o objetivo desta função é simples, normalizar todos os pesos para **que eles somem 1 no total**.
|
||||
>
|
||||
> Além disso, a função **softmax** é usada porque acentua diferenças devido à parte exponencial, facilitando a detecção de valores úteis.
|
||||
|
||||
Aplique a **função softmax** às pontuações de atenção para convertê-las em pesos de atenção que somam 1.
|
||||
|
||||
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
|
||||
|
||||
Calculando os exponenciais:
|
||||
|
||||
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="249"><figcaption></figcaption></figure>
|
||||
|
||||
Calculando a soma:
|
||||
|
||||
<figure><img src="../../images/image (5) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
Calculando pesos de atenção:
|
||||
|
||||
<figure><img src="../../images/image (6) (1) (1).png" alt="" width="404"><figcaption></figcaption></figure>
|
||||
|
||||
#### Passo 3: Calcular o Vetor de Contexto
|
||||
|
||||
> [!TIP]
|
||||
> Basta pegar cada peso de atenção e multiplicá-lo pelas dimensões do token relacionado e, em seguida, somar todas as dimensões para obter apenas 1 vetor (o vetor de contexto)
|
||||
|
||||
O **vetor de contexto** é calculado como a soma ponderada dos embeddings de todas as palavras, usando os pesos de atenção.
|
||||
|
||||
<figure><img src="../../images/image (16).png" alt="" width="369"><figcaption></figcaption></figure>
|
||||
|
||||
Calculando cada componente:
|
||||
|
||||
- **Embedding Ponderado de "Hello"**:
|
||||
|
||||
<figure><img src="../../images/image (7) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
- **Embedding Ponderado de "shiny"**:
|
||||
|
||||
<figure><img src="../../images/image (8) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
- **Embedding Ponderado de "sun"**:
|
||||
|
||||
<figure><img src="../../images/image (9) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
Somando os embeddings ponderados:
|
||||
|
||||
`context vector=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]`
|
||||
|
||||
**Este vetor de contexto representa o embedding enriquecido para a palavra "shiny", incorporando informações de todas as palavras na frase.**
|
||||
|
||||
### Resumo do Processo
|
||||
|
||||
1. **Calcular Pontuações de Atenção**: Use o produto escalar entre o embedding da palavra-alvo e os embeddings de todas as palavras na sequência.
|
||||
2. **Normalizar Pontuações para Obter Pesos de Atenção**: Aplique a função softmax às pontuações de atenção para obter pesos que somem 1.
|
||||
3. **Calcular o Vetor de Contexto**: Multiplique o embedding de cada palavra pelo seu peso de atenção e some os resultados.
|
||||
|
||||
## Auto-Atenção com Pesos Treináveis
|
||||
|
||||
Na prática, os mecanismos de auto-atensão usam **pesos treináveis** para aprender as melhores representações para consultas, chaves e valores. Isso envolve a introdução de três matrizes de peso:
|
||||
|
||||
<figure><img src="../../images/image (10) (1) (1).png" alt="" width="239"><figcaption></figcaption></figure>
|
||||
|
||||
A consulta é os dados a serem usados como antes, enquanto as matrizes de chaves e valores são apenas matrizes aleatórias treináveis.
|
||||
|
||||
#### Passo 1: Calcular Consultas, Chaves e Valores
|
||||
|
||||
Cada token terá sua própria matriz de consulta, chave e valor multiplicando seus valores de dimensão pelas matrizes definidas:
|
||||
|
||||
<figure><img src="../../images/image (11).png" alt="" width="253"><figcaption></figcaption></figure>
|
||||
|
||||
Essas matrizes transformam os embeddings originais em um novo espaço adequado para calcular a atenção.
|
||||
|
||||
**Exemplo**
|
||||
|
||||
Assumindo:
|
||||
|
||||
- Dimensão de entrada `din=3` (tamanho do embedding)
|
||||
- Dimensão de saída `dout=2` (dimensão desejada para consultas, chaves e valores)
|
||||
|
||||
Inicialize as matrizes de peso:
|
||||
```python
|
||||
import torch.nn as nn
|
||||
|
||||
d_in = 3
|
||||
d_out = 2
|
||||
|
||||
W_query = nn.Parameter(torch.rand(d_in, d_out))
|
||||
W_key = nn.Parameter(torch.rand(d_in, d_out))
|
||||
W_value = nn.Parameter(torch.rand(d_in, d_out))
|
||||
```
|
||||
Calcule consultas, chaves e valores:
|
||||
```python
|
||||
queries = torch.matmul(inputs, W_query)
|
||||
keys = torch.matmul(inputs, W_key)
|
||||
values = torch.matmul(inputs, W_value)
|
||||
```
|
||||
#### Passo 2: Calcular Atenção de Produto Escalonado
|
||||
|
||||
**Calcular Pontuações de Atenção**
|
||||
|
||||
Semelhante ao exemplo anterior, mas desta vez, em vez de usar os valores das dimensões dos tokens, usamos a matriz de chave do token (já calculada usando as dimensões):. Assim, para cada consulta `qi` e chave `kj`:
|
||||
|
||||
<figure><img src="../../images/image (12).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
**Escalonar as Pontuações**
|
||||
|
||||
Para evitar que os produtos escalares se tornem muito grandes, escalone-os pela raiz quadrada da dimensão da chave `dk`:
|
||||
|
||||
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
|
||||
|
||||
> [!TIP]
|
||||
> A pontuação é dividida pela raiz quadrada das dimensões porque os produtos escalares podem se tornar muito grandes e isso ajuda a regulá-los.
|
||||
|
||||
**Aplicar Softmax para Obter Pesos de Atenção:** Como no exemplo inicial, normalize todos os valores para que somem 1.
|
||||
|
||||
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
|
||||
|
||||
#### Passo 3: Calcular Vetores de Contexto
|
||||
|
||||
Como no exemplo inicial, basta somar todas as matrizes de valores multiplicando cada uma pelo seu peso de atenção:
|
||||
|
||||
<figure><img src="../../images/image (15).png" alt="" width="328"><figcaption></figcaption></figure>
|
||||
|
||||
### Exemplo de Código
|
||||
|
||||
Pegando um exemplo de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) você pode conferir esta classe que implementa a funcionalidade de auto-atenção que discutimos:
|
||||
```python
|
||||
import torch
|
||||
|
||||
inputs = torch.tensor(
|
||||
[[0.43, 0.15, 0.89], # Your (x^1)
|
||||
[0.55, 0.87, 0.66], # journey (x^2)
|
||||
[0.57, 0.85, 0.64], # starts (x^3)
|
||||
[0.22, 0.58, 0.33], # with (x^4)
|
||||
[0.77, 0.25, 0.10], # one (x^5)
|
||||
[0.05, 0.80, 0.55]] # step (x^6)
|
||||
)
|
||||
|
||||
import torch.nn as nn
|
||||
class SelfAttention_v2(nn.Module):
|
||||
|
||||
def __init__(self, d_in, d_out, qkv_bias=False):
|
||||
super().__init__()
|
||||
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
|
||||
def forward(self, x):
|
||||
keys = self.W_key(x)
|
||||
queries = self.W_query(x)
|
||||
values = self.W_value(x)
|
||||
|
||||
attn_scores = queries @ keys.T
|
||||
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
|
||||
|
||||
context_vec = attn_weights @ values
|
||||
return context_vec
|
||||
|
||||
d_in=3
|
||||
d_out=2
|
||||
torch.manual_seed(789)
|
||||
sa_v2 = SelfAttention_v2(d_in, d_out)
|
||||
print(sa_v2(inputs))
|
||||
```
|
||||
> [!TIP]
|
||||
> Note que, em vez de inicializar as matrizes com valores aleatórios, `nn.Linear` é usado para marcar todos os pesos como parâmetros a serem treinados.
|
||||
|
||||
## Atenção Causal: Ocultando Palavras Futuras
|
||||
|
||||
Para LLMs, queremos que o modelo considere apenas os tokens que aparecem antes da posição atual para **prever o próximo token**. **Atenção causal**, também conhecida como **atenção mascarada**, alcança isso modificando o mecanismo de atenção para impedir o acesso a tokens futuros.
|
||||
|
||||
### Aplicando uma Máscara de Atenção Causal
|
||||
|
||||
Para implementar a atenção causal, aplicamos uma máscara aos scores de atenção **antes da operação softmax** para que os restantes ainda somem 1. Essa máscara define os scores de atenção dos tokens futuros como negativo infinito, garantindo que, após o softmax, seus pesos de atenção sejam zero.
|
||||
|
||||
**Passos**
|
||||
|
||||
1. **Calcular Scores de Atenção**: Igual ao anterior.
|
||||
2. **Aplicar Máscara**: Use uma matriz triangular superior preenchida com negativo infinito acima da diagonal.
|
||||
|
||||
```python
|
||||
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf')
|
||||
masked_scores = attention_scores + mask
|
||||
```
|
||||
|
||||
3. **Aplicar Softmax**: Calcule os pesos de atenção usando os scores mascarados.
|
||||
|
||||
```python
|
||||
attention_weights = torch.softmax(masked_scores, dim=-1)
|
||||
```
|
||||
|
||||
### Mascarando Pesos de Atenção Adicionais com Dropout
|
||||
|
||||
Para **prevenir overfitting**, podemos aplicar **dropout** aos pesos de atenção após a operação softmax. O dropout **zera aleatoriamente alguns dos pesos de atenção** durante o treinamento.
|
||||
```python
|
||||
dropout = nn.Dropout(p=0.5)
|
||||
attention_weights = dropout(attention_weights)
|
||||
```
|
||||
Uma taxa de dropout regular é de cerca de 10-20%.
|
||||
|
||||
### Exemplo de Código
|
||||
|
||||
Exemplo de código de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb):
|
||||
```python
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
inputs = torch.tensor(
|
||||
[[0.43, 0.15, 0.89], # Your (x^1)
|
||||
[0.55, 0.87, 0.66], # journey (x^2)
|
||||
[0.57, 0.85, 0.64], # starts (x^3)
|
||||
[0.22, 0.58, 0.33], # with (x^4)
|
||||
[0.77, 0.25, 0.10], # one (x^5)
|
||||
[0.05, 0.80, 0.55]] # step (x^6)
|
||||
)
|
||||
|
||||
batch = torch.stack((inputs, inputs), dim=0)
|
||||
print(batch.shape)
|
||||
|
||||
class CausalAttention(nn.Module):
|
||||
|
||||
def __init__(self, d_in, d_out, context_length,
|
||||
dropout, qkv_bias=False):
|
||||
super().__init__()
|
||||
self.d_out = d_out
|
||||
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)) # New
|
||||
|
||||
def forward(self, x):
|
||||
b, num_tokens, d_in = x.shape
|
||||
# b is the num of batches
|
||||
# num_tokens is the number of tokens per batch
|
||||
# d_in is the dimensions er token
|
||||
|
||||
keys = self.W_key(x) # This generates the keys of the tokens
|
||||
queries = self.W_query(x)
|
||||
values = self.W_value(x)
|
||||
|
||||
attn_scores = queries @ keys.transpose(1, 2) # Moves the third dimension to the second one and the second one to the third one to be able to multiply
|
||||
attn_scores.masked_fill_( # New, _ ops are in-place
|
||||
self.mask.bool()[:num_tokens, :num_tokens], -torch.inf) # `:num_tokens` to account for cases where the number of tokens in the batch is smaller than the supported context_size
|
||||
attn_weights = torch.softmax(
|
||||
attn_scores / keys.shape[-1]**0.5, dim=-1
|
||||
)
|
||||
attn_weights = self.dropout(attn_weights)
|
||||
|
||||
context_vec = attn_weights @ values
|
||||
return context_vec
|
||||
|
||||
torch.manual_seed(123)
|
||||
|
||||
context_length = batch.shape[1]
|
||||
d_in = 3
|
||||
d_out = 2
|
||||
ca = CausalAttention(d_in, d_out, context_length, 0.0)
|
||||
|
||||
context_vecs = ca(batch)
|
||||
|
||||
print(context_vecs)
|
||||
print("context_vecs.shape:", context_vecs.shape)
|
||||
```
|
||||
## Estendendo a Atenção de Cabeça Única para Atenção de Múltiplas Cabeças
|
||||
|
||||
**Atenção de múltiplas cabeças** em termos práticos consiste em executar **várias instâncias** da função de autoatenção, cada uma com **seus próprios pesos**, de modo que vetores finais diferentes sejam calculados.
|
||||
|
||||
### Exemplo de Código
|
||||
|
||||
Pode ser possível reutilizar o código anterior e apenas adicionar um wrapper que o execute várias vezes, mas esta é uma versão mais otimizada de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) que processa todas as cabeças ao mesmo tempo (reduzindo o número de loops for caros). Como você pode ver no código, as dimensões de cada token são divididas em diferentes dimensões de acordo com o número de cabeças. Dessa forma, se o token tiver 8 dimensões e quisermos usar 3 cabeças, as dimensões serão divididas em 2 arrays de 4 dimensões e cada cabeça usará uma delas:
|
||||
```python
|
||||
class MultiHeadAttention(nn.Module):
|
||||
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
|
||||
super().__init__()
|
||||
assert (d_out % num_heads == 0), \
|
||||
"d_out must be divisible by num_heads"
|
||||
|
||||
self.d_out = d_out
|
||||
self.num_heads = num_heads
|
||||
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
|
||||
|
||||
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.register_buffer(
|
||||
"mask",
|
||||
torch.triu(torch.ones(context_length, context_length),
|
||||
diagonal=1)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
b, num_tokens, d_in = x.shape
|
||||
# b is the num of batches
|
||||
# num_tokens is the number of tokens per batch
|
||||
# d_in is the dimensions er token
|
||||
|
||||
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
|
||||
queries = self.W_query(x)
|
||||
values = self.W_value(x)
|
||||
|
||||
# We implicitly split the matrix by adding a `num_heads` dimension
|
||||
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
|
||||
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
|
||||
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
|
||||
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
|
||||
|
||||
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
|
||||
keys = keys.transpose(1, 2)
|
||||
queries = queries.transpose(1, 2)
|
||||
values = values.transpose(1, 2)
|
||||
|
||||
# Compute scaled dot-product attention (aka self-attention) with a causal mask
|
||||
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
|
||||
|
||||
# Original mask truncated to the number of tokens and converted to boolean
|
||||
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
|
||||
|
||||
# Use the mask to fill attention scores
|
||||
attn_scores.masked_fill_(mask_bool, -torch.inf)
|
||||
|
||||
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
|
||||
attn_weights = self.dropout(attn_weights)
|
||||
|
||||
# Shape: (b, num_tokens, num_heads, head_dim)
|
||||
context_vec = (attn_weights @ values).transpose(1, 2)
|
||||
|
||||
# Combine heads, where self.d_out = self.num_heads * self.head_dim
|
||||
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
|
||||
context_vec = self.out_proj(context_vec) # optional projection
|
||||
|
||||
return context_vec
|
||||
|
||||
torch.manual_seed(123)
|
||||
|
||||
batch_size, context_length, d_in = batch.shape
|
||||
d_out = 2
|
||||
mha = MultiHeadAttention(d_in, d_out, context_length, 0.0, num_heads=2)
|
||||
|
||||
context_vecs = mha(batch)
|
||||
|
||||
print(context_vecs)
|
||||
print("context_vecs.shape:", context_vecs.shape)
|
||||
|
||||
```
|
||||
Para uma implementação compacta e eficiente, você pode usar a [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) classe no PyTorch.
|
||||
|
||||
> [!TIP]
|
||||
> Resposta curta do ChatGPT sobre por que é melhor dividir as dimensões dos tokens entre as cabeças em vez de fazer com que cada cabeça verifique todas as dimensões de todos os tokens:
|
||||
>
|
||||
> Embora permitir que cada cabeça processe todas as dimensões de embedding possa parecer vantajoso porque cada cabeça teria acesso a todas as informações, a prática padrão é **dividir as dimensões de embedding entre as cabeças**. Essa abordagem equilibra a eficiência computacional com o desempenho do modelo e incentiva cada cabeça a aprender representações diversas. Portanto, dividir as dimensões de embedding é geralmente preferido em relação a fazer com que cada cabeça verifique todas as dimensões.
|
||||
|
||||
## Referências
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
666
src/AI/AI-llm-architecture/5.-llm-architecture.md
Normal file
666
src/AI/AI-llm-architecture/5.-llm-architecture.md
Normal file
@ -0,0 +1,666 @@
|
||||
# 5. Arquitetura LLM
|
||||
|
||||
## Arquitetura LLM
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta quinta fase é muito simples: **Desenvolver a arquitetura do LLM completo**. Junte tudo, aplique todas as camadas e crie todas as funções para gerar texto ou transformar texto em IDs e vice-versa.
|
||||
>
|
||||
> Esta arquitetura será usada tanto para treinar quanto para prever texto após ter sido treinada.
|
||||
|
||||
Exemplo de arquitetura LLM de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
|
||||
|
||||
Uma representação de alto nível pode ser observada em:
|
||||
|
||||
<figure><img src="../../images/image (3) (1) (1) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31">https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31</a></p></figcaption></figure>
|
||||
|
||||
1. **Entrada (Texto Tokenizado)**: O processo começa com texto tokenizado, que é convertido em representações numéricas.
|
||||
2. **Camada de Embedding de Token e Camada de Embedding Posicional**: O texto tokenizado é passado por uma **camada de embedding de token** e uma **camada de embedding posicional**, que captura a posição dos tokens em uma sequência, crítica para entender a ordem das palavras.
|
||||
3. **Blocos Transformer**: O modelo contém **12 blocos transformer**, cada um com várias camadas. Esses blocos repetem a seguinte sequência:
|
||||
- **Atenção Multi-Cabeça Mascarada**: Permite que o modelo se concentre em diferentes partes do texto de entrada ao mesmo tempo.
|
||||
- **Normalização de Camada**: Um passo de normalização para estabilizar e melhorar o treinamento.
|
||||
- **Camada Feed Forward**: Responsável por processar as informações da camada de atenção e fazer previsões sobre o próximo token.
|
||||
- **Camadas de Dropout**: Essas camadas previnem o overfitting ao descartar unidades aleatoriamente durante o treinamento.
|
||||
4. **Camada de Saída Final**: O modelo produz um **tensor de 4x50.257 dimensões**, onde **50.257** representa o tamanho do vocabulário. Cada linha neste tensor corresponde a um vetor que o modelo usa para prever a próxima palavra na sequência.
|
||||
5. **Objetivo**: O objetivo é pegar esses embeddings e convertê-los de volta em texto. Especificamente, a última linha da saída é usada para gerar a próxima palavra, representada como "forward" neste diagrama.
|
||||
|
||||
### Representação de Código
|
||||
```python
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import tiktoken
|
||||
|
||||
class GELU(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return 0.5 * x * (1 + torch.tanh(
|
||||
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
|
||||
(x + 0.044715 * torch.pow(x, 3))
|
||||
))
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.layers = nn.Sequential(
|
||||
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
|
||||
GELU(),
|
||||
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.layers(x)
|
||||
|
||||
class MultiHeadAttention(nn.Module):
|
||||
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
|
||||
super().__init__()
|
||||
assert d_out % num_heads == 0, "d_out must be divisible by num_heads"
|
||||
|
||||
self.d_out = d_out
|
||||
self.num_heads = num_heads
|
||||
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
|
||||
|
||||
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||||
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
|
||||
|
||||
def forward(self, x):
|
||||
b, num_tokens, d_in = x.shape
|
||||
|
||||
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
|
||||
queries = self.W_query(x)
|
||||
values = self.W_value(x)
|
||||
|
||||
# We implicitly split the matrix by adding a `num_heads` dimension
|
||||
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
|
||||
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
|
||||
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
|
||||
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
|
||||
|
||||
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
|
||||
keys = keys.transpose(1, 2)
|
||||
queries = queries.transpose(1, 2)
|
||||
values = values.transpose(1, 2)
|
||||
|
||||
# Compute scaled dot-product attention (aka self-attention) with a causal mask
|
||||
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
|
||||
|
||||
# Original mask truncated to the number of tokens and converted to boolean
|
||||
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
|
||||
|
||||
# Use the mask to fill attention scores
|
||||
attn_scores.masked_fill_(mask_bool, -torch.inf)
|
||||
|
||||
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
|
||||
attn_weights = self.dropout(attn_weights)
|
||||
|
||||
# Shape: (b, num_tokens, num_heads, head_dim)
|
||||
context_vec = (attn_weights @ values).transpose(1, 2)
|
||||
|
||||
# Combine heads, where self.d_out = self.num_heads * self.head_dim
|
||||
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
|
||||
context_vec = self.out_proj(context_vec) # optional projection
|
||||
|
||||
return context_vec
|
||||
|
||||
class LayerNorm(nn.Module):
|
||||
def __init__(self, emb_dim):
|
||||
super().__init__()
|
||||
self.eps = 1e-5
|
||||
self.scale = nn.Parameter(torch.ones(emb_dim))
|
||||
self.shift = nn.Parameter(torch.zeros(emb_dim))
|
||||
|
||||
def forward(self, x):
|
||||
mean = x.mean(dim=-1, keepdim=True)
|
||||
var = x.var(dim=-1, keepdim=True, unbiased=False)
|
||||
norm_x = (x - mean) / torch.sqrt(var + self.eps)
|
||||
return self.scale * norm_x + self.shift
|
||||
|
||||
class TransformerBlock(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.att = MultiHeadAttention(
|
||||
d_in=cfg["emb_dim"],
|
||||
d_out=cfg["emb_dim"],
|
||||
context_length=cfg["context_length"],
|
||||
num_heads=cfg["n_heads"],
|
||||
dropout=cfg["drop_rate"],
|
||||
qkv_bias=cfg["qkv_bias"])
|
||||
self.ff = FeedForward(cfg)
|
||||
self.norm1 = LayerNorm(cfg["emb_dim"])
|
||||
self.norm2 = LayerNorm(cfg["emb_dim"])
|
||||
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
|
||||
|
||||
def forward(self, x):
|
||||
# Shortcut connection for attention block
|
||||
shortcut = x
|
||||
x = self.norm1(x)
|
||||
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
|
||||
x = self.drop_shortcut(x)
|
||||
x = x + shortcut # Add the original input back
|
||||
|
||||
# Shortcut connection for feed forward block
|
||||
shortcut = x
|
||||
x = self.norm2(x)
|
||||
x = self.ff(x)
|
||||
x = self.drop_shortcut(x)
|
||||
x = x + shortcut # Add the original input back
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class GPTModel(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
|
||||
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
|
||||
self.drop_emb = nn.Dropout(cfg["drop_rate"])
|
||||
|
||||
self.trf_blocks = nn.Sequential(
|
||||
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
|
||||
|
||||
self.final_norm = LayerNorm(cfg["emb_dim"])
|
||||
self.out_head = nn.Linear(
|
||||
cfg["emb_dim"], cfg["vocab_size"], bias=False
|
||||
)
|
||||
|
||||
def forward(self, in_idx):
|
||||
batch_size, seq_len = in_idx.shape
|
||||
tok_embeds = self.tok_emb(in_idx)
|
||||
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
|
||||
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
|
||||
x = self.drop_emb(x)
|
||||
x = self.trf_blocks(x)
|
||||
x = self.final_norm(x)
|
||||
logits = self.out_head(x)
|
||||
return logits
|
||||
|
||||
GPT_CONFIG_124M = {
|
||||
"vocab_size": 50257, # Vocabulary size
|
||||
"context_length": 1024, # Context length
|
||||
"emb_dim": 768, # Embedding dimension
|
||||
"n_heads": 12, # Number of attention heads
|
||||
"n_layers": 12, # Number of layers
|
||||
"drop_rate": 0.1, # Dropout rate
|
||||
"qkv_bias": False # Query-Key-Value bias
|
||||
}
|
||||
|
||||
torch.manual_seed(123)
|
||||
model = GPTModel(GPT_CONFIG_124M)
|
||||
out = model(batch)
|
||||
print("Input batch:\n", batch)
|
||||
print("\nOutput shape:", out.shape)
|
||||
print(out)
|
||||
```
|
||||
### **Função de Ativação GELU**
|
||||
```python
|
||||
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
||||
class GELU(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return 0.5 * x * (1 + torch.tanh(
|
||||
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
|
||||
(x + 0.044715 * torch.pow(x, 3))
|
||||
))
|
||||
```
|
||||
#### **Propósito e Funcionalidade**
|
||||
|
||||
- **GELU (Unidade Linear de Erro Gaussiano):** Uma função de ativação que introduz não-linearidade no modelo.
|
||||
- **Ativação Suave:** Ao contrário do ReLU, que zera entradas negativas, o GELU mapeia suavemente entradas para saídas, permitindo pequenos valores não nulos para entradas negativas.
|
||||
- **Definição Matemática:**
|
||||
|
||||
<figure><img src="../../images/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo do uso desta função após camadas lineares dentro da camada FeedForward é mudar os dados lineares para não lineares, permitindo que o modelo aprenda relações complexas e não lineares.
|
||||
|
||||
### **Rede Neural FeedForward**
|
||||
|
||||
_Formas foram adicionadas como comentários para entender melhor as formas das matrizes:_
|
||||
```python
|
||||
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
||||
class FeedForward(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.layers = nn.Sequential(
|
||||
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
|
||||
GELU(),
|
||||
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
# x shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
x = self.layers[0](x)# x shape: (batch_size, seq_len, 4 * emb_dim)
|
||||
x = self.layers[1](x) # x shape remains: (batch_size, seq_len, 4 * emb_dim)
|
||||
x = self.layers[2](x) # x shape: (batch_size, seq_len, emb_dim)
|
||||
return x # Output shape: (batch_size, seq_len, emb_dim)
|
||||
```
|
||||
#### **Propósito e Funcionalidade**
|
||||
|
||||
- **Rede FeedForward por Posição:** Aplica uma rede totalmente conectada de duas camadas a cada posição separadamente e de forma idêntica.
|
||||
- **Detalhes da Camada:**
|
||||
- **Primeira Camada Linear:** Expande a dimensionalidade de `emb_dim` para `4 * emb_dim`.
|
||||
- **Ativação GELU:** Aplica não-linearidade.
|
||||
- **Segunda Camada Linear:** Reduz a dimensionalidade de volta para `emb_dim`.
|
||||
|
||||
> [!TIP]
|
||||
> Como você pode ver, a rede Feed Forward usa 3 camadas. A primeira é uma camada linear que multiplicará as dimensões por 4 usando pesos lineares (parâmetros a serem treinados dentro do modelo). Em seguida, a função GELU é usada em todas essas dimensões para aplicar variações não-lineares para capturar representações mais ricas e, finalmente, outra camada linear é usada para retornar ao tamanho original das dimensões.
|
||||
|
||||
### **Mecanismo de Atenção Multi-Cabeça**
|
||||
|
||||
Isso já foi explicado em uma seção anterior.
|
||||
|
||||
#### **Propósito e Funcionalidade**
|
||||
|
||||
- **Auto-Atenção Multi-Cabeça:** Permite que o modelo se concentre em diferentes posições dentro da sequência de entrada ao codificar um token.
|
||||
- **Componentes Chave:**
|
||||
- **Consultas, Chaves, Valores:** Projeções lineares da entrada, usadas para calcular pontuações de atenção.
|
||||
- **Cabeças:** Múltiplos mecanismos de atenção funcionando em paralelo (`num_heads`), cada um com uma dimensão reduzida (`head_dim`).
|
||||
- **Pontuações de Atenção:** Calculadas como o produto escalar de consultas e chaves, escaladas e mascaradas.
|
||||
- **Mascaramento:** Uma máscara causal é aplicada para evitar que o modelo preste atenção a tokens futuros (importante para modelos autoregressivos como o GPT).
|
||||
- **Pesos de Atenção:** Softmax das pontuações de atenção mascaradas e escaladas.
|
||||
- **Vetor de Contexto:** Soma ponderada dos valores, de acordo com os pesos de atenção.
|
||||
- **Projeção de Saída:** Camada linear para combinar as saídas de todas as cabeças.
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta rede é encontrar as relações entre tokens no mesmo contexto. Além disso, os tokens são divididos em diferentes cabeças para evitar overfitting, embora as relações finais encontradas por cabeça sejam combinadas no final desta rede.
|
||||
>
|
||||
> Além disso, durante o treinamento, uma **máscara causal** é aplicada para que tokens posteriores não sejam levados em conta ao buscar as relações específicas de um token e algum **dropout** também é aplicado para **prevenir overfitting**.
|
||||
|
||||
### **Normalização** da Camada
|
||||
```python
|
||||
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
||||
class LayerNorm(nn.Module):
|
||||
def __init__(self, emb_dim):
|
||||
super().__init__()
|
||||
self.eps = 1e-5 # Prevent division by zero during normalization.
|
||||
self.scale = nn.Parameter(torch.ones(emb_dim))
|
||||
self.shift = nn.Parameter(torch.zeros(emb_dim))
|
||||
|
||||
def forward(self, x):
|
||||
mean = x.mean(dim=-1, keepdim=True)
|
||||
var = x.var(dim=-1, keepdim=True, unbiased=False)
|
||||
norm_x = (x - mean) / torch.sqrt(var + self.eps)
|
||||
return self.scale * norm_x + self.shift
|
||||
```
|
||||
#### **Propósito e Funcionalidade**
|
||||
|
||||
- **Layer Normalization:** Uma técnica usada para normalizar as entradas entre as características (dimensões de embedding) para cada exemplo individual em um lote.
|
||||
- **Componentes:**
|
||||
- **`eps`:** Uma constante pequena (`1e-5`) adicionada à variância para evitar divisão por zero durante a normalização.
|
||||
- **`scale` e `shift`:** Parâmetros aprendíveis (`nn.Parameter`) que permitem ao modelo escalar e deslocar a saída normalizada. Eles são inicializados como um e zero, respectivamente.
|
||||
- **Processo de Normalização:**
|
||||
- **Calcular Média (`mean`):** Calcula a média da entrada `x` ao longo da dimensão de embedding (`dim=-1`), mantendo a dimensão para broadcasting (`keepdim=True`).
|
||||
- **Calcular Variância (`var`):** Calcula a variância de `x` ao longo da dimensão de embedding, também mantendo a dimensão. O parâmetro `unbiased=False` garante que a variância seja calculada usando o estimador enviesado (dividindo por `N` em vez de `N-1`), o que é apropriado ao normalizar sobre características em vez de amostras.
|
||||
- **Normalizar (`norm_x`):** Subtrai a média de `x` e divide pela raiz quadrada da variância mais `eps`.
|
||||
- **Escalar e Deslocar:** Aplica os parâmetros aprendíveis `scale` e `shift` à saída normalizada.
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo é garantir uma média de 0 com uma variância de 1 em todas as dimensões do mesmo token. O objetivo disso é **estabilizar o treinamento de redes neurais profundas** reduzindo a mudança de covariáveis internas, que se refere à mudança na distribuição das ativações da rede devido à atualização de parâmetros durante o treinamento.
|
||||
|
||||
### **Bloco Transformer**
|
||||
|
||||
_Shapes foram adicionadas como comentários para entender melhor as formas das matrizes:_
|
||||
```python
|
||||
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
||||
|
||||
class TransformerBlock(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.att = MultiHeadAttention(
|
||||
d_in=cfg["emb_dim"],
|
||||
d_out=cfg["emb_dim"],
|
||||
context_length=cfg["context_length"],
|
||||
num_heads=cfg["n_heads"],
|
||||
dropout=cfg["drop_rate"],
|
||||
qkv_bias=cfg["qkv_bias"]
|
||||
)
|
||||
self.ff = FeedForward(cfg)
|
||||
self.norm1 = LayerNorm(cfg["emb_dim"])
|
||||
self.norm2 = LayerNorm(cfg["emb_dim"])
|
||||
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
|
||||
|
||||
def forward(self, x):
|
||||
# x shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
# Shortcut connection for attention block
|
||||
shortcut = x # shape: (batch_size, seq_len, emb_dim)
|
||||
x = self.norm1(x) # shape remains (batch_size, seq_len, emb_dim)
|
||||
x = self.att(x) # shape: (batch_size, seq_len, emb_dim)
|
||||
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
|
||||
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
# Shortcut connection for feedforward block
|
||||
shortcut = x # shape: (batch_size, seq_len, emb_dim)
|
||||
x = self.norm2(x) # shape remains (batch_size, seq_len, emb_dim)
|
||||
x = self.ff(x) # shape: (batch_size, seq_len, emb_dim)
|
||||
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
|
||||
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
return x # Output shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
```
|
||||
#### **Propósito e Funcionalidade**
|
||||
|
||||
- **Composição de Camadas:** Combina atenção multi-head, rede feedforward, normalização de camada e conexões residuais.
|
||||
- **Normalização de Camada:** Aplicada antes das camadas de atenção e feedforward para treinamento estável.
|
||||
- **Conexões Residuais (Atalhos):** Adiciona a entrada de uma camada à sua saída para melhorar o fluxo de gradiente e permitir o treinamento de redes profundas.
|
||||
- **Dropout:** Aplicado após as camadas de atenção e feedforward para regularização.
|
||||
|
||||
#### **Funcionalidade Passo a Passo**
|
||||
|
||||
1. **Primeiro Caminho Residual (Auto-Atenção):**
|
||||
- **Entrada (`shortcut`):** Salvar a entrada original para a conexão residual.
|
||||
- **Norma de Camada (`norm1`):** Normalizar a entrada.
|
||||
- **Atenção Multi-Head (`att`):** Aplicar auto-atendimento.
|
||||
- **Dropout (`drop_shortcut`):** Aplicar dropout para regularização.
|
||||
- **Adicionar Residual (`x + shortcut`):** Combinar com a entrada original.
|
||||
2. **Segundo Caminho Residual (FeedForward):**
|
||||
- **Entrada (`shortcut`):** Salvar a entrada atualizada para a próxima conexão residual.
|
||||
- **Norma de Camada (`norm2`):** Normalizar a entrada.
|
||||
- **Rede FeedForward (`ff`):** Aplicar a transformação feedforward.
|
||||
- **Dropout (`drop_shortcut`):** Aplicar dropout.
|
||||
- **Adicionar Residual (`x + shortcut`):** Combinar com a entrada do primeiro caminho residual.
|
||||
|
||||
> [!TIP]
|
||||
> O bloco transformer agrupa todas as redes e aplica alguma **normalização** e **dropouts** para melhorar a estabilidade e os resultados do treinamento.\
|
||||
> Note como os dropouts são feitos após o uso de cada rede, enquanto a normalização é aplicada antes.
|
||||
>
|
||||
> Além disso, também utiliza atalhos que consistem em **adicionar a saída de uma rede com sua entrada**. Isso ajuda a prevenir o problema do gradiente que desaparece, garantindo que as camadas iniciais contribuam "tanto" quanto as últimas.
|
||||
|
||||
### **GPTModel**
|
||||
|
||||
_Formas foram adicionadas como comentários para entender melhor as formas das matrizes:_
|
||||
```python
|
||||
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
|
||||
class GPTModel(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
|
||||
# shape: (vocab_size, emb_dim)
|
||||
|
||||
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
|
||||
# shape: (context_length, emb_dim)
|
||||
|
||||
self.drop_emb = nn.Dropout(cfg["drop_rate"])
|
||||
|
||||
self.trf_blocks = nn.Sequential(
|
||||
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
|
||||
)
|
||||
# Stack of TransformerBlocks
|
||||
|
||||
self.final_norm = LayerNorm(cfg["emb_dim"])
|
||||
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
|
||||
# shape: (emb_dim, vocab_size)
|
||||
|
||||
def forward(self, in_idx):
|
||||
# in_idx shape: (batch_size, seq_len)
|
||||
batch_size, seq_len = in_idx.shape
|
||||
|
||||
# Token embeddings
|
||||
tok_embeds = self.tok_emb(in_idx)
|
||||
# shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
# Positional embeddings
|
||||
pos_indices = torch.arange(seq_len, device=in_idx.device)
|
||||
# shape: (seq_len,)
|
||||
pos_embeds = self.pos_emb(pos_indices)
|
||||
# shape: (seq_len, emb_dim)
|
||||
|
||||
# Add token and positional embeddings
|
||||
x = tok_embeds + pos_embeds # Broadcasting over batch dimension
|
||||
# x shape: (batch_size, seq_len, emb_dim)
|
||||
|
||||
x = self.drop_emb(x) # Dropout applied
|
||||
# x shape remains: (batch_size, seq_len, emb_dim)
|
||||
|
||||
x = self.trf_blocks(x) # Pass through Transformer blocks
|
||||
# x shape remains: (batch_size, seq_len, emb_dim)
|
||||
|
||||
x = self.final_norm(x) # Final LayerNorm
|
||||
# x shape remains: (batch_size, seq_len, emb_dim)
|
||||
|
||||
logits = self.out_head(x) # Project to vocabulary size
|
||||
# logits shape: (batch_size, seq_len, vocab_size)
|
||||
|
||||
return logits # Output shape: (batch_size, seq_len, vocab_size)
|
||||
```
|
||||
#### **Propósito e Funcionalidade**
|
||||
|
||||
- **Camadas de Embedding:**
|
||||
- **Token Embeddings (`tok_emb`):** Converte índices de tokens em embeddings. Como lembrete, estes são os pesos dados a cada dimensão de cada token no vocabulário.
|
||||
- **Positional Embeddings (`pos_emb`):** Adiciona informações posicionais aos embeddings para capturar a ordem dos tokens. Como lembrete, estes são os pesos dados ao token de acordo com sua posição no texto.
|
||||
- **Dropout (`drop_emb`):** Aplicado aos embeddings para regularização.
|
||||
- **Blocos Transformer (`trf_blocks`):** Pilha de `n_layers` blocos transformer para processar embeddings.
|
||||
- **Normalização Final (`final_norm`):** Normalização de camada antes da camada de saída.
|
||||
- **Camada de Saída (`out_head`):** Projeta os estados ocultos finais para o tamanho do vocabulário para produzir logits para previsão.
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta classe é usar todas as outras redes mencionadas para **prever o próximo token em uma sequência**, o que é fundamental para tarefas como geração de texto.
|
||||
>
|
||||
> Note como ela **usará tantos blocos transformer quanto indicado** e que cada bloco transformer está usando uma rede de atenção multi-head, uma rede feed forward e várias normalizações. Portanto, se 12 blocos transformer forem usados, multiplique isso por 12.
|
||||
>
|
||||
> Além disso, uma camada de **normalização** é adicionada **antes** da **saída** e uma camada linear final é aplicada no final para obter os resultados com as dimensões adequadas. Note como cada vetor final tem o tamanho do vocabulário utilizado. Isso ocorre porque está tentando obter uma probabilidade por token possível dentro do vocabulário.
|
||||
|
||||
## Número de Parâmetros a Treinar
|
||||
|
||||
Tendo a estrutura do GPT definida, é possível descobrir o número de parâmetros a treinar:
|
||||
```python
|
||||
GPT_CONFIG_124M = {
|
||||
"vocab_size": 50257, # Vocabulary size
|
||||
"context_length": 1024, # Context length
|
||||
"emb_dim": 768, # Embedding dimension
|
||||
"n_heads": 12, # Number of attention heads
|
||||
"n_layers": 12, # Number of layers
|
||||
"drop_rate": 0.1, # Dropout rate
|
||||
"qkv_bias": False # Query-Key-Value bias
|
||||
}
|
||||
|
||||
model = GPTModel(GPT_CONFIG_124M)
|
||||
total_params = sum(p.numel() for p in model.parameters())
|
||||
print(f"Total number of parameters: {total_params:,}")
|
||||
# Total number of parameters: 163,009,536
|
||||
```
|
||||
### **Cálculo Passo a Passo**
|
||||
|
||||
#### **1. Camadas de Embedding: Token Embedding & Position Embedding**
|
||||
|
||||
- **Camada:** `nn.Embedding(vocab_size, emb_dim)`
|
||||
- **Parâmetros:** `vocab_size * emb_dim`
|
||||
```python
|
||||
token_embedding_params = 50257 * 768 = 38,597,376
|
||||
```
|
||||
- **Camada:** `nn.Embedding(context_length, emb_dim)`
|
||||
- **Parâmetros:** `context_length * emb_dim`
|
||||
```python
|
||||
position_embedding_params = 1024 * 768 = 786,432
|
||||
```
|
||||
**Total de Parâmetros de Embedding**
|
||||
```python
|
||||
embedding_params = token_embedding_params + position_embedding_params
|
||||
embedding_params = 38,597,376 + 786,432 = 39,383,808
|
||||
```
|
||||
#### **2. Blocos Transformer**
|
||||
|
||||
Existem 12 blocos transformer, então vamos calcular os parâmetros para um bloco e depois multiplicar por 12.
|
||||
|
||||
**Parâmetros por Bloco Transformer**
|
||||
|
||||
**a. Atenção Multi-Cabeça**
|
||||
|
||||
- **Componentes:**
|
||||
- **Camada Linear de Consulta (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
||||
- **Camada Linear de Chave (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
||||
- **Camada Linear de Valor (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
||||
- **Projeção de Saída (`out_proj`):** `nn.Linear(emb_dim, emb_dim)`
|
||||
- **Cálculos:**
|
||||
|
||||
- **Cada um de `W_query`, `W_key`, `W_value`:**
|
||||
|
||||
```python
|
||||
qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824
|
||||
```
|
||||
|
||||
Como existem três dessas camadas:
|
||||
|
||||
```python
|
||||
total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472
|
||||
```
|
||||
|
||||
- **Projeção de Saída (`out_proj`):**
|
||||
|
||||
```python
|
||||
out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592
|
||||
```
|
||||
|
||||
- **Total de Parâmetros de Atenção Multi-Cabeça:**
|
||||
|
||||
```python
|
||||
mha_params = total_qkv_params + out_proj_params
|
||||
mha_params = 1,769,472 + 590,592 = 2,360,064
|
||||
```
|
||||
|
||||
**b. Rede FeedForward**
|
||||
|
||||
- **Componentes:**
|
||||
- **Primeira Camada Linear:** `nn.Linear(emb_dim, 4 * emb_dim)`
|
||||
- **Segunda Camada Linear:** `nn.Linear(4 * emb_dim, emb_dim)`
|
||||
- **Cálculos:**
|
||||
|
||||
- **Primeira Camada Linear:**
|
||||
|
||||
```python
|
||||
ff_first_layer_params = (emb_dim * 4 * emb_dim) + (4 * emb_dim)
|
||||
ff_first_layer_params = (768 * 3072) + 3072 = 2,359,296 + 3,072 = 2,362,368
|
||||
```
|
||||
|
||||
- **Segunda Camada Linear:**
|
||||
|
||||
```python
|
||||
ff_second_layer_params = (4 * emb_dim * emb_dim) + emb_dim
|
||||
ff_second_layer_params = (3072 * 768) + 768 = 2,359,296 + 768 = 2,360,064
|
||||
```
|
||||
|
||||
- **Total de Parâmetros FeedForward:**
|
||||
|
||||
```python
|
||||
ff_params = ff_first_layer_params + ff_second_layer_params
|
||||
ff_params = 2,362,368 + 2,360,064 = 4,722,432
|
||||
```
|
||||
|
||||
**c. Normalizações de Camada**
|
||||
|
||||
- **Componentes:**
|
||||
- Duas instâncias de `LayerNorm` por bloco.
|
||||
- Cada `LayerNorm` tem `2 * emb_dim` parâmetros (escala e deslocamento).
|
||||
- **Cálculos:**
|
||||
|
||||
```python
|
||||
layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072
|
||||
```
|
||||
|
||||
**d. Total de Parâmetros por Bloco Transformer**
|
||||
```python
|
||||
pythonCopy codeparams_per_block = mha_params + ff_params + layer_norm_params_per_block
|
||||
params_per_block = 2,360,064 + 4,722,432 + 3,072 = 7,085,568
|
||||
```
|
||||
**Total de Parâmetros para Todos os Blocos Transformer**
|
||||
```python
|
||||
pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers
|
||||
total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
|
||||
```
|
||||
#### **3. Camadas Finais**
|
||||
|
||||
**a. Normalização da Camada Final**
|
||||
|
||||
- **Parâmetros:** `2 * emb_dim` (escala e deslocamento)
|
||||
```python
|
||||
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
|
||||
```
|
||||
**b. Camada de Projeção de Saída (`out_head`)**
|
||||
|
||||
- **Camada:** `nn.Linear(emb_dim, vocab_size, bias=False)`
|
||||
- **Parâmetros:** `emb_dim * vocab_size`
|
||||
```python
|
||||
pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376
|
||||
```
|
||||
#### **4. Resumindo Todos os Parâmetros**
|
||||
```python
|
||||
pythonCopy codetotal_params = (
|
||||
embedding_params +
|
||||
total_transformer_blocks_params +
|
||||
final_layer_norm_params +
|
||||
output_projection_params
|
||||
)
|
||||
total_params = (
|
||||
39,383,808 +
|
||||
85,026,816 +
|
||||
1,536 +
|
||||
38,597,376
|
||||
)
|
||||
total_params = 163,009,536
|
||||
```
|
||||
## Gerar Texto
|
||||
|
||||
Tendo um modelo que prevê o próximo token como o anterior, é necessário apenas pegar os últimos valores de token da saída (já que eles serão os do token previsto), que será um **valor por entrada no vocabulário** e então usar a função `softmax` para normalizar as dimensões em probabilidades que somam 1 e, em seguida, obter o índice da maior entrada, que será o índice da palavra dentro do vocabulário.
|
||||
|
||||
Código de [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
|
||||
```python
|
||||
def generate_text_simple(model, idx, max_new_tokens, context_size):
|
||||
# idx is (batch, n_tokens) array of indices in the current context
|
||||
for _ in range(max_new_tokens):
|
||||
|
||||
# Crop current context if it exceeds the supported context size
|
||||
# E.g., if LLM supports only 5 tokens, and the context size is 10
|
||||
# then only the last 5 tokens are used as context
|
||||
idx_cond = idx[:, -context_size:]
|
||||
|
||||
# Get the predictions
|
||||
with torch.no_grad():
|
||||
logits = model(idx_cond)
|
||||
|
||||
# Focus only on the last time step
|
||||
# (batch, n_tokens, vocab_size) becomes (batch, vocab_size)
|
||||
logits = logits[:, -1, :]
|
||||
|
||||
# Apply softmax to get probabilities
|
||||
probas = torch.softmax(logits, dim=-1) # (batch, vocab_size)
|
||||
|
||||
# Get the idx of the vocab entry with the highest probability value
|
||||
idx_next = torch.argmax(probas, dim=-1, keepdim=True) # (batch, 1)
|
||||
|
||||
# Append sampled index to the running sequence
|
||||
idx = torch.cat((idx, idx_next), dim=1) # (batch, n_tokens+1)
|
||||
|
||||
return idx
|
||||
|
||||
|
||||
start_context = "Hello, I am"
|
||||
|
||||
encoded = tokenizer.encode(start_context)
|
||||
print("encoded:", encoded)
|
||||
|
||||
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
|
||||
print("encoded_tensor.shape:", encoded_tensor.shape)
|
||||
|
||||
model.eval() # disable dropout
|
||||
|
||||
out = generate_text_simple(
|
||||
model=model,
|
||||
idx=encoded_tensor,
|
||||
max_new_tokens=6,
|
||||
context_size=GPT_CONFIG_124M["context_length"]
|
||||
)
|
||||
|
||||
print("Output:", out)
|
||||
print("Output length:", len(out[0]))
|
||||
```
|
||||
## Referências
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -0,0 +1,61 @@
|
||||
# 7.0. Melhorias do LoRA no ajuste fino
|
||||
|
||||
## Melhorias do LoRA
|
||||
|
||||
> [!TIP]
|
||||
> O uso de **LoRA reduz muito a computação** necessária para **ajustar** modelos já treinados.
|
||||
|
||||
LoRA torna possível ajustar **grandes modelos** de forma eficiente, alterando apenas uma **pequena parte** do modelo. Isso reduz o número de parâmetros que você precisa treinar, economizando **memória** e **recursos computacionais**. Isso ocorre porque:
|
||||
|
||||
1. **Reduz o Número de Parâmetros Treináveis**: Em vez de atualizar toda a matriz de pesos no modelo, o LoRA **divide** a matriz de pesos em duas matrizes menores (chamadas **A** e **B**). Isso torna o treinamento **mais rápido** e requer **menos memória** porque menos parâmetros precisam ser atualizados.
|
||||
|
||||
1. Isso ocorre porque, em vez de calcular a atualização completa de pesos de uma camada (matriz), ele a aproxima a um produto de 2 matrizes menores, reduzindo a atualização a calcular:\
|
||||
|
||||
<figure><img src="../../images/image (9) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
2. **Mantém os Pesos do Modelo Original Inalterados**: O LoRA permite que você mantenha os pesos do modelo original os mesmos e apenas atualize as **novas matrizes pequenas** (A e B). Isso é útil porque significa que o conhecimento original do modelo é preservado, e você apenas ajusta o que é necessário.
|
||||
3. **Ajuste Fino Eficiente Específico para Tarefas**: Quando você deseja adaptar o modelo a uma **nova tarefa**, pode apenas treinar as **pequenas matrizes LoRA** (A e B) enquanto deixa o resto do modelo como está. Isso é **muito mais eficiente** do que re-treinar o modelo inteiro.
|
||||
4. **Eficiência de Armazenamento**: Após o ajuste fino, em vez de salvar um **novo modelo completo** para cada tarefa, você só precisa armazenar as **matrizes LoRA**, que são muito pequenas em comparação com o modelo inteiro. Isso facilita a adaptação do modelo a muitas tarefas sem usar muito armazenamento.
|
||||
|
||||
Para implementar LoraLayers em vez de Linear durante um ajuste fino, este código é proposto aqui [https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb):
|
||||
```python
|
||||
import math
|
||||
|
||||
# Create the LoRA layer with the 2 matrices and the alpha
|
||||
class LoRALayer(torch.nn.Module):
|
||||
def __init__(self, in_dim, out_dim, rank, alpha):
|
||||
super().__init__()
|
||||
self.A = torch.nn.Parameter(torch.empty(in_dim, rank))
|
||||
torch.nn.init.kaiming_uniform_(self.A, a=math.sqrt(5)) # similar to standard weight initialization
|
||||
self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
|
||||
self.alpha = alpha
|
||||
|
||||
def forward(self, x):
|
||||
x = self.alpha * (x @ self.A @ self.B)
|
||||
return x
|
||||
|
||||
# Combine it with the linear layer
|
||||
class LinearWithLoRA(torch.nn.Module):
|
||||
def __init__(self, linear, rank, alpha):
|
||||
super().__init__()
|
||||
self.linear = linear
|
||||
self.lora = LoRALayer(
|
||||
linear.in_features, linear.out_features, rank, alpha
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.linear(x) + self.lora(x)
|
||||
|
||||
# Replace linear layers with LoRA ones
|
||||
def replace_linear_with_lora(model, rank, alpha):
|
||||
for name, module in model.named_children():
|
||||
if isinstance(module, torch.nn.Linear):
|
||||
# Replace the Linear layer with LinearWithLoRA
|
||||
setattr(model, name, LinearWithLoRA(module, rank, alpha))
|
||||
else:
|
||||
# Recursively apply the same function to child modules
|
||||
replace_linear_with_lora(module, rank, alpha)
|
||||
```
|
||||
## Referências
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -0,0 +1,100 @@
|
||||
# 7.2. Ajuste Fino para Seguir Instruções
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta seção é mostrar como **ajustar finamente um modelo já pré-treinado para seguir instruções** em vez de apenas gerar texto, por exemplo, respondendo a tarefas como um chatbot.
|
||||
|
||||
## Conjunto de Dados
|
||||
|
||||
Para ajustar finamente um LLM para seguir instruções, é necessário ter um conjunto de dados com instruções e respostas para ajustar o LLM. Existem diferentes formatos para treinar um LLM a seguir instruções, por exemplo:
|
||||
|
||||
- O exemplo de estilo de prompt Apply Alpaca:
|
||||
```csharp
|
||||
Below is an instruction that describes a task. Write a response that appropriately completes the request.
|
||||
|
||||
### Instruction:
|
||||
Calculate the area of a circle with a radius of 5 units.
|
||||
|
||||
### Response:
|
||||
The area of a circle is calculated using the formula \( A = \pi r^2 \). Plugging in the radius of 5 units:
|
||||
|
||||
\( A = \pi (5)^2 = \pi \times 25 = 25\pi \) square units.
|
||||
```
|
||||
- Exemplo de Estilo de Prompt Phi-3:
|
||||
```vbnet
|
||||
<|User|>
|
||||
Can you explain what gravity is in simple terms?
|
||||
|
||||
<|Assistant|>
|
||||
Absolutely! Gravity is a force that pulls objects toward each other.
|
||||
```
|
||||
Treinar um LLM com esse tipo de conjuntos de dados em vez de apenas texto bruto ajuda o LLM a entender que ele precisa fornecer respostas específicas às perguntas que recebe.
|
||||
|
||||
Portanto, uma das primeiras coisas a fazer com um conjunto de dados que contém solicitações e respostas é modelar esses dados no formato de prompt desejado, como:
|
||||
```python
|
||||
# Code from https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/ch07.ipynb
|
||||
def format_input(entry):
|
||||
instruction_text = (
|
||||
f"Below is an instruction that describes a task. "
|
||||
f"Write a response that appropriately completes the request."
|
||||
f"\n\n### Instruction:\n{entry['instruction']}"
|
||||
)
|
||||
|
||||
input_text = f"\n\n### Input:\n{entry['input']}" if entry["input"] else ""
|
||||
|
||||
return instruction_text + input_text
|
||||
|
||||
model_input = format_input(data[50])
|
||||
|
||||
desired_response = f"\n\n### Response:\n{data[50]['output']}"
|
||||
|
||||
print(model_input + desired_response)
|
||||
```
|
||||
Então, como sempre, é necessário separar o conjunto de dados em conjuntos para treinamento, validação e teste.
|
||||
|
||||
## Batching & Data Loaders
|
||||
|
||||
Então, é necessário agrupar todas as entradas e saídas esperadas para o treinamento. Para isso, é necessário:
|
||||
|
||||
- Tokenizar os textos
|
||||
- Preencher todas as amostras para o mesmo comprimento (geralmente o comprimento será tão grande quanto o comprimento do contexto usado para pré-treinar o LLM)
|
||||
- Criar os tokens esperados deslocando 1 a entrada em uma função de colagem personalizada
|
||||
- Substituir alguns tokens de preenchimento por -100 para excluí-los da perda de treinamento: Após o primeiro token `endoftext`, substituir todos os outros tokens `endoftext` por -100 (porque usar `cross_entropy(...,ignore_index=-100)` significa que ele ignorará alvos com -100)
|
||||
- \[Opcional] Mascarar usando -100 também todos os tokens pertencentes à pergunta para que o LLM aprenda apenas como gerar a resposta. No estilo Apply Alpaca, isso significará mascarar tudo até `### Response:`
|
||||
|
||||
Com isso criado, é hora de criar os carregadores de dados para cada conjunto de dados (treinamento, validação e teste).
|
||||
|
||||
## Load pre-trained LLM & Fine tune & Loss Checking
|
||||
|
||||
É necessário carregar um LLM pré-treinado para ajustá-lo. Isso já foi discutido em outras páginas. Então, é possível usar a função de treinamento previamente utilizada para ajustar o LLM.
|
||||
|
||||
Durante o treinamento, também é possível ver como a perda de treinamento e a perda de validação variam durante as épocas para ver se a perda está sendo reduzida e se o overfitting está ocorrendo.\
|
||||
Lembre-se de que o overfitting ocorre quando a perda de treinamento está sendo reduzida, mas a perda de validação não está sendo reduzida ou até mesmo aumentando. Para evitar isso, a coisa mais simples a fazer é parar o treinamento na época em que esse comportamento começa.
|
||||
|
||||
## Response Quality
|
||||
|
||||
Como este não é um ajuste fino de classificação onde é possível confiar mais nas variações de perda, também é importante verificar a qualidade das respostas no conjunto de teste. Portanto, é recomendado reunir as respostas geradas de todos os conjuntos de teste e **verificar sua qualidade manualmente** para ver se há respostas erradas (note que é possível que o LLM crie corretamente o formato e a sintaxe da frase de resposta, mas dê uma resposta completamente errada. A variação da perda não refletirá esse comportamento).\
|
||||
Note que também é possível realizar essa revisão passando as respostas geradas e as respostas esperadas para **outros LLMs e pedir que avaliem as respostas**.
|
||||
|
||||
Outros testes a serem realizados para verificar a qualidade das respostas:
|
||||
|
||||
1. **Measuring Massive Multitask Language Understanding (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLU avalia o conhecimento e as habilidades de resolução de problemas de um modelo em 57 disciplinas, incluindo humanidades, ciências e mais. Ele usa perguntas de múltipla escolha para avaliar a compreensão em vários níveis de dificuldade, desde o elementar até o profissional avançado.
|
||||
2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Esta plataforma permite que os usuários comparem respostas de diferentes chatbots lado a lado. Os usuários inserem um prompt, e vários chatbots geram respostas que podem ser comparadas diretamente.
|
||||
3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEval é uma estrutura de avaliação automatizada onde um LLM avançado como o GPT-4 avalia as respostas de outros modelos a vários prompts.
|
||||
4. **General Language Understanding Evaluation (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE é uma coleção de nove tarefas de compreensão de linguagem natural, incluindo análise de sentimentos, implicação textual e resposta a perguntas.
|
||||
5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** Construindo sobre o GLUE, o SuperGLUE inclui tarefas mais desafiadoras projetadas para serem difíceis para os modelos atuais.
|
||||
6. **Beyond the Imitation Game Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench é um benchmark em larga escala com mais de 200 tarefas que testam as habilidades de um modelo em áreas como raciocínio, tradução e resposta a perguntas.
|
||||
7. **Holistic Evaluation of Language Models (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM fornece uma avaliação abrangente em várias métricas, como precisão, robustez e justiça.
|
||||
8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Uma estrutura de avaliação de código aberto da OpenAI que permite o teste de modelos de IA em tarefas personalizadas e padronizadas.
|
||||
9. [**HumanEval**](https://github.com/openai/human-eval)**:** Uma coleção de problemas de programação usados para avaliar as habilidades de geração de código de modelos de linguagem.
|
||||
10. **Stanford Question Answering Dataset (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuAD consiste em perguntas sobre artigos da Wikipedia, onde os modelos devem compreender o texto para responder com precisão.
|
||||
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Um conjunto de dados em larga escala de perguntas e respostas de trivia, juntamente com documentos de evidência.
|
||||
|
||||
e muitos, muitos mais
|
||||
|
||||
## Follow instructions fine-tuning code
|
||||
|
||||
Você pode encontrar um exemplo do código para realizar esse ajuste fino em [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py)
|
||||
|
||||
## References
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -43,8 +43,8 @@ Você deve começar lendo este post para alguns conceitos básicos que você dev
|
||||
## 4. Mecanismos de Atenção
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta quarta fase é muito simples: **Aplicar alguns mecanismos de atenção**. Estas serão muitas **camadas repetidas** que vão **capturar a relação de uma palavra no vocabulário com seus vizinhos na sentença atual sendo usada para treinar o LLM**.\
|
||||
> Muitas camadas são usadas para isso, então muitos parâmetros treináveis estarão capturando essa informação.
|
||||
> O objetivo desta quarta fase é muito simples: **Aplicar alguns mecanismos de atenção**. Estes serão muitas **camadas repetidas** que vão **capturar a relação de uma palavra no vocabulário com seus vizinhos na sentença atual sendo usada para treinar o LLM**.\
|
||||
> Muitas camadas são usadas para isso, então muitos parâmetros treináveis vão capturar essa informação.
|
||||
|
||||
{{#ref}}
|
||||
4.-attention-mechanisms.md
|
||||
@ -61,7 +61,7 @@ Você deve começar lendo este post para alguns conceitos básicos que você dev
|
||||
5.-llm-architecture.md
|
||||
{{#endref}}
|
||||
|
||||
## 6. Pré-treinamento e Carregamento de Modelos
|
||||
## 6. Pré-treinamento & Carregamento de modelos
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta sexta fase é muito simples: **Treinar o modelo do zero**. Para isso, a arquitetura LLM anterior será usada com alguns loops sobre os conjuntos de dados usando as funções de perda e otimizador definidos para treinar todos os parâmetros do modelo.
|
||||
@ -70,7 +70,7 @@ Você deve começar lendo este post para alguns conceitos básicos que você dev
|
||||
6.-pre-training-and-loading-models.md
|
||||
{{#endref}}
|
||||
|
||||
## 7.0. Melhorias de LoRA em Fine-Tuning
|
||||
## 7.0. Melhorias de LoRA em ajuste fino
|
||||
|
||||
> [!TIP]
|
||||
> O uso de **LoRA reduz muito a computação** necessária para **ajustar** modelos já treinados.
|
||||
@ -79,19 +79,19 @@ Você deve começar lendo este post para alguns conceitos básicos que você dev
|
||||
7.0.-lora-improvements-in-fine-tuning.md
|
||||
{{#endref}}
|
||||
|
||||
## 7.1. Fine-Tuning para Classificação
|
||||
## 7.1. Ajuste Fino para Classificação
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta seção é mostrar como ajustar um modelo já pré-treinado para que, em vez de gerar novo texto, o LLM selecione e forneça as **probabilidades do texto dado ser categorizado em cada uma das categorias dadas** (como se um texto é spam ou não).
|
||||
> O objetivo desta seção é mostrar como ajustar finamente um modelo já pré-treinado para que, em vez de gerar novo texto, o LLM selecione e forneça as **probabilidades do texto dado ser categorizado em cada uma das categorias dadas** (como se um texto é spam ou não).
|
||||
|
||||
{{#ref}}
|
||||
7.1.-fine-tuning-for-classification.md
|
||||
{{#endref}}
|
||||
|
||||
## 7.2. Fine-Tuning para Seguir Instruções
|
||||
## 7.2. Ajuste Fino para seguir instruções
|
||||
|
||||
> [!TIP]
|
||||
> O objetivo desta seção é mostrar como **ajustar um modelo já pré-treinado para seguir instruções** em vez de apenas gerar texto, por exemplo, respondendo a tarefas como um chatbot.
|
||||
> O objetivo desta seção é mostrar como **ajustar finamente um modelo já pré-treinado para seguir instruções** em vez de apenas gerar texto, por exemplo, respondendo a tarefas como um chatbot.
|
||||
|
||||
{{#ref}}
|
||||
7.2.-fine-tuning-to-follow-instructions.md
|
||||
|
Loading…
x
Reference in New Issue
Block a user