2. Amostragem de Dados

Reading time: 9 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Amostragem de Dados

Amostragem de Dados é um processo crucial na preparação de dados para treinar grandes modelos de linguagem (LLMs) como o GPT. Envolve organizar dados textuais em sequências de entrada e alvo que o modelo usa para aprender a prever a próxima palavra (ou token) com base nas palavras anteriores. A amostragem de dados adequada garante que o modelo capture efetivamente padrões e dependências da linguagem.

tip

O objetivo desta segunda fase é muito simples: Amostrar os dados de entrada e prepará-los para a fase de treinamento, geralmente separando o conjunto de dados em frases de um comprimento específico e gerando também a resposta esperada.

Por que a Amostragem de Dados é Importante

LLMs como o GPT são treinados para gerar ou prever texto entendendo o contexto fornecido pelas palavras anteriores. Para alcançar isso, os dados de treinamento devem ser estruturados de uma maneira que o modelo possa aprender a relação entre sequências de palavras e suas palavras subsequentes. Essa abordagem estruturada permite que o modelo generalize e gere texto coerente e contextualmente relevante.

Conceitos Chave na Amostragem de Dados

  1. Tokenização: Dividir o texto em unidades menores chamadas tokens (por exemplo, palavras, subpalavras ou caracteres).
  2. Comprimento da Sequência (max_length): O número de tokens em cada sequência de entrada.
  3. Janela Deslizante: Um método para criar sequências de entrada sobrepostas movendo uma janela sobre o texto tokenizado.
  4. Stride: O número de tokens que a janela deslizante avança para criar a próxima sequência.

Exemplo Passo a Passo

Vamos percorrer um exemplo para ilustrar a amostragem de dados.

Texto de Exemplo

arduino
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."

Tokenização

Assuma que usamos um tokenizador básico que divide o texto em palavras e sinais de pontuação:

vbnet
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]

Parâmetros

  • Comprimento Máximo da Sequência (max_length): 4 tokens
  • Passo da Janela Deslizante: 1 token

Criando Sequências de Entrada e Alvo

  1. Abordagem da Janela Deslizante:
  • Sequências de Entrada: Cada sequência de entrada consiste em max_length tokens.
  • Sequências de Alvo: Cada sequência de alvo consiste nos tokens que imediatamente seguem a sequência de entrada correspondente.
  1. Gerando Sequências:
Posição da JanelaSequência de EntradaSequência de Alvo
1["Lorem", "ipsum", "dolor", "sit"]["ipsum", "dolor", "sit", "amet,"]
2["ipsum", "dolor", "sit", "amet,"]["dolor", "sit", "amet,", "consectetur"]
3["dolor", "sit", "amet,", "consectetur"]["sit", "amet,", "consectetur", "adipiscing"]
4["sit", "amet,", "consectetur", "adipiscing"]["amet,", "consectetur", "adipiscing", "elit."]
  1. Arrays de Entrada e Alvo Resultantes:
  • Entrada:
python
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
  • Alvo:
python
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]

Representação Visual

Posição do TokenToken
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.

Janela Deslizante com Passo 1:

  • Primeira Janela (Posições 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Alvo: ["ipsum", "dolor", "sit", "amet,"]
  • Segunda Janela (Posições 2-5): ["ipsum", "dolor", "sit", "amet,"] → Alvo: ["dolor", "sit", "amet,", "consectetur"]
  • Terceira Janela (Posições 3-6): ["dolor", "sit", "amet,", "consectetur"] → Alvo: ["sit", "amet,", "consectetur", "adipiscing"]
  • Quarta Janela (Posições 4-7): ["sit", "amet,", "consectetur", "adipiscing"] → Alvo: ["amet,", "consectetur", "adipiscing", "elit."]

Entendendo o Passo

  • Passo de 1: A janela avança um token a cada vez, resultando em sequências altamente sobrepostas. Isso pode levar a um melhor aprendizado das relações contextuais, mas pode aumentar o risco de overfitting, uma vez que pontos de dados semelhantes são repetidos.
  • Passo de 2: A janela avança dois tokens a cada vez, reduzindo a sobreposição. Isso diminui a redundância e a carga computacional, mas pode perder algumas nuances contextuais.
  • Passo Igual a max_length: A janela avança pelo tamanho total da janela, resultando em sequências não sobrepostas. Isso minimiza a redundância de dados, mas pode limitar a capacidade do modelo de aprender dependências entre sequências.

Exemplo com Passo de 2:

Usando o mesmo texto tokenizado e max_length de 4:

  • Primeira Janela (Posições 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Alvo: ["ipsum", "dolor", "sit", "amet,"]
  • Segunda Janela (Posições 3-6): ["dolor", "sit", "amet,", "consectetur"] → Alvo: ["sit", "amet,", "consectetur", "adipiscing"]
  • Terceira Janela (Posições 5-8): ["amet,", "consectetur", "adipiscing", "elit."] → Alvo: ["consectetur", "adipiscing", "elit.", "sed"] (Assumindo continuaçã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:

python
# Download the text to pre-train the LLM
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()

"""
Create a class that will receive some params lie tokenizer and text
and will prepare the input chunks and the target chunks to prepare
the LLM to learn which next token to generate
"""
import torch
from torch.utils.data import Dataset, DataLoader

class GPTDatasetV1(Dataset):
def __init__(self, txt, tokenizer, max_length, stride):
self.input_ids = []
self.target_ids = []

# Tokenize the entire text
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})

# Use a sliding window to chunk the book into overlapping sequences of max_length
for i in range(0, len(token_ids) - max_length, stride):
input_chunk = token_ids[i:i + max_length]
target_chunk = token_ids[i + 1: i + max_length + 1]
self.input_ids.append(torch.tensor(input_chunk))
self.target_ids.append(torch.tensor(target_chunk))

def __len__(self):
return len(self.input_ids)

def __getitem__(self, idx):
return self.input_ids[idx], self.target_ids[idx]


"""
Create a data loader which given the text and some params will
prepare the inputs and targets with the previous class and
then create a torch DataLoader with the info
"""

import tiktoken

def create_dataloader_v1(txt, batch_size=4, max_length=256,
stride=128, shuffle=True, drop_last=True,
num_workers=0):

# Initialize the tokenizer
tokenizer = tiktoken.get_encoding("gpt2")

# Create dataset
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)

# Create dataloader
dataloader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=shuffle,
drop_last=drop_last,
num_workers=num_workers
)

return dataloader


"""
Finally, create the data loader with the params we want:
- The used text for training
- batch_size: The size of each batch
- max_length: The size of each entry on each batch
- stride: The sliding window (how many tokens should the next entry advance compared to the previous one). The smaller the more overfitting, usually this is equals to the max_length so the same tokens aren't repeated.
- shuffle: Re-order randomly
"""
dataloader = create_dataloader_v1(
raw_text, batch_size=8, max_length=4, stride=1, shuffle=False
)

data_iter = iter(dataloader)
first_batch = next(data_iter)
print(first_batch)

# Note the batch_size of 8, the max_length of 4 and the stride of 1
[
# Input
tensor([[   40,   367,  2885,  1464],
[  367,  2885,  1464,  1807],
[ 2885,  1464,  1807,  3619],
[ 1464,  1807,  3619,   402],
[ 1807,  3619,   402,   271],
[ 3619,   402,   271, 10899],
[  402,   271, 10899,  2138],
[  271, 10899,  2138,   257]]),
# Target
tensor([[  367,  2885,  1464,  1807],
[ 2885,  1464,  1807,  3619],
[ 1464,  1807,  3619,   402],
[ 1807,  3619,   402,   271],
[ 3619,   402,   271, 10899],
[  402,   271, 10899,  2138],
[  271, 10899,  2138,   257],
[10899,  2138,   257,  7026]])
]

# With stride=4 this will be the result:
[
# Input
tensor([[   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]]),
# Target
tensor([[  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]])
]

Estratégias Avançadas de Amostragem (2023-2025)

1. Pesagem de Mistura Baseada em Temperatura

LLMs de última geração raramente são treinados em um único corpus. Em vez disso, eles amostram de várias fontes de dados heterogêneas (código, web, artigos acadêmicos, fóruns…). A proporção relativa de cada fonte pode afetar fortemente o desempenho subsequente. Modelos de código aberto recentes, como o Llama 2, introduziram um esquema de amostragem baseado em temperatura onde a probabilidade de selecionar um documento do corpus i se torna

p(i) = \frac{w_i^{\alpha}}{\sum_j w_j^{\alpha}}

wi – porcentagem de token bruto do corpus i
α ("temperatura") – um valor em (0,1]. α < 1 achata a distribuição, dando mais peso a corpora menores e de alta qualidade.

Llama 2 usou α = 0.7 e mostrou que diminuir α aumentou as pontuações de avaliação em tarefas com alto conteúdo de conhecimento, mantendo a mistura de treinamento estável. O mesmo truque é adotado pelo Mistral (2023) e Claude 3.

python
from collections import Counter

def temperature_sample(corpus_ids, alpha=0.7):
counts = Counter(corpus_ids)           # number of tokens seen per corpus
probs  = {c: c_count**alpha for c, c_count in counts.items()}
Z = sum(probs.values())
probs = {c: p/Z for c, p in probs.items()}
# Now draw according to probs to fill every batch