- Die Encoder-Decoder-Architektur des Transformer[1] besteht aus sechs präzise konzipierten Komponenten: Multi-Head Attention, Feed-Forward-Netzwerk, Residualverbindungen, Layer-Normalisierung[2], Positionskodierung und Maskierungsmechanismus
- Drei große Pre-Training-Paradigmen — Masked Language Model (BERT[5]), Causal Language Model (GPT[4]), Denoising-Rekonstruktion (T5[7]/BART[8]) — haben jeweils eigene Stärken und bilden die Grundlage aller modernen Large Language Models
- Der Transformer hat sich von NLP auf Computer Vision (ViT[11]), Multimodalität (CLIP[13]), Proteinfaltung, Robotersteuerung und weitere Bereiche ausgeweitet und ist zum universellen Rechenprimitiv der KI geworden
- Dieser Artikel enthält zwei Google Colab Praxisübungen: Ein Transformer-Übersetzungsmodell von Grund auf erstellen sowie ViT-Bildklassifikation mit Feintuning und Feature-Visualisierung
I. Warum der Transformer alles verändert hat
Als Vaswani et al. 2017 den Transformer in „Attention Is All You Need"[1] vorstellten, war er lediglich ein Modell für maschinelle Übersetzung. Doch innerhalb weniger Jahre wurde diese Architektur zur universellen Infrastruktur der künstlichen Intelligenz — GPT-4 nutzt sie zur Textgenerierung, DALL-E zur Bildgenerierung, AlphaFold zur Vorhersage von Proteinstrukturen und RT-2 zur Steuerung von Robotern.
Warum kann der Transformer so unterschiedliche Bereiche vereinen? Weil er ein äußerst vielseitiges Rechenprimitiv bereitstellt: Eine Gruppe von Elementen kann über den Attention-Mechanismus dynamisch miteinander kommunizieren. Unabhängig davon, ob es sich um Text-Token, Bild-Patches, Aminosäurereste oder Roboteraktionen handelt — der Transformer kann die Beziehungen zwischen ihnen erlernen.
Dieser Artikel analysiert den Transformer als vollständiges System im Detail — nicht nur den Attention-Mechanismus (der bereits im Vollständigen Leitfaden zum Self-Attention-Mechanismus ausführlich behandelt wird), sondern auch alle Engineering-Entscheidungen, die ihn trainierbar, skalierbar und transferierbar machen.
II. Architektur-Anatomie: Die sechs Kernkomponenten
Der ursprüngliche Transformer ist eine Encoder-Decoder-Architektur, deren Kernkomponenten in allen Varianten wiederholt auftreten:
Vollständige Transformer-Architektur:
Encoder (×N Schichten): Decoder (×N Schichten):
┌─────────────────────┐ ┌─────────────────────┐
│ Multi-Head │ │ Masked Multi-Head │
│ Self-Attention │ │ Self-Attention │
│ + Residual + LN │ │ + Residual + LN │
├─────────────────────┤ ├─────────────────────┤
│ Feed-Forward │ │ Cross-Attention │
│ Network (FFN) │ │ (Q=Dec, K,V=Enc) │
│ + Residual + LN │ │ + Residual + LN │
└─────────────────────┘ ├─────────────────────┤
│ Feed-Forward │
│ Network (FFN) │
│ + Residual + LN │
└─────────────────────┘
Komponente 1: Multi-Head Self-Attention
Ermöglicht es jeder Position, direkt auf alle anderen Positionen in der Sequenz zu achten. Der Encoder verwendet bidirektionale Attention; der Decoder verwendet kausale Maskierung (kann nur vergangene Token sehen). Eine detaillierte mathematische Herleitung finden Sie im Vollständigen Leitfaden zum Self-Attention-Mechanismus.
Komponente 2: Feed-Forward-Netzwerk (FFN)
Die Attention-Ausgabe jeder Schicht durchläuft ein zweischichtiges vollständig verbundenes Netzwerk — dies ist der „Denkraum" des Transformers:
FFN(x) = max(0, x · W₁ + b₁) · W₂ + b₂
Ursprüngliche Konfiguration: d_model=512, d_ff=2048 (4-fache Erweiterung)
Moderne Konfiguration: SwiGLU-Aktivierung (LLaMA) oder GELU (GPT) anstelle von ReLU
Forschungsergebnisse zeigen, dass FFN-Schichten große Mengen an „Faktenwissen" speichern. In Modellen wie GPT machen die FFN-Parameter etwa 2/3 der Gesamtparameter aus und sind der Hauptträger der Speicherfähigkeit des Modells.
Komponente 3: Residualverbindungen
Die Eingabe jeder Teilschicht wird zu deren Ausgabe addiert: output = sublayer(x) + x. Dies ermöglicht den direkten Gradientenfluss über Schichten hinweg und ist entscheidend für das Training tiefer Transformer (100+ Schichten).
Komponente 4: Layer-Normalisierung
Die von Ba et al.[2] vorgeschlagene Layer-Normalisierung normalisiert die Darstellung jedes Tokens auf Null-Mittelwert und Einheitsvarianz. Der ursprüngliche Transformer verwendet Post-LN (Normalisierung nach der Residualverbindung), aber Xiong et al.[3] haben gezeigt, dass Pre-LN (Normalisierung vor der Residualverbindung) das Aufwärmen der Lernrate überflüssig macht und das Training stabiler gestaltet. Moderne große Modelle verwenden fast ausnahmslos Pre-LN oder RMSNorm.
| Konfiguration | Reihenfolge | Eigenschaften | Repräsentative Modelle |
|---|---|---|---|
| Post-LN | Attn → Add → LN | Erfordert Lernraten-Aufwärmung, kann aber letztlich bessere Leistung erzielen | Ursprünglicher Transformer, BERT |
| Pre-LN | LN → Attn → Add | Stabiles Training, keine Aufwärmung erforderlich | GPT-2, GPT-3 |
| RMSNorm | Ähnlich Pre-LN | Nur Skalierung (keine Zentrierung), schnellere Berechnung | LLaMA, PaLM |
Komponente 5: Positionskodierung
Injiziert Ordnungsinformationen in die Sequenz. Der ursprüngliche Transformer verwendet sinusförmige Positionskodierung[1], moderne Modelle verwenden überwiegend Rotary Position Embedding (RoPE) oder ALiBi.
Komponente 6: Cross-Attention und Maskierung
Die Cross-Attention im Decoder ermöglicht es dem Generierungsprozess, die Ausgabe des Encoders zu „konsultieren": Die Query stammt vom Decoder, Key und Value stammen vom Encoder. Die kausale Maskierung stellt sicher, dass der Decoder bei der Generierung des t-ten Tokens nur die vorherigen t-1 Token sehen kann.
III. Die Kunst des Trainings: Den Transformer zur Konvergenz bringen
Das Training des Transformers ist anspruchsvoller als bei herkömmlichen Modellen. Einige scheinbar unscheinbare Techniken aus der Originalveröffentlichung[1] sind tatsächlich von entscheidender Bedeutung:
| Technik | Details | Warum sie wichtig ist |
|---|---|---|
| Lernraten-Aufwärmung | Linearer Anstieg in den ersten 4000 Schritten, danach Quadratwurzel-Abklingen | Vermeidet Gradientenexplosion bei Post-LN in der frühen Trainingsphase[3] |
| Label Smoothing | Zielverteilung wechselt von One-Hot zu 0,9/0,1-Mischung | Verbessert die Generalisierungsfähigkeit, verhindert Überconfidence |
| Dropout | Je 0,1 nach Attention-Gewichten + FFN + Residual | Verhindert Overfitting, bietet Regularisierung |
| Adam + β₂=0,98 | Höherer Momentum-Abklingfaktor | Stabilisiert die Gradienten der Attention-Matrix |
| Gradientenclipping | Globales Gradientennorm-Clipping auf 1,0 | Verhindert Gradientenexplosion |
IV. Pre-Training-Paradigmen: Die Gabelung dreier Wege
Die wahre Stärke des Transformers entfaltet sich im Paradigma „Pre-Training → Feintuning". Drei wesentliche Pre-Training-Strategien haben jeweils eigene Vorzüge:
Weg eins: Masked Language Model (MLM) — BERT
BERT[5] verwendet eine Encoder-only-Architektur und maskiert zufällig 15 % der Token, die das Modell vorhersagen soll:
Eingabe: The [MASK] sat on the [MASK]
Ziel: Vorhersage [MASK] = "cat" und [MASK] = "mat"
Vorteil: Bidirektionaler Kontext — jedes Token kann alle Nachbarn links und rechts sehen
Nachteil: [MASK] beim Training, aber nicht bei der Inferenz → Pre-Training-Feintuning-Diskrepanz
Weg zwei: Causal Language Model (CLM) — GPT
GPT[4] verwendet eine Decoder-only-Architektur und sagt das nächste Token vorher:
Eingabe: The cat sat on the
Ziel: Vorhersage des nächsten Tokens = "mat"
Vorteil: Natürlich geeignet für Generierungsaufgaben, keine Pre-Training-Feintuning-Diskrepanz
Nachteil: Unidirektionaler Kontext — jedes Token kann nur nach links sehen
Die Version von GPT-3[6] mit 175 Milliarden Parametern demonstrierte In-Context Learning — ohne Feintuning kann das Modell Aufgaben allein durch wenige Beispiele im Prompt ausführen. Dies hat die Art und Weise, wie KI eingesetzt wird, grundlegend verändert.
Weg drei: Denoising-Rekonstruktion — T5 / BART
T5[7] vereinheitlicht alle NLP-Aufgaben im Text-to-Text-Format und verwendet die vollständige Encoder-Decoder-Architektur:
Übersetzung: "translate English to German: That is good" → "Das ist gut"
Zusammenfassung: "summarize: Langer Text..." → "Kurze Zusammenfassung"
Klassifikation: "sentiment: This movie is great" → "positive"
Pre-Training: Span Corruption — zufälliges Maskieren zusammenhängender Spans zur Rekonstruktion durch das Modell
BART[8] hingegen verwendet vielfältigere Denoising-Strategien — Löschen, Umordnen, Maskieren, Rotieren — und trainiert den Encoder-Decoder, den Originaltext aus beschädigtem Text zu rekonstruieren.
| Paradigma | Architektur | Vertreter | Am besten geeignet für |
|---|---|---|---|
| MLM | Encoder-only | BERT[5] | Klassifikation, NER, Frage-Antwort |
| CLM | Decoder-only | GPT[4], LLaMA[9] | Generierung, Dialog, Code |
| Denoising | Encoder-Decoder | T5[7], BART[8] | Übersetzung, Zusammenfassung, strukturierte Generierung |
V. Evolutionsdiagramm der wichtigsten Varianten
Vom ursprünglichen Transformer aus dem Jahr 2017 bis zu GPT-4, Claude und Gemini im Jahr 2024 hat die Architektur eine dramatische Evolution durchlaufen:
| Modell | Jahr | Parameter | Architektur | Schlüsselinnovation |
|---|---|---|---|---|
| Transformer[1] | 2017 | 65M | Enc-Dec | Self-Attention ersetzt RNN |
| GPT-1[4] | 2018 | 117M | Dec-only | Generatives Pre-Training + diskriminatives Feintuning |
| BERT[5] | 2019 | 340M | Enc-only | Masked Language Model, bidirektionaler Kontext |
| T5[7] | 2020 | 11B | Enc-Dec | Einheitliches Text-to-Text-Framework |
| GPT-3[6] | 2020 | 175B | Dec-only | Few-shot In-Context Learning |
| PaLM[10] | 2022 | 540B | Dec-only | Pathways-System, SwiGLU, RoPE |
| LLaMA[9] | 2023 | 65B | Dec-only | Öffentliche Daten, rechenoptimales Training |
Ein klarer Trend zeichnet sich ab: Die Decoder-only-Architektur hat sich als Mainstream für Large Language Models durchgesetzt. LLaMA[9] hat eine wichtige Erkenntnis belegt: Bei gleichem Rechenbudget ist es effektiver, ein kleineres Modell mit mehr Daten zu trainieren, als ein riesiges Modell mit weniger Daten. LLaMA-13B wurde ausschließlich mit öffentlichen Daten trainiert und übertraf dennoch GPT-3 (175B) bei den meisten Benchmarks.
VI. Der Transformer erobert Computer Vision und Multimodalität
Vision Transformer (ViT)
ViT[11] teilt Bilder in 16×16 Patches auf, wobei jeder Patch als Token behandelt und direkt in den Transformer-Encoder eingespeist wird. DeiT[12] führte die Wissensdestillation ein und löste damit das Problem, dass ViT riesige Datenmengen (JFT-300M) zum Training benötigt — wettbewerbsfähige Leistung wird allein mit ImageNet erreicht.
CLIP: Vereinigung von Vision und Sprache
CLIP[13] trainiert gleichzeitig einen Bild-Transformer und einen Text-Transformer mit kontrastivem Lernen und erlernt alignierte visuell-sprachliche Repräsentationen. Dies ermöglicht es dem Modell, Bilder mittels natürlichsprachlicher Beschreibungen zu „suchen" — Zero-Shot-Bildklassifikation, Bildretrieval und sogar den Einsatz als Bedingungsencoder für Stable Diffusion.
Flamingo: Multimodales Few-Shot-Lernen
Flamingo[14] überbrückt einen eingefrorenen visuellen Encoder und ein eingefrorenes Sprachmodell durch Cross-Attention-Gating und ermöglicht so multimodales Few-Shot-Lernen — mit nur wenigen Beispielbildern und Fragen können Fragen zu neuen Bildern beantwortet werden.
VII. Training im großen Maßstab: Die Kunst des Engineering
Das Training eines Transformers mit Hunderten von Milliarden Parametern bringt Engineering-Herausforderungen mit sich, die weit über das Modelldesign hinausgehen. Im Folgenden werden die Schlüsseltechnologien vorgestellt:
| Technologie | Kernidee | Effekt |
|---|---|---|
| Datenparallelismus | Jede GPU erhält eine Modellkopie, die Daten werden aufgeteilt | Lineare Beschleunigung, aber jede GPU benötigt vollständigen Modellspeicher |
| Tensorparallelismus[15] | Matrixmultiplikation einer einzelnen Schicht wird auf mehrere GPUs aufgeteilt | Parallelisierung innerhalb einer Schicht, niedrige Latenz |
| Pipeline-Parallelismus | Verschiedene Schichten werden auf verschiedene GPUs verteilt | Reduziert den Speicher pro GPU, hat aber Blasen (Bubbles) |
| ZeRO[16] | Optimizer-Zustände/Gradienten/Parameter werden GPU-übergreifend partitioniert | Speicher sinkt auf 1/N, Billionen-Parameter werden möglich |
| Mixed Precision | Berechnung in FP16/BF16, Akkumulation in FP32 | Halbierter Speicher, doppelte Geschwindigkeit |
| Gradient Checkpointing | Nur Aktivierungen einiger Schichten werden gespeichert, Rückwärtsdurchlauf berechnet neu | Speicher O(√n), Kosten ein zusätzlicher Vorwärtsdurchlauf |
Megatron-LM[15] erreicht auf 512 GPUs eine Skalierungseffizienz von 76 %. ZeRO[16] eliminiert die Speicherredundanz im Datenparallelismus — bei herkömmlichem Datenparallelismus speichert jede GPU den vollständigen Optimizer-Zustand (Adam benötigt 16 Bytes/Parameter), ZeRO partitioniert diesen auf alle GPUs, sodass Billionen-Parameter-Modelle auf vernünftiger Hardware trainiert werden können.
VIII. Neueste Entwicklungen: MoE und die Post-Transformer-Ära
Mixture-of-Experts-Modelle (Mixture of Experts)
Switch Transformer[17] stellte eine kühne Idee vor: Nicht jedes Token muss alle Parameter durchlaufen. MoE ersetzt jede FFN-Schicht durch mehrere „Experten"-Netzwerke, und jedes Token wird nur an einen Experten weitergeleitet:
MoE Layer:
Eingabe-Token → Router (kleiner Klassifikator) → Auswahl des Top-1-Experten
Experte 1: FFN₁(x) ← verarbeitet bestimmte Token
Experte 2: FFN₂(x) ← verarbeitet andere Token
...
Experte N: FFNₙ(x)
Ergebnis: Billionen Parameter, aber jedes Token aktiviert nur ~1/N der Parameter
→ Riesige Parameterzahl, aber Rechenaufwand vergleichbar mit dichtem Modell
Mixtral 8x7B ist ein erfolgreiches Beispiel für MoE — ein 46B-Parameter-Modell bestehend aus 8 Experten mit je 7B Parametern, wobei jedes Token tatsächlich nur etwa 12B Parameter nutzt, die Leistung aber einem dichten 70B-Modell entspricht.
Post-Transformer: State-Space-Modelle
Mamba[18] fordert mit selektiven State-Space-Modellen (Selective SSM) die Dominanz des Transformers heraus. Es verarbeitet Sequenzen mit linearer Zeitkomplexität und erreicht bei vergleichbarer Größe den 5-fachen Durchsatz eines Transformers. Mamba-3B erreicht die Leistung eines Transformer-6B, was darauf hindeutet, dass Self-Attention möglicherweise nicht der einzige Weg ist.
Doch der Transformer schlägt zurück: Hybridarchitekturen wie Jamba verwenden Transformer-Schichten und Mamba-Schichten abwechselnd und kombinieren die Vorteile beider Ansätze. Der künftige Mainstream könnte weder ein reiner Transformer noch ein reines SSM sein, sondern eine Hybridarchitektur.
IX. Hands-on Lab 1: Ein Transformer-Übersetzungsmodell von Grund auf erstellen (Google Colab)
Das folgende Experiment implementiert eine vollständige Encoder-Decoder Transformer-Architektur von Grund auf für eine einfache Englisch-Deutsch-Übersetzungsaufgabe.
# ============================================================
# Lab 1: Transformer von Grund auf — Englisch-Deutsch-Übersetzungsmodell
# Umgebung: Google Colab (GPU)
# ============================================================
# --- 0. Installation ---
!pip install -q datasets tokenizers
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import numpy as np
from torch.utils.data import DataLoader, Dataset
from datasets import load_dataset
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Device: {device}")
# --- 1. Datenvorbereitung ---
# Verwendung von Tatoeba Englisch-Deutsch Satzpaaren (einfache kurze Sätze)
dataset = load_dataset("Helsinki-NLP/tatoeba_mt", "deu-eng", split="test")
pairs = [(ex["sourceString"], ex["targetString"]) for ex in dataset]
pairs = [p for p in pairs if len(p[0].split()) <= 15 and len(p[1].split()) <= 15]
pairs = pairs[:8000]
print(f"Sentence pairs: {len(pairs)}")
print(f"Example: '{pairs[0][0]}' → '{pairs[0][1]}'")
# Einfacher zeichenbasierter Tokenizer
class SimpleTokenizer:
def __init__(self):
self.char2idx = {"<pad>": 0, "<bos>": 1, "<eos>": 2, "<unk>": 3}
self.idx2char = {0: "<pad>", 1: "<bos>", 2: "<eos>", 3: "<unk>"}
def fit(self, texts):
for text in texts:
for ch in text:
if ch not in self.char2idx:
idx = len(self.char2idx)
self.char2idx[ch] = idx
self.idx2char[idx] = ch
def encode(self, text, max_len=80):
ids = [1] + [self.char2idx.get(ch, 3) for ch in text[:max_len-2]] + [2]
return ids
def decode(self, ids):
chars = []
for idx in ids:
if idx == 2: break
if idx > 2: chars.append(self.idx2char.get(idx, "?"))
return "".join(chars)
@property
def vocab_size(self):
return len(self.char2idx)
src_tokenizer = SimpleTokenizer()
tgt_tokenizer = SimpleTokenizer()
src_tokenizer.fit([p[0] for p in pairs])
tgt_tokenizer.fit([p[1] for p in pairs])
print(f"Source vocab: {src_tokenizer.vocab_size}, Target vocab: {tgt_tokenizer.vocab_size}")
MAX_LEN = 80
class TranslationDataset(Dataset):
def __init__(self, pairs, src_tok, tgt_tok):
self.pairs = pairs
self.src_tok = src_tok
self.tgt_tok = tgt_tok
def __len__(self):
return len(self.pairs)
def __getitem__(self, idx):
src, tgt = self.pairs[idx]
return self.src_tok.encode(src, MAX_LEN), self.tgt_tok.encode(tgt, MAX_LEN)
def collate_fn(batch):
src_batch, tgt_batch = zip(*batch)
src_max = max(len(s) for s in src_batch)
tgt_max = max(len(t) for t in tgt_batch)
src_padded = torch.zeros(len(batch), src_max, dtype=torch.long)
tgt_padded = torch.zeros(len(batch), tgt_max, dtype=torch.long)
for i, (s, t) in enumerate(zip(src_batch, tgt_batch)):
src_padded[i, :len(s)] = torch.tensor(s)
tgt_padded[i, :len(t)] = torch.tensor(t)
return src_padded, tgt_padded
train_ds = TranslationDataset(pairs[:7000], src_tokenizer, tgt_tokenizer)
val_ds = TranslationDataset(pairs[7000:], src_tokenizer, tgt_tokenizer)
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_ds, batch_size=64, collate_fn=collate_fn)
# --- 2. Transformer-Modell (vollständiger Encoder-Decoder) ---
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=200):
super().__init__()
pe = torch.zeros(max_len, d_model)
pos = torch.arange(0, max_len).unsqueeze(1).float()
div = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(pos * div)
pe[:, 1::2] = torch.cos(pos * div)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :x.size(1)]
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.d_k = d_model // n_heads
self.n_heads = n_heads
self.W_Q = nn.Linear(d_model, d_model)
self.W_K = nn.Linear(d_model, d_model)
self.W_V = nn.Linear(d_model, d_model)
self.W_O = nn.Linear(d_model, d_model)
def forward(self, Q, K, V, mask=None):
B = Q.size(0)
Q = self.W_Q(Q).view(B, -1, self.n_heads, self.d_k).transpose(1, 2)
K = self.W_K(K).view(B, -1, self.n_heads, self.d_k).transpose(1, 2)
V = self.W_V(V).view(B, -1, self.n_heads, self.d_k).transpose(1, 2)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, V)
out = out.transpose(1, 2).contiguous().view(B, -1, self.n_heads * self.d_k)
return self.W_O(out)
class EncoderLayer(nn.Module):
def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, n_heads)
self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.ReLU(), nn.Linear(d_ff, d_model))
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.drop = nn.Dropout(dropout)
def forward(self, x, src_mask):
x = self.norm1(x + self.drop(self.self_attn(x, x, x, src_mask)))
x = self.norm2(x + self.drop(self.ffn(x)))
return x
class DecoderLayer(nn.Module):
def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, n_heads)
self.cross_attn = MultiHeadAttention(d_model, n_heads)
self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.ReLU(), nn.Linear(d_ff, d_model))
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.drop = nn.Dropout(dropout)
def forward(self, x, enc_out, src_mask, tgt_mask):
x = self.norm1(x + self.drop(self.self_attn(x, x, x, tgt_mask)))
x = self.norm2(x + self.drop(self.cross_attn(x, enc_out, enc_out, src_mask)))
x = self.norm3(x + self.drop(self.ffn(x)))
return x
class Transformer(nn.Module):
def __init__(self, src_vocab, tgt_vocab, d_model=128, n_heads=4,
n_layers=3, d_ff=256, dropout=0.1):
super().__init__()
self.src_emb = nn.Embedding(src_vocab, d_model, padding_idx=0)
self.tgt_emb = nn.Embedding(tgt_vocab, d_model, padding_idx=0)
self.pos_enc = PositionalEncoding(d_model)
self.encoder = nn.ModuleList([EncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)])
self.decoder = nn.ModuleList([DecoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)])
self.output_proj = nn.Linear(d_model, tgt_vocab)
self.dropout = nn.Dropout(dropout)
self.d_model = d_model
def make_src_mask(self, src):
return (src != 0).unsqueeze(1).unsqueeze(2)
def make_tgt_mask(self, tgt):
B, N = tgt.shape
pad_mask = (tgt != 0).unsqueeze(1).unsqueeze(2)
causal_mask = torch.tril(torch.ones(N, N, device=tgt.device)).bool()
return pad_mask & causal_mask.unsqueeze(0).unsqueeze(0)
def encode(self, src):
src_mask = self.make_src_mask(src)
x = self.dropout(self.pos_enc(self.src_emb(src) * math.sqrt(self.d_model)))
for layer in self.encoder:
x = layer(x, src_mask)
return x, src_mask
def decode(self, tgt, enc_out, src_mask):
tgt_mask = self.make_tgt_mask(tgt)
x = self.dropout(self.pos_enc(self.tgt_emb(tgt) * math.sqrt(self.d_model)))
for layer in self.decoder:
x = layer(x, enc_out, src_mask, tgt_mask)
return self.output_proj(x)
def forward(self, src, tgt):
enc_out, src_mask = self.encode(src)
return self.decode(tgt, enc_out, src_mask)
# --- 3. Training ---
model = Transformer(src_tokenizer.vocab_size, tgt_tokenizer.vocab_size).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.98), eps=1e-9)
criterion = nn.CrossEntropyLoss(ignore_index=0, label_smoothing=0.1)
print(f"Parameters: {sum(p.numel() for p in model.parameters()):,}")
for epoch in range(20):
model.train()
total_loss = 0
for src, tgt in train_loader:
src, tgt = src.to(device), tgt.to(device)
tgt_input = tgt[:, :-1]
tgt_target = tgt[:, 1:]
logits = model(src, tgt_input)
loss = criterion(logits.reshape(-1, logits.size(-1)), tgt_target.reshape(-1))
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
total_loss += loss.item()
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1}: loss={total_loss/len(train_loader):.4f}")
# --- 4. Übersetzungsinferenz (Greedy Decoding) ---
def translate(text, model, src_tok, tgt_tok, max_len=80):
model.eval()
src = torch.tensor([src_tok.encode(text, max_len)]).to(device)
enc_out, src_mask = model.encode(src)
tgt_ids = [1] # <bos>
for _ in range(max_len):
tgt = torch.tensor([tgt_ids]).to(device)
logits = model.decode(tgt, enc_out, src_mask)
next_id = logits[0, -1].argmax().item()
if next_id == 2: break # <eos>
tgt_ids.append(next_id)
return tgt_tok.decode(tgt_ids)
# --- 5. Übersetzungstest ---
print("\n=== Translation Results ===")
test_sentences = [p[0] for p in pairs[7000:7010]]
for src_text in test_sentences:
pred = translate(src_text, model, src_tokenizer, tgt_tokenizer)
print(f" EN: {src_text}")
print(f" DE: {pred}")
print()
print("Lab 1 Complete!")
X. Hands-on Lab 2: ViT-Bildklassifikation mit Feintuning + Feature-Visualisierung (Google Colab)
Das folgende Experiment führt ein Feintuning eines vortrainierten ViT auf CIFAR-10 zur Bildklassifikation durch und visualisiert die vom Transformer erlernten Patch-Embeddings und [CLS]-Token-Features.
# ============================================================
# Lab 2: ViT Feintuning CIFAR-10 + Feature-Visualisierung
# Umgebung: Google Colab (GPU)
# ============================================================
# --- 0. Installation ---
!pip install -q transformers datasets timm
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from datasets import load_dataset
from transformers import ViTForImageClassification, ViTFeatureExtractor
from torch.utils.data import DataLoader
from torchvision import transforms
from sklearn.decomposition import PCA
import warnings
warnings.filterwarnings('ignore')
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Device: {device}")
# --- 1. Datenvorbereitung ---
dataset = load_dataset("cifar10")
train_data = dataset["train"].shuffle(seed=42).select(range(5000))
test_data = dataset["test"].shuffle(seed=42).select(range(1000))
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
def collate_fn(batch):
images = torch.stack([transform(ex["img"]) for ex in batch])
labels = torch.tensor([ex["label"] for ex in batch])
return images, labels
train_loader = DataLoader(train_data, batch_size=32, shuffle=True, collate_fn=collate_fn)
test_loader = DataLoader(test_data, batch_size=64, shuffle=False, collate_fn=collate_fn)
print(f"Train: {len(train_data)}, Test: {len(test_data)}")
# --- 2. Vortrainiertes ViT laden und Klassifikationskopf ersetzen ---
model_name = "google/vit-base-patch16-224-in21k"
model = ViTForImageClassification.from_pretrained(
model_name,
num_labels=10,
ignore_mismatched_sizes=True
).to(device)
# Alle Parameter einfrieren außer den letzten beiden Schichten und dem Klassifikationskopf
for name, param in model.named_parameters():
if "classifier" not in name and "layernorm" not in name and "encoder.layer.11" not in name:
param.requires_grad = False
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"Trainable: {trainable:,} / {total:,} ({trainable/total*100:.1f}%)")
# --- 3. Feintuning-Training ---
optimizer = torch.optim.AdamW(
[p for p in model.parameters() if p.requires_grad],
lr=2e-4, weight_decay=0.01
)
for epoch in range(5):
model.train()
total_loss, correct, total = 0, 0, 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(pixel_values=images, labels=labels)
loss = outputs.loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item() * images.size(0)
correct += (outputs.logits.argmax(1) == labels).sum().item()
total += images.size(0)
print(f"Epoch {epoch+1}: loss={total_loss/total:.4f}, acc={correct/total:.4f}")
# --- 4. Test ---
model.eval()
correct, total = 0, 0
all_features, all_labels = [], []
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(pixel_values=images, output_hidden_states=True)
correct += (outputs.logits.argmax(1) == labels).sum().item()
total += images.size(0)
# [CLS]-Token-Features sammeln
cls_features = outputs.hidden_states[-1][:, 0] # [B, 768]
all_features.append(cls_features.cpu())
all_labels.append(labels.cpu())
print(f"\nTest Accuracy: {correct/total:.4f}")
# --- 5. [CLS]-Token-Feature PCA-Visualisierung ---
features = torch.cat(all_features).numpy()
labels = torch.cat(all_labels).numpy()
pca = PCA(n_components=2)
features_2d = pca.fit_transform(features)
plt.figure(figsize=(12, 8))
scatter = plt.scatter(features_2d[:, 0], features_2d[:, 1],
c=labels, cmap='tab10', alpha=0.6, s=15)
plt.colorbar(scatter, ticks=range(10), label='Class')
plt.clim(-0.5, 9.5)
# Klassenzentren beschriften
for i in range(10):
mask = labels == i
cx, cy = features_2d[mask, 0].mean(), features_2d[mask, 1].mean()
plt.annotate(class_names[i], (cx, cy), fontsize=10,
fontweight='bold', ha='center',
bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.8))
plt.title('ViT [CLS] Token Features — PCA Projection (CIFAR-10)', fontsize=14)
plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.1%} variance)')
plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.1%} variance)')
plt.tight_layout()
plt.show()
# --- 6. Position Embedding Visualisierung ---
pos_embed = model.vit.embeddings.position_embeddings[0].detach().cpu() # [197, 768]
print(f"\nPosition embeddings shape: {pos_embed.shape}")
# Kosinus-Ähnlichkeit zwischen Patch-Position-Embeddings berechnen
patch_pos = pos_embed[1:] # [CLS] entfernen, [196, 768]
sim_matrix = F.cosine_similarity(
patch_pos.unsqueeze(0), patch_pos.unsqueeze(1), dim=-1
) # [196, 196]
# Einige Patch-Positionen auswählen und ihre Ähnlichkeit zu allen anderen Positionen visualisieren
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
positions = [0, 7, 48, 97, 112, 140, 175, 195] # Verschiedene Positionen
for idx, pos in enumerate(positions):
ax = axes[idx // 4][idx % 4]
sim = sim_matrix[pos].reshape(14, 14).numpy()
im = ax.imshow(sim, cmap='RdBu_r', vmin=-1, vmax=1)
row, col = pos // 14, pos % 14
ax.plot(col, row, 'k*', markersize=10)
ax.set_title(f'Patch ({row},{col})', fontsize=10)
ax.axis('off')
plt.suptitle('Position Embedding Cosine Similarity (ViT)', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# --- 7. Vorhersage-Konfidenzverteilung pro Klasse ---
model.eval()
all_probs, all_preds, all_true = [], [], []
with torch.no_grad():
for images, labels in test_loader:
images = images.to(device)
logits = model(pixel_values=images).logits
probs = F.softmax(logits, dim=-1)
all_probs.append(probs.cpu())
all_preds.append(logits.argmax(1).cpu())
all_true.append(labels)
all_probs = torch.cat(all_probs)
all_preds = torch.cat(all_preds)
all_true = torch.cat(all_true)
fig, axes = plt.subplots(2, 5, figsize=(20, 8))
for i in range(10):
ax = axes[i // 5][i % 5]
mask = all_true == i
correct_conf = all_probs[mask & (all_preds == all_true)].max(dim=1).values
wrong_conf = all_probs[mask & (all_preds != all_true)].max(dim=1).values
if len(correct_conf) > 0:
ax.hist(correct_conf.numpy(), bins=20, alpha=0.7, color='#0077b6', label='Correct')
if len(wrong_conf) > 0:
ax.hist(wrong_conf.numpy(), bins=20, alpha=0.7, color='#e63946', label='Wrong')
ax.set_title(class_names[i], fontsize=11)
ax.set_xlim(0, 1)
ax.legend(fontsize=8)
plt.suptitle('Prediction Confidence Distribution per Class', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
print("\nLab 2 Complete!")
XI. Fazit: Der Transformer als universelles Rechenprimitiv
Der Einfluss des Transformers geht weit über den Rahmen einer „guten Modellarchitektur" hinaus. Er entwickelt sich zum universellen Rechenprimitiv der künstlichen Intelligenz — so wie die CPU für das traditionelle Computing und die GPU für das Grafikrendering steht, wird der Transformer zur zentralen Recheneinheit des intelligenten Computings.
Rückblick auf die Kernthemen dieses Artikels:
- Architekturgrundlagen: Das präzise Zusammenspiel der sechs Komponenten (Attention, FFN, Residualverbindungen, LN, Positionskodierung, Maskierung)[1]
- Pre-Training-Paradigmen: MLM[5], CLM[4] und Denoising-Rekonstruktion[7] — drei Wege mit jeweils eigenen Stärken
- Modalitätserweiterung: Von NLP über Computer Vision[11] bis hin zur Multimodalität[13] — der Transformer beweist domänenübergreifende Universalität
- Skalierungs-Engineering: Tensorparallelismus[15] und ZeRO[16] machen Billionen-Parameter zur Realität
- Kontinuierliche Evolution: MoE[17] ermöglicht sparsame Skalierung, SSM[18] stellt die Notwendigkeit von Attention infrage
Der Transformer wird vielleicht eines Tages übertroffen, doch das von ihm etablierte Paradigma des „Pre-Training → Transfer", das Konzept der „Scaling Laws" sowie die Vision einer „einheitlichen Architektur für mehrere Modalitäten" werden die Entwicklung der KI nachhaltig prägen. Den Transformer zu verstehen bedeutet, die grundlegende Logik der KI unserer Zeit zu verstehen.



