mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
300 lines
14 KiB
Markdown
300 lines
14 KiB
Markdown
# 2. Muestreo de Datos
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## **Muestreo de Datos**
|
||
|
||
**Muestreo de Datos** es un proceso crucial en la preparación de datos para entrenar modelos de lenguaje grande (LLMs) como GPT. Implica organizar datos de texto en secuencias de entrada y objetivo que el modelo utiliza para aprender a predecir la siguiente palabra (o token) en función de las palabras anteriores. Un muestreo de datos adecuado asegura que el modelo capture efectivamente los patrones y dependencias del lenguaje.
|
||
|
||
> [!TIP]
|
||
> El objetivo de esta segunda fase es muy simple: **Muestrear los datos de entrada y prepararlos para la fase de entrenamiento, generalmente separando el conjunto de datos en oraciones de una longitud específica y generando también la respuesta esperada.**
|
||
|
||
### **Por qué es Importante el Muestreo de Datos**
|
||
|
||
Los LLMs como GPT son entrenados para generar o predecir texto al entender el contexto proporcionado por las palabras anteriores. Para lograr esto, los datos de entrenamiento deben estar estructurados de tal manera que el modelo pueda aprender la relación entre secuencias de palabras y sus palabras subsecuentes. Este enfoque estructurado permite que el modelo generalice y genere texto coherente y contextualmente relevante.
|
||
|
||
### **Conceptos Clave en el Muestreo de Datos**
|
||
|
||
1. **Tokenización:** Descomponer el texto en unidades más pequeñas llamadas tokens (por ejemplo, palabras, subpalabras o caracteres).
|
||
2. **Longitud de Secuencia (max_length):** El número de tokens en cada secuencia de entrada.
|
||
3. **Ventana Deslizante:** Un método para crear secuencias de entrada superpuestas moviendo una ventana sobre el texto tokenizado.
|
||
4. **Stride:** El número de tokens que la ventana deslizante avanza para crear la siguiente secuencia.
|
||
|
||
### **Ejemplo Paso a Paso**
|
||
|
||
Vamos a recorrer un ejemplo para ilustrar el muestreo de datos.
|
||
|
||
**Texto de Ejemplo**
|
||
```arduino
|
||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||
```
|
||
**Tokenización**
|
||
|
||
Supongamos que usamos un **tokenizador básico** que divide el texto en palabras y signos de puntuación:
|
||
```vbnet
|
||
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]
|
||
```
|
||
**Parámetros**
|
||
|
||
- **Longitud Máxima de Secuencia (max_length):** 4 tokens
|
||
- **Desplazamiento de Ventana Deslizante:** 1 token
|
||
|
||
**Creando Secuencias de Entrada y Objetivo**
|
||
|
||
1. **Enfoque de Ventana Deslizante:**
|
||
- **Secuencias de Entrada:** Cada secuencia de entrada consiste en `max_length` tokens.
|
||
- **Secuencias de Objetivo:** Cada secuencia de objetivo consiste en los tokens que siguen inmediatamente a la secuencia de entrada correspondiente.
|
||
2. **Generando Secuencias:**
|
||
|
||
<table><thead><tr><th width="177">Posición de Ventana</th><th>Secuencia de Entrada</th><th>Secuencia de Objetivo</th></tr></thead><tbody><tr><td>1</td><td>["Lorem", "ipsum", "dolor", "sit"]</td><td>["ipsum", "dolor", "sit", "amet,"]</td></tr><tr><td>2</td><td>["ipsum", "dolor", "sit", "amet,"]</td><td>["dolor", "sit", "amet,", "consectetur"]</td></tr><tr><td>3</td><td>["dolor", "sit", "amet,", "consectetur"]</td><td>["sit", "amet,", "consectetur", "adipiscing"]</td></tr><tr><td>4</td><td>["sit", "amet,", "consectetur", "adipiscing"]</td><td>["amet,", "consectetur", "adipiscing", "elit."]</td></tr></tbody></table>
|
||
|
||
3. **Arreglos de Entrada y Objetivo Resultantes:**
|
||
|
||
- **Entrada:**
|
||
|
||
```python
|
||
[
|
||
["Lorem", "ipsum", "dolor", "sit"],
|
||
["ipsum", "dolor", "sit", "amet,"],
|
||
["dolor", "sit", "amet,", "consectetur"],
|
||
["sit", "amet,", "consectetur", "adipiscing"],
|
||
]
|
||
```
|
||
|
||
- **Objetivo:**
|
||
|
||
```python
|
||
[
|
||
["ipsum", "dolor", "sit", "amet,"],
|
||
["dolor", "sit", "amet,", "consectetur"],
|
||
["sit", "amet,", "consectetur", "adipiscing"],
|
||
["amet,", "consectetur", "adipiscing", "elit."],
|
||
]
|
||
```
|
||
|
||
**Representación Visual**
|
||
|
||
<table><thead><tr><th width="222">Posición de Token</th><th>Token</th></tr></thead><tbody><tr><td>1</td><td>Lorem</td></tr><tr><td>2</td><td>ipsum</td></tr><tr><td>3</td><td>dolor</td></tr><tr><td>4</td><td>sit</td></tr><tr><td>5</td><td>amet,</td></tr><tr><td>6</td><td>consectetur</td></tr><tr><td>7</td><td>adipiscing</td></tr><tr><td>8</td><td>elit.</td></tr></tbody></table>
|
||
|
||
**Ventana Deslizante con Desplazamiento 1:**
|
||
|
||
- **Primera Ventana (Posiciones 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Objetivo:** \["ipsum", "dolor", "sit", "amet,"]
|
||
- **Segunda Ventana (Posiciones 2-5):** \["ipsum", "dolor", "sit", "amet,"] → **Objetivo:** \["dolor", "sit", "amet,", "consectetur"]
|
||
- **Tercera Ventana (Posiciones 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Objetivo:** \["sit", "amet,", "consectetur", "adipiscing"]
|
||
- **Cuarta Ventana (Posiciones 4-7):** \["sit", "amet,", "consectetur", "adipiscing"] → **Objetivo:** \["amet,", "consectetur", "adipiscing", "elit."]
|
||
|
||
**Entendiendo el Desplazamiento**
|
||
|
||
- **Desplazamiento de 1:** La ventana se mueve hacia adelante un token cada vez, resultando en secuencias altamente superpuestas. Esto puede llevar a un mejor aprendizaje de relaciones contextuales, pero puede aumentar el riesgo de sobreajuste ya que se repiten puntos de datos similares.
|
||
- **Desplazamiento de 2:** La ventana se mueve hacia adelante dos tokens cada vez, reduciendo la superposición. Esto disminuye la redundancia y la carga computacional, pero podría perder algunas sutilezas contextuales.
|
||
- **Desplazamiento Igual a max_length:** La ventana se mueve hacia adelante por todo el tamaño de la ventana, resultando en secuencias no superpuestas. Esto minimiza la redundancia de datos, pero puede limitar la capacidad del modelo para aprender dependencias entre secuencias.
|
||
|
||
**Ejemplo con Desplazamiento de 2:**
|
||
|
||
Usando el mismo texto tokenizado y `max_length` de 4:
|
||
|
||
- **Primera Ventana (Posiciones 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Objetivo:** \["ipsum", "dolor", "sit", "amet,"]
|
||
- **Segunda Ventana (Posiciones 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Objetivo:** \["sit", "amet,", "consectetur", "adipiscing"]
|
||
- **Tercera Ventana (Posiciones 5-8):** \["amet,", "consectetur", "adipiscing", "elit."] → **Objetivo:** \["consectetur", "adipiscing", "elit.", "sed"] _(Asumiendo continuación)_
|
||
|
||
## Ejemplo de Código
|
||
|
||
Entendamos esto mejor a partir de un ejemplo 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 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]])
|
||
]
|
||
```
|
||
## Estrategias de Muestreo Avanzadas (2023-2025)
|
||
|
||
### 1. Ponderación de Mezcla Basada en Temperatura
|
||
Los LLMs de última generación rara vez se entrenan en un solo corpus. En cambio, muestrean de varias fuentes de datos heterogéneas (código, web, artículos académicos, foros…). La proporción relativa de cada fuente puede afectar fuertemente el rendimiento posterior. Modelos recientes de código abierto como Llama 2 introdujeron un **esquema de muestreo basado en temperatura** donde la probabilidad de extraer un documento del corpus *i* se convierte en
|
||
```
|
||
p(i) = \frac{w_i^{\alpha}}{\sum_j w_j^{\alpha}}
|
||
```
|
||
• *w<sub>i</sub>* – porcentaje de token bruto del corpus *i*
|
||
• *α* ("temperatura") – un valor en (0,1]. α < 1 aplana la distribución, dando más peso a corpus pequeños de alta calidad.
|
||
|
||
Llama 2 utilizó α = 0.7 y mostró que disminuir α aumentó las puntuaciones de evaluación en tareas con alto contenido de conocimiento mientras mantenía estable la mezcla de entrenamiento. El mismo truco es adoptado por Mistral (2023) y 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
|
||
```
|
||
|
||
```
|
||
|
||
### 2. Sequence Packing / Dynamic Batching
|
||
GPU memory is wasted when every sequence in a batch is padded to the longest example. "Packing" concatenates multiple shorter sequences until the **exact** `max_length` is reached and builds a parallel `attention_mask` so that tokens do not attend across segment boundaries. Packing can improve throughput by 20–40 % with no gradient change and is supported out-of-the-box in
|
||
|
||
* PyTorch `torchtext.experimental.agents.PackedBatch`
|
||
* HuggingFace `DataCollatorForLanguageModeling(pad_to_multiple_of=…)`
|
||
|
||
Dynamic batching frameworks (e.g. FlashAttention 2, vLLM 2024) combine sequence packing with just-in-time kernel selection, enabling thousand-token context training at 400+ K tokens/s on A100-80G.
|
||
|
||
### 3. Deduplication & Quality Filtering
|
||
Repeated passages cause memorization and provide an easy channel for data-poisoning. Modern pipelines therefore:
|
||
|
||
1. MinHash/FAISS near-duplicate detection at **document** and **128-gram** level.
|
||
2. Filter documents whose perplexity under a small reference model is > µ + 3σ (noisy OCR, garbled HTML).
|
||
3. Block-list documents that contain PII or CWE keywords using regex & spaCy NER.
|
||
|
||
The Llama 2 team deduplicated with 8-gram MinHash and removed ~15 % of CommonCrawl before sampling. OpenAI’s 2024 "Deduplicate Everything" paper demonstrates ≤0.04 duplicate ratio reduces over-fitting and speeds convergence.
|
||
|
||
## Security & Privacy Considerations During Sampling
|
||
|
||
### Data-Poisoning / Backdoor Attacks
|
||
Researchers showed that inserting <1 % backdoored sentences can make a model obey a hidden trigger ("PoisonGPT", 2023). Recommended mitigations:
|
||
|
||
* **Shuffled mixing** – make sure adjacent training examples originate from different sources; this dilutes gradient alignment of malicious spans.
|
||
* **Gradient similarity scoring** – compute cosine similarity of example gradient to batch average; outliers are candidates for removal.
|
||
* **Dataset versioning & hashes** – freeze immutable tarballs and verify SHA-256 before each training run.
|
||
|
||
### Membership-Inference & Memorization
|
||
Long overlap between sliding-window samples increases the chance that rare strings (telephone numbers, secret keys) are memorized. OpenAI’s 2024 study on ChatGPT memorization reports that raising stride from 1 × `max_length` to 4 × reduces verbatim leakage by ≈50 % with negligible loss in perplexity.
|
||
|
||
Practical recommendations:
|
||
|
||
* Use **stride ≥ max_length** except for <1B parameter models where data volume is scarce.
|
||
* Add random masking of 1-3 tokens per window during training; this lowers memorization while preserving utility.
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- [Build a Large Language Model from Scratch (Manning, 2024)](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
||
- [Llama 2: Open Foundation and Fine-Tuned Chat Models (2023)](https://arxiv.org/abs/2307.09288)
|
||
- [PoisonGPT: Assessing Backdoor Vulnerabilities in Large Language Models (BlackHat EU 2023)](https://arxiv.org/abs/2308.12364)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|