Einleitung: Wie CNNs die Welt sehen
Convolutional Neural Networks (CNN) haben die Computer Vision revolutioniert und die automatische Bilderkennung mit übermenschlicher Genauigkeit ermöglicht. Im Gegensatz zu vollvernetzten Netzen nutzen CNNs die räumliche Struktur der Daten: Benachbarte Pixel sind tendenziell korreliert, und lokale Muster (Kanten, Texturen) wiederholen sich an verschiedenen Positionen im Bild.
Die Schlüsselidee der CNNs ist die Faltungsoperation: Ein Filter (Kernel) gleitet über das Bild und extrahiert lokale Features. Schicht für Schicht baut das Netz eine Hierarchie von immer abstrakteren Features auf: von einfachen Kanten über komplexe Formen bis hin zu vollständigen Objekten.
Was Sie lernen werden
- Die Faltungsoperation: Kernel, Stride, Padding
- Pooling: Dimensionsreduktion und Positionsinvarianz
- Historische Architekturen: LeNet, AlexNet, VGG, ResNet
- Skip Connections und das Vanishing-Gradient-Problem in tiefen Netzen
- Transfer Learning: Vortrainierte Modelle von ImageNet wiederverwenden
- Data Augmentation zur Verbesserung der Modellrobustheit
- Vollständige Implementierung in PyTorch mit Training und Evaluation
Die Faltungsoperation
Das Herzstück eines CNN ist die Faltungsschicht. Ein kleiner Filter (Kernel), typischerweise 3x3 oder 5x5 groß, gleitet über das Bild und berechnet das elementweise Produkt zwischen dem Filter und dem darunterliegenden Bildausschnitt. Das Ergebnis ist eine Feature Map, die das Vorhandensein eines bestimmten Musters an jeder Position hervorhebt.
Zwei Parameter steuern das Verhalten der Faltung:
- Stride: Die Schrittweite, mit der sich der Kernel bewegt. Stride=1 erzeugt eine Feature Map gleicher Größe, Stride=2 halbiert die Dimensionen
- Padding: Hinzufügen von Nullen an den Rändern des Bildes. "Same"-Padding erhält die Originaldimensionen, "Valid"-Padding reduziert sie
import torch
import torch.nn as nn
# 2D-Faltung: 3 Eingangskanäle (RGB), 16 Ausgangsfilter, 3x3-Kernel
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3,
stride=1, padding=1)
# Eingabe: Batch von 4 RGB-Bildern 32x32
x = torch.randn(4, 3, 32, 32)
output = conv_layer(x)
print(f"Input shape: {x.shape}") # [4, 3, 32, 32]
print(f"Output shape: {output.shape}") # [4, 16, 32, 32]
# Mit Stride=2 und Padding=1
conv_stride2 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1)
output_s2 = conv_stride2(x)
print(f"Stride 2 output: {output_s2.shape}") # [4, 16, 16, 16]
Pooling: Dimensionsreduktion
Nach der Faltung reduzieren Pooling-Schichten die räumlichen Dimensionen der Feature Maps und verringern die Anzahl der Parameter und den Rechenaufwand. Pooling führt außerdem eine Form der Translationsinvarianz ein: Kleine Verschiebungen des Objekts im Bild verändern die extrahierte Feature nicht.
Die zwei Haupttypen sind:
- Max Pooling: Wählt den Maximalwert in jedem Fenster. Bewahrt die prominentesten Features und ist der am häufigsten verwendete Typ
- Average Pooling: Berechnet den Durchschnitt in jedem Fenster. Erzeugt glattere Features, typischerweise vor der Ausgabe verwendet
Warum CNNs so gut funktionieren
CNNs nutzen drei grundlegende Eigenschaften von Bildern: Lokalität (wichtige Features sind lokal), Gewichtsteilung (derselbe Filter wird überall angewendet, wodurch die Parameter drastisch reduziert werden) und Translationsinvarianz (eine Katze ist eine Katze, sowohl in der Mitte als auch in einer Ecke des Bildes). Diese Eigenschaften machen CNNs um Größenordnungen effizienter als vollvernetzte Netze für Daten mit räumlicher Struktur.
Historische Architekturen: Von LeNet bis ResNet
LeNet-5 (1998)
Von Yann LeCun für die Erkennung handgeschriebener Ziffern entworfen, ist LeNet-5 das erste erfolgreiche CNN. Mit nur 5 Schichten (2 Faltungen + 3 Fully Connected) zeigte es, dass Faltungsnetzwerke traditionelle Feature-Engineering-Methoden übertreffen können.
VGG (2014)
VGGNet demonstrierte, dass Tiefe wichtig ist: Durch ausschließliche Verwendung von gestapelten 3x3-Kerneln in 16 oder 19 Schichten erreichte es hervorragende Leistung auf ImageNet. Zwei 3x3-Filter in Folge decken dasselbe rezeptive Feld wie ein 5x5-Filter ab, aber mit weniger Parametern und mehr Nichtlinearität.
ResNet (2015)
ResNet (Residual Network) löste das Problem des Trainings sehr tiefer Netze mit Skip Connections: Anstatt eine direkte Transformation F(x) zu lernen, lernt jeder Block die residuale Differenz F(x) + x. Dies ermöglicht es dem Gradienten, direkt durch die Schichten zu fließen, und macht das Training von Netzen mit 152+ Schichten möglich.
import torch
import torch.nn as nn
class ResidualBlock(nn.Module):
"""Grundlegender Residualblock von ResNet"""
def __init__(self, channels):
super().__init__()
self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(channels)
self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
identity = x # Skip Connection
out = self.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += identity # Residual: F(x) + x
return self.relu(out)
class SimpleCNN(nn.Module):
"""CNN für CIFAR-10-Klassifikation"""
def __init__(self, num_classes=10):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, 2), # 32x32 -> 16x16
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, 2), # 16x16 -> 8x8
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d((1, 1)) # 8x8 -> 1x1
)
self.classifier = nn.Linear(128, num_classes)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1)
return self.classifier(x)
model = SimpleCNN()
x = torch.randn(8, 3, 32, 32)
print(f"Output: {model(x).shape}") # [8, 10]
Transfer Learning: Vortrainierte Modelle wiederverwenden
Ein CNN von Grund auf auf großen Datensätzen zu trainieren erfordert enorme Rechenressourcen. Transfer Learning löst dieses Problem: Man nimmt ein Modell, das auf einem riesigen Datensatz vortrainiert wurde (typischerweise ImageNet mit 14 Millionen Bildern), und passt es an die eigene spezifische Aufgabe an.
Die gängigste Strategie umfasst zwei Phasen:
- Feature Extraction: Die Gewichte des vortrainierten Modells werden eingefroren und nur der finale Klassifizierer wird ersetzt
- Fine-Tuning: Einige obere Schichten werden aufgetaut und mit einer sehr niedrigen Lernrate nachtrainiert
import torchvision.models as models
# ResNet50 vortrainiert auf ImageNet laden
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
# Alle Parameter einfrieren
for param in model.parameters():
param.requires_grad = False
# Finalen Klassifizierer für 5 Klassen ersetzen
num_features = model.fc.in_features
model.fc = nn.Sequential(
nn.Dropout(0.3),
nn.Linear(num_features, 256),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, 5) # 5 benutzerdefinierte Klassen
)
# Nur die Parameter des neuen Klassifizierers werden trainiert
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:,} Parameter")
Data Augmentation: Robustheit durch Transformationen
Data Augmentation ist eine Regularisierungstechnik, die die Vielfalt des Trainingsdatensatzes künstlich erhöht, indem zufällige Transformationen auf die Bilder angewendet werden. Rotationen, Ausschnitte, horizontales Spiegeln und Farbvariationen lehren das Netz, invariant gegenüber diesen Transformationen zu sein.
from torchvision import transforms
# Data-Augmentation-Pipeline für das Training
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.RandomRotation(15),
transforms.ColorJitter(brightness=0.2, contrast=0.2,
saturation=0.2, hue=0.1),
transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225]),
transforms.RandomErasing(p=0.1)
])
# Für Validierung/Test: nur Skalieren und Normalisieren
val_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])
Nächste Schritte in der Serie
- Im nächsten Artikel werden wir Rekurrente Neuronale Netze (RNN) und LSTM für die Sequenzverarbeitung erkunden
- Wir werden sehen, wie LSTMs den Vanishing Gradient lösen und zeitliche Abhängigkeiten modellieren
- Wir werden ein Modell zur Sentimentanalyse und Textgenerierung implementieren







