Eingabedaten, Gewichte, Batches – wie ML-Frameworks Tensoren nutzen
SerieTensoren verstehen
Teil 4 von 8
Die vorigen Teile dieser Serie haben Tensoren als mathematische Objekte eingeführt — mehrdimensionale Arrays, die sich bei Koordinatentransformationen vorhersagbar verhalten. Das ist die formale Grundlage. In der Praxis des Machine Learnings sind Tensoren etwas Konkretes: jede Zahl, die durch ein neuronales Netz fließt, steckt in einem Tensor. Eingaben, Gewichte, Aktivierungen, Gradienten — alles Tensoren.
Dieser Teil zeigt, welche Tensoren in einem neuronalen Netz vorkommen, wie ihre Shapes entstehen, und wie das in PyTorch und TensorFlow aussieht.
Eingabedaten werden zu Tensoren
Der erste Schritt bei jedem ML-Modell ist Preprocessing: Rohdaten werden in Tensoren umgewandelt, die ein Framework verarbeiten kann. Je nach Datentyp sieht das unterschiedlich aus.
Bilder
Ein einzelnes Farbbild hat drei Dimensionen: Höhe, Breite und Farbkanäle (Rot, Grün, Blau). Ein 256 mal 256 Pixel großes RGB-Bild ist damit ein Tensor der Shape (3, 256, 256) in PyTorch — Channels-first — oder (256, 256, 3) in TensorFlow — Channels-last.
import torch
from torchvision import transforms
from PIL import Image
img = Image.open("katze.jpg")
transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor(), # PIL → Tensor (C, H, W), Werte 0.0–1.0
])
tensor = transform(img)
print(tensor.shape) # torch.Size([3, 256, 256])
print(tensor.dtype) # torch.float32
Die Konvention (C, H, W) — Channels, Height, Width — ist in PyTorch Standard. TensorFlow bevorzugt (H, W, C). Das ist keine Kleinigkeit: Wer einen PyTorch-Datensatz mit einem TensorFlow-Modell kombinieren möchte, muss explizit umpermutieren.
Text
Text wird zunächst tokenisiert — in eine Folge von Ganzzahlen umgewandelt, wobei jede Zahl einem Wort oder Wort-Stück im Vokabular entspricht. Ein Satz mit 12 Tokens ist ein 1D-Tensor der Shape (12,).
import torch
# Tokenisierter Satz (vereinfacht)
satz = "Tensoren sind mehrdimensionale Arrays"
token_ids = [4821, 312, 7043, 9821] # hypothetische IDs
tensor = torch.tensor(token_ids)
print(tensor.shape) # torch.Size([4])
print(tensor.dtype) # torch.int64
Vor dem Eingang in die eigentliche Modellarchitektur werden diese Integer-IDs in Embedding-Vektoren umgewandelt — jeder Token bekommt einen dichten Vektor fester Länge (z. B. 768 Dimensionen bei BERT). Aus einem (12,)-Tensor wird so ein (12, 768)-Tensor.
Tabellarische Daten
Ein einzelner Datenpunkt mit fünf numerischen Features ist ein Vektor der Shape (5,). Eine Tabelle mit 1000 solchen Datenpunkten ist eine Matrix der Shape (1000, 5).
import torch
import pandas as pd
df = pd.read_csv("kunden.csv")
X = torch.tensor(df[["alter", "einkommen", "bestellungen", "rueckgaben", "tage_aktiv"]].values, dtype=torch.float32)
print(X.shape) # torch.Size([1000, 5])
Die Batch-Dimension
Kein ML-Modell verarbeitet Beispiele einzeln. Stattdessen werden Eingaben zu Batches gebündelt: mehrere Beispiele auf einmal durch das Netz geschickt.
Dafür fügen alle Frameworks eine zusätzliche erste Dimension hinzu — die Batch-Dimension N. Aus einem einzelnen Bild der Shape (3, 256, 256) wird ein Batch von 32 Bildern mit der Shape (32, 3, 256, 256).
Die Kurzform für die vier Bildachsen lautet NCHW: Batch, Channels, Height, Width. TensorFlow nutzt NHWC. Beide sind gebräuchlich, aber inkompatibel.
import torch
from torch.utils.data import DataLoader, TensorDataset
# 1000 Beispiele, je 5 Features
X = torch.randn(1000, 5)
y = torch.randint(0, 2, (1000,))
dataset = TensorDataset(X, y)
loader = DataLoader(dataset, batch_size=32, shuffle=True)
for X_batch, y_batch in loader:
print(X_batch.shape) # torch.Size([32, 5])
print(y_batch.shape) # torch.Size([32])
break
Warum Batches? Zwei Gründe. Erstens lassen sich viele Berechnungen auf Batch-Ebene parallel ausführen — GPUs sind dafür optimiert. Zweitens stabilisiert Batch-Gradientenabstieg das Training: Statt die Gewichte nach jedem einzelnen Beispiel zu aktualisieren, mittelt man über einen Batch.
Die Batch-Dimension ist immer die erste. Jede Tensoroperation in PyTorch und TensorFlow respektiert das — sie weiß, dass Achse 0 für Beispiele reserviert ist.
Gewichte und Bias als Tensoren
Die lernbaren Parameter eines Modells — die Gewichte und Biases — sind ebenfalls Tensoren. Ihre Shapes hängen von der Schichtarchitektur ab.
Linear-Schicht
Eine vollverbundene Schicht (Linear oder Dense) transformiert einen Eingangsvektor der Länge in_features in einen Ausgangsvektor der Länge out_features. Dafür braucht sie:
- eine Gewichtsmatrix
Wder Shape(out_features, in_features) - einen Bias-Vektor
bder Shape(out_features,)
import torch.nn as nn
schicht = nn.Linear(in_features=5, out_features=3)
print(schicht.weight.shape) # torch.Size([3, 5])
print(schicht.bias.shape) # torch.Size([3])
Die Vorwärtsberechnung ist eine Matrixmultiplikation plus Addition: y = X @ W.T + b. Für einen Batch von 32 Beispielen multipliziert man eine (32, 5)-Matrix mit einer (5, 3)-Matrix und erhält eine (32, 3)-Matrix — 32 Ausgangsvektoren, je mit 3 Werten.
Convolutional-Schicht
Bei Faltungsschichten sind die Gewichte Filter-Tensoren. Ein Convolutional Layer mit 32 Ausgabe-Channels, 3-mal-3-Kernel und 3 Eingabe-Channels hat Filter der Shape (32, 3, 3, 3):
conv = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)
print(conv.weight.shape) # torch.Size([32, 3, 3, 3])
print(conv.bias.shape) # torch.Size([32])
Die vier Dimensionen stehen für: Anzahl Filter, Eingabe-Channels, Filterhöhe, Filterbreite. Jeder der 32 Filter „schaut” auf alle 3 Eingabe-Channels gleichzeitig.
Ein Forward Pass, Schritt für Schritt
Mit diesen Bausteinen lässt sich verfolgen, wie sich Tensor-Shapes durch ein einfaches Netz verändern.
import torch
import torch.nn as nn
# Einfaches Netz: zwei Linear-Schichten
modell = nn.Sequential(
nn.Linear(5, 64),
nn.ReLU(),
nn.Linear(64, 2),
)
# Batch: 32 Beispiele, je 5 Features
x = torch.randn(32, 5)
# Forward Pass
out = modell(x)
print(out.shape) # torch.Size([32, 2])
Was passiert intern:
xhat Shape(32, 5)- Erste Linear-Schicht:
(32, 5) @ (5, 64).T + (64,)→(32, 64) - ReLU verändert die Shape nicht — sie wirkt elementweise:
(32, 64)→(32, 64) - Zweite Linear-Schicht:
(32, 64) @ (64, 2).T + (2,)→(32, 2)
Das Ergebnis sind 32 Vektoren der Länge 2 — für jedes Beispiel im Batch zwei Ausgabewerte (z. B. Logits für eine binäre Klassifikation).
Dasselbe in TensorFlow
TensorFlow und Keras folgen denselben Prinzipien, aber mit leicht abweichender API und der NHWC-Konvention für Bilder:
import tensorflow as tf
modell = tf.keras.Sequential([
tf.keras.layers.Dense(64, activation='relu', input_shape=(5,)),
tf.keras.layers.Dense(2),
])
x = tf.random.normal((32, 5))
out = modell(x)
print(out.shape) # (32, 2)
Der Unterschied liegt in Details: TensorFlow baut den Graphen lazy (erst bei erstem Aufruf), PyTorch führt eager aus. Für das Verständnis von Tensor-Shapes spielt das keine Rolle — beide Frameworks verarbeiten dieselben mehrdimensionalen Arrays auf dieselbe Art.
Aktivierungen und Zwischenschichten
Aktivierungsfunktionen wie ReLU, Sigmoid oder Softmax sind ebenfalls Tensoroperationen — aber elementweise. Sie verändern die Werte, nicht die Shape.
import torch
import torch.nn.functional as F
x = torch.tensor([-2.0, -0.5, 0.0, 1.0, 3.0])
print(F.relu(x)) # tensor([0., 0., 0., 1., 3.]) — negative auf 0
print(F.sigmoid(x)) # tensor([0.12, 0.38, 0.50, 0.73, 0.95])
print(F.softmax(x, dim=0)) # tensor([...]) — summiert zu 1.0
Softmax ist eine Ausnahme: Sie normiert über eine Dimension (oft die letzte), sodass alle Werte zusammen 1 ergeben — nützlich für Wahrscheinlichkeiten in Klassifikationsaufgaben. Die Shape bleibt gleich, aber die Semantik der Werte ändert sich.
Gradienten sind Tensoren mit derselben Shape
Training bedeutet, Gewichte anhand des Fehlers anzupassen. Das geschieht durch Backpropagation — aber die Gradienten, die dabei berechnet werden, sind keine neue Datenstruktur. Sie sind Tensoren mit exakt derselben Shape wie die Parameter, die sie beschreiben.
import torch
import torch.nn as nn
schicht = nn.Linear(5, 3)
x = torch.randn(32, 5)
ausgabe = schicht(x)
verlust = ausgabe.mean() # vereinfachter Verlust
verlust.backward()
print(schicht.weight.shape) # torch.Size([3, 5])
print(schicht.weight.grad.shape) # torch.Size([3, 5]) — identisch
weight.grad hat dieselbe Shape wie weight. Das ist kein Zufall: Der Gradient gibt an, wie stark sich der Verlust ändert, wenn man jeden einzelnen Gewichtswert leicht verschiebt. Für jeden Gewichtswert gibt es genau eine solche Zahl — deshalb passt die Shape.
Der Optimierer — SGD, Adam oder ein anderer — nimmt diese Gradienten-Tensoren und subtrahiert einen skalierten Anteil von den Gewichts-Tensoren. Alles tensorweise, elementweise.
Shape-Fehler lesen und verstehen
Der häufigste Fehler beim Arbeiten mit ML-Frameworks ist ein Shape-Mismatch: Zwei Tensoren passen nicht zusammen, weil eine Dimension nicht übereinstimmt.
import torch
A = torch.randn(32, 5)
B = torch.randn(5, 8)
C = A @ B # Shape: (32, 8) — funktioniert
D = torch.randn(32, 6)
E = D @ B # RuntimeError: mat1 and mat2 shapes cannot be multiplied (32x6 and 5x8)
Die Fehlermeldung nennt immer die konkreten Shapes. Wer weiß, dass @ die letzte Dimension des linken Tensors mit der vorletzten des rechten Tensors zusammenführen muss, liest den Fehler sofort richtig: 32x6 und 5x8 passen nicht, weil 6 ≠ 5.
Ein zweites typisches Problem: vergessene oder falsch platzierte Batch-Dimension.
import torch.nn as nn
schicht = nn.Linear(5, 3)
# Ohne Batch — funktioniert nicht
x_single = torch.randn(5)
# schicht(x_single) → würde Fehler geben oder unerwartete Ergebnisse
# Mit Batch-Dimension von 1
x_batched = x_single.unsqueeze(0) # Shape: (1, 5)
out = schicht(x_batched)
print(out.shape) # torch.Size([1, 3])
unsqueeze(0) fügt eine neue Dimension an Position 0 ein. Das ist die typische Lösung, wenn man einen einzelnen Datenpunkt durch ein Modell schicken möchte, das Batches erwartet.
Was im nächsten Teil kommt
Die hier betrachteten Tensoren — Eingaben, Gewichte, Aktivierungen — fließen auch durch Transformer-Architekturen wie GPT oder BERT. Dort kommen Attention-Matrizen und mehrdimensionale Projektionen hinzu, die auf denselben Grundprinzipien basieren, aber eigene Shape-Konventionen mitbringen. Der nächste Teil der Serie zeigt, wie Tensoren durch einen Transformer-Block fließen.