2. Échantillonnage des Données
Reading time: 7 minutes
Échantillonnage des Données
L'échantillonnage des données est un processus crucial dans la préparation des données pour l'entraînement de modèles de langage de grande taille (LLMs) comme GPT. Il implique l'organisation des données textuelles en séquences d'entrée et de cible que le modèle utilise pour apprendre à prédire le mot suivant (ou le jeton) en fonction des mots précédents. Un échantillonnage des données approprié garantit que le modèle capture efficacement les motifs et les dépendances linguistiques.
tip
L'objectif de cette deuxième phase est très simple : Échantillonner les données d'entrée et les préparer pour la phase d'entraînement, généralement en séparant l'ensemble de données en phrases d'une longueur spécifique et en générant également la réponse attendue.
Pourquoi l'Échantillonnage des Données est Important
Les LLMs tels que GPT sont entraînés à générer ou prédire du texte en comprenant le contexte fourni par les mots précédents. Pour y parvenir, les données d'entraînement doivent être structurées de manière à ce que le modèle puisse apprendre la relation entre les séquences de mots et leurs mots suivants. Cette approche structurée permet au modèle de généraliser et de générer un texte cohérent et contextuellement pertinent.
Concepts Clés dans l'Échantillonnage des Données
- Tokenisation : Décomposer le texte en unités plus petites appelées jetons (par exemple, mots, sous-mots ou caractères).
- Longueur de Séquence (max_length) : Le nombre de jetons dans chaque séquence d'entrée.
- Fenêtre Glissante : Une méthode pour créer des séquences d'entrée qui se chevauchent en déplaçant une fenêtre sur le texte tokenisé.
- Pas : Le nombre de jetons que la fenêtre glissante avance pour créer la prochaine séquence.
Exemple Étape par Étape
Passons en revue un exemple pour illustrer l'échantillonnage des données.
Texte d'Exemple
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
Tokenization
Supposons que nous utilisions un tokenizer de base qui divise le texte en mots et en signes de ponctuation :
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]
Paramètres
- Longueur maximale de séquence (max_length) : 4 tokens
- Pas de fenêtre glissante : 1 token
Création de séquences d'entrée et de cible
- Approche de fenêtre glissante :
- Séquences d'entrée : Chaque séquence d'entrée se compose de
max_length
tokens. - Séquences cibles : Chaque séquence cible se compose des tokens qui suivent immédiatement la séquence d'entrée correspondante.
- Génération de séquences :
Position de la fenêtre | Séquence d'entrée | Séquence cible |
---|---|---|
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."] |
- Tableaux d'entrée et de cible résultants :
- Entrée :
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
- Cible :
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]
Représentation visuelle
Position du token | Token |
---|---|
1 | Lorem |
2 | ipsum |
3 | dolor |
4 | sit |
5 | amet, |
6 | consectetur |
7 | adipiscing |
8 | elit. |
Fenêtre glissante avec un pas de 1 :
- Première fenêtre (positions 1-4) : ["Lorem", "ipsum", "dolor", "sit"] → Cible : ["ipsum", "dolor", "sit", "amet,"]
- Deuxième fenêtre (positions 2-5) : ["ipsum", "dolor", "sit", "amet,"] → Cible : ["dolor", "sit", "amet,", "consectetur"]
- Troisième fenêtre (positions 3-6) : ["dolor", "sit", "amet,", "consectetur"] → Cible : ["sit", "amet,", "consectetur", "adipiscing"]
- Quatrième fenêtre (positions 4-7) : ["sit", "amet,", "consectetur", "adipiscing"] → Cible : ["amet,", "consectetur", "adipiscing", "elit."]
Comprendre le pas
- Pas de 1 : La fenêtre avance d'un token à chaque fois, ce qui entraîne des séquences très chevauchantes. Cela peut conduire à un meilleur apprentissage des relations contextuelles mais peut augmenter le risque de surajustement puisque des points de données similaires sont répétés.
- Pas de 2 : La fenêtre avance de deux tokens à chaque fois, réduisant le chevauchement. Cela diminue la redondance et la charge computationnelle mais pourrait manquer certaines nuances contextuelles.
- Pas égal à max_length : La fenêtre avance de la taille entière de la fenêtre, entraînant des séquences non chevauchantes. Cela minimise la redondance des données mais peut limiter la capacité du modèle à apprendre des dépendances entre les séquences.
Exemple avec un pas de 2 :
En utilisant le même texte tokenisé et un max_length
de 4 :
- Première fenêtre (positions 1-4) : ["Lorem", "ipsum", "dolor", "sit"] → Cible : ["ipsum", "dolor", "sit", "amet,"]
- Deuxième fenêtre (positions 3-6) : ["dolor", "sit", "amet,", "consectetur"] → Cible : ["sit", "amet,", "consectetur", "adipiscing"]
- Troisième fenêtre (positions 5-8) : ["amet,", "consectetur", "adipiscing", "elit."] → Cible : ["consectetur", "adipiscing", "elit.", "sed"] (En supposant une continuation)
Exemple de code
Comprenons cela mieux à partir d'un exemple de code 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]])
]