Backpropagation je srdcem každé neuronové sítě - elegantní algoritmus, který umožňuje strojům učit se z vlastních chyb. Pochopíte, jak tento matematický princip řídí proces učení od rozpoznávání obrázků až po jazykové modely.
Jak funguje backpropagation v neuronových sítích¶
Backpropagation je algoritmus, který umožňuje neuronovým sítím učit se z chyb postupným šířením gradientů zpět přes síť. Bez tohoto mechanismu by deep learning neexistoval v dnešní podobě. Podívejme se, jak přesně funguje a proč je tak důležitý.
Základní princip forward a backward pass¶
Učení neuronové sítě probíhá ve dvou fázích. Nejdříve forward pass — data projdou sítí směrem dopředu a vytvoří se predikce. Pak následuje backward pass — chyba se šíří zpět a aktualizují se váhy.
# Forward pass - jednoduchá síť s jednou skrytou vrstvou
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def forward_pass(X, W1, b1, W2, b2):
# Skrytá vrstva
z1 = X.dot(W1) + b1
a1 = sigmoid(z1)
# Výstupní vrstva
z2 = a1.dot(W2) + b2
a2 = sigmoid(z2)
return z1, a1, z2, a2
Výpočet chyby a gradientů¶
Klíčové je pochopení, jak se počítají gradienty. Použijeme chain rule z matematické analýzy — derivaci složené funkce rozložíme na součin derivací jednotlivých částí.
Pro mean squared error a sigmoid aktivaci vypadá gradient takto:
def compute_gradients(X, y, z1, a1, z2, a2, W1, W2):
m = X.shape[0] # počet vzorků
# Gradient pro výstupní vrstvu
dz2 = a2 - y # derivace MSE loss
dW2 = (1/m) * a1.T.dot(dz2)
db2 = (1/m) * np.sum(dz2, axis=0)
# Gradient pro skrytou vrstvu (chain rule)
da1 = dz2.dot(W2.T)
dz1 = da1 * a1 * (1 - a1) # derivace sigmoidu
dW1 = (1/m) * X.T.dot(dz1)
db1 = (1/m) * np.sum(dz1, axis=0)
return dW1, db1, dW2, db2
Aktualizace vah pomocí gradient descent¶
Jakmile máme gradienty, můžeme aktualizovat váhy. Gradient descent upraví každou váhu v opačném směru, než ukazuje gradient — tím se postupně dostáváme k minimu loss funkce.
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
# Kompletní tréninkový cyklus
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
Problémy s vanishing a exploding gradients¶
V hlubokých sítích může docházet k vanishing gradients — gradienty se během backpropagation zmenšují exponenciálně. Opačný problém jsou exploding gradients, kdy gradienty rostou nekonečně.
Řešení zahrnují:
- Gradient clipping — omezení maximální velikosti gradientu
- Lepší aktivační funkce — ReLU místo sigmoidu
- Batch normalization — normalizace vstupů do každé vrstvy
- Residual connections — přímé spojení mezi vzdálenými vrstvami
# Gradient clipping v praxi
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
Optimalizace backpropagation¶
Moderní implementace používají pokročilé optimalizéry, které upravují learning rate nebo přidávají momentum:
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 v praxi s PyTorch¶
V reálných projektech používáme frameworky, které backpropagation implementují automaticky:
import torch
import torch.nn as nn
# Definice sítě
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
# Trénink s automatickým 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 - automaticky!
optimizer.zero_grad()
loss.backward()
optimizer.step()
Shrnutí¶
Backpropagation je algoritmus, který umožňuje neuronové síti učit se minimalizací chyby prostřednictvím gradient descent. Klíčové jsou tři kroky: forward pass pro výpočet predikce, backward pass pro šíření gradientů a update vah. V praxi používáme optimalizéry jako Adam a frameworky jako PyTorch, které celý proces automatizují. Pochopení principů backpropagation je ale zásadní pro ladění a optimalizaci deep learning modelů.