Key Findings
  • 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.

KonfigurationReihenfolgeEigenschaftenRepräsentative Modelle
Post-LNAttn → Add → LNErfordert Lernraten-Aufwärmung, kann aber letztlich bessere Leistung erzielenUrsprünglicher Transformer, BERT
Pre-LNLN → Attn → AddStabiles Training, keine Aufwärmung erforderlichGPT-2, GPT-3
RMSNormÄhnlich Pre-LNNur Skalierung (keine Zentrierung), schnellere BerechnungLLaMA, 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:

TechnikDetailsWarum sie wichtig ist
Lernraten-AufwärmungLinearer Anstieg in den ersten 4000 Schritten, danach Quadratwurzel-AbklingenVermeidet Gradientenexplosion bei Post-LN in der frühen Trainingsphase[3]
Label SmoothingZielverteilung wechselt von One-Hot zu 0,9/0,1-MischungVerbessert die Generalisierungsfähigkeit, verhindert Überconfidence
DropoutJe 0,1 nach Attention-Gewichten + FFN + ResidualVerhindert Overfitting, bietet Regularisierung
Adam + β₂=0,98Höherer Momentum-AbklingfaktorStabilisiert die Gradienten der Attention-Matrix
GradientenclippingGlobales Gradientennorm-Clipping auf 1,0Verhindert 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.

ParadigmaArchitekturVertreterAm besten geeignet für
MLMEncoder-onlyBERT[5]Klassifikation, NER, Frage-Antwort
CLMDecoder-onlyGPT[4], LLaMA[9]Generierung, Dialog, Code
DenoisingEncoder-DecoderT5[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:

ModellJahrParameterArchitekturSchlüsselinnovation
Transformer[1]201765MEnc-DecSelf-Attention ersetzt RNN
GPT-1[4]2018117MDec-onlyGeneratives Pre-Training + diskriminatives Feintuning
BERT[5]2019340MEnc-onlyMasked Language Model, bidirektionaler Kontext
T5[7]202011BEnc-DecEinheitliches Text-to-Text-Framework
GPT-3[6]2020175BDec-onlyFew-shot In-Context Learning
PaLM[10]2022540BDec-onlyPathways-System, SwiGLU, RoPE
LLaMA[9]202365BDec-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:

TechnologieKernideeEffekt
DatenparallelismusJede GPU erhält eine Modellkopie, die Daten werden aufgeteiltLineare Beschleunigung, aber jede GPU benötigt vollständigen Modellspeicher
Tensorparallelismus[15]Matrixmultiplikation einer einzelnen Schicht wird auf mehrere GPUs aufgeteiltParallelisierung innerhalb einer Schicht, niedrige Latenz
Pipeline-ParallelismusVerschiedene Schichten werden auf verschiedene GPUs verteiltReduziert den Speicher pro GPU, hat aber Blasen (Bubbles)
ZeRO[16]Optimizer-Zustände/Gradienten/Parameter werden GPU-übergreifend partitioniertSpeicher sinkt auf 1/N, Billionen-Parameter werden möglich
Mixed PrecisionBerechnung in FP16/BF16, Akkumulation in FP32Halbierter Speicher, doppelte Geschwindigkeit
Gradient CheckpointingNur Aktivierungen einiger Schichten werden gespeichert, Rückwärtsdurchlauf berechnet neuSpeicher 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:

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.