2. Δειγματοληψία Δεδομένων
Reading time: 7 minutes
Δειγματοληψία Δεδομένων
Η Δειγματοληψία Δεδομένων είναι μια κρίσιμη διαδικασία στην προετοιμασία δεδομένων για την εκπαίδευση μεγάλων γλωσσικών μοντέλων (LLMs) όπως το GPT. Περιλαμβάνει την οργάνωση των κειμένων σε εισόδους και στόχους που το μοντέλο χρησιμοποιεί για να μάθει πώς να προβλέπει την επόμενη λέξη (ή το token) με βάση τις προηγούμενες λέξεις. Η σωστή δειγματοληψία δεδομένων διασφαλίζει ότι το μοντέλο συλλαμβάνει αποτελεσματικά τα γλωσσικά μοτίβα και τις εξαρτήσεις.
tip
Ο στόχος αυτής της δεύτερης φάσης είναι πολύ απλός: Δειγματοληψία των εισερχόμενων δεδομένων και προετοιμασία τους για τη φάση εκπαίδευσης, συνήθως διαχωρίζοντας το σύνολο δεδομένων σε προτάσεις συγκεκριμένου μήκους και δημιουργώντας επίσης την αναμενόμενη απάντηση.
Γιατί η Δειγματοληψία Δεδομένων έχει Σημασία
Τα LLMs όπως το GPT εκπαιδεύονται να παράγουν ή να προβλέπουν κείμενο κατανοώντας το πλαίσιο που παρέχεται από τις προηγούμενες λέξεις. Για να επιτευχθεί αυτό, τα δεδομένα εκπαίδευσης πρέπει να είναι δομημένα με τρόπο που το μοντέλο να μπορεί να μάθει τη σχέση μεταξύ ακολουθιών λέξεων και των επόμενων λέξεων τους. Αυτή η δομημένη προσέγγιση επιτρέπει στο μοντέλο να γενικεύει και να παράγει συνεκτικό και σχετικό κείμενο.
Βασικές Έννοιες στη Δειγματοληψία Δεδομένων
- Tokenization: Διαχωρισμός του κειμένου σε μικρότερες μονάδες που ονομάζονται tokens (π.χ., λέξεις, υπολέξεις ή χαρακτήρες).
- Μήκος Ακολουθίας (max_length): Ο αριθμός των tokens σε κάθε ακολουθία εισόδου.
- Ολισθητό Παράθυρο: Μια μέθοδος για τη δημιουργία επικαλυπτόμενων ακολουθιών εισόδου μετακινώντας ένα παράθυρο πάνω από το κειμένο που έχει διαχωριστεί σε tokens.
- Stride: Ο αριθμός των tokens που το ολισθητό παράθυρο μετακινείται προς τα εμπρός για να δημιουργήσει την επόμενη ακολουθία.
Βήμα-Βήμα Παράδειγμα
Ας περάσουμε από ένα παράδειγμα για να απεικονίσουμε τη δειγματοληψία δεδομένων.
Παράδειγμα Κειμένου
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
Tokenization
Υποθέστε ότι χρησιμοποιούμε έναν βασικό tokenizer που χωρίζει το κείμενο σε λέξεις και σημεία στίξης:
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]
Παράμετροι
- Μέγιστο Μήκος Ακολουθίας (max_length): 4 tokens
- Βήμα Ολίσθησης: 1 token
Δημιουργία Εισόδων και Στοχαστικών Ακολουθιών
- Προσέγγιση Ολίσθησης:
- Εισόδους: Κάθε είσοδος αποτελείται από
max_length
tokens. - Στοχαστικές Ακολουθίες: Κάθε στοχαστική ακολουθία αποτελείται από τα tokens που ακολουθούν άμεσα την αντίστοιχη είσοδο.
- Δημιουργία Ακολουθιών:
Θέση Παραθύρου | Είσοδος | Στοχαστική Ακολουθία |
---|---|---|
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."] |
- Αποτελέσματα Εισόδων και Στοχαστικών Πινάκων:
- Είσοδος:
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
- Στοχαστική Ακολουθία:
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]
Οπτική Αναπαράσταση
Θέση Token | Token |
---|---|
1 | Lorem |
2 | ipsum |
3 | dolor |
4 | sit |
5 | amet, |
6 | consectetur |
7 | adipiscing |
8 | elit. |
Ολίσθηση με Βήμα 1:
- Πρώτο Παράθυρο (Θέσεις 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Στοχαστική Ακολουθία: ["ipsum", "dolor", "sit", "amet,"]
- Δεύτερο Παράθυρο (Θέσεις 2-5): ["ipsum", "dolor", "sit", "amet,"] → Στοχαστική Ακολουθία: ["dolor", "sit", "amet,", "consectetur"]
- Τρίτο Παράθυρο (Θέσεις 3-6): ["dolor", "sit", "amet,", "consectetur"] → Στοχαστική Ακολουθία: ["sit", "amet,", "consectetur", "adipiscing"]
- Τέταρτο Παράθυρο (Θέσεις 4-7): ["sit", "amet,", "consectetur", "adipiscing"] → Στοχαστική Ακολουθία: ["amet,", "consectetur", "adipiscing", "elit."]
Κατανόηση Βήματος
- Βήμα 1: Το παράθυρο προχωράει μπροστά κατά ένα token κάθε φορά, με αποτέλεσμα πολύ επικαλυπτόμενες ακολουθίες. Αυτό μπορεί να οδηγήσει σε καλύτερη εκμάθηση των συμφραζομένων αλλά μπορεί να αυξήσει τον κίνδυνο υπερπροσαρμογής καθώς τα παρόμοια σημεία δεδομένων επαναλαμβάνονται.
- Βήμα 2: Το παράθυρο προχωράει μπροστά κατά δύο tokens κάθε φορά, μειώνοντας την επικάλυψη. Αυτό μειώνει την πλεονασματικότητα και το υπολογιστικό φορτίο αλλά μπορεί να χάσει κάποιες λεπτομέρειες συμφραζομένων.
- Βήμα Ίσο με το max_length: Το παράθυρο προχωράει μπροστά κατά το συνολικό μέγεθος του παραθύρου, με αποτέλεσμα μη επικαλυπτόμενες ακολουθίες. Αυτό ελαχιστοποιεί την πλεονασματικότητα των δεδομένων αλλά μπορεί να περιορίσει την ικανότητα του μοντέλου να μάθει εξαρτήσεις μεταξύ των ακολουθιών.
Παράδειγμα με Βήμα 2:
Χρησιμοποιώντας το ίδιο κείμενο με tokens και max_length
4:
- Πρώτο Παράθυρο (Θέσεις 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Στοχαστική Ακολουθία: ["ipsum", "dolor", "sit", "amet,"]
- Δεύτερο Παράθυρο (Θέσεις 3-6): ["dolor", "sit", "amet,", "consectetur"] → Στοχαστική Ακολουθία: ["sit", "amet,", "consectetur", "adipiscing"]
- Τρίτο Παράθυρο (Θέσεις 5-8): ["amet,", "consectetur", "adipiscing", "elit."] → Στοχαστική Ακολουθία: ["consectetur", "adipiscing", "elit.", "sed"] (Υποθέτοντας συνέχεια)
Παράδειγμα Κώδικα
Ας κατανοήσουμε αυτό καλύτερα από ένα παράδειγμα κώδικα από 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]])
]