2. Datenstichprobe

Reading time: 6 minutes

Datenstichprobe

Datenstichprobe ist ein entscheidender Prozess bei der Vorbereitung von Daten für das Training großer Sprachmodelle (LLMs) wie GPT. Es beinhaltet die Organisation von Textdaten in Eingabe- und Zielsequenzen, die das Modell verwendet, um zu lernen, wie man das nächste Wort (oder Token) basierend auf den vorhergehenden Wörtern vorhersagt. Eine ordnungsgemäße Datenstichprobe stellt sicher, dass das Modell Sprachmuster und Abhängigkeiten effektiv erfasst.

tip

Das Ziel dieser zweiten Phase ist sehr einfach: Proben Sie die Eingabedaten und bereiten Sie sie für die Trainingsphase vor, indem Sie den Datensatz normalerweise in Sätze einer bestimmten Länge unterteilen und auch die erwartete Antwort generieren.

Warum Datenstichprobe wichtig ist

LLMs wie GPT werden trainiert, um Text zu generieren oder vorherzusagen, indem sie den Kontext verstehen, der durch vorherige Wörter bereitgestellt wird. Um dies zu erreichen, müssen die Trainingsdaten so strukturiert sein, dass das Modell die Beziehung zwischen Wortsequenzen und ihren nachfolgenden Wörtern lernen kann. Dieser strukturierte Ansatz ermöglicht es dem Modell, zu verallgemeinern und kohärenten sowie kontextuell relevanten Text zu generieren.

Schlüsselkonzepte in der Datenstichprobe

  1. Tokenisierung: Zerlegen von Text in kleinere Einheiten, die als Tokens bezeichnet werden (z. B. Wörter, Subwörter oder Zeichen).
  2. Sequenzlänge (max_length): Die Anzahl der Tokens in jeder Eingabesequenz.
  3. Gleitendes Fenster: Eine Methode zur Erstellung überlappender Eingabesequenzen, indem ein Fenster über den tokenisierten Text bewegt wird.
  4. Stride: Die Anzahl der Tokens, die das gleitende Fenster vorwärts bewegt, um die nächste Sequenz zu erstellen.

Schritt-für-Schritt-Beispiel

Lassen Sie uns ein Beispiel durchgehen, um die Datenstichprobe zu veranschaulichen.

Beispieltext

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

Tokenisierung

Angenommen, wir verwenden einen einfachen Tokenizer, der den Text in Wörter und Satzzeichen aufteilt:

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

Parameter

  • Maximale Sequenzlänge (max_length): 4 Tokens
  • Schiebefenster-Schritt: 1 Token

Erstellen von Eingabe- und Zielsequenzen

  1. Schiebefensteransatz:
  • Eingabesequenzen: Jede Eingabesequenz besteht aus max_length Tokens.
  • Zielsequenzen: Jede Zielsequenz besteht aus den Tokens, die unmittelbar auf die entsprechende Eingabesequenz folgen.
  1. Generierung von Sequenzen:
FensterpositionEingabesequenzZielsequenz
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. Ergebnis der Eingabe- und Zielarrays:
  • Eingabe:
python
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
  • Ziel:
python
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]

Visuelle Darstellung

Token-PositionToken
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.

Schiebefenster mit Schritt 1:

  • Erstes Fenster (Positionen 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Ziel: ["ipsum", "dolor", "sit", "amet,"]
  • Zweites Fenster (Positionen 2-5): ["ipsum", "dolor", "sit", "amet,"] → Ziel: ["dolor", "sit", "amet,", "consectetur"]
  • Drittes Fenster (Positionen 3-6): ["dolor", "sit", "amet,", "consectetur"] → Ziel: ["sit", "amet,", "consectetur", "adipiscing"]
  • Viertes Fenster (Positionen 4-7): ["sit", "amet,", "consectetur", "adipiscing"] → Ziel: ["amet,", "consectetur", "adipiscing", "elit."]

Verständnis des Schrittes

  • Schritt von 1: Das Fenster bewegt sich jedes Mal um ein Token nach vorne, was zu stark überlappenden Sequenzen führt. Dies kann zu einem besseren Lernen der kontextuellen Beziehungen führen, erhöht jedoch das Risiko von Overfitting, da ähnliche Datenpunkte wiederholt werden.
  • Schritt von 2: Das Fenster bewegt sich jedes Mal um zwei Tokens nach vorne, wodurch die Überlappung verringert wird. Dies reduziert Redundanz und Rechenaufwand, könnte jedoch einige kontextuelle Nuancen übersehen.
  • Schritt gleich max_length: Das Fenster bewegt sich um die gesamte Fenstergröße nach vorne, was zu nicht überlappenden Sequenzen führt. Dies minimiert die Datenredundanz, könnte jedoch die Fähigkeit des Modells einschränken, Abhängigkeiten zwischen Sequenzen zu lernen.

Beispiel mit Schritt von 2:

Unter Verwendung des gleichen tokenisierten Textes und max_length von 4:

  • Erstes Fenster (Positionen 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Ziel: ["ipsum", "dolor", "sit", "amet,"]
  • Zweites Fenster (Positionen 3-6): ["dolor", "sit", "amet,", "consectetur"] → Ziel: ["sit", "amet,", "consectetur", "adipiscing"]
  • Drittes Fenster (Positionen 5-8): ["amet,", "consectetur", "adipiscing", "elit."] → Ziel: ["consectetur", "adipiscing", "elit.", "sed"] (Annahme der Fortsetzung)

Codebeispiel

Lass uns das besser anhand eines Codebeispiels von https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb verstehen:

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]])
]

Referenzen