Translated ['src/AI/AI-llm-architecture/0.-basic-llm-concepts.md', 'src/

This commit is contained in:
Translator 2025-06-08 18:12:36 +00:00
parent 31fe23a67a
commit 115058571d
11 changed files with 1351 additions and 67 deletions

View File

@ -6,7 +6,7 @@ Vortraining ist die grundlegende Phase bei der Entwicklung eines großen Sprachm
## Hauptkomponenten von LLM
Ein LLM wird normalerweise durch die Konfiguration charakterisiert, die zu seiner Ausbildung verwendet wird. Dies sind die gängigen Komponenten beim Training eines LLM:
Ein LLM wird normalerweise durch die Konfiguration charakterisiert, die zu seiner Ausbildung verwendet wurde. Dies sind die gängigen Komponenten beim Training eines LLM:
- **Parameter**: Parameter sind die **lernbaren Gewichte und Verzerrungen** im neuronalen Netzwerk. Dies sind die Zahlen, die der Trainingsprozess anpasst, um die Verlustfunktion zu minimieren und die Leistung des Modells bei der Aufgabe zu verbessern. LLMs verwenden normalerweise Millionen von Parametern.
- **Kontextlänge**: Dies ist die maximale Länge jedes Satzes, der zum Vortraining des LLM verwendet wird.
@ -41,11 +41,11 @@ In PyTorch ist ein **Tensor** eine grundlegende Datenstruktur, die als mehrdimen
### Tensoren als Datencontainer
Aus einer rechnerischen Perspektive fungieren Tensoren als Container für mehrdimensionale Daten, wobei jede Dimension unterschiedliche Merkmale oder Aspekte der Daten darstellen kann. Dies macht Tensoren besonders geeignet für die Verarbeitung komplexer Datensätze in maschinellen Lernaufgaben.
Aus einer rechnerischen Perspektive fungieren Tensoren als Container für mehrdimensionale Daten, wobei jede Dimension verschiedene Merkmale oder Aspekte der Daten darstellen kann. Dies macht Tensoren besonders geeignet für die Verarbeitung komplexer Datensätze in Machine Learning-Aufgaben.
### PyTorch-Tensoren vs. NumPy-Arrays
Während PyTorch-Tensoren NumPy-Arrays in ihrer Fähigkeit ähneln, numerische Daten zu speichern und zu manipulieren, bieten sie zusätzliche Funktionalitäten, die für Deep Learning entscheidend sind:
Während PyTorch-Tensoren NumPy-Arrays in ihrer Fähigkeit, numerische Daten zu speichern und zu manipulieren, ähnlich sind, bieten sie zusätzliche Funktionalitäten, die für Deep Learning entscheidend sind:
- **Automatische Differenzierung**: PyTorch-Tensoren unterstützen die automatische Berechnung von Gradienten (autograd), was den Prozess der Berechnung von Ableitungen vereinfacht, die für das Training neuronaler Netzwerke erforderlich sind.
- **GPU-Beschleunigung**: Tensoren in PyTorch können auf GPUs verschoben und dort berechnet werden, was großangelegte Berechnungen erheblich beschleunigt.
@ -119,7 +119,7 @@ result = tensor2d @ tensor2d.T
Tensors sind in PyTorch unerlässlich für den Aufbau und das Training von neuronalen Netzwerken:
- Sie speichern Eingabedaten, Gewichte und Bias.
- Sie speichern Eingabedaten, Gewichte und Biases.
- Sie erleichtern die für Vorwärts- und Rückwärtsdurchläufe in Trainingsalgorithmen erforderlichen Operationen.
- Mit Autograd ermöglichen Tensors die automatische Berechnung von Gradienten, was den Optimierungsprozess vereinfacht.
@ -205,7 +205,7 @@ In größeren neuronalen Netzwerken mit mehreren Schichten wird der Prozess der
- **Compute Loss:** Bewerte die Verlustfunktion unter Verwendung der Ausgabe des Netzwerks und der Zielbeschriftungen.
- **Backward Pass (Backpropagation):** Berechne die Gradienten des Verlusts in Bezug auf jeden Parameter im Netzwerk, indem du die Kettenregel rekursiv von der Ausgabeschicht zurück zur Eingabeschicht anwendest.
### **2. Backpropagation-Algorithmus**
### **2. Backpropagation Algorithmus**
- **Schritt 1:** Initialisiere die Netzwerkparameter (Gewichte und Biases).
- **Schritt 2:** Führe für jedes Trainingsbeispiel einen Forward Pass durch, um die Ausgaben zu berechnen.
@ -219,7 +219,7 @@ Betrachte ein einfaches neuronales Netzwerk mit einer versteckten Schicht:
<figure><img src="../../images/image (5) (1).png" alt=""><figcaption></figcaption></figure>
### **4. PyTorch-Implementierung**
### **4. PyTorch Implementierung**
PyTorch vereinfacht diesen Prozess mit seiner Autograd-Engine.
```python

View File

@ -17,7 +17,7 @@ Tokens: `["Hallo", ",", "Welt", "!"]`
2. **Erstellen eines Vokabulars:**
- Um Tokens in numerische IDs umzuwandeln, wird ein **Vokabular** erstellt. Dieses Vokabular listet alle einzigartigen Tokens (Wörter und Symbole) auf und weist jedem eine spezifische ID zu.
- **Spezielle Tokens:** Dies sind spezielle Symbole, die dem Vokabular hinzugefügt werden, um verschiedene Szenarien zu behandeln:
- `[BOS]` (Beginn der Sequenz): Kennzeichnet den Anfang eines Textes.
- `[BOS]` (Anfang der Sequenz): Kennzeichnet den Beginn eines Textes.
- `[EOS]` (Ende der Sequenz): Kennzeichnet das Ende eines Textes.
- `[PAD]` (Padding): Wird verwendet, um alle Sequenzen in einem Batch auf die gleiche Länge zu bringen.
- `[UNK]` (Unbekannt): Stellt Tokens dar, die nicht im Vokabular enthalten sind.
@ -53,15 +53,15 @@ Während der einfache Tokenizer gut für einfache Texte funktioniert, hat er Ein
- Verwendet ein probabilistisches Modell, um zu entscheiden, welche Subwörter zusammengeführt werden sollen.
- **Vorteile:**
- Balanciert zwischen einer handhabbaren Vokabulargröße und einer effektiven Darstellung von Wörtern.
- Handhabt seltene und zusammengesetzte Wörter effizient.
- Behandelt seltene und zusammengesetzte Wörter effizient.
- _Beispiel:_\
`"Unglück"` könnte als `["un", "glück"]` oder `["un", "glücklich", "keit"]` tokenisiert werden, je nach Vokabular.
`"Unglück"` könnte als `["un", "glück"]` oder `["un", "glücklich", "keit"]` tokenisiert werden, abhängig vom Vokabular.
3. **Unigram-Sprachmodell:**
- **Verwendet von:** Modellen wie SentencePiece.
- **Zweck:** Verwendet ein probabilistisches Modell, um die wahrscheinlichste Menge von Subwort-Tokens zu bestimmen.
- **Wie es funktioniert:**
- Beginnt mit einer großen Menge potenzieller Tokens.
- Entfernt iterativ Tokens, die die Wahrscheinlichkeit der Trainingsdaten am wenigsten verbessern.
- Entfernt iterativ Tokens, die die Wahrscheinlichkeit des Modells für die Trainingsdaten am wenigsten verbessern.
- Finalisiert ein Vokabular, in dem jedes Wort durch die wahrscheinlichsten Subwort-Einheiten dargestellt wird.
- **Vorteile:**
- Flexibel und kann Sprache natürlicher modellieren.

View File

@ -0,0 +1,233 @@
# 2. Datenstichprobe
## **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.
2. **Generierung von Sequenzen:**
<table><thead><tr><th width="177">Fensterposition</th><th>Eingabesequenz</th><th>Zielsequenz</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. **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**
<table><thead><tr><th width="222">Token-Position</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>
**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](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
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -2,10 +2,10 @@
## Token-Embeddings
Nach der Tokenisierung von Textdaten ist der nächste kritische Schritt bei der Vorbereitung von Daten für das Training großer Sprachmodelle (LLMs) wie GPT die Erstellung von **Token-Embeddings**. Token-Embeddings transformieren diskrete Tokens (wie Wörter oder Subwörter) in kontinuierliche numerische Vektoren, die das Modell verarbeiten und aus denen es lernen kann. Diese Erklärung zerlegt Token-Embeddings, deren Initialisierung, Verwendung und die Rolle von Positions-Embeddings zur Verbesserung des Verständnisses des Modells für Token-Sequenzen.
Nach der Tokenisierung von Textdaten ist der nächste kritische Schritt zur Vorbereitung der Daten für das Training großer Sprachmodelle (LLMs) wie GPT die Erstellung von **Token-Embeddings**. Token-Embeddings transformieren diskrete Tokens (wie Wörter oder Subwörter) in kontinuierliche numerische Vektoren, die das Modell verarbeiten und aus denen es lernen kann. Diese Erklärung zerlegt Token-Embeddings, deren Initialisierung, Verwendung und die Rolle der Positions-Embeddings zur Verbesserung des Modells beim Verständnis von Token-Sequenzen.
> [!TIP]
> Das Ziel dieser dritten Phase ist sehr einfach: **Weisen Sie jedem der vorherigen Tokens im Vokabular einen Vektor der gewünschten Dimensionen zu, um das Modell zu trainieren.** Jedes Wort im Vokabular wird einen Punkt in einem Raum von X Dimensionen haben.\
> Das Ziel dieser dritten Phase ist sehr einfach: **Jedem der vorherigen Tokens im Vokabular einen Vektor der gewünschten Dimensionen zuzuweisen, um das Modell zu trainieren.** Jedes Wort im Vokabular wird einen Punkt in einem Raum von X Dimensionen haben.\
> Beachten Sie, dass die Position jedes Wortes im Raum zunächst "zufällig" initialisiert wird und diese Positionen trainierbare Parameter sind (während des Trainings verbessert werden).
>
> Darüber hinaus wird während des Token-Embeddings **eine weitere Schicht von Embeddings erstellt**, die (in diesem Fall) die **absolute Position des Wortes im Trainingssatz** darstellt. Auf diese Weise hat ein Wort an verschiedenen Positionen im Satz eine unterschiedliche Darstellung (Bedeutung).
@ -82,7 +82,7 @@ Während des Trainings wird jedes Token in den Eingabedaten in seinen entspreche
**Datenstruktur:**
- Jeder Batch wird als 3D-Tensor mit der Form `(batch_size, max_length, embedding_dim)` dargestellt.
- Jedes Batch wird als 3D-Tensor mit der Form `(batch_size, max_length, embedding_dim)` dargestellt.
- Für unser Beispiel wäre die Form `(8, 4, 256)`.
**Visualisierung:**
@ -127,8 +127,8 @@ Während Token-Embeddings die Bedeutung einzelner Tokens erfassen, kodieren sie
### **Warum positionale Embeddings benötigt werden:**
- **Token-Reihenfolge ist wichtig:** In Sätzen hängt die Bedeutung oft von der Reihenfolge der Wörter ab. Zum Beispiel: "Die Katze saß auf der Matte" vs. "Die Matte saß auf der Katze."
- **Einschränkung der Embeddings:** Ohne Positionsinformationen behandelt das Modell Tokens als eine "Tüte voller Wörter" und ignoriert ihre Reihenfolge.
- **Token-Reihenfolge ist wichtig:** In Sätzen hängt die Bedeutung oft von der Reihenfolge der Wörter ab. Zum Beispiel "Die Katze saß auf der Matte" vs. "Die Matte saß auf der Katze."
- **Einschränkung des Embeddings:** Ohne Positionsinformationen behandelt das Modell Tokens als eine "Tasche von Wörtern" und ignoriert ihre Reihenfolge.
### **Arten von positionalen Embeddings:**
@ -148,7 +148,7 @@ Während Token-Embeddings die Bedeutung einzelner Tokens erfassen, kodieren sie
**Beispiel für das Hinzufügen von positionalen Embeddings:**
Angenommen, ein Token-Embedding-Vektor ist `[0.5, -0.2, 0.1]` und sein positionales Embedding ist `[0.1, 0.3, -0.1]`. Das kombinierte Embedding, das vom Modell verwendet wird, wäre:
Angenommen, ein Token-Embedding-Vektor ist `[0.5, -0.2, 0.1]` und sein positionales Embedding-Vektor ist `[0.1, 0.3, -0.1]`. Das kombinierte Embedding, das vom Modell verwendet wird, wäre:
```css
Combined Embedding = Token Embedding + Positional Embedding
= [0.5 + 0.1, -0.2 + 0.3, 0.1 + (-0.1)]

View File

@ -10,7 +10,7 @@ Aufmerksamkeitsmechanismen ermöglichen es neuronalen Netzwerken, sich **auf spe
### Verständnis der Aufmerksamkeitsmechanismen
In traditionellen Sequenz-zu-Sequenz-Modellen, die für die Sprachübersetzung verwendet werden, kodiert das Modell eine Eingabesequenz in einen kontextuellen Vektor fester Größe. Dieses Vorgehen hat jedoch Schwierigkeiten mit langen Sätzen, da der kontextuelle Vektor fester Größe möglicherweise nicht alle notwendigen Informationen erfasst. Aufmerksamkeitsmechanismen beheben diese Einschränkung, indem sie es dem Modell ermöglichen, alle Eingabetoken zu berücksichtigen, wenn es jedes Ausgabetoken generiert.
In traditionellen Sequenz-zu-Sequenz-Modellen, die für die Sprachübersetzung verwendet werden, kodiert das Modell eine Eingabesequenz in einen kontextuellen Vektor fester Größe. Dieses Vorgehen hat jedoch Schwierigkeiten mit langen Sätzen, da der kontextuelle Vektor fester Größe möglicherweise nicht alle notwendigen Informationen erfasst. Aufmerksamkeitsmechanismen beheben diese Einschränkung, indem sie dem Modell erlauben, alle Eingabetoken zu berücksichtigen, wenn es jedes Ausgabetoken generiert.
#### Beispiel: Maschinelle Übersetzung
@ -62,7 +62,7 @@ Für jedes Wort im Satz berechnen Sie den **Aufmerksamkeitswert** in Bezug auf "
>
> Darüber hinaus wird die **Softmax**-Funktion verwendet, da sie Unterschiede aufgrund des exponentiellen Teils verstärkt, was es einfacher macht, nützliche Werte zu erkennen.
Wenden Sie die **Softmax-Funktion** auf die Aufmerksamkeitswerte an, um sie in Aufmerksamkeitsgewichte umzuwandeln, die sich auf 1 summieren.
Wenden Sie die **Softmax-Funktion** auf die Aufmerksamkeitswerte an, um sie in Aufmerksamkeitsgewichte umzuwandeln, die sich zu 1 summieren.
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
@ -110,7 +110,7 @@ Summierung der gewichteten Embeddings:
### Zusammenfassung des Prozesses
1. **Berechnung der Aufmerksamkeitswerte**: Verwenden Sie das Skalarprodukt zwischen dem Embedding des Zielworts und den Embeddings aller Wörter in der Sequenz.
2. **Normalisieren der Werte zur Ermittlung der Aufmerksamkeitsgewichte**: Wenden Sie die Softmax-Funktion auf die Aufmerksamkeitswerte an, um Gewichte zu erhalten, die sich auf 1 summieren.
2. **Normalisieren der Werte zur Ermittlung der Aufmerksamkeitsgewichte**: Wenden Sie die Softmax-Funktion auf die Aufmerksamkeitswerte an, um Gewichte zu erhalten, die sich zu 1 summieren.
3. **Berechnung des Kontextvektors**: Multiplizieren Sie das Embedding jedes Wortes mit seinem Aufmerksamkeitsgewicht und summieren Sie die Ergebnisse.
## Selbstaufmerksamkeit mit trainierbaren Gewichten
@ -167,10 +167,10 @@ Um zu verhindern, dass die Dot-Produkte zu groß werden, skalieren Sie sie durch
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
> [!TIP]
> Der Wert wird durch die Quadratwurzel der Dimensionen geteilt, da Dot-Produkte sehr groß werden können, und dies hilft, sie zu regulieren.
> [!TIPP]
> Der Wert wird durch die Quadratwurzel der Dimensionen geteilt, da Dot-Produkte sehr groß werden können und dies hilft, sie zu regulieren.
**Anwendung von Softmax zur Ermittlung der Attention-Gewichte:** Wie im ursprünglichen Beispiel, normalisieren Sie alle Werte, sodass sie 1 ergeben.
**Anwenden von Softmax zur Ermittlung der Attention-Gewichte:** Wie im ursprünglichen Beispiel, normalisieren Sie alle Werte, sodass sie 1 ergeben.
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
@ -230,7 +230,7 @@ Für LLMs möchten wir, dass das Modell nur die Tokens berücksichtigt, die vor
### Anwendung einer kausalen Aufmerksamkeitsmaske
Um kausale Aufmerksamkeit zu implementieren, wenden wir eine Maske auf die Aufmerksamkeitswerte **vor der Softmax-Operation** an, damit die verbleibenden Werte immer noch 1 ergeben. Diese Maske setzt die Aufmerksamkeitswerte zukünftiger Tokens auf negative Unendlichkeit, wodurch sichergestellt wird, dass nach der Softmax ihre Aufmerksamkeitsgewichte null sind.
Um kausale Aufmerksamkeit zu implementieren, wenden wir eine Maske auf die Aufmerksamkeitswerte **vor der Softmax-Operation** an, sodass die verbleibenden Werte immer noch 1 ergeben. Diese Maske setzt die Aufmerksamkeitswerte zukünftiger Tokens auf negative Unendlichkeit, wodurch sichergestellt wird, dass nach der Softmax ihre Aufmerksamkeitsgewichte null sind.
**Schritte**
@ -250,7 +250,7 @@ attention_weights = torch.softmax(masked_scores, dim=-1)
### Maskierung zusätzlicher Aufmerksamkeitsgewichte mit Dropout
Um **Überanpassung zu verhindern**, können wir **Dropout** auf die Aufmerksamkeitsgewichte nach der Softmax-Operation anwenden. Dropout **setzt zufällig einige der Aufmerksamkeitsgewichte während des Trainings auf null**.
Um **Überanpassung zu verhindern**, können wir **Dropout** auf die Aufmerksamkeitsgewichte nach der Softmax-Operation anwenden. Dropout **setzt zufällig einige der Aufmerksamkeitsgewichte während des Trainings auf null.**
```python
dropout = nn.Dropout(p=0.5)
attention_weights = dropout(attention_weights)
@ -407,9 +407,9 @@ print("context_vecs.shape:", context_vecs.shape)
Für eine weitere kompakte und effiziente Implementierung könnten Sie die [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) Klasse in PyTorch verwenden.
> [!TIP]
> Kurze Antwort von ChatGPT, warum es besser ist, die Dimensionen der Tokens auf die Köpfe zu verteilen, anstatt dass jeder Kopf alle Dimensionen aller Tokens überprüft:
> Kurze Antwort von ChatGPT, warum es besser ist, die Dimensionen der Tokens unter den Köpfen zu teilen, anstatt dass jeder Kopf alle Dimensionen aller Tokens überprüft:
>
> Während es vorteilhaft erscheinen mag, jedem Kopf den Zugriff auf alle Einbettungsdimensionen zu ermöglichen, da jeder Kopf auf die vollständigen Informationen zugreifen würde, ist die gängige Praxis, die **Einbettungsdimensionen auf die Köpfe zu verteilen**. Dieser Ansatz balanciert die rechnerische Effizienz mit der Modellleistung und ermutigt jeden Kopf, vielfältige Darstellungen zu lernen. Daher wird das Aufteilen der Einbettungsdimensionen im Allgemeinen bevorzugt, anstatt dass jeder Kopf alle Dimensionen überprüft.
> Während es vorteilhaft erscheinen mag, jedem Kopf den Zugriff auf alle Einbettungsdimensionen zu ermöglichen, da jeder Kopf auf die vollständigen Informationen zugreifen könnte, ist die gängige Praxis, die **Einbettungsdimensionen unter den Köpfen zu teilen**. Dieser Ansatz balanciert die rechnerische Effizienz mit der Modellleistung und fördert, dass jeder Kopf unterschiedliche Darstellungen lernt. Daher wird das Teilen der Einbettungsdimensionen im Allgemeinen bevorzugt, anstatt dass jeder Kopf alle Dimensionen überprüft.
## References

View File

@ -18,7 +18,7 @@ Eine hochrangige Darstellung kann beobachtet werden in:
3. **Transformer-Blöcke**: Das Modell enthält **12 Transformer-Blöcke**, jeder mit mehreren Schichten. Diese Blöcke wiederholen die folgende Sequenz:
- **Maskierte Multi-Head-Attention**: Ermöglicht es dem Modell, sich gleichzeitig auf verschiedene Teile des Eingabetextes zu konzentrieren.
- **Schichtnormalisierung**: Ein Normalisierungsschritt zur Stabilisierung und Verbesserung des Trainings.
- **Feed-Forward-Schicht**: Verantwortlich für die Verarbeitung der Informationen aus der Attention-Schicht und die Vorhersage des nächsten Tokens.
- **Feed-Forward-Schicht**: Verantwortlich für die Verarbeitung der Informationen aus der Aufmerksamkeits-Schicht und für die Vorhersage des nächsten Tokens.
- **Dropout-Schichten**: Diese Schichten verhindern Überanpassung, indem sie während des Trainings zufällig Einheiten fallen lassen.
4. **Letzte Ausgabeschicht**: Das Modell gibt einen **4x50.257-dimensionalen Tensor** aus, wobei **50.257** die Größe des Wortschatzes darstellt. Jede Zeile in diesem Tensor entspricht einem Vektor, den das Modell verwendet, um das nächste Wort in der Sequenz vorherzusagen.
5. **Ziel**: Das Ziel ist es, diese Embeddings zu nehmen und sie wieder in Text umzuwandeln. Insbesondere wird die letzte Zeile der Ausgabe verwendet, um das nächste Wort zu generieren, das in diesem Diagramm als "vorwärts" dargestellt ist.
@ -195,7 +195,7 @@ print("Input batch:\n", batch)
print("\nOutput shape:", out.shape)
print(out)
```
### **GELU-Aktivierungsfunktion**
### **GELU Aktivierungsfunktion**
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GELU(nn.Module):
@ -211,13 +211,13 @@ torch.sqrt(torch.tensor(2.0 / torch.pi)) *
#### **Zweck und Funktionalität**
- **GELU (Gaussian Error Linear Unit):** Eine Aktivierungsfunktion, die Nichtlinearität in das Modell einführt.
- **Glatte Aktivierung:** Im Gegensatz zu ReLU, das negative Eingaben auf null setzt, ordnet GELU Eingaben sanft Ausgaben zu und ermöglicht kleine, von null verschiedene Werte für negative Eingaben.
- **Glatte Aktivierung:** Im Gegensatz zu ReLU, das negative Eingaben auf null setzt, ordnet GELU Eingaben glatt Ausgaben zu und ermöglicht kleine, von null verschiedene Werte für negative Eingaben.
- **Mathematische Definition:**
<figure><img src="../../images/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
> [!TIP]
> Das Ziel der Verwendung dieser Funktion nach linearen Schichten innerhalb der FeedForward-Schicht besteht darin, die linearen Daten nicht linear zu gestalten, um dem Modell zu ermöglichen, komplexe, nicht-lineare Beziehungen zu lernen.
> Das Ziel der Verwendung dieser Funktion nach linearen Schichten innerhalb der FeedForward-Schicht besteht darin, die linearen Daten nicht linear zu machen, um dem Modell zu ermöglichen, komplexe, nicht-lineare Beziehungen zu lernen.
### **FeedForward-Neuronales Netzwerk**
@ -259,10 +259,10 @@ Dies wurde bereits in einem früheren Abschnitt erklärt.
#### **Zweck und Funktionalität**
- **Multi-Head Self-Attention:** Ermöglicht es dem Modell, sich auf verschiedene Positionen innerhalb der Eingabesequenz zu konzentrieren, wenn es ein Token kodiert.
- **Schlüsselelemente:**
- **Schlüsselfunktionen:**
- **Abfragen, Schlüssel, Werte:** Lineare Projektionen der Eingabe, die zur Berechnung der Aufmerksamkeitswerte verwendet werden.
- **Köpfe:** Mehrere Aufmerksamkeitsmechanismen, die parallel laufen (`num_heads`), jeder mit einer reduzierten Dimension (`head_dim`).
- **Aufmerksamkeitswerte:** Werden als Skalarprodukt von Abfragen und Schlüsseln berechnet, skaliert und maskiert.
- **Aufmerksamkeitswerte:** Berechnet als das Skalarprodukt von Abfragen und Schlüsseln, skaliert und maskiert.
- **Maskierung:** Eine kausale Maske wird angewendet, um zu verhindern, dass das Modell zukünftige Tokens berücksichtigt (wichtig für autoregressive Modelle wie GPT).
- **Aufmerksamkeitsgewichte:** Softmax der maskierten und skalierten Aufmerksamkeitswerte.
- **Kontextvektor:** Gewichtete Summe der Werte, entsprechend den Aufmerksamkeitsgewichten.
@ -297,16 +297,16 @@ return self.scale * norm_x + self.shift
- **`scale` und `shift`:** Lernbare Parameter (`nn.Parameter`), die es dem Modell ermöglichen, die normalisierte Ausgabe zu skalieren und zu verschieben. Sie werden jeweils mit Einsen und Nullen initialisiert.
- **Normalisierungsprozess:**
- **Mittelwert berechnen (`mean`):** Berechnet den Mittelwert der Eingabe `x` über die Einbettungsdimension (`dim=-1`), wobei die Dimension für das Broadcasting beibehalten wird (`keepdim=True`).
- **Varianz berechnen (`var`):** Berechnet die Varianz von `x` über die Einbettungsdimension und behält ebenfalls die Dimension bei. Der Parameter `unbiased=False` stellt sicher, dass die Varianz mit dem verzerrten Schätzer (Division durch `N` anstelle von `N-1`) berechnet wird, was angemessen ist, wenn über Merkmale und nicht über Proben normalisiert wird.
- **Varianz berechnen (`var`):** Berechnet die Varianz von `x` über die Einbettungsdimension und behält ebenfalls die Dimension bei. Der Parameter `unbiased=False` stellt sicher, dass die Varianz mit dem verzerrten Schätzer berechnet wird (Division durch `N` anstelle von `N-1`), was angemessen ist, wenn über Merkmale und nicht über Proben normalisiert wird.
- **Normalisieren (`norm_x`):** Subtrahiert den Mittelwert von `x` und dividiert durch die Quadratwurzel der Varianz plus `eps`.
- **Skalieren und Verschieben:** Wendet die lernbaren `scale`- und `shift`-Parameter auf die normalisierte Ausgabe an.
> [!TIP]
> Das Ziel ist es, einen Mittelwert von 0 mit einer Varianz von 1 über alle Dimensionen des gleichen Tokens sicherzustellen. Das Ziel davon ist es, **das Training von tiefen neuronalen Netzwerken zu stabilisieren**, indem der interne Kovariatenverschiebung reduziert wird, die sich auf die Änderung der Verteilung der Netzwerkaktivierungen aufgrund der Aktualisierung der Parameter während des Trainings bezieht.
> Das Ziel ist es, einen Mittelwert von 0 mit einer Varianz von 1 über alle Dimensionen des gleichen Tokens sicherzustellen. Das Ziel davon ist es, **das Training von tiefen neuronalen Netzwerken zu stabilisieren**, indem der interne Kovariate Shift reduziert wird, der sich auf die Veränderung der Verteilung der Netzwerkaktivierungen aufgrund der Aktualisierung der Parameter während des Trainings bezieht.
### **Transformer Block**
_Schablonen wurden als Kommentare hinzugefügt, um die Formen der Matrizen besser zu verstehen:_
_Shapes wurden als Kommentare hinzugefügt, um die Formen der Matrizen besser zu verstehen:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
@ -348,35 +348,35 @@ return x # Output shape: (batch_size, seq_len, emb_dim)
```
#### **Zweck und Funktionalität**
- **Zusammensetzung der Schichten:** Kombiniert Multi-Head-Attention, Feedforward-Netzwerk, Schichtnormalisierung und Residualverbindungen.
- **Schichtnormalisierung:** Vor der Attention- und Feedforward-Schicht für stabiles Training angewendet.
- **Residualverbindungen (Abkürzungen):** Fügen den Eingang einer Schicht zu ihrem Ausgang hinzu, um den Gradientenfluss zu verbessern und das Training tiefer Netzwerke zu ermöglichen.
- **Zusammensetzung der Schichten:** Kombiniert Multi-Head-Attention, Feedforward-Netzwerk, Layer-Normalisierung und Residualverbindungen.
- **Layer-Normalisierung:** Vor der Attention- und Feedforward-Schicht angewendet für stabiles Training.
- **Residualverbindungen (Abkürzungen):** Fügen den Eingang einer Schicht zu ihrem Ausgang hinzu, um den Gradientfluss zu verbessern und das Training tiefer Netzwerke zu ermöglichen.
- **Dropout:** Nach der Attention- und Feedforward-Schicht zur Regularisierung angewendet.
#### **Schritt-für-Schritt-Funktionalität**
1. **Erster Residualpfad (Selbst-Attention):**
- **Eingang (`shortcut`):** Speichern des ursprünglichen Eingangs für die Residualverbindung.
- **Schichtnorm (`norm1`):** Normalisieren des Eingangs.
- **Layer Norm (`norm1`):** Normalisieren des Eingangs.
- **Multi-Head-Attention (`att`):** Selbst-Attention anwenden.
- **Dropout (`drop_shortcut`):** Dropout zur Regularisierung anwenden.
- **Residual hinzufügen (`x + shortcut`):** Mit dem ursprünglichen Eingang kombinieren.
- **Add Residual (`x + shortcut`):** Mit dem ursprünglichen Eingang kombinieren.
2. **Zweiter Residualpfad (FeedForward):**
- **Eingang (`shortcut`):** Speichern des aktualisierten Eingangs für die nächste Residualverbindung.
- **Schichtnorm (`norm2`):** Normalisieren des Eingangs.
- **Layer Norm (`norm2`):** Normalisieren des Eingangs.
- **FeedForward-Netzwerk (`ff`):** Die Feedforward-Transformation anwenden.
- **Dropout (`drop_shortcut`):** Dropout anwenden.
- **Residual hinzufügen (`x + shortcut`):** Mit dem Eingang vom ersten Residualpfad kombinieren.
- **Add Residual (`x + shortcut`):** Mit dem Eingang vom ersten Residualpfad kombinieren.
> [!TIP]
> Der Transformer-Block gruppiert alle Netzwerke zusammen und wendet einige **Normalisierungen** und **Dropouts** an, um die Trainingsstabilität und -ergebnisse zu verbessern.\
> Beachten Sie, wie Dropouts nach der Verwendung jedes Netzwerks durchgeführt werden, während die Normalisierung vorher angewendet wird.
> Beachten Sie, dass Dropouts nach der Verwendung jedes Netzwerks durchgeführt werden, während die Normalisierung vorher angewendet wird.
>
> Darüber hinaus verwendet er auch Abkürzungen, die darin bestehen, **den Ausgang eines Netzwerks mit seinem Eingang zu addieren**. Dies hilft, das Problem des verschwindenden Gradienten zu verhindern, indem sichergestellt wird, dass die anfänglichen Schichten "genauso viel" beitragen wie die letzten.
### **GPTModel**
_Schablonen wurden als Kommentare hinzugefügt, um die Formen der Matrizen besser zu verstehen:_
_ Formen wurden als Kommentare hinzugefügt, um die Formen der Matrizen besser zu verstehen:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GPTModel(nn.Module):
@ -442,11 +442,11 @@ return logits # Output shape: (batch_size, seq_len, vocab_size)
- **Ausgabeschicht (`out_head`):** Projiziert die finalen versteckten Zustände auf die Größe des Vokabulars, um Logits für die Vorhersage zu erzeugen.
> [!TIP]
> Das Ziel dieser Klasse ist es, alle anderen genannten Netzwerke zu **benutzen, um das nächste Token in einer Sequenz vorherzusagen**, was grundlegend für Aufgaben wie die Textgenerierung ist.
> Das Ziel dieser Klasse ist es, alle anderen genannten Netzwerke zu **verwenden, um das nächste Token in einer Sequenz vorherzusagen**, was grundlegend für Aufgaben wie die Textgenerierung ist.
>
> Beachten Sie, wie es **so viele Transformer-Blöcke wie angegeben verwenden wird** und dass jeder Transformer-Block ein Multi-Head-Attention-Netz, ein Feed-Forward-Netz und mehrere Normalisierungen verwendet. Wenn also 12 Transformer-Blöcke verwendet werden, multiplizieren Sie dies mit 12.
>
> Darüber hinaus wird eine **Normalisierungs**-Schicht **vor** der **Ausgabe** hinzugefügt und eine finale lineare Schicht wird am Ende angewendet, um die Ergebnisse mit den richtigen Dimensionen zu erhalten. Beachten Sie, dass jeder finale Vektor die Größe des verwendeten Vokabulars hat. Dies liegt daran, dass versucht wird, eine Wahrscheinlichkeit pro möglichem Token im Vokabular zu erhalten.
> Darüber hinaus wird eine **Normalisierungs**-Schicht **vor** der **Ausgabe** hinzugefügt und eine finale lineare Schicht wird am Ende angewendet, um die Ergebnisse mit den richtigen Dimensionen zu erhalten. Beachten Sie, dass jeder finale Vektor die Größe des verwendeten Vokabulars hat. Dies liegt daran, dass versucht wird, eine Wahrscheinlichkeit pro mögliches Token im Vokabular zu erhalten.
## Anzahl der zu trainierenden Parameter
@ -583,7 +583,7 @@ total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
```python
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
```
**b. Ausgabeprojektionsebene (`out_head`)**
**b. Ausgabewurfebene (`out_head`)**
- **Ebene:** `nn.Linear(emb_dim, vocab_size, bias=False)`
- **Parameter:** `emb_dim * vocab_size`
@ -608,7 +608,7 @@ total_params = 163,009,536
```
## Text generieren
Ein Modell, das das nächste Token wie das vorherige vorhersagt, benötigt lediglich die letzten Token-Werte aus der Ausgabe (da dies die Werte des vorhergesagten Tokens sein werden), was ein **Wert pro Eintrag im Vokabular** ist. Anschließend wird die `softmax`-Funktion verwendet, um die Dimensionen in Wahrscheinlichkeiten zu normalisieren, die sich auf 1 summieren, und dann wird der Index des größten Eintrags ermittelt, der der Index des Wortes im Vokabular sein wird.
Ein Modell, das das nächste Token wie das vorherige vorhersagt, benötigt lediglich die letzten Token-Werte aus der Ausgabe (da sie die Werte des vorhergesagten Tokens sein werden), was ein **Wert pro Eintrag im Vokabular** sein wird, und dann die `softmax`-Funktion verwenden, um die Dimensionen in Wahrscheinlichkeiten zu normalisieren, die sich auf 1 summieren, und dann den Index des größten Eintrags zu erhalten, der der Index des Wortes im Vokabular sein wird.
Code von [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
```python

View File

@ -0,0 +1,941 @@
# 6. Pre-Training & Laden von Modellen
## Textgenerierung
Um ein Modell zu trainieren, müssen wir sicherstellen, dass das Modell in der Lage ist, neue Tokens zu generieren. Dann werden wir die generierten Tokens mit den erwarteten vergleichen, um das Modell darin zu trainieren, **die Tokens zu lernen, die es generieren muss**.
Wie in den vorherigen Beispielen haben wir bereits einige Tokens vorhergesagt, es ist möglich, diese Funktion für diesen Zweck wiederzuverwenden.
> [!TIP]
> Das Ziel dieser sechsten Phase ist sehr einfach: **Trainiere das Modell von Grund auf neu**. Dafür wird die vorherige LLM-Architektur mit einigen Schleifen über die Datensätze verwendet, wobei die definierten Verlustfunktionen und der Optimierer verwendet werden, um alle Parameter des Modells zu trainieren.
## Textbewertung
Um ein korrektes Training durchzuführen, ist es notwendig, die Vorhersagen für das erwartete Token zu überprüfen. Das Ziel des Trainings ist es, die Wahrscheinlichkeit des korrekten Tokens zu maximieren, was bedeutet, dass seine Wahrscheinlichkeit im Verhältnis zu anderen Tokens erhöht wird.
Um die Wahrscheinlichkeit des korrekten Tokens zu maximieren, müssen die Gewichte des Modells so modifiziert werden, dass diese Wahrscheinlichkeit maximiert wird. Die Aktualisierungen der Gewichte erfolgen über **Backpropagation**. Dies erfordert eine **Verlustfunktion zur Maximierung**. In diesem Fall wird die Funktion die **Differenz zwischen der durchgeführten Vorhersage und der gewünschten** sein.
Statt mit den rohen Vorhersagen zu arbeiten, wird mit einem Logarithmus zur Basis n gearbeitet. Wenn die aktuelle Vorhersage des erwarteten Tokens also 7.4541e-05 war, ist der natürliche Logarithmus (Basis *e*) von **7.4541e-05** ungefähr **-9.5042**.\
Dann muss das Modell für jeden Eintrag mit einer Kontextlänge von 5 Tokens beispielsweise 5 Tokens vorhersagen, wobei die ersten 4 Tokens die letzten des Eingangs sind und das fünfte das vorhergesagte. Daher haben wir in diesem Fall für jeden Eintrag 5 Vorhersagen (auch wenn die ersten 4 im Eingang waren, weiß das Modell dies nicht) mit 5 erwarteten Tokens und damit 5 Wahrscheinlichkeiten, die maximiert werden müssen.
Daher wird nach der Durchführung des natürlichen Logarithmus auf jede Vorhersage der **Durchschnitt** berechnet, das **Minuszeichen entfernt** (dies wird _Kreuzentropieverlust_ genannt) und das ist die **Zahl, die so nah wie möglich an 0 reduziert werden soll**, da der natürliche Logarithmus von 1 gleich 0 ist:
<figure><img src="../../images/image (10) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233">https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233</a></p></figcaption></figure>
Eine weitere Möglichkeit, wie gut das Modell ist, zu messen, wird als Perplexität bezeichnet. **Perplexität** ist eine Metrik, die verwendet wird, um zu bewerten, wie gut ein Wahrscheinlichkeitsmodell eine Probe vorhersagt. In der Sprachmodellierung repräsentiert sie die **Unsicherheit des Modells**, wenn es das nächste Token in einer Sequenz vorhersagt.\
Zum Beispiel bedeutet ein Perplexitätswert von 48725, dass das Modell, wenn es ein Token vorhersagen muss, unsicher ist, welches von 48.725 Tokens im Vokabular das richtige ist.
## Pre-Train Beispiel
Dies ist der anfängliche Code, der in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb) vorgeschlagen wird, manchmal leicht modifiziert.
<details>
<summary>Früherer hier verwendeter Code, der bereits in vorherigen Abschnitten erklärt wurde</summary>
```python
"""
This is code explained before so it won't be exaplained
"""
import tiktoken
import torch
import torch.nn as nn
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]
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
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0, "d_out must be divisible by n_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
def forward(self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.reshape(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"])
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# Shortcut connection for attention block
shortcut = x
x = self.norm1(x)
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
# Shortcut connection for feed-forward block
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
return x
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
return logits
```
</details>
```python
# Download contents to train the data with
import os
import urllib.request
file_path = "the-verdict.txt"
url = "https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt"
if not os.path.exists(file_path):
with urllib.request.urlopen(url) as response:
text_data = response.read().decode('utf-8')
with open(file_path, "w", encoding="utf-8") as file:
file.write(text_data)
else:
with open(file_path, "r", encoding="utf-8") as file:
text_data = file.read()
total_characters = len(text_data)
tokenizer = tiktoken.get_encoding("gpt2")
total_tokens = len(tokenizer.encode(text_data))
print("Data downloaded")
print("Characters:", total_characters)
print("Tokens:", total_tokens)
# Model initialization
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 256, # Shortened context length (orig: 1024)
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-key-value bias
}
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.eval()
print ("Model initialized")
# Functions to transform from tokens to ids and from to ids to tokens
def text_to_token_ids(text, tokenizer):
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
return encoded_tensor
def token_ids_to_text(token_ids, tokenizer):
flat = token_ids.squeeze(0) # remove batch dimension
return tokenizer.decode(flat.tolist())
# Define loss functions
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
return loss
def calc_loss_loader(data_loader, model, device, num_batches=None):
total_loss = 0.
if len(data_loader) == 0:
return float("nan")
elif num_batches is None:
num_batches = len(data_loader)
else:
# Reduce the number of batches to match the total number of batches in the data loader
# if num_batches exceeds the number of batches in the data loader
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
loss = calc_loss_batch(input_batch, target_batch, model, device)
total_loss += loss.item()
else:
break
return total_loss / num_batches
# Apply Train/validation ratio and create dataloaders
train_ratio = 0.90
split_idx = int(train_ratio * len(text_data))
train_data = text_data[:split_idx]
val_data = text_data[split_idx:]
torch.manual_seed(123)
train_loader = create_dataloader_v1(
train_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=True,
shuffle=True,
num_workers=0
)
val_loader = create_dataloader_v1(
val_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=False,
shuffle=False,
num_workers=0
)
# Sanity checks
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the training loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"increase the `training_ratio`")
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the validation loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"decrease the `training_ratio`")
print("Train loader:")
for x, y in train_loader:
print(x.shape, y.shape)
print("\nValidation loader:")
for x, y in val_loader:
print(x.shape, y.shape)
train_tokens = 0
for input_batch, target_batch in train_loader:
train_tokens += input_batch.numel()
val_tokens = 0
for input_batch, target_batch in val_loader:
val_tokens += input_batch.numel()
print("Training tokens:", train_tokens)
print("Validation tokens:", val_tokens)
print("All tokens:", train_tokens + val_tokens)
# Indicate the device to use
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Using {device} device.")
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
# Pre-calculate losses without starting yet
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
train_loss = calc_loss_loader(train_loader, model, device)
val_loss = calc_loss_loader(val_loader, model, device)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
# Functions to train the data
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
eval_freq, eval_iter, start_context, tokenizer):
# Initialize lists to track losses and tokens seen
train_losses, val_losses, track_tokens_seen = [], [], []
tokens_seen, global_step = 0, -1
# Main training loop
for epoch in range(num_epochs):
model.train() # Set model to training mode
for input_batch, target_batch in train_loader:
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward() # Calculate loss gradients
optimizer.step() # Update model weights using loss gradients
tokens_seen += input_batch.numel()
global_step += 1
# Optional evaluation step
if global_step % eval_freq == 0:
train_loss, val_loss = evaluate_model(
model, train_loader, val_loader, device, eval_iter)
train_losses.append(train_loss)
val_losses.append(val_loss)
track_tokens_seen.append(tokens_seen)
print(f"Ep {epoch+1} (Step {global_step:06d}): "
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
# Print a sample text after each epoch
generate_and_print_sample(
model, tokenizer, device, start_context
)
return train_losses, val_losses, track_tokens_seen
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
model.eval()
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train()
return train_loss, val_loss
def generate_and_print_sample(model, tokenizer, device, start_context):
model.eval()
context_size = model.pos_emb.weight.shape[0]
encoded = text_to_token_ids(start_context, tokenizer).to(device)
with torch.no_grad():
token_ids = generate_text(
model=model, idx=encoded,
max_new_tokens=50, context_size=context_size
)
decoded_text = token_ids_to_text(token_ids, tokenizer)
print(decoded_text.replace("\n", " ")) # Compact print format
model.train()
# Start training!
import time
start_time = time.time()
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
num_epochs = 10
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context="Every effort moves you", tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
# Show graphics with the training process
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import math
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
fig, ax1 = plt.subplots(figsize=(5, 3))
ax1.plot(epochs_seen, train_losses, label="Training loss")
ax1.plot(
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
)
ax1.set_xlabel("Epochs")
ax1.set_ylabel("Loss")
ax1.legend(loc="upper right")
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
ax2 = ax1.twiny()
ax2.plot(tokens_seen, train_losses, alpha=0)
ax2.set_xlabel("Tokens seen")
fig.tight_layout()
plt.show()
# Compute perplexity from the loss values
train_ppls = [math.exp(loss) for loss in train_losses]
val_ppls = [math.exp(loss) for loss in val_losses]
# Plot perplexity over tokens seen
plt.figure()
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
plt.xlabel('Tokens Seen')
plt.ylabel('Perplexity')
plt.title('Perplexity over Training')
plt.legend()
plt.show()
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
torch.save({
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
},
"/tmp/model_and_optimizer.pth"
)
```
### Funktionen zur Transformation von Text <--> IDs
Dies sind einige einfache Funktionen, die verwendet werden können, um von Texten aus dem Vokabular in IDs und umgekehrt zu transformieren. Dies ist zu Beginn der Verarbeitung des Textes und am Ende der Vorhersagen erforderlich:
```python
# Functions to transform from tokens to ids and from to ids to tokens
def text_to_token_ids(text, tokenizer):
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
return encoded_tensor
def token_ids_to_text(token_ids, tokenizer):
flat = token_ids.squeeze(0) # remove batch dimension
return tokenizer.decode(flat.tolist())
```
### Textgenerierungsfunktionen
In einem vorherigen Abschnitt wurde eine Funktion vorgestellt, die nur das **wahrscheinlichste Token** nach dem Erhalten der Logits auswählt. Dies bedeutet jedoch, dass für jeden Eingang immer die gleiche Ausgabe generiert wird, was es sehr deterministisch macht.
Die folgende `generate_text`-Funktion wird die Konzepte `top-k`, `temperature` und `multinomial` anwenden.
- Das **`top-k`** bedeutet, dass wir alle Wahrscheinlichkeiten aller Tokens bis auf die Top-k-Tokens auf `-inf` reduzieren. Wenn k=3, werden vor der Entscheidungsfindung nur die 3 wahrscheinlichsten Tokens eine Wahrscheinlichkeit ungleich `-inf` haben.
- Die **`temperature`** bedeutet, dass jede Wahrscheinlichkeit durch den Temperaturwert geteilt wird. Ein Wert von `0.1` wird die höchste Wahrscheinlichkeit im Vergleich zur niedrigsten erhöhen, während eine Temperatur von `5` beispielsweise sie flacher macht. Dies hilft, die Variation in den Antworten zu verbessern, die wir vom LLM erwarten.
- Nach der Anwendung der Temperatur wird erneut eine **`softmax`**-Funktion angewendet, um sicherzustellen, dass alle verbleibenden Tokens eine Gesamtwahrscheinlichkeit von 1 haben.
- Schließlich wird anstelle der Auswahl des Tokens mit der größten Wahrscheinlichkeit die Funktion **`multinomial`** angewendet, um **das nächste Token gemäß den endgültigen Wahrscheinlichkeiten vorherzusagen**. Wenn Token 1 also eine Wahrscheinlichkeit von 70%, Token 2 eine von 20% und Token 3 eine von 10% hatte, wird in 70% der Fälle Token 1 ausgewählt, in 20% der Fälle Token 2 und in 10% der Fälle Token 3.
```python
# Generate text function
def generate_text(model, idx, max_new_tokens, context_size, temperature=0.0, top_k=None, eos_id=None):
# For-loop is the same as before: Get logits, and only focus on last time step
for _ in range(max_new_tokens):
idx_cond = idx[:, -context_size:]
with torch.no_grad():
logits = model(idx_cond)
logits = logits[:, -1, :]
# New: Filter logits with top_k sampling
if top_k is not None:
# Keep only top_k values
top_logits, _ = torch.topk(logits, top_k)
min_val = top_logits[:, -1]
logits = torch.where(logits < min_val, torch.tensor(float("-inf")).to(logits.device), logits)
# New: Apply temperature scaling
if temperature > 0.0:
logits = logits / temperature
# Apply softmax to get probabilities
probs = torch.softmax(logits, dim=-1) # (batch_size, context_len)
# Sample from the distribution
idx_next = torch.multinomial(probs, num_samples=1) # (batch_size, 1)
# Otherwise same as before: get idx of the vocab entry with the highest logits value
else:
idx_next = torch.argmax(logits, dim=-1, keepdim=True) # (batch_size, 1)
if idx_next == eos_id: # Stop generating early if end-of-sequence token is encountered and eos_id is specified
break
# Same as before: append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (batch_size, num_tokens+1)
return idx
```
> [!TIP]
> Es gibt eine gängige Alternative zu `top-k`, die [**`top-p`**](https://en.wikipedia.org/wiki/Top-p_sampling) genannt wird, auch bekannt als Nucleus Sampling. Anstatt k Proben mit der höchsten Wahrscheinlichkeit zu erhalten, **ordnet** es das gesamte resultierende **Wortschatz** nach Wahrscheinlichkeiten und **summiert** sie von der höchsten Wahrscheinlichkeit bis zur niedrigsten, bis ein **Schwellenwert erreicht ist**.
>
> Dann werden **nur diese Wörter** des Wortschatzes entsprechend ihrer relativen Wahrscheinlichkeiten berücksichtigt.
>
> Dies ermöglicht es, keine Anzahl von `k` Proben auszuwählen, da das optimale k in jedem Fall unterschiedlich sein könnte, sondern **nur einen Schwellenwert**.
>
> _Beachten Sie, dass diese Verbesserung im vorherigen Code nicht enthalten ist._
> [!TIP]
> Eine weitere Möglichkeit, den generierten Text zu verbessern, besteht darin, **Beam Search** anstelle der in diesem Beispiel verwendeten gierigen Suche zu verwenden.\
> Im Gegensatz zur gierigen Suche, die das wahrscheinlichste nächste Wort in jedem Schritt auswählt und eine einzelne Sequenz aufbaut, **verfolgt Beam Search die top 𝑘 k am höchsten bewerteten Teilssequenzen** (genannt "Beams") in jedem Schritt. Durch die gleichzeitige Erkundung mehrerer Möglichkeiten balanciert es Effizienz und Qualität und erhöht die Chancen, eine **bessere Gesamtsequenz** zu finden, die von der gierigen Methode aufgrund früher, suboptimaler Entscheidungen übersehen werden könnte.
>
> _Beachten Sie, dass diese Verbesserung im vorherigen Code nicht enthalten ist._
### Verlustfunktionen
Die **`calc_loss_batch`**-Funktion berechnet die Kreuzentropie einer Vorhersage eines einzelnen Batches.\
Die **`calc_loss_loader`** erhält die Kreuzentropie aller Batches und berechnet die **durchschnittliche Kreuzentropie**.
```python
# Define loss functions
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
return loss
def calc_loss_loader(data_loader, model, device, num_batches=None):
total_loss = 0.
if len(data_loader) == 0:
return float("nan")
elif num_batches is None:
num_batches = len(data_loader)
else:
# Reduce the number of batches to match the total number of batches in the data loader
# if num_batches exceeds the number of batches in the data loader
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
loss = calc_loss_batch(input_batch, target_batch, model, device)
total_loss += loss.item()
else:
break
return total_loss / num_batches
```
> [!TIP]
> **Gradient Clipping** ist eine Technik, die verwendet wird, um die **Trainingsstabilität** in großen neuronalen Netzwerken zu verbessern, indem ein **maximaler Schwellenwert** für die Gradientenbeträge festgelegt wird. Wenn Gradienten diesen vordefinierten `max_norm` überschreiten, werden sie proportional skaliert, um sicherzustellen, dass die Aktualisierungen der Modellparameter innerhalb eines handhabbaren Bereichs bleiben, wodurch Probleme wie explodierende Gradienten verhindert und ein kontrollierteres und stabileres Training gewährleistet wird.
>
> _Beachten Sie, dass diese Verbesserung im vorherigen Code nicht enthalten ist._
>
> Überprüfen Sie das folgende Beispiel:
<figure><img src="../../images/image (6) (1).png" alt=""><figcaption></figcaption></figure>
### Daten Laden
Die Funktionen `create_dataloader_v1` und `create_dataloader_v1` wurden bereits in einem vorherigen Abschnitt besprochen.
Hier ist zu beachten, wie definiert ist, dass 90 % des Textes für das Training verwendet werden, während die 10 % für die Validierung verwendet werden, und beide Sätze in 2 verschiedenen Datenladeprogrammen gespeichert werden.\
Beachten Sie, dass manchmal ein Teil des Datensatzes auch für einen Testdatensatz reserviert bleibt, um die Leistung des Modells besser zu bewerten.
Beide Datenladeprogramme verwenden die gleiche Batch-Größe, maximale Länge, Schrittweite und Anzahl der Arbeiter (0 in diesem Fall).\
Die Hauptunterschiede sind die verwendeten Daten und dass der Validator die letzten Daten nicht verwirft und die Daten nicht mischt, da dies für Validierungszwecke nicht erforderlich ist.
Auch die Tatsache, dass **die Schrittweite so groß ist wie die Kontextlänge**, bedeutet, dass es keine Überlappungen zwischen den Kontexten gibt, die zum Trainieren der Daten verwendet werden (verringert Overfitting, aber auch den Trainingsdatensatz).
Darüber hinaus beachten Sie, dass die Batch-Größe in diesem Fall 2 beträgt, um die Daten in 2 Batches zu unterteilen. Das Hauptziel dabei ist es, parallele Verarbeitung zu ermöglichen und den Verbrauch pro Batch zu reduzieren.
```python
train_ratio = 0.90
split_idx = int(train_ratio * len(text_data))
train_data = text_data[:split_idx]
val_data = text_data[split_idx:]
torch.manual_seed(123)
train_loader = create_dataloader_v1(
train_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=True,
shuffle=True,
num_workers=0
)
val_loader = create_dataloader_v1(
val_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=False,
shuffle=False,
num_workers=0
)
```
## Sanity Checks
Das Ziel ist es, zu überprüfen, ob genügend Tokens für das Training vorhanden sind, ob die Formen den erwarteten entsprechen und einige Informationen über die Anzahl der für das Training und die Validierung verwendeten Tokens zu erhalten:
```python
# Sanity checks
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the training loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"increase the `training_ratio`")
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the validation loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"decrease the `training_ratio`")
print("Train loader:")
for x, y in train_loader:
print(x.shape, y.shape)
print("\nValidation loader:")
for x, y in val_loader:
print(x.shape, y.shape)
train_tokens = 0
for input_batch, target_batch in train_loader:
train_tokens += input_batch.numel()
val_tokens = 0
for input_batch, target_batch in val_loader:
val_tokens += input_batch.numel()
print("Training tokens:", train_tokens)
print("Validation tokens:", val_tokens)
print("All tokens:", train_tokens + val_tokens)
```
### Wählen Sie das Gerät für das Training und die Vorberechnungen
Der folgende Code wählt einfach das zu verwendende Gerät aus und berechnet einen Trainingsverlust und einen Validierungsverlust (ohne bisher etwas trainiert zu haben) als Ausgangspunkt.
```python
# Indicate the device to use
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Using {device} device.")
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
# Pre-calculate losses without starting yet
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
train_loss = calc_loss_loader(train_loader, model, device)
val_loss = calc_loss_loader(val_loader, model, device)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
```
### Trainingsfunktionen
Die Funktion `generate_and_print_sample` erhält einfach einen Kontext und generiert einige Tokens, um ein Gefühl dafür zu bekommen, wie gut das Modell zu diesem Zeitpunkt ist. Dies wird von `train_model_simple` in jedem Schritt aufgerufen.
Die Funktion `evaluate_model` wird so häufig aufgerufen, wie es die Trainingsfunktion angibt, und wird verwendet, um den Trainingsverlust und den Validierungsverlust zu diesem Zeitpunkt im Modelltraining zu messen.
Dann ist die große Funktion `train_model_simple` diejenige, die tatsächlich das Modell trainiert. Sie erwartet:
- Den Trainingsdaten-Loader (mit den bereits getrennten und für das Training vorbereiteten Daten)
- Den Validierungs-Loader
- Den **Optimizer**, der während des Trainings verwendet wird: Dies ist die Funktion, die die Gradienten verwendet und die Parameter aktualisiert, um den Verlust zu reduzieren. In diesem Fall, wie Sie sehen werden, wird `AdamW` verwendet, aber es gibt viele weitere.
- `optimizer.zero_grad()` wird aufgerufen, um die Gradienten in jeder Runde zurückzusetzen, um eine Akkumulation zu vermeiden.
- Der **`lr`**-Parameter ist die **Lernrate**, die die **Größe der Schritte** bestimmt, die während des Optimierungsprozesses beim Aktualisieren der Modellparameter unternommen werden. Eine **kleinere** Lernrate bedeutet, dass der Optimizer **kleinere Updates** für die Gewichte vornimmt, was zu einer **genaueren** Konvergenz führen kann, aber das Training **verlangsamen** könnte. Eine **größere** Lernrate kann das Training beschleunigen, birgt jedoch das Risiko, das Minimum der Verlustfunktion **zu überschreiten** (**über** den Punkt hinauszuspringen, an dem die Verlustfunktion minimiert wird).
- **Weight Decay** modifiziert den Schritt der **Verlustberechnung**, indem ein zusätzlicher Term hinzugefügt wird, der große Gewichte bestraft. Dies ermutigt den Optimizer, Lösungen mit kleineren Gewichten zu finden, und balanciert zwischen einer guten Anpassung an die Daten und der Beibehaltung des Modells einfach, um Überanpassung in maschinellen Lernmodellen zu verhindern, indem das Modell davon abgehalten wird, einer einzelnen Eigenschaft zu viel Bedeutung beizumessen.
- Traditionelle Optimierer wie SGD mit L2-Regularisierung koppeln Weight Decay mit dem Gradienten der Verlustfunktion. **AdamW** (eine Variante des Adam-Optimierers) entkoppelt jedoch Weight Decay von der Gradientenaktualisierung, was zu einer effektiveren Regularisierung führt.
- Das Gerät, das für das Training verwendet werden soll
- Die Anzahl der Epochen: Anzahl der Durchläufe über die Trainingsdaten
- Die Evaluierungsfrequenz: Die Häufigkeit, mit der `evaluate_model` aufgerufen wird
- Die Evaluierungsiteration: Die Anzahl der Batches, die verwendet werden, wenn der aktuelle Zustand des Modells bei der Aufruf von `generate_and_print_sample` bewertet wird
- Der Startkontext: Der Satz, der beim Aufruf von `generate_and_print_sample` verwendet werden soll
- Der Tokenizer
```python
# Functions to train the data
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
eval_freq, eval_iter, start_context, tokenizer):
# Initialize lists to track losses and tokens seen
train_losses, val_losses, track_tokens_seen = [], [], []
tokens_seen, global_step = 0, -1
# Main training loop
for epoch in range(num_epochs):
model.train() # Set model to training mode
for input_batch, target_batch in train_loader:
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward() # Calculate loss gradients
optimizer.step() # Update model weights using loss gradients
tokens_seen += input_batch.numel()
global_step += 1
# Optional evaluation step
if global_step % eval_freq == 0:
train_loss, val_loss = evaluate_model(
model, train_loader, val_loader, device, eval_iter)
train_losses.append(train_loss)
val_losses.append(val_loss)
track_tokens_seen.append(tokens_seen)
print(f"Ep {epoch+1} (Step {global_step:06d}): "
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
# Print a sample text after each epoch
generate_and_print_sample(
model, tokenizer, device, start_context
)
return train_losses, val_losses, track_tokens_seen
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
model.eval() # Set in eval mode to avoid dropout
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train() # Back to training model applying all the configurations
return train_loss, val_loss
def generate_and_print_sample(model, tokenizer, device, start_context):
model.eval() # Set in eval mode to avoid dropout
context_size = model.pos_emb.weight.shape[0]
encoded = text_to_token_ids(start_context, tokenizer).to(device)
with torch.no_grad():
token_ids = generate_text(
model=model, idx=encoded,
max_new_tokens=50, context_size=context_size
)
decoded_text = token_ids_to_text(token_ids, tokenizer)
print(decoded_text.replace("\n", " ")) # Compact print format
model.train() # Back to training model applying all the configurations
```
> [!TIP]
> Um die Lernrate zu verbessern, gibt es einige relevante Techniken namens **linear warmup** und **cosine decay.**
>
> **Linear warmup** besteht darin, eine anfängliche Lernrate und eine maximale Lernrate zu definieren und diese nach jeder Epoche konsistent zu aktualisieren. Dies liegt daran, dass das Starten des Trainings mit kleineren Gewichtsanpassungen das Risiko verringert, dass das Modell während seiner Trainingsphase auf große, destabilisierende Anpassungen stößt.\
> **Cosine decay** ist eine Technik, die die **Lernrate allmählich reduziert**, indem sie einer halben Kosinuskurve **nach der Warmup**-Phase folgt, wodurch die Gewichtsanpassungen verlangsamt werden, um **das Risiko des Überschreitens** der Verlustminima zu minimieren und die Trainingsstabilität in späteren Phasen zu gewährleisten.
>
> _Beachten Sie, dass diese Verbesserungen im vorherigen Code nicht enthalten sind._
### Start training
```python
import time
start_time = time.time()
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
num_epochs = 10
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context="Every effort moves you", tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
```
### Drucke die Trainingsentwicklung
Mit der folgenden Funktion ist es möglich, die Entwicklung des Modells während des Trainings zu drucken.
```python
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import math
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
fig, ax1 = plt.subplots(figsize=(5, 3))
ax1.plot(epochs_seen, train_losses, label="Training loss")
ax1.plot(
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
)
ax1.set_xlabel("Epochs")
ax1.set_ylabel("Loss")
ax1.legend(loc="upper right")
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
ax2 = ax1.twiny()
ax2.plot(tokens_seen, train_losses, alpha=0)
ax2.set_xlabel("Tokens seen")
fig.tight_layout()
plt.show()
# Compute perplexity from the loss values
train_ppls = [math.exp(loss) for loss in train_losses]
val_ppls = [math.exp(loss) for loss in val_losses]
# Plot perplexity over tokens seen
plt.figure()
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
plt.xlabel('Tokens Seen')
plt.ylabel('Perplexity')
plt.title('Perplexity over Training')
plt.legend()
plt.show()
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
```
### Modell speichern
Es ist möglich, das Modell + den Optimierer zu speichern, wenn Sie das Training später fortsetzen möchten:
```python
# Save the model and the optimizer for later training
torch.save({
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
},
"/tmp/model_and_optimizer.pth"
)
# Note that this model with the optimizer occupied close to 2GB
# Restore model and optimizer for training
checkpoint = torch.load("/tmp/model_and_optimizer.pth", map_location=device)
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(checkpoint["model_state_dict"])
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4, weight_decay=0.1)
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
model.train(); # Put in training mode
```
Oder nur das Modell, wenn Sie es nur verwenden möchten:
```python
# Save the model
torch.save(model.state_dict(), "model.pth")
# Load it
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(torch.load("model.pth", map_location=device))
model.eval() # Put in eval mode
```
## Laden der GPT2-Gewichte
Es gibt 2 schnelle Skripte, um die GPT2-Gewichte lokal zu laden. Für beide können Sie das Repository [https://github.com/rasbt/LLMs-from-scratch](https://github.com/rasbt/LLMs-from-scratch) lokal klonen, dann:
- Das Skript [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py) lädt alle Gewichte herunter und transformiert die Formate von OpenAI in die, die von unserem LLM erwartet werden. Das Skript ist auch mit der benötigten Konfiguration und mit dem Prompt: "Every effort moves you" vorbereitet.
- Das Skript [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb) ermöglicht es Ihnen, beliebige GPT2-Gewichte lokal zu laden (ändern Sie einfach die `CHOOSE_MODEL`-Variable) und Text aus einigen Prompts vorherzusagen.
## Referenzen
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -3,19 +3,19 @@
## LoRA-Verbesserungen
> [!TIP]
> Die Verwendung von **LoRA reduziert erheblich die benötigte Berechnung**, um **bereits trainierte Modelle fein abzustimmen**.
> Die Verwendung von **LoRA reduziert erheblich die benötigte Berechnung**, um **bereits trainierte Modelle feinzutunen**.
LoRA ermöglicht es, **große Modelle** effizient fein abzustimmen, indem nur ein **kleiner Teil** des Modells geändert wird. Es reduziert die Anzahl der Parameter, die Sie trainieren müssen, und spart **Speicher** und **Rechenressourcen**. Das liegt daran, dass:
LoRA ermöglicht es, **große Modelle** effizient zu feintunen, indem nur ein **kleiner Teil** des Modells geändert wird. Es reduziert die Anzahl der Parameter, die Sie trainieren müssen, und spart **Speicher** und **Rechenressourcen**. Das liegt daran, dass:
1. **Die Anzahl der trainierbaren Parameter reduziert wird**: Anstatt die gesamte Gewichtsmatrix im Modell zu aktualisieren, **teilt** LoRA die Gewichtsmatrix in zwei kleinere Matrizen (genannt **A** und **B**). Dies macht das Training **schneller** und erfordert **weniger Speicher**, da weniger Parameter aktualisiert werden müssen.
1. Das liegt daran, dass anstatt die vollständige Gewichtsanpassung einer Schicht (Matrix) zu berechnen, sie auf ein Produkt von 2 kleineren Matrizen approximiert wird, wodurch die Aktualisierung zur Berechnung reduziert wird:\
1. Das liegt daran, dass anstatt die vollständige Gewichtsanpassung einer Schicht (Matrix) zu berechnen, sie auf ein Produkt von 2 kleineren Matrizen approximiert wird, wodurch die Anpassung reduziert wird, die berechnet werden muss:\
<figure><img src="../../images/image (9) (1).png" alt=""><figcaption></figcaption></figure>
2. **Hält die ursprünglichen Modellgewichte unverändert**: LoRA ermöglicht es Ihnen, die ursprünglichen Modellgewichte gleich zu lassen und nur die **neuen kleinen Matrizen** (A und B) zu aktualisieren. Dies ist hilfreich, da es bedeutet, dass das ursprüngliche Wissen des Modells erhalten bleibt und Sie nur das anpassen, was notwendig ist.
3. **Effizientes aufgabenspezifisches Feintuning**: Wenn Sie das Modell an eine **neue Aufgabe** anpassen möchten, können Sie einfach die **kleinen LoRA-Matrizen** (A und B) trainieren, während der Rest des Modells unverändert bleibt. Dies ist **viel effizienter** als das gesamte Modell neu zu trainieren.
4. **Speichereffizienz**: Nach dem Feintuning müssen Sie anstatt ein **komplett neues Modell** für jede Aufgabe zu speichern, nur die **LoRA-Matrizen** speichern, die im Vergleich zum gesamten Modell sehr klein sind. Dies erleichtert es, das Modell an viele Aufgaben anzupassen, ohne zu viel Speicherplatz zu verwenden.
4. **Speichereffizienz**: Nach dem Feintuning müssen Sie anstelle eines **komplett neuen Modells** für jede Aufgabe nur die **LoRA-Matrizen** speichern, die im Vergleich zum gesamten Modell sehr klein sind. Dies erleichtert es, das Modell an viele Aufgaben anzupassen, ohne zu viel Speicherplatz zu verwenden.
Um LoraLayers anstelle von linearen während eines Feintunings zu implementieren, wird hier dieser Code vorgeschlagen [https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb):
```python

View File

@ -0,0 +1,110 @@
# 7.1. Feinabstimmung für die Klassifikation
## Was ist
Feinabstimmung ist der Prozess, ein **vortrainiertes Modell** zu nehmen, das **allgemeine Sprachmuster** aus großen Datenmengen gelernt hat, und es **anzupassen**, um eine **spezifische Aufgabe** auszuführen oder domänenspezifische Sprache zu verstehen. Dies wird erreicht, indem das Training des Modells auf einem kleineren, aufgabenbezogenen Datensatz fortgesetzt wird, wodurch es seine Parameter anpassen kann, um besser auf die Nuancen der neuen Daten einzugehen, während es das breite Wissen, das es bereits erworben hat, nutzt. Feinabstimmung ermöglicht es dem Modell, genauere und relevantere Ergebnisse in spezialisierten Anwendungen zu liefern, ohne ein neues Modell von Grund auf neu trainieren zu müssen.
> [!TIP]
> Da das Vortrainieren eines LLM, das den Text "versteht", ziemlich teuer ist, ist es normalerweise einfacher und günstiger, Open-Source-vortrainierte Modelle für eine spezifische Aufgabe, die wir möchten, dass es ausführt, fein abzustimmen.
> [!TIP]
> Das Ziel dieses Abschnitts ist es zu zeigen, wie man ein bereits vortrainiertes Modell fein abstimmt, sodass das LLM anstelle von neuem Text die **Wahrscheinlichkeiten angibt, dass der gegebene Text in jede der gegebenen Kategorien eingeordnet wird** (zum Beispiel, ob ein Text Spam ist oder nicht).
## Vorbereitung des Datensatzes
### Größe des Datensatzes
Um ein Modell fein abzustimmen, benötigt man natürlich einige strukturierte Daten, um das LLM zu spezialisieren. Im vorgeschlagenen Beispiel in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb) wird GPT2 fein abgestimmt, um zu erkennen, ob eine E-Mail Spam ist oder nicht, unter Verwendung der Daten von [https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip](https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip)_._
Dieser Datensatz enthält viel mehr Beispiele von "nicht Spam" als von "Spam", daher schlägt das Buch vor, **nur so viele Beispiele von "nicht Spam" wie von "Spam" zu verwenden** (daher werden alle zusätzlichen Beispiele aus den Trainingsdaten entfernt). In diesem Fall waren es 747 Beispiele von jedem.
Dann werden **70%** des Datensatzes für das **Training**, **10%** für die **Validierung** und **20%** für das **Testen** verwendet.
- Der **Validierungsdatensatz** wird während der Trainingsphase verwendet, um die **Hyperparameter** des Modells fein abzustimmen und Entscheidungen über die Modellarchitektur zu treffen, wodurch effektiv Überanpassung verhindert wird, indem Feedback darüber gegeben wird, wie das Modell bei ungesehenen Daten abschneidet. Er ermöglicht iterative Verbesserungen, ohne die endgültige Bewertung zu verzerren.
- Das bedeutet, dass obwohl die in diesem Datensatz enthaltenen Daten nicht direkt für das Training verwendet werden, sie dazu dienen, die besten **Hyperparameter** zu optimieren, sodass dieser Datensatz nicht zur Bewertung der Leistung des Modells wie der Testdatensatz verwendet werden kann.
- Im Gegensatz dazu wird der **Testdatensatz** **nur nach** dem vollständigen Training des Modells und nach Abschluss aller Anpassungen verwendet; er bietet eine unvoreingenommene Bewertung der Fähigkeit des Modells, auf neue, ungesehene Daten zu verallgemeinern. Diese endgültige Bewertung des Testdatensatzes gibt einen realistischen Hinweis darauf, wie das Modell in realen Anwendungen abschneiden wird.
### Länge der Einträge
Da das Trainingsbeispiel Einträge (E-Mail-Text in diesem Fall) derselben Länge erwartet, wurde beschlossen, jeden Eintrag so groß wie den größten zu machen, indem die IDs von `<|endoftext|>` als Padding hinzugefügt werden.
### Modell initialisieren
Verwenden Sie die Open-Source-vortrainierten Gewichte, um das Modell für das Training zu initialisieren. Wir haben dies bereits zuvor getan und folgen den Anweisungen von [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb), können Sie dies leicht tun.
## Klassifikationskopf
In diesem speziellen Beispiel (Vorhersage, ob ein Text Spam ist oder nicht) sind wir nicht daran interessiert, die Feinabstimmung gemäß dem vollständigen Vokabular von GPT2 vorzunehmen, sondern wir möchten nur, dass das neue Modell sagt, ob die E-Mail Spam (1) oder nicht (0) ist. Daher werden wir die **letzte Schicht, die** die Wahrscheinlichkeiten pro Token des Vokabulars angibt, so ändern, dass sie nur die Wahrscheinlichkeiten angibt, Spam oder nicht zu sein (also wie ein Vokabular von 2 Wörtern).
```python
# This code modified the final layer with a Linear one with 2 outs
num_classes = 2
model.out_head = torch.nn.Linear(
in_features=BASE_CONFIG["emb_dim"],
out_features=num_classes
)
```
## Parameter zum Abstimmen
Um schnell abzustimmen, ist es einfacher, nicht alle Parameter, sondern nur einige finale zu optimieren. Das liegt daran, dass bekannt ist, dass die unteren Schichten im Allgemeinen grundlegende Sprachstrukturen und anwendbare Semantiken erfassen. Daher ist es in der Regel ausreichend und schneller, **nur die letzten Schichten abzustimmen**.
```python
# This code makes all the parameters of the model unrtainable
for param in model.parameters():
param.requires_grad = False
# Allow to fine tune the last layer in the transformer block
for param in model.trf_blocks[-1].parameters():
param.requires_grad = True
# Allow to fine tune the final layer norm
for param in model.final_norm.parameters():
param.requires_grad = True
```
## Einträge zur Verwendung für das Training
In den vorherigen Abschnitten wurde das LLM trainiert, indem der Verlust jedes vorhergesagten Tokens reduziert wurde, obwohl fast alle vorhergesagten Tokens im Eingabesatz waren (nur 1 am Ende wurde wirklich vorhergesagt), um dem Modell zu helfen, die Sprache besser zu verstehen.
In diesem Fall interessiert uns nur, ob das Modell Spam ist oder nicht, daher konzentrieren wir uns nur auf das letzte vorhergesagte Token. Daher ist es notwendig, unsere vorherigen Trainingsverlustfunktionen so zu modifizieren, dass nur dieses Token berücksichtigt wird.
Dies ist implementiert in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb) als:
```python
def calc_accuracy_loader(data_loader, model, device, num_batches=None):
model.eval()
correct_predictions, num_examples = 0, 0
if num_batches is None:
num_batches = len(data_loader)
else:
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
with torch.no_grad():
logits = model(input_batch)[:, -1, :] # Logits of last output token
predicted_labels = torch.argmax(logits, dim=-1)
num_examples += predicted_labels.shape[0]
correct_predictions += (predicted_labels == target_batch).sum().item()
else:
break
return correct_predictions / num_examples
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)[:, -1, :] # Logits of last output token
loss = torch.nn.functional.cross_entropy(logits, target_batch)
return loss
```
Beachten Sie, dass wir für jede Charge nur an den **Logits des letzten vorhergesagten Tokens** interessiert sind.
## Vollständiger GPT2 Feinabstimmungs-Klassifizierungscode
Sie finden den gesamten Code zur Feinabstimmung von GPT2 als Spam-Klassifizierer in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/load-finetuned-model.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/load-finetuned-model.ipynb)
## Referenzen
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -61,18 +61,18 @@ Dann ist es notwendig, alle Eingaben und erwarteten Ausgaben für das Training z
- Einige Padding-Tokens mit -100 zu ersetzen, um sie vom Trainingsverlust auszuschließen: Nach dem ersten `endoftext`-Token alle anderen `endoftext`-Tokens durch -100 zu ersetzen (da die Verwendung von `cross_entropy(...,ignore_index=-100)` bedeutet, dass Ziele mit -100 ignoriert werden)
- \[Optional] Auch alle Tokens, die zur Frage gehören, mit -100 zu maskieren, damit das LLM nur lernt, wie man die Antwort generiert. Im Apply Alpaca-Stil bedeutet dies, alles bis `### Response:` zu maskieren.
Damit erstellt, ist es Zeit, die Datenlader für jeden Datensatz (Training, Validierung und Test) zu erstellen.
Damit dies erstellt ist, ist es Zeit, die Datenlader für jeden Datensatz (Training, Validierung und Test) zu erstellen.
## Vortrainiertes LLM laden & Feinabstimmung & Verlustüberprüfung
Es ist notwendig, ein vortrainiertes LLM zu laden, um es feinabzustimmen. Dies wurde bereits auf anderen Seiten besprochen. Dann ist es möglich, die zuvor verwendete Trainingsfunktion zu nutzen, um das LLM feinabzustimmen.
Es ist notwendig, ein vortrainiertes LLM zu laden, um es fein abzustimmen. Dies wurde bereits auf anderen Seiten besprochen. Dann ist es möglich, die zuvor verwendete Trainingsfunktion zu nutzen, um das LLM fein abzustimmen.
Während des Trainings ist es auch möglich zu sehen, wie der Trainingsverlust und der Validierungsverlust während der Epochen variieren, um zu überprüfen, ob der Verlust reduziert wird und ob Overfitting auftritt.\
Denken Sie daran, dass Overfitting auftritt, wenn der Trainingsverlust reduziert wird, der Validierungsverlust jedoch nicht reduziert wird oder sogar steigt. Um dies zu vermeiden, ist das Einfachste, das Training in der Epoche zu stoppen, in der dieses Verhalten beginnt.
## Antwortqualität
Da dies keine Klassifizierungsfeinabstimmung ist, bei der man den Verlustvariationen mehr vertrauen kann, ist es auch wichtig, die Qualität der Antworten im Testdatensatz zu überprüfen. Daher wird empfohlen, die generierten Antworten aus allen Testdatensätzen zu sammeln und **ihre Qualität manuell zu überprüfen**, um festzustellen, ob es falsche Antworten gibt (beachten Sie, dass es möglich ist, dass das LLM das Format und die Syntax des Antwortsatzes korrekt erstellt, aber eine völlig falsche Antwort gibt. Die Verlustvariation wird dieses Verhalten nicht widerspiegeln).\
Da dies keine Klassifizierungsfeinabstimmung ist, bei der man den Verlustvariationen mehr vertrauen kann, ist es auch wichtig, die Qualität der Antworten im Testdatensatz zu überprüfen. Daher wird empfohlen, die generierten Antworten aus allen Testdatensätzen zu sammeln und **ihre Qualität manuell zu überprüfen**, um zu sehen, ob es falsche Antworten gibt (beachten Sie, dass es möglich ist, dass das LLM das Format und die Syntax des Antwortsatzes korrekt erstellt, aber eine völlig falsche Antwort gibt. Die Verlustvariation wird dieses Verhalten nicht widerspiegeln).\
Beachten Sie, dass es auch möglich ist, diese Überprüfung durchzuführen, indem man die generierten Antworten und die erwarteten Antworten an **andere LLMs weitergibt und sie bittet, die Antworten zu bewerten**.
Weitere Tests zur Überprüfung der Qualität der Antworten:
@ -80,21 +80,21 @@ Weitere Tests zur Überprüfung der Qualität der Antworten:
1. **Messung des Massive Multitask Language Understanding (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLU bewertet das Wissen und die Problemlösungsfähigkeiten eines Modells in 57 Fächern, einschließlich Geisteswissenschaften, Naturwissenschaften und mehr. Es verwendet Multiple-Choice-Fragen, um das Verständnis auf verschiedenen Schwierigkeitsgraden zu bewerten, von elementar bis fortgeschritten.
2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Diese Plattform ermöglicht es Benutzern, Antworten von verschiedenen Chatbots nebeneinander zu vergleichen. Benutzer geben einen Prompt ein, und mehrere Chatbots generieren Antworten, die direkt verglichen werden können.
3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEval ist ein automatisiertes Bewertungsframework, bei dem ein fortgeschrittenes LLM wie GPT-4 die Antworten anderer Modelle auf verschiedene Prompts bewertet.
4. **Allgemeine Bewertung des Sprachverständnisses (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE ist eine Sammlung von neun Aufgaben zum Verständnis natürlicher Sprache, einschließlich Sentiment-Analyse, textueller Folgerung und Fragebeantwortung.
4. **Allgemeine Sprachverständnisbewertung (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE ist eine Sammlung von neun Aufgaben zum Verständnis natürlicher Sprache, einschließlich Sentiment-Analyse, textueller Folgerung und Fragebeantwortung.
5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** Aufbauend auf GLUE umfasst SuperGLUE herausforderndere Aufgaben, die für aktuelle Modelle schwierig sein sollen.
6. **Über das Imitationsspiel-Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench ist ein großangelegtes Benchmark mit über 200 Aufgaben, die die Fähigkeiten eines Modells in Bereichen wie Schlussfolgern, Übersetzen und Fragebeantwortung testen.
7. **Ganzheitliche Bewertung von Sprachmodellen (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM bietet eine umfassende Bewertung über verschiedene Metriken wie Genauigkeit, Robustheit und Fairness.
6. **Beyond the Imitation Game Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench ist ein großangelegter Benchmark mit über 200 Aufgaben, die die Fähigkeiten eines Modells in Bereichen wie Schlussfolgern, Übersetzen und Fragebeantwortung testen.
7. **Holistic Evaluation of Language Models (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM bietet eine umfassende Bewertung über verschiedene Metriken wie Genauigkeit, Robustheit und Fairness.
8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Ein Open-Source-Bewertungsframework von OpenAI, das das Testen von KI-Modellen bei benutzerdefinierten und standardisierten Aufgaben ermöglicht.
9. [**HumanEval**](https://github.com/openai/human-eval)**:** Eine Sammlung von Programmierproblemen, die zur Bewertung der Codegenerierungsfähigkeiten von Sprachmodellen verwendet wird.
10. **Stanford Question Answering Dataset (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuAD besteht aus Fragen zu Wikipedia-Artikeln, bei denen Modelle den Text verstehen müssen, um genau zu antworten.
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Ein großangelegter Datensatz von Trivia-Fragen und -Antworten sowie Beweis-Dokumenten.
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Ein großangelegter Datensatz von Triviafragen und -antworten sowie Beweisunterlagen.
und viele, viele mehr
## Code zur Feinabstimmung von Anweisungen
## Follow instructions fine-tuning code
Ein Beispiel für den Code zur Durchführung dieser Feinabstimmung finden Sie unter [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py)
Sie finden ein Beispiel für den Code zur Durchführung dieser Feinabstimmung unter [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py)
## Referenzen
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -2,7 +2,7 @@
**Dies sind meine Notizen aus dem sehr empfohlenen Buch** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **mit einigen zusätzlichen Informationen.**
## Grundinformationen
## Grundlegende Informationen
Sie sollten mit dem Lesen dieses Beitrags beginnen, um einige grundlegende Konzepte zu verstehen, die Sie wissen sollten:
@ -13,13 +13,13 @@ Sie sollten mit dem Lesen dieses Beitrags beginnen, um einige grundlegende Konze
## 1. Tokenisierung
> [!TIP]
> Das Ziel dieser Anfangsphase ist sehr einfach: **Teilen Sie die Eingabe in Tokens (IDs) auf eine Weise, die Sinn macht**.
> Das Ziel dieser ersten Phase ist sehr einfach: **Teilen Sie die Eingabe in Tokens (IDs) auf eine Weise, die Sinn macht**.
{{#ref}}
1.-tokenizing.md
{{#endref}}
## 2. Datensampling
## 2. Datenstichproben
> [!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.**
@ -64,7 +64,7 @@ Sie sollten mit dem Lesen dieses Beitrags beginnen, um einige grundlegende Konze
## 6. Vortraining & Laden von Modellen
> [!TIP]
> Das Ziel dieser sechsten Phase ist sehr einfach: **Trainieren Sie das Modell von Grund auf neu**. Dazu wird die vorherige LLM-Architektur mit einigen Schleifen über die Datensätze unter Verwendung der definierten Verlustfunktionen und des Optimierers verwendet, um alle Parameter des Modells zu trainieren.
> Das Ziel dieser sechsten Phase ist sehr einfach: **Trainieren Sie das Modell von Grund auf neu**. Dazu wird die vorherige LLM-Architektur mit einigen Schleifen über die Datensätze verwendet, wobei die definierten Verlustfunktionen und der Optimierer verwendet werden, um alle Parameter des Modells zu trainieren.
{{#ref}}
6.-pre-training-and-loading-models.md
@ -91,7 +91,7 @@ Sie sollten mit dem Lesen dieses Beitrags beginnen, um einige grundlegende Konze
## 7.2. Feintuning zur Befolgung von Anweisungen
> [!TIP]
> Das Ziel dieses Abschnitts ist zu zeigen, wie man ein **bereits vortrainiertes Modell fein abstimmt, um Anweisungen zu befolgen**, anstatt nur Text zu generieren, zum Beispiel, um auf Aufgaben als Chatbot zu reagieren.
> Das Ziel dieses Abschnitts ist zu zeigen, wie man ein **bereits vortrainiertes Modell fein abstimmt, um Anweisungen zu befolgen**, anstatt nur Text zu generieren, zum Beispiel, um auf Aufgaben als Chatbot zu antworten.
{{#ref}}
7.2.-fine-tuning-to-follow-instructions.md