2. Campionamento Dati

Reading time: 7 minutes

Campionamento Dati

Il Campionamento Dati è un processo cruciale nella preparazione dei dati per l'addestramento di modelli di linguaggio di grandi dimensioni (LLM) come GPT. Comporta l'organizzazione dei dati testuali in sequenze di input e target che il modello utilizza per imparare a prevedere la parola successiva (o token) basandosi sulle parole precedenti. Un corretto campionamento dei dati assicura che il modello catturi efficacemente i modelli linguistici e le dipendenze.

tip

L'obiettivo di questa seconda fase è molto semplice: Campionare i dati di input e prepararli per la fase di addestramento solitamente separando il dataset in frasi di una lunghezza specifica e generando anche la risposta attesa.

Perché il Campionamento Dati è Importante

I LLM come GPT sono addestrati a generare o prevedere testo comprendendo il contesto fornito dalle parole precedenti. Per raggiungere questo obiettivo, i dati di addestramento devono essere strutturati in modo che il modello possa apprendere la relazione tra sequenze di parole e le loro parole successive. Questo approccio strutturato consente al modello di generalizzare e generare testo coerente e contestualmente rilevante.

Concetti Chiave nel Campionamento Dati

  1. Tokenizzazione: Suddividere il testo in unità più piccole chiamate token (ad es., parole, sottoparole o caratteri).
  2. Lunghezza della Sequenza (max_length): Il numero di token in ciascuna sequenza di input.
  3. Finestra Scorrevole: Un metodo per creare sequenze di input sovrapposte spostando una finestra sul testo tokenizzato.
  4. Passo: Il numero di token che la finestra scorrevole si sposta in avanti per creare la sequenza successiva.

Esempio Passo-Passo

Esploriamo un esempio per illustrare il campionamento dei dati.

Testo di Esempio

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

Tokenizzazione

Assumiamo di utilizzare un tokenizer di base che suddivide il testo in parole e segni di punteggiatura:

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

Parametri

  • Lunghezza Massima della Sequenza (max_length): 4 token
  • Passo della Finestra Scorrevole: 1 token

Creazione di Sequenze di Input e Target

  1. Approccio della Finestra Scorrevole:
  • Sequenze di Input: Ogni sequenza di input è composta da max_length token.
  • Sequenze di Target: Ogni sequenza di target è composta dai token che seguono immediatamente la corrispondente sequenza di input.
  1. Generazione delle Sequenze:
Posizione della FinestraSequenza di InputSequenza di Target
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. Array di Input e Target Risultanti:
  • Input:
python
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
  • Target:
python
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]

Rappresentazione Visiva

Posizione del TokenToken
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.

Finestra Scorrevole con Passo 1:

  • Prima Finestra (Posizioni 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Target: ["ipsum", "dolor", "sit", "amet,"]
  • Seconda Finestra (Posizioni 2-5): ["ipsum", "dolor", "sit", "amet,"] → Target: ["dolor", "sit", "amet,", "consectetur"]
  • Terza Finestra (Posizioni 3-6): ["dolor", "sit", "amet,", "consectetur"] → Target: ["sit", "amet,", "consectetur", "adipiscing"]
  • Quarta Finestra (Posizioni 4-7): ["sit", "amet,", "consectetur", "adipiscing"] → Target: ["amet,", "consectetur", "adipiscing", "elit."]

Comprendere il Passo

  • Passo di 1: La finestra si sposta in avanti di un token ogni volta, risultando in sequenze altamente sovrapposte. Questo può portare a un miglior apprendimento delle relazioni contestuali ma può aumentare il rischio di overfitting poiché punti dati simili vengono ripetuti.
  • Passo di 2: La finestra si sposta in avanti di due token ogni volta, riducendo la sovrapposizione. Questo diminuisce la ridondanza e il carico computazionale ma potrebbe perdere alcune sfumature contestuali.
  • Passo Uguale a max_length: La finestra si sposta in avanti per l'intera dimensione della finestra, risultando in sequenze non sovrapposte. Questo minimizza la ridondanza dei dati ma può limitare la capacità del modello di apprendere dipendenze tra le sequenze.

Esempio con Passo di 2:

Utilizzando lo stesso testo tokenizzato e max_length di 4:

  • Prima Finestra (Posizioni 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Target: ["ipsum", "dolor", "sit", "amet,"]
  • Seconda Finestra (Posizioni 3-6): ["dolor", "sit", "amet,", "consectetur"] → Target: ["sit", "amet,", "consectetur", "adipiscing"]
  • Terza Finestra (Posizioni 5-8): ["amet,", "consectetur", "adipiscing", "elit."] → Target: ["consectetur", "adipiscing", "elit.", "sed"] (Assumendo continuazione)

Esempio di Codice

Cerchiamo di capire meglio questo attraverso un esempio di codice da 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]])
]

Riferimenti