Loss a gradient říká, že „gradient se spočítá jedním průchodem zpět“. To je pravda, ale nic to nevysvětlí. Tady si to spočítáme na jednom neuronu, na papíře, s konkrétními čísly a uvidíš, že backpropagation není algoritmus, který si musíš pamatovat. Je to jen řetízkové pravidlo z derivací, aplikované pozpátku.
Náš neuron
Nejmenší možný příklad. Dva vstupy, dvě váhy, bias, aktivace a jedno číslo na výstupu:
z = x₁·w₁ + x₂·w₂ + b
h = tanh(z)
L = (h − y)² ← jak moc jsme se spletli oproti cíli y
Dosadíme:
x₁ = 2,0 w₁ = 0,5
x₂ = −1,0 w₂ = −0,3
b = 0,1 y = 1,0 ← cíl
Dopředný průchod
z = 2,0 × 0,5 + (−1,0) × (−0,3) + 0,1
= 1,0 + 0,3 + 0,1
= 1,4
h = tanh(1,4) = 0,885352
L = (0,885352 − 1,0)² = 0,013144
Máme chybu 0,0131. Otázka zní: kterým směrem posunout w₁, w₂ a b, aby byla menší?
Zpětný průchod, krok za krokem
Jdeme od konce a ptáme se pořád na totéž: jak se změní L, když se změní tohle?
1. Jak L závisí na h
L = (h − y)², tedy derivace je 2(h − y):
∂L/∂h = 2 × (0,885352 − 1,0) = −0,229297
Záporné číslo znamená: kdyby h bylo větší, loss by klesla. Dává smysl, jsme pod cílem.
2. Jak h závisí na z
Derivace tanh je 1 − tanh², což je hezké, protože tanh(z) už máme spočítané:
∂h/∂z = 1 − 0,885352² = 0,216152
3. Řetízkové pravidlo: jak L závisí na z
A tady je celý trik backpropu, prostě se to vynásobí:
∂L/∂z = ∂L/∂h × ∂h/∂z = −0,229297 × 0,216152 = −0,049563
Tomuhle číslu se říká „gradient, který dotekl do uzlu z“. Všechno další se z něj odvodí.
4. Rozdělení mezi vstupy
z = x₁w₁ + x₂w₂ + b, takže derivace podle jednotlivých členů jsou triviální:
∂z/∂w₁ = x₁ = 2,0 → ∂L/∂w₁ = −0,049563 × 2,0 = −0,099126
∂z/∂w₂ = x₂ = −1,0 → ∂L/∂w₂ = −0,049563 × −1,0 = 0,049563
∂z/∂b = 1 → ∂L/∂b = −0,049563
Všimni si dvou věcí:
- Gradient váhy je úměrný jejímu vstupu.
w₁má dvakrát větší vstup nežw₂, tak dostane dvakrát větší gradient. Proto se vstupy normalizují, jinak by se některé váhy učily mnohem rychleji než jiné. - Znaménko se obrátilo u
w₂, protože jeho vstup je záporný.
A kdybychom pokračovali dál doleva (v hlubší síti), pošleme gradient i do vstupů:
∂L/∂x₁ = ∂L/∂z × w₁ = −0,049563 × 0,5 = −0,024782
Tohle je přesně to, co se v hluboké síti předává do předchozí vrstvy.
Kontrola: numerická derivace
Nemusíš mi věřit. Derivaci jde ověřit hrubou silou, posuň váhu o kousek a podívej se, co udělá loss:
eps = 1e-6
num = (loss(w1 + eps) - loss(w1 - eps)) / (2 * eps)
analyticky: ∂L/∂w₁ = −0,099126
numericky: ∂L/∂w₁ = −0,099126 ✓
Sedí. Tohle je mimochodem standardní test při psaní vlastních vrstev: gradient check. Když se analytický a numerický gradient rozejdou, máš chybu v backwardu.
Krok učení
Gradient říká, kterým směrem loss roste, tak jdeme opačně:
w₁ ← 0,5 − 0,5 × (−0,099126) = 0,5496
w₂ ← −0,3 − 0,5 × ( 0,049563) = −0,3248
b ← 0,1 − 0,5 × (−0,049563) = 0,1248
A přepočítáme:
z = 1,4744 h = 0,9006 L = 0,007470
Loss klesla z 0,013144 na 0,007470. Přesně o tomhle je celý trénink, jen se to místo tří parametrů dělá pro sedm miliard a místo jednou pro milion kroků.
Proč je to efektivní
Naivní přístup by pro každý parametr zkusil, co udělá jeho změna, u modelu se 7 miliardami parametrů tedy 7 miliard dopředných průchodů na jeden krok učení. Nemyslitelné.
Backprop místo toho spočítá všechny gradienty jedním průchodem zpět, protože každý uzel potřebuje jen dvě věci:
- gradient, který k němu dotekl shora,
- lokální derivaci vlastní operace.
Vynásobí je a pošle dál. Cena zpětného průchodu je zhruba dvojnásobek dopředného, bez ohledu na to, kolik má síť parametrů.
naivně: N parametrů → N průchodů
backprop: N parametrů → 1 průchod (2× cena forwardu)
Jak to vypadá v opravdovém modelu
Přesně tyhle dva kroky, jen s maticemi. Podívej se na funkci backward v kapitole
postav si vlastní model, každý řádek je „gradient shora × lokální
derivace“:
g['Wu'] = h.T @ dlogits # gradient váhy = vstup × gradient výstupu
dh = dlogits @ P['Wu'].T # gradient posílaný dál doleva
Je to totéž jako ∂L/∂w₁ = ∂L/∂z × x₁, jen hromadně pro celou matici.
Cvičení
- Spočítej gradient pro
w₂, kdyby byl cíly = 0místoy = 1. Změní se znaménko? - Co se stane s gradientem, když je
zvelké (třeba 5)? Spočítej1 − tanh²(5)a zamysli se, proč se tomu říká nasycený neuron. - Proč se pro kontrolu používá
(f(w+ε) − f(w−ε)) / 2εa ne(f(w+ε) − f(w)) / ε?
Náčrt řešení: rozbal, až si cvičení zkusíš sám
- Ano, znaménko se otočí: z
+0,049563na−0,382742. Řetízek jedL/dw₂ = 2(h − y) × (1 − tanh²z) × x₂. Prostřední člen0,216152je kladný vždy,x₂ = −1je záporné vždy, takže o znaménku rozhoduje jen(h − y). Přiy = 1model podstřeluje (h < y), přiy = 0přestřeluje. Gradient je navíc přiy = 0skoro osmkrát větší, protože chyba je větší. 1 − tanh²(5) = 0,000182, oproti0,216152přiz = 1,4je to 1190krát méně. Neuron je nasycený: tanh je u pěti prakticky na jedničce a je tam plochý, takže sebevětší změna vah výstupem skoro nehne. Gradient se tím vynásobí a zmizí. Přesně proto se aktivace normalizují a proto se inicializuje malými čísly, aby se model do plochých oblastí vůbec nedostal.- Protože centrální diference má chybu řádu
ε², zatímco jednostranná řáduε. Rozvineš-lif(w ± ε)do Taylorovy řady, u centrální varianty se členy s druhou derivací odečtou a zbyde až třetí derivace. Prakticky to znamená o několik řádů přesnější kontrolu při stejnémε, což je při ověřování gradientů rozdíl mezi „sedí“ a „nevím“.
Shrnutí
- Backprop = řetízkové pravidlo aplikované od konce: gradient shora × lokální derivace.
- Gradient váhy je úměrný jejímu vstupu, proto se vstupy normalizují.
- Analytický gradient se dá ověřit numericky; je to standardní test vlastních vrstev.
- Jeden zpětný průchod spočítá gradienty všech parametrů za dvojnásobek ceny forwardu.
Proč je gradient váhy úměrný jejímu vstupu?
Protože v součtu z = x·w + … je derivace podle w rovna právě x. Váha, která dostává velký vstup, tak dostane velký gradient a mění se rychleji. Právě proto se vstupy a aktivace normalizují, jinak by se různé části sítě učily nesouměřitelně rychle.
Jak ověříš, že máš správně napsaný backward vlastní vrstvy?
Gradient checkem: spočítáš derivaci numericky jako (f(w+ε) − f(w−ε)) / 2ε a porovnáš s analytickým gradientem. Když se rozcházejí, je chyba v backwardu. Je to standardní test při implementaci nových vrstev.
Proč nejde gradienty počítat prostě tak, že pro každý parametr zkusíme, co udělá jeho změna?
Protože by to znamenalo jeden dopředný průchod na každý parametr, u modelu s miliardami parametrů miliardy průchodů na jediný krok učení. Backprop spočítá všechny gradienty jedním zpětným průchodem, který stojí zhruba dvojnásobek dopředného.
