2. Muestreo de Datos

Reading time: 9 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

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.
  1. Generando Secuencias:
Posición de VentanaSecuencia de EntradaSecuencia de Objetivo
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. 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

Posición de TokenToken
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.

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:

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}}

wi – 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