2. Muestreo de Datos
Reading time: 7 minutes
Muestreo de Datos
El Muestreo de Datos es un proceso crucial en la preparación de datos para entrenar modelos de lenguaje grande (LLMs) como GPT. Implica organizar los 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
- Tokenización: Descomponer el texto en unidades más pequeñas llamadas tokens (por ejemplo, palabras, subpalabras o caracteres).
- Longitud de Secuencia (max_length): El número de tokens en cada secuencia de entrada.
- Ventana Deslizante: Un método para crear secuencias de entrada superpuestas moviendo una ventana sobre el texto tokenizado.
- 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
"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:
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
- 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.
- Generando Secuencias:
Posición de Ventana | Secuencia de Entrada | Secuencia 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."] |
- Arreglos de Entrada y Objetivo Resultantes:
- Entrada:
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
- Objetivo:
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]
Representación Visual
Posición de Token | Token |
---|---|
1 | Lorem |
2 | ipsum |
3 | dolor |
4 | sit |
5 | amet, |
6 | consectetur |
7 | adipiscing |
8 | elit. |
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 matices 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:
# 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]])
]