Backpropagation ist das Herz jedes neuronalen Netzes – ein eleganter Algorithmus, der es Maschinen ermoeglicht, aus eigenen Fehlern zu lernen. Sie werden verstehen, wie dieses mathematische Prinzip den Lernprozess von der Bilderkennung bis zu Sprachmodellen steuert.
Wie Backpropagation in neuronalen Netzen funktioniert¶
Backpropagation ist ein Algorithmus, der es neuronalen Netzen ermoeglicht, aus Fehlern zu lernen, indem Gradienten schrittweise rueckwaerts durch das Netz propagiert werden. Ohne diesen Mechanismus wuerde Deep Learning in seiner heutigen Form nicht existieren. Schauen wir uns genau an, wie es funktioniert und warum es so wichtig ist.
Grundprinzip von Forward und Backward Pass¶
Das Lernen neuronaler Netze erfolgt in zwei Phasen. Zuerst der Forward Pass — Daten fliessen vorwaerts durch das Netz und erzeugen Vorhersagen. Dann folgt der Backward Pass — der Fehler wird zurueckpropagiert und die Gewichte werden aktualisiert.
# Forward pass - simple network with one hidden layer
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def forward_pass(X, W1, b1, W2, b2):
# Hidden layer
z1 = X.dot(W1) + b1
a1 = sigmoid(z1)
# Output layer
z2 = a1.dot(W2) + b2
a2 = sigmoid(z2)
return z1, a1, z2, a2
Berechnung von Fehler und Gradienten¶
Der Schluessel ist das Verstaendnis, wie Gradienten berechnet werden. Wir verwenden die Kettenregel aus der mathematischen Analysis — wir zerlegen die Ableitung einer zusammengesetzten Funktion in ein Produkt einzelner Ableitungen.
Fuer den Mean Squared Error und die Sigmoid-Aktivierung sieht der Gradient so aus:
def compute_gradients(X, y, z1, a1, z2, a2, W1, W2):
m = X.shape[0] # number of samples
# Gradient for output layer
dz2 = a2 - y # derivative of MSE loss
dW2 = (1/m) * a1.T.dot(dz2)
db2 = (1/m) * np.sum(dz2, axis=0)
# Gradient for hidden layer (chain rule)
da1 = dz2.dot(W2.T)
dz1 = da1 * a1 * (1 - a1) # derivative of sigmoid
dW1 = (1/m) * X.T.dot(dz1)
db1 = (1/m) * np.sum(dz1, axis=0)
return dW1, db1, dW2, db2
Gewichtsaktualisierung mittels Gradient Descent¶
Sobald wir die Gradienten haben, koennen wir die Gewichte aktualisieren. Gradient Descent passt jedes Gewicht in die entgegengesetzte Richtung des Gradienten an — dadurch gelangen wir schrittweise zum Minimum der Verlustfunktion.
def update_weights(W1, b1, W2, b2, dW1, db1, dW2, db2, learning_rate):
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
return W1, b1, W2, b2
# Complete training cycle
def train_step(X, y, W1, b1, W2, b2, learning_rate=0.01):
# Forward pass
z1, a1, z2, a2 = forward_pass(X, W1, b1, W2, b2)
# Compute loss
loss = np.mean((a2 - y)**2)
# Backward pass
dW1, db1, dW2, db2 = compute_gradients(X, y, z1, a1, z2, a2, W1, W2)
# Update weights
W1, b1, W2, b2 = update_weights(W1, b1, W2, b2,
dW1, db1, dW2, db2, learning_rate)
return W1, b1, W2, b2, loss
Probleme mit Vanishing und Exploding Gradients¶
In tiefen Netzen koennen Vanishing Gradients auftreten — Gradienten nehmen waehrend der Backpropagation exponentiell ab. Das entgegengesetzte Problem sind Exploding Gradients, bei denen Gradienten unendlich wachsen.
Loesungen umfassen:
- Gradient Clipping — Begrenzung der maximalen Gradientengroesse
- Bessere Aktivierungsfunktionen — ReLU anstelle von Sigmoid
- Batch Normalization — Normalisierung der Eingaben fuer jede Schicht
- Residual Connections — direkte Verbindungen zwischen entfernten Schichten
# Gradient clipping in practice
def clip_gradients(gradients, max_norm=1.0):
total_norm = 0
for grad in gradients:
total_norm += np.sum(grad**2)
total_norm = np.sqrt(total_norm)
clip_coef = max_norm / (total_norm + 1e-6)
if clip_coef < 1:
for i, grad in enumerate(gradients):
gradients[i] = grad * clip_coef
return gradients
Backpropagation-Optimierung¶
Moderne Implementierungen verwenden fortgeschrittene Optimierer, die die Learning Rate anpassen oder Momentum hinzufuegen:
class AdamOptimizer:
def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999):
self.lr = learning_rate
self.beta1 = beta1
self.beta2 = beta2
self.m = {} # first moment
self.v = {} # second moment
self.t = 0 # time step
def update(self, params, gradients):
self.t += 1
for key in params:
if key not in self.m:
self.m[key] = np.zeros_like(params[key])
self.v[key] = np.zeros_like(params[key])
# Update biased first/second moment estimates
self.m[key] = self.beta1 * self.m[key] + (1 - self.beta1) * gradients[key]
self.v[key] = self.beta2 * self.v[key] + (1 - self.beta2) * gradients[key]**2
# Bias correction
m_hat = self.m[key] / (1 - self.beta1**self.t)
v_hat = self.v[key] / (1 - self.beta2**self.t)
# Update parameters
params[key] -= self.lr * m_hat / (np.sqrt(v_hat) + 1e-8)
Backpropagation in der Praxis mit PyTorch¶
In realen Projekten verwenden wir Frameworks, die Backpropagation automatisch implementieren:
import torch
import torch.nn as nn
# Network definition
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super().__init__()
self.hidden = nn.Linear(input_size, hidden_size)
self.output = nn.Linear(hidden_size, output_size)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.sigmoid(self.hidden(x))
x = self.sigmoid(self.output(x))
return x
# Training with automatic backpropagation
model = SimpleNet(10, 20, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(1000):
# Forward pass
outputs = model(X)
loss = criterion(outputs, y)
# Backward pass - automatically!
optimizer.zero_grad()
loss.backward()
optimizer.step()
Zusammenfassung¶
Backpropagation ist ein Algorithmus, der es neuronalen Netzen ermoeglicht, durch Minimierung des Fehlers mittels Gradient Descent zu lernen. Die Schluesselkomponenten sind drei Schritte: Forward Pass zur Berechnung von Vorhersagen, Backward Pass zur Propagierung von Gradienten und Gewichtsaktualisierungen. In der Praxis verwenden wir Optimierer wie Adam und Frameworks wie PyTorch, die den gesamten Prozess automatisieren. Das Verstaendnis der Backpropagation-Prinzipien ist jedoch fuer das Debugging und die Optimierung von Deep-Learning-Modellen unerlaesslich.