- Das Kerndesign von CNNs wurde durch die Forschung von Hubel und Wiesel am visuellen Kortex der Katze inspiriert[1] — die Konzepte lokaler rezeptiver Felder und hierarchischer Merkmalsextraktion werden seit 1962 bis heute angewandt
- Von LeNet-5[2] uber ResNet[6] bis EfficientNet[7] spiegelt die Entwicklung der CNN-Architekturen eine zentrale Fragestellung wider: Wie findet man die optimale Balance zwischen Tiefe, Breite und Effizienz?
- CNNs sind nicht nur auf Bilder beschrankt — TextCNN[8] bewies, dass 1D-Faltungen bei der Textklassifikation sowohl schnell als auch effektiv sind und bis heute eine kosteneffiziente Wahl fur die industrielle NLP-Klassifikation darstellen
- Dieser Artikel enthalt zwei Google Colab-Praxisprojekte: MNIST-Handschrifterkennung x Feature-Map-Visualisierung sowie TextCNN x Textsentiment-Klassifikation
I. Vom biologischen Sehen zum kunstlichen Sehen: Der Ursprung der CNNs
Im Jahr 1962 fuhrten die Neurowissenschaftler David Hubel und Torsten Wiesel ein geschichtstrachtiges Experiment an einer narkotisierten Katze durch[1]. Sie entdeckten, dass im visuellen Kortex der Katze zwei Zelltypen existieren: einfache Zellen (empfindlich fur Kanten in bestimmten Orientierungen) und komplexe Zellen (mit einer gewissen Positionsinvarianz). Noch bedeutsamer war die Erkenntnis, dass visuelle Informationen hierarchisch verarbeitet werden — niedrige Schichten erkennen Kanten, mittlere Schichten kombinieren diese zu Formen und hohere Schichten identifizieren Objekte.
Diese Entdeckung inspirierte direkt die drei Kerndesignprinzipien von Convolutional Neural Networks:
- Lokale rezeptive Felder (Local Receptive Fields): Jedes Neuron betrachtet nur einen kleinen Bereich der Eingabe, was dem Faltungskern im CNN entspricht
- Gewichtsteilung (Weight Sharing): Derselbe Merkmalsdetektor gleitet uber das gesamte Bild, was der Faltungsoperation im CNN entspricht
- Raumliche Unterabtastung (Spatial Subsampling): Die Auflosung wird Schicht fur Schicht reduziert, was der Pooling-Operation im CNN entspricht
1998 setzten Yann LeCun und Kollegen[2] diese Prinzipien in LeNet-5 ingenieurtechnisch um — ein nur 5-schichtiges Faltungsnetzwerk, das handgeschriebene Ziffern mit einer Genauigkeit von 99,2 % erkennen konnte und von der US-Post zur Verarbeitung von 10 % aller handgeschriebenen Schecks des Landes eingesetzt wurde. LeNet-5 bewies eine fundamentale Erkenntnis: Ein guter induktiver Bias (Inductive Bias) ist wichtiger als mehr Parameter.
Der eigentliche Durchbruch der CNNs kam jedoch erst 2012. AlexNet[3] von Krizhevsky, Sutskever und Hinton gewann die ImageNet Large Scale Visual Recognition Challenge mit uberwaltigendem Vorsprung — eine Top-5-Fehlerrate von 16,4 %, wahrend der zweitplatzierte klassische Ansatz bei 26,2 % lag. Dieser Unterschied von 10 Prozentpunkten erschutterte die gesamte Computer-Vision-Community und markierte offiziell den Beginn des Deep-Learning-Zeitalters[16].
II. Kernmechanismen: Faltung, Pooling, Merkmalshierarchie
2.1 Die Faltungsoperation: Lokale Merkmalserkennung
Das Wesen der Faltung ist ein Musterabgleich mit gleitendem Fenster. Ein 3x3-Faltungskern (Kernel/Filter) gleitet uber das Eingabebild, berechnet an jeder Position das Skalarprodukt zwischen Kern und dem entsprechenden Bereich und erzeugt einen Ausgabewert. Nachdem das gesamte Bild durchlaufen wurde, entsteht eine Feature Map (Merkmalskarte).
Eine Faltungsschicht enthalt typischerweise mehrere Faltungskerne — jeder Kern lernt, ein anderes Muster zu erkennen. Beispielsweise konnten die 6 Faltungskerne der ersten Schicht lernen, horizontale Kanten, vertikale Kanten, 45°-Diagonalen, 135°-Diagonalen, Ecken und Kurven zu erkennen.
Warum ist Faltung effektiver als eine vollstandig verbundene Schicht? Das klassische Lehrbuch von Goodfellow et al.[10] nennt drei entscheidende Vorteile:
- Sparse Interaktion: Jede Ausgabe hangt nur von lokalen Eingaben ab — die Parameteranzahl sinkt von O(n²) auf O(k²) (wobei k die Kerngrosse ist)
- Gewichtsteilung: Derselbe Kern wird an allen Positionen wiederverwendet — unabhangig von der Bildgrosse bleibt die Parameteranzahl des Kerns konstant
- Translationsaquivarianz: Ob eine Katze links oder rechts im Bild ist, derselbe Kern kann sie erkennen — CNNs besitzen von Natur aus Translationsinvarianz
2.2 Die Pooling-Operation: Raumliche Kompression und Invarianz
Pooling fuhrt eine raumliche Unterabtastung auf der Feature Map durch — das gangigste 2x2-Max-Pooling nimmt aus jeweils 4 Pixeln den Maximalwert und halbiert so die Hohe und Breite der Feature Map.
Die Bedeutung von Pooling geht uber blosse Datenkompression hinaus — es liefert lokale Translationsinvarianz. Unabhangig davon, an welcher genauen Position innerhalb eines 2x2-Bereichs ein Merkmal erkannt wird, bleibt die Pooling-Ausgabe identisch. Dies verleiht CNNs Robustheit gegenuber kleinen Verschiebungen und Verformungen.
2.3 Hierarchische Merkmale: Von Kanten zu Objekten
Die klassische Visualisierungsstudie von Zeiler und Fergus[9] enthullte, dass die von CNN-Schichten gelernten Merkmale eine klare hierarchische Struktur aufweisen:
- Schicht 1: Kanten und Farbgradienten — die grundlegendsten visuellen Elemente
- Schicht 2: Ecken, Texturen und einfache Formen — Kombinationen von Kanten
- Schicht 3: Objektteile — Augen, Rader, Fenster
- Schichten 4–5: Vollstandige Objekte und Szenen — Gesichter, Autos, Gebaude
Diese hierarchische Struktur stimmt erstaunlich genau mit dem von Hubel und Wiesel entdeckten Verarbeitungsmechanismus des visuellen Kortex uberein — CNNs haben tatsachlich in gewissem Sinne die Informationsverarbeitungsstrategie des biologischen Sehens „neu erfunden".
III. Architekturentwicklung: Von LeNet bis ConvNeXt
| Jahr | Architektur | Tiefe | Kerninnovation | ImageNet Top-5 Error |
|---|---|---|---|---|
| 1998 | LeNet-5[2] | 5 | Grundparadigma: Faltung + Pooling | — (MNIST 0,8 %) |
| 2012 | AlexNet[3] | 8 | ReLU, Dropout, GPU-Training | 16,4 % |
| 2014 | VGGNet[4] | 16–19 | Einheitliche 3x3-Kerne, stapelweise vertieft | 7,3 % |
| 2014 | GoogLeNet[5] | 22 | Inception: parallele Faltungen mit mehreren Massstaben | 6,7 % |
| 2015 | ResNet[6] | 152 | Residual Connections (Skip Connections) | 3,6 % |
| 2017 | MobileNet[12] | 28 | Depthwise Separable Convolutions | — (effizientes Deployment) |
| 2019 | EfficientNet[7] | Auto-Skalierung | Compound Scaling (Tiefe x Breite x Auflosung) | 2,9 % (B7) |
| 2022 | ConvNeXt[15] | variabel | Modernisierung von CNNs mit Designprinzipien der Transformer-Architektur | auf Augenhohe mit ViT |
Einige zentrale Wendepunkte verdienen eine vertiefte Betrachtung:
Die Erkenntnis von VGGNet[4]: Zwei hintereinander geschaltete 3x3-Faltungskerne haben dasselbe rezeptive Feld wie ein einzelner 5x5-Kern, jedoch mit weniger Parametern (18 vs. 25) und einer zusatzlichen nichtlinearen Aktivierung dazwischen. Die Schlussfolgerung: Tiefer und schmaler ist besser als flacher und breiter.
Der Durchbruch von ResNet[6]: Bei Netzwerktiefen uber 20 Schichten stieg die Trainingsfehlerrate paradoxerweise an — kein Overfitting, sondern das Degradierungsproblem. Die Residual Connections von ResNet (F(x) + x) losten dieses Problem: Indem das Netzwerk „Residuen" statt direkter Abbildungen lernt, konnen Gradienten reibungslos durch 152 Schichten fliessen. Dies ist eine der bedeutendsten Architekturinnovationen im Deep Learning.
1x1-Faltung[14]: Scheinbar paradox — ein 1x1-Faltungskern hat kein raumliches rezeptives Feld. Doch er fuhrt lineare Kombinationen in der Kanaldimension durch und kann so Dimensionen reduzieren oder erhohen. Sowohl das Inception-Modul von GoogLeNet als auch die Bottleneck-Struktur von ResNet nutzen 1x1-Faltungen zur Steuerung des Rechenaufwands.
Batch Normalization[13]: Normalisierung der Ausgabe jeder Schicht stabilisiert den Trainingsprozess und ermoglicht hohere Lernraten. BN ist so effektiv, dass es in nahezu allen modernen CNNs als Standardkomponente eingesetzt wird.
IV. Kernanwendungen von CNNs in der Bild-AI
Die Anwendungen von CNNs im Bereich Computer Vision haben sich in jeden Teilbereich ausgebreitet:
- Bildklassifikation: Von der 1000-Klassen-Klassifikation bei ImageNet bis zur Lasionserkennung in der medizinischen Bildgebung — CNNs sind die ausgereifteste Losung
- Objekterkennung: Die R-CNN-Familie, YOLO und SSD erweitern CNN-Backbones um Region Proposals und Bounding-Box-Regression
- Semantische Segmentierung: FCN (Fully Convolutional Networks) und U-Net erweitern die Klassifikation von der Bild- auf die Pixelebene
- Bilderzeugung: Sowohl Diskriminator und Generator von Generative Adversarial Networks als auch das U-Net-Backbone von Diffusionsmodellen basieren im Kern auf CNNs
- Visuelle Merkmalsextraktion: Zwischenschichtmerkmale vortrainierter CNNs (ResNet, EfficientNet) werden umfassend fur Transfer Learning eingesetzt
V. CNNs in der Text-AI: TextCNN
Im Jahr 2014 veroffentlichte Yoon Kim[8] eine pragnante und uberzeugende Arbeit, die bewies, dass Convolutional Neural Networks nicht nur Bilder sehen, sondern auch Text lesen konnen. Das Design von TextCNN ist ausserst elegant:
- Eingabedarstellung: Jedes Wort im Satz wird in einen Word-Embedding-Vektor (z. B. 300-dimensional) umgewandelt, sodass der gesamte Satz eine n x 300-Matrix bildet — Text wird als „Bild" betrachtet
- 1D-Faltung: Faltungskerne verschiedener Grossen (z. B. Fenster von 3, 4, 5 Wortern) gleiten uber den Satz und erfassen n-gram-Muster unterschiedlicher Langen
- Globales Max-Pooling: Von der Ausgabe jedes Faltungskerns wird der Maximalwert genommen — unabhangig von der Satzlange wird alles auf einen Vektor fester Lange komprimiert
- Vollstandig verbundene Klassifikation: Alle Kernausgaben werden verkettet und uber eine FC-Schicht + Softmax klassifiziert
Die Intuition hinter TextCNN: Faltungskerne verschiedener Grossen suchen nach „Schlusselphrasen" unterschiedlicher Langen im Text. Ein 3-gram-Kern konnte „not very good" erfassen, ein 4-gram-Kern konnte „waste of my time" erkennen — diese lokalen Muster zusammengenommen genugen zur Bestimmung der Sentiment-Tendenz.
Obwohl Transformer und BERT TextCNN bei vielen NLP-Aufgaben ubertroffen haben, behalt TextCNN in der Industrie eine nicht ersetzbare Position: 10- bis 100-fach schnellere Inferenz (ohne die quadratische Komplexitat der Self-Attention), geringer Deployment-Ressourcenbedarf und bei der Klassifikation kurzer Texte eine BERT-nahe Genauigkeit.
VI. Interaktive 3D-Visualisierung: Wie ein CNN handgeschriebene Ziffern erkennt
Der beste Weg, CNNs zu verstehen, ist, sie zu „sehen". Wir haben mit Three.js eine interaktive 3D-Visualisierung erstellt, die zeigt, wie ein CNN im LeNet-Stil MNIST-Handschriftziffern verarbeitet:
In diesem Visualisierungsmodell konnen Sie sich vorstellen:
- Verschiedene Ziffern (0–9) auswahlen und beobachten, wie sich die Eingabe andert
- Verschiedene Schichten anklicken (Conv1 → Pool1 → Conv2 → Pool2 → FC → Output), um Informationen zu jeder Schicht anzuzeigen
- Per Drag-and-Drop rotieren und die 3D-Netzwerkstruktur aus verschiedenen Blickwinkeln betrachten
- Die Datenflussanimation aktivieren und beobachten, wie das Signal vom Eingang zum Ausgang fliesst
Wichtige Beobachtungspunkte:
- Hierarchische Progression der Feature Maps: Die Eingabeschicht besteht aus klaren 28x28 Pixeln, Conv1 erzeugt 6 unscharfe Feature Maps (Kantenerkennung), Conv2 erzeugt 16 kleinere, abstraktere Feature Maps (Musterkombinationen)
- Kompression der raumlichen Dimensionen: Nach jedem Pooling halbiert sich die Feature Map (28→14→7→...), aber die Kanalanzahl steigt (1→6→16) — mehr „Arten" von Merkmalen kompensieren den Verlust raumlicher Information
- Ubergang von raumlich zu semantisch: Faltungsschichten bewahren die raumliche Struktur, wahrend vollstandig verbundene Schichten sie aufbrechen — FC-Schichten fragen nicht mehr, „wo" ein Merkmal ist, sondern nur noch, „was" es ist
VII. Hands-on Lab 1: MNIST-Handschrifterkennung x Feature-Map-Visualisierung (Google Colab)
Dieses Lab baut von Grund auf ein CNN auf, trainiert es mit MNIST und visualisiert die Faltungskerne und Feature Maps der einzelnen Schichten — damit Sie mit eigenen Augen sehen, „was das CNN gelernt hat".
Google Colab offnen (CPU genugt, GPU ist schneller), ein neues Notebook erstellen und die folgenden Codeabschnitte der Reihe nach einfugen:
7.1 Datenvorbereitung und Baseline-Test
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
# ★ MNIST-Datensatz laden ★
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
# ★ Einen Teil der Trainingsbeispiele visualisieren ★
fig, axes = plt.subplots(2, 8, figsize=(16, 4))
for i in range(16):
img, label = train_dataset[i]
ax = axes[i // 8][i % 8]
ax.imshow(img.squeeze(), cmap='gray')
ax.set_title(f'{label}', fontsize=12)
ax.axis('off')
plt.suptitle('MNIST Training Samples', fontsize=14)
plt.tight_layout()
plt.show()
print(f"Training set: {len(train_dataset)} images")
print(f"Test set: {len(test_dataset)} images")
print(f"Image shape: {train_dataset[0][0].shape}")
7.2 Aufbau eines LeNet-5-artigen CNN
# ★ LeNet-5-artiges CNN (entspricht der Three.js-Visualisierung) ★
class LeNetCNN(nn.Module):
def __init__(self):
super().__init__()
# Conv1: 1 -> 6 channels, 5x5 kernel
self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=0)
# Conv2: 6 -> 16 channels, 5x5 kernel
self.conv2 = nn.Conv2d(6, 16, kernel_size=5, padding=0)
# FC layers
self.fc1 = nn.Linear(16 * 4 * 4, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
# Speichert Zwischen-Feature-Maps (fur Visualisierung)
self.feature_maps = {}
def forward(self, x):
# Conv1 + ReLU + MaxPool
x = self.conv1(x) # [B, 6, 24, 24]
self.feature_maps['conv1'] = x.detach()
x = F.relu(x)
x = F.max_pool2d(x, 2) # [B, 6, 12, 12]
self.feature_maps['pool1'] = x.detach()
# Conv2 + ReLU + MaxPool
x = self.conv2(x) # [B, 16, 8, 8]
self.feature_maps['conv2'] = x.detach()
x = F.relu(x)
x = F.max_pool2d(x, 2) # [B, 16, 4, 4]
self.feature_maps['pool2'] = x.detach()
# Flatten + FC
x = x.view(x.size(0), -1) # [B, 256]
x = F.relu(self.fc1(x)) # [B, 120]
x = F.relu(self.fc2(x)) # [B, 84]
x = self.fc3(x) # [B, 10]
return x
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = LeNetCNN().to(device)
total_params = sum(p.numel() for p in model.parameters())
print(f"Model: LeNet-5 style CNN")
print(f"Total parameters: {total_params:,}")
print(f"Device: {device}")
print(f"\nArchitecture:")
print(model)
7.3 Modelltraining
# ★ Training uber 5 Epochen ★
optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
train_losses = []
test_accs = []
for epoch in range(5):
model.train()
running_loss = 0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
avg_loss = running_loss / len(train_loader)
train_losses.append(avg_loss)
# Test
model.eval()
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
pred = output.argmax(dim=1)
correct += pred.eq(target).sum().item()
acc = correct / len(test_dataset) * 100
test_accs.append(acc)
print(f"Epoch {epoch+1}: loss={avg_loss:.4f}, test_acc={acc:.2f}%")
print(f"\nFinal test accuracy: {test_accs[-1]:.2f}%")
7.4 Visualisierung der Faltungskerne
# ★ Visualisierung der 6 Faltungskerne von Conv1 ★
conv1_weights = model.conv1.weight.data.cpu()
fig, axes = plt.subplots(1, 6, figsize=(15, 3))
for i in range(6):
kernel = conv1_weights[i, 0] # [5, 5]
axes[i].imshow(kernel, cmap='RdBu_r', vmin=-0.5, vmax=0.5)
axes[i].set_title(f'Filter {i}', fontsize=11)
axes[i].axis('off')
plt.suptitle('Conv1 Learned Filters (5x5) — Rot=positive Gewichte, Blau=negative Gewichte', fontsize=13)
plt.tight_layout()
plt.show()
# ★ Beobachtungspunkte ★
# Welche Muster haben diese 5x5-Faltungskerne gelernt?
# - Horizontale Streifenmuster → Horizontaler Kantendetektor
# - Vertikale Streifenmuster → Vertikaler Kantendetektor
# - Diagonale Muster → Diagonalendetektor
# - Helle Mitte / dunkle Umgebung → Fleckendetektor
7.5 Feature-Map-Visualisierung: Was das CNN „sieht"
# ★ Vorwartspass fur ein Testbild, Visualisierung der Feature Maps jeder Schicht ★
model.eval()
sample_img, sample_label = test_dataset[0]
sample_input = sample_img.unsqueeze(0).to(device)
with torch.no_grad():
output = model(sample_input)
pred = output.argmax(dim=1).item()
prob = F.softmax(output, dim=1)[0, pred].item()
print(f"True label: {sample_label}, Predicted: {pred} ({prob:.1%})")
# ★ Feature Maps der einzelnen Schichten darstellen ★
fig, axes = plt.subplots(4, 8, figsize=(20, 10))
# Zeile 0: Originaleingabe
axes[0][0].imshow(sample_img.squeeze().cpu(), cmap='gray')
axes[0][0].set_title('Input', fontsize=11)
for j in range(1, 8):
axes[0][j].axis('off')
axes[0][0].axis('off')
# Zeile 1: Conv1-Feature-Maps (6 Kanale)
conv1_maps = model.feature_maps['conv1'][0].cpu()
for j in range(6):
axes[1][j].imshow(conv1_maps[j], cmap='viridis')
axes[1][j].set_title(f'Conv1-{j}', fontsize=10)
axes[1][j].axis('off')
for j in range(6, 8):
axes[1][j].axis('off')
# Zeile 2: Pool1-Feature-Maps (6 Kanale, halbe Grosse)
pool1_maps = model.feature_maps['pool1'][0].cpu()
for j in range(6):
axes[2][j].imshow(pool1_maps[j], cmap='viridis')
axes[2][j].set_title(f'Pool1-{j}', fontsize=10)
axes[2][j].axis('off')
for j in range(6, 8):
axes[2][j].axis('off')
# Zeile 3: Conv2-Feature-Maps (erste 8 von 16 Kanalen)
conv2_maps = model.feature_maps['conv2'][0].cpu()
for j in range(8):
axes[3][j].imshow(conv2_maps[j], cmap='viridis')
axes[3][j].set_title(f'Conv2-{j}', fontsize=10)
axes[3][j].axis('off')
row_labels = ['Input', 'Conv1 (6ch, 24x24)', 'Pool1 (6ch, 12x12)', 'Conv2 (16ch, 8x8)']
for i, label in enumerate(row_labels):
axes[i][7].text(1.1, 0.5, label, transform=axes[i][7].transAxes,
fontsize=11, verticalalignment='center', color='#333')
plt.suptitle(f'Feature Maps — Ziffer "{sample_label}" → Vorhersage "{pred}"', fontsize=15)
plt.tight_layout()
plt.show()
# ★ Beobachtungspunkte ★
# Conv1: Jeder Kanal erkennt Kanten in verschiedenen Richtungen, die Konturen der Ziffer sind klar erkennbar
# Pool1: Geringere Auflosung, aber Kanteninformationen bleiben erhalten, erhohte Positionsinvarianz
# Conv2: Abstraktere Merkmale, nicht mehr einfache Kanten, sondern „Kombinationen von Kanten"
7.6 Feature-Map-Vergleich verschiedener Ziffern
# ★ Vergleich der Conv2-Feature-Maps verschiedener Ziffern ★
fig, axes = plt.subplots(4, 9, figsize=(22, 10))
# 4 Beispiele verschiedener Ziffern finden
digits_to_show = [0, 1, 5, 8]
for row, digit in enumerate(digits_to_show):
# Erstes Beispiel dieser Ziffer finden
for i in range(len(test_dataset)):
if test_dataset[i][1] == digit:
img, label = test_dataset[i]
break
inp = img.unsqueeze(0).to(device)
with torch.no_grad():
out = model(inp)
# Originalbild
axes[row][0].imshow(img.squeeze().cpu(), cmap='gray')
axes[row][0].set_title(f'Digit {digit}', fontsize=12, fontweight='bold')
axes[row][0].axis('off')
# Conv2: erste 8 Kanale
fmaps = model.feature_maps['conv2'][0].cpu()
for j in range(8):
axes[row][j+1].imshow(fmaps[j], cmap='hot')
if row == 0:
axes[row][j+1].set_title(f'Ch-{j}', fontsize=10)
axes[row][j+1].axis('off')
plt.suptitle('Conv2 Feature Maps — Verschiedene Ziffern aktivieren verschiedene Kanale', fontsize=15)
plt.tight_layout()
plt.show()
# ★ Kernbeobachtungen ★
# "0" und "8" haben beide eine Ringstruktur → bestimmte Kanale zeigen hohe Aktivierung fur beide
# "1" besteht aus vertikalen Linien → nur Kanale fur vertikale Merkmale werden aktiviert
# "5" hat horizontale und kurvige Elemente → Kanale mit gemischten Mustern werden aktiviert
# Dies ist die „visuelle Grundlage", auf der das CNN verschiedene Ziffern unterscheidet
VIII. Hands-on Lab 2: TextCNN x Textsentiment-Klassifikation (Google Colab)
Dieses Lab baut von Grund auf ein TextCNN[8] auf und fuhrt auf einem Filmkritik-Datensatz eine Sentiment-Klassifikation durch — es zeigt, wie CNNs nahtlos vom „Bild"-Bereich in den „Text"-Bereich ubertragen werden konnen.
Google Colab offnen (CPU genugt), ein neues Notebook erstellen und die folgenden Codeabschnitte der Reihe nach einfugen:
8.1 Datenvorbereitung
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
# ★ Einfacher Filmkritik-Datensatz ★
train_texts = [
"this movie is great and wonderful",
"excellent film with brilliant acting",
"amazing story beautifully told",
"fantastic performances and stunning visuals",
"loved every moment of this film",
"one of the best movies i have seen",
"a masterpiece of modern cinema",
"incredibly moving and powerful",
"this movie is terrible and boring",
"worst film i have ever watched",
"awful acting and terrible script",
"complete waste of time and money",
"painfully bad from start to finish",
"disappointing and utterly forgettable",
"horrible movie with no redeeming qualities",
"dull uninspired and predictable",
]
train_labels = [1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0] # 1=positive, 0=negative
test_texts = [
"a great and wonderful experience",
"terrible waste of my time",
"brilliant acting in this film",
"the worst movie this year",
"absolutely stunning and moving",
"boring and utterly terrible",
]
test_labels = [1, 0, 1, 0, 1, 0]
# ★ Vokabular aufbauen ★
all_words = ' '.join(train_texts + test_texts).split()
word_counts = Counter(all_words)
vocab = {word: idx + 2 for idx, (word, _) in enumerate(word_counts.most_common())}
vocab[''] = 0
vocab[''] = 1
def text_to_ids(text, max_len=12):
words = text.lower().split()
ids = [vocab.get(w, 1) for w in words]
# Padding oder Truncating auf feste Lange
if len(ids) < max_len:
ids += [0] * (max_len - ids.__len__())
else:
ids = ids[:max_len]
return ids
MAX_LEN = 12
X_train = torch.tensor([text_to_ids(t, MAX_LEN) for t in train_texts])
y_train = torch.tensor(train_labels, dtype=torch.long)
X_test = torch.tensor([text_to_ids(t, MAX_LEN) for t in test_texts])
y_test = torch.tensor(test_labels, dtype=torch.long)
print(f"Vocabulary size: {len(vocab)}")
print(f"Training samples: {len(train_texts)}")
print(f"Sequence length: {MAX_LEN}")
print(f"\nExample encoding:")
print(f" '{train_texts[0]}'")
print(f" → {X_train[0].tolist()}")
8.2 Aufbau des TextCNN-Modells
# ★ TextCNN — im Stil von Kim (2014) ★
class TextCNN(nn.Module):
def __init__(self, vocab_size, embed_dim=32, num_filters=16, filter_sizes=[2, 3, 4], num_classes=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
# Mehrmassstabliche 1D-Faltungen (erfassen 2-gram, 3-gram, 4-gram)
self.convs = nn.ModuleList([
nn.Conv1d(embed_dim, num_filters, kernel_size=fs)
for fs in filter_sizes
])
self.dropout = nn.Dropout(0.3)
self.fc = nn.Linear(num_filters * len(filter_sizes), num_classes)
# Speichert Zwischenmerkmale
self.conv_outputs = {}
def forward(self, x):
# x: [batch, seq_len]
embedded = self.embedding(x) # [batch, seq_len, embed_dim]
embedded = embedded.permute(0, 2, 1) # [batch, embed_dim, seq_len]
conv_outs = []
for i, conv in enumerate(self.convs):
c = F.relu(conv(embedded)) # [batch, num_filters, seq_len - fs + 1]
self.conv_outputs[f'conv_{conv.kernel_size[0]}gram'] = c.detach()
pooled = F.max_pool1d(c, c.size(2)).squeeze(2) # [batch, num_filters]
conv_outs.append(pooled)
# Alle Faltungsausgaben verketten
cat = torch.cat(conv_outs, dim=1) # [batch, num_filters * 3]
cat = self.dropout(cat)
logits = self.fc(cat) # [batch, num_classes]
return logits
model = TextCNN(vocab_size=len(vocab), embed_dim=32, num_filters=16, filter_sizes=[2, 3, 4])
total_params = sum(p.numel() for p in model.parameters())
print(f"TextCNN Architecture:")
print(f" Embedding: {len(vocab)} × 32")
print(f" Conv filters: 16 × [2-gram, 3-gram, 4-gram]")
print(f" Total parameters: {total_params:,}")
print(f"\n{model}")
8.3 Training des TextCNN
# ★ TextCNN trainieren ★
optimizer = optim.Adam(model.parameters(), lr=0.005)
criterion = nn.CrossEntropyLoss()
losses = []
for epoch in range(80):
model.train()
optimizer.zero_grad()
output = model(X_train)
loss = criterion(output, y_train)
loss.backward()
optimizer.step()
losses.append(loss.item())
if (epoch + 1) % 20 == 0:
model.eval()
with torch.no_grad():
train_pred = model(X_train).argmax(dim=1)
test_pred = model(X_test).argmax(dim=1)
train_acc = (train_pred == y_train).float().mean().item()
test_acc = (test_pred == y_test).float().mean().item()
print(f"Epoch {epoch+1:3d}: loss={loss.item():.4f}, train_acc={train_acc:.0%}, test_acc={test_acc:.0%}")
plt.figure(figsize=(8, 3))
plt.plot(losses, color='#0077b6')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('TextCNN Training Loss')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
8.4 Visualisierung der N-gram-Faltungsaktivierungen
# ★ Visualisierung der n-gram-Aktivierungsmuster von TextCNN fur jeden Satz ★
model.eval()
def visualize_text_cnn(text, label_name):
ids = torch.tensor([text_to_ids(text, MAX_LEN)])
with torch.no_grad():
logits = model(ids)
pred = logits.argmax(dim=1).item()
probs = F.softmax(logits, dim=1)[0]
words = text.split()[:MAX_LEN]
sentiment = "Positive" if pred == 1 else "Negative"
fig, axes = plt.subplots(1, 3, figsize=(18, 2.5))
for idx, (name, conv_out) in enumerate(model.conv_outputs.items()):
activations = conv_out[0].mean(dim=0).numpy() # Durchschnitt uber alle Filter
n = len(activations)
gram_size = int(name.split('_')[1][0])
# N-gram-Labels erstellen
ngram_labels = []
for i in range(n):
if i + gram_size <= len(words):
ngram_labels.append(' '.join(words[i:i+gram_size]))
else:
ngram_labels.append(f'pad-{i}')
colors = ['#b8922e' if v > 0 else '#0077b6' for v in activations[:len(ngram_labels)]]
axes[idx].barh(range(len(ngram_labels)), activations[:len(ngram_labels)], color=colors)
axes[idx].set_yticks(range(len(ngram_labels)))
axes[idx].set_yticklabels(ngram_labels, fontsize=9)
axes[idx].set_title(f'{gram_size}-gram activation', fontsize=11)
axes[idx].invert_yaxis()
plt.suptitle(f'"{text}" → {sentiment} (pos={probs[1]:.0%})', fontsize=13)
plt.tight_layout()
plt.show()
# ★ Einige Testbeispiele analysieren ★
visualize_text_cnn("a great and wonderful experience", "Positive")
visualize_text_cnn("terrible waste of my time", "Negative")
visualize_text_cnn("brilliant acting in this film", "Positive")
# ★ Beobachtungspunkte ★
# - In positiven Kritiken haben n-grams wie "great and wonderful", "brilliant acting" die hochste Aktivierung
# - In negativen Kritiken haben "terrible waste", "waste of my" die hochste Aktivierung
# - Faltungskerne verschiedener Grossen erfassen Schlusselphrasen unterschiedlicher Langen
# - Dies ist der "Merkmalserkennungs"-Mechanismus von CNNs bei der Textklassifikation
8.5 Benutzerdefinierte Texte testen
# ★ Benutzerdefinierte Texte testen ★
custom_texts = [
"this is a great movie",
"terrible and boring film",
"wonderful story with great acting",
"waste of time terrible movie",
]
model.eval()
print("Custom Text Predictions:")
print("=" * 50)
for text in custom_texts:
ids = torch.tensor([text_to_ids(text, MAX_LEN)])
with torch.no_grad():
logits = model(ids)
probs = F.softmax(logits, dim=1)[0]
pred = "Positive" if logits.argmax(dim=1).item() == 1 else "Negative"
print(f" [{pred:>8}] (pos={probs[1]:.0%}) {text}")
# ★ TextCNN vs. Image CNN — Architekturvergleich ★
print("\n" + "=" * 50)
print("TextCNN vs. Image CNN — Architekturvergleich:")
print(f" {'Image CNN':>12} | {'TextCNN':>12}")
print(f" {'2D-Faltung':>12} | {'1D-Faltung':>12}")
print(f" {'Pixelnachb.':>12} | {'Wortfenster':>12}")
print(f" {'Kanten/Text.':>12} | {'n-gram':>12}")
print(f" {'Raum. Pool.':>12} | {'Glob. Max-P.':>12}")
print(f" {'Klass./Detek.':>12} | {'Sentim./Thema':>12}")
IX. Von CNNs zur modernen Architektur: Die Herausforderung durch Vision Transformer
Im Jahr 2021 stellten Dosovitskiy et al.[11] mit dem Vision Transformer (ViT) die Vorherrschaft von CNNs im visuellen Bereich direkt in Frage. ViT teilt Bilder in 16x16 Patches auf, wobei jeder Patch als Token in einen Standard-Transformer eingespeist wird — ganz ohne Faltungsoperationen.
Bei grossangelegtem Pretraining erreichte ViT die Leistung gleichwertiger CNNs oder ubertraf sie sogar. Dies warf eine grundlegende Frage auf: Ist der induktive Bias von CNNs (Lokalitat, Translationsaquivarianz) ein Vorteil oder eine Einschrankung?
Liu et al.[15] gaben 2022 eine tiefgrundige Antwort: ConvNeXt modernisierte CNNs mit Designprinzipien der Transformer-Architektur — grossere Faltungskerne (7x7), Layer Normalization statt Batch Normalization, weniger Aktivierungsfunktionen — mit dem Ergebnis, dass eine reine CNN-Architektur wieder mit ViT gleichzog.
Die Erkenntnis: Nicht CNNs sind uberholt, sondern bestimmte Designkonventionen alterer CNNs. Die Kernvorteile von CNNs — Lokalitat und Gewichtsteilung — sind nach wie vor wirksam; was aktualisiert werden muss, sind die konkreten ingenieurtechnischen Entscheidungen.
In der Praxis hangt die Wahl zwischen CNN und Transformer vom Einsatzszenario ab:
- Edge-Gerate / Echtzeit-Inferenz: CNNs (insbesondere MobileNet, EfficientNet) bleiben die beste Wahl — der Vorteil bei Recheneffizienz und Modellgrosse ist deutlich
- Grossangelegtes Pretraining / Bedarf an globalem Kontext: ViT und Transformer-Varianten sind uberlegen — das globale rezeptive Feld der Self-Attention bietet starkere Lernfahigkeit bei grossen Datenmengen
- Hybridarchitekturen: Immer mehr Modelle integrieren Attention-Mechanismen in CNN-Backbones (z. B. EfficientNetV2) oder fugen Faltungen in Transformer ein (z. B. CvT) — die Grenzen zwischen beiden verschwimmen zunehmend
X. Fazit
Von Hubel und Wiesels Experiment am visuellen Kortex der Katze im Jahr 1962 uber LeCuns LeNet-5 im Jahr 1998 und die durch AlexNet 2012 ausgeloste Deep-Learning-Revolution bis hin zur heutigen Konvergenz von CNNs und Transformern — die Geschichte der Convolutional Neural Networks ist eine der elegantesten Erzahlungen der kunstlichen Intelligenz.
Die Kernprinzipien, die uns CNNs gelehrt haben — lokale Merkmalserkennung, hierarchisches Reprasentationslernen, die Kraft des induktiven Bias — werden durch den Aufstieg der Transformer nicht obsolet. Im Gegenteil: Diese Prinzipien werden in neuen Formen in modernere Architekturen integriert. CNNs zu verstehen bedeutet, die grundlegenden „First Principles" des Deep Learnings zu verstehen.
Wenn Ihr Team technische Losungen fur Computer Vision oder Textklassifikation evaluiert oder zwischen CNN, ViT und Hybridarchitekturen wahlen muss, freuen wir uns auf ein tiefgehendes technisches Gesprach. Das Forschungsteam von Meta Intelligence kann Sie dabei unterstutzen, basierend auf Ihrer spezifischen Deployment-Umgebung, Ihrem Datenvolumen und Ihren Latenzanforderungen die optimale Architekturlosung zu finden.



