mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
239 lines
10 KiB
Markdown
239 lines
10 KiB
Markdown
# 2. Campionamento Dati
|
|
|
|
{{#include /banners/hacktricks-training.md}}
|
|
|
|
## **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**
|
|
|
|
Gli 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.
|
|
2. **Generazione delle Sequenze:**
|
|
|
|
<table><thead><tr><th width="177">Posizione della Finestra</th><th>Sequenza di Input</th><th>Sequenza di Target</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. **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**
|
|
|
|
<table><thead><tr><th width="222">Posizione del 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>
|
|
|
|
**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](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
|
|
|
|
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
|
|
|
|
|
{{#include /banners/hacktricks-training.md}}
|