11.3 Láncszabály és számítási gráf

11.3.1 Cél és konkrét helyzet

Egyetlen megfigyelési egységgel dolgozunk: az (x=4,y=10)(x=4,\; y=10) tanulóponttal. Az xx a bemeneti jellemző, a yy a megfigyelt célérték; mindkettő ebben a redukált tanulási példában dimenziótlan skalár float64. A modell legyen ŷ=wx\hat y = w x, egyetlen becsülendő meredekséggel, a veszteség pedig az egyszerű négyzetes hiba:

L(w)=(wx−y)2=(4w−10)2.L(w) = (wx - y)^2 = (4w - 10)^2.

A kérdés: mennyi a veszteség deriváltja a w=1w=1 kezdőpontban, azaz L′(1)L'(1)? Mielőtt a láncszabályt kimondjuk, adjon becslést. A parabola minimuma w=10/4=2,5w=10/4=2{,}5; a w=1w=1 pont ettől balra van, ezért a függvény ott csökkenő, tehát a derivált negatív. A pontos értéket a láncszabályból kapjuk meg, és számítási gráf gráfként is ellenőrizzük.

11.3.2 Fogalmi magyarázat: összetett függvény, láncszabály, helyi derivált

Az összetett függvény (composition) lényege, hogy egy függvény kimenete egy másik függvény bemenete lesz. A fenti veszteség három lépésre bontható:

u=wx=4w,a=u−y=u−10,L=a2. u = wx = 4w, \qquad a = u - y = u - 10, \qquad L = a^2.

A ww szerinti teljes változási ráta a láncszabály (chain rule) szerint a lépésenkénti változási ráták szorzata:

dLdw=dLda⋅∂a∂u⋅∂u∂w. \frac{dL}{dw} = \frac{dL}{da}\cdot\frac{\partial a}{\partial u}\cdot\frac{\partial u}{\partial w}.

A dd jelölés az egyváltozós külső függvényre utal, a ∂\partial pedig arra, hogy a csomópontnak több bemenete is van, és a többit rögzítettnek tekintjük. A helyi derivált (local derivative) mindig csak egyetlen csomópont közvetlen bemeneteire vonatkozik; a csomópontnak nem kell tudnia, mi van a gráf többi részén. A három helyi derivált:

dLda=2a,∂a∂u=1,∂u∂w=x. \frac{dL}{da} = 2a, \qquad \frac{\partial a}{\partial u} = 1, \qquad \frac{\partial u}{\partial w} = x.

A láncszabály így:

L′(w)=2a⋅1⋅x=2(wx−y)x=2(4w−10)⋅4. L'(w) = 2a \cdot 1 \cdot x = 2(wx-y)x = 2(4w-10)\cdot 4.

A forward pass (előremenő fázis) a bemenettől a kimenetig kiszámítja és eltárolja az összes köztes értéket. A w=1w=1 pontban:

forward pass:
  u = 4 * 1 = 4
  a = 4 - 10 = -6
  L = (-6) * (-6) = 36

A backward pass előkép (visszamenő fázis) a kimenettől indul. Vezessük be a v‾\bar v jelölést a vv csomópont szerinti kimeneti gradiensre: v‾=∂L/∂v\bar v = \partial L/\partial v. A legfelső csomópontra L‾=∂L/∂L=1\bar L = \partial L/\partial L = 1. Utána minden élen visszafelé megszorozzuk a fentről érkező gradienst az élhez tartozó helyi deriválttal:

backward pass:
  bar_L = 1
  bar_a = bar_L * (2a)   = 1 * (2 * -6) = -12
  bar_u = bar_a * 1       = -12
  bar_y = bar_a * (-1)    = 12
  bar_w = bar_u * x       = -12 * 4 = -48

A w‾=−48\bar w = -48 az LL deriváltja a ww paraméter szerint. A y‾=12\bar y=12 azt mutatja, hogy a megfigyelt cél növelése növelné a hibát; a yy nem paraméter, de a gráf teljes gradiense szempontjából ugyanúgy kiszámítható. A szorzat „tagonkénti” olvasata: a négyzetből jön a pillanatnyi hiba kétszerese, 2a=−122a=-12; a kivonásból az 11; a wxwx szorzatból pedig a másik tényező, x=4x=4. Ezért a végső gradiens 2(wx−y)x2(wx-y)x, vagyis a hiba kétszeresének és a bemenetnek a szorzata.

11.3.3 Teljes minimális munkapélda

A példa a tanulási lab környezetben futtatható; a lab/pyproject.toml alap numerikus csomagjain kívül más függőséget nem igényel. A kódot a Jupyter-környezet cellájában, vagy egy chain_rule_demo.py fájlba mentve a uv run python chain_rule_demo.py paranccsal indíthatjuk a lab projektgyökérből. A kód és a levezetés alább szemléltető, nem állítjuk, hogy a szöveget író modell ténylegesen futtatta; a kézi számpélda a levezetés ellenőrzése.

import numpy as np

x = 4.0
y = 10.0
w0 = 1.0

def forward(w):
    """Előremenő fázis: köztes értékek és veszteség."""
    u = w * x          # szorzat
    a = u - y          # eltérés
    L = a * a          # négyzetes hiba
    return u, a, L

u, a, L = forward(w0)

# Backward pass: minden lépésben egy helyi derivált.
bar_L = 1.0
bar_a = bar_L * (2.0 * a)   # dL/da
bar_u = bar_a * 1.0         # da/du
bar_y = bar_a * (-1.0)      # da/dy
bar_w = bar_u * x           # du/dw
bar_x = bar_u * w0          # du/dx

# Numerikus kontroll központi differenciával.
h = 1e-6
_, _, Lp = forward(w0 + h)
_, _, Lm = forward(w0 - h)
numeric_grad = (Lp - Lm) / (2.0 * h)

print("forward: u, a, L =", u, a, L)
print("bar_w =", bar_w)
print("bar_x, bar_y =", bar_x, bar_y)
print("numeric grad:", numeric_grad)
print("ellenorzes tolerancian belul:",
      np.allclose(bar_w, numeric_grad, atol=1e-8))

A futás szemléltető elvárt kimenete:

forward: u, a, L = 4.0 -6.0 36.0
bar_w = -48.0
bar_x, bar_y = -12.0 12.0
numeric grad: -48.0
ellenorzes tolerancian belul: True

A L(w)=(4w−10)2L(w)=(4w-10)^2 másodfokú függvény, ezért a központi differencia elméletileg pontosan −48-48-at ad; lebegőpontos kerekítés miatt ettől 10−810^{-8} nagyságrendben eltérhet, amire a np.allclose toleranciája van beállítva.

A görbét egy chart-blokkban is szemléltethetjük:

L(w)=(4w-10)^2 egy szakasza - Számított függvénypélda, nem mérés. Az érintő meredeksége w=1-ben -48.

A pontok a megadott képletből származnak; a w=1w=1 pontban az érintő egyenes L=36−48(w−1)L = 36 - 48(w-1), tehát a negatív meredekség látható a csökkenő szakaszon.

11.3.4 Eredmény értelmezése

A láncszabályos gradiens w‾=2a⋅1⋅x=2(−6)⋅4=−48\bar w = 2a \cdot 1 \cdot x = 2(-6)\cdot 4 = -48. Az egyszerűsített analitikus formula ugyanezt adja: 2(wx−y)x=2(4−10)⋅4=−482(wx-y)x = 2(4-10)\cdot 4 = -48. A negatív előjel jelentése: ww kis növelése csökkenti a veszteséget, mert a predikció 44-ről közelít a 1010-es cél felé. A w=2,5w=2{,}5 pontban a hiba a=0a=0, ezért a gradiens is nulla lesz; ettől balra negatív, jobbra pozitív.

A gráf invariant ellenőrzése: a backward passban a a‾=u‾\bar a = \bar u azért igaz, mert a=u−ya = u-y és yy konstans, tehát a uu változása ugyanakkora változást okoz az aa-ban. A w‾=u‾⋅x\bar w = \bar u \cdot x azért, mert a ŷ=wx\hat y = wx predikció w-szerinti helyi deriváltja a másik tényező, xx. A y‾=−a‾\bar y = -\bar a pedig a kivonásból adódik. Minden mennyiség skalár, dtype float64; vektoros változatban a 𝐱\mathbf x jellemzővektor jelenne meg a gradiens helyén, de a lánclogika ugyanez marad.

A számítási gráf láncszabálya lokális deriváltakat szoroz és összegez. A gradiensellenőrzéshez használjunk középpontos véges differenciát több paraméterponton; egyetlen ponton kapott egyezés nem garantálja, hogy az implementáció általánosan helyes.

11.3.5 Feladatok

Az 1–4. feladatot önállóan oldja meg; a megoldások utána, indoklással jönnek.

  1. Értelmezés. Tekintse az L(w)=(3w−7)2L(w)=(3w-7)^2 veszteséget a w=1w=1 pontban. Bontsa fel u=3wu=3w, e=u−7e=u-7, L=e2L=e^2 lépésekre, végezze el a forward és backward passzt, és adja meg w‾\bar w-t. Indokolja a derivált előjelét: miért negatív?

  2. Megvalósítás. Írjon grad_linear_squared(x, y, w) függvényt, amely a forward pass és a helyi deriváltak alapján visszaadja a (u, a, L, bar_w) értékeket a u=wxu=wx, a=u−ya=u-y, L=a2L=a^2 gráfra. Tesztelje (x,y,w)=(2,5,1)(x,y,w)=(2,5,1) bemenettel; az elvárt gradiens −12-12.

  3. Hibakeresés. Egy kolléga a fenti gráfban ezt írta: bar_w = bar_a * 1.0, tehát elhagyta az * x szorzót. Az (x,y,w)=(4,10,1)(x,y,w)=(4,10,1) esetre milyen értéket ad a programja, és miért hibás? Hogyan dönthető el a hiba numerikus kontrollal?

  4. Megváltozott feltétel. A modell bővül intercepttel: ŷ=wx+b\hat y = wx + b, a veszteség L=(ŷ−y)2L=(\hat y-y)^2. Adja meg a számítási gráfot, és a láncszabállyal számítsa ki mindkét parciális deriváltat, w‾\bar w-t és b‾\bar b-t, az (x,y,w,b)=(3,2,1,1)(x,y,w,b)=(3,2,1,1) pontban.

11.3.6 Megoldások és indoklás

1. megoldás. Forward: u=3⋅1=3u=3\cdot 1=3, e=3−7=−4e=3-7=-4, L=16L=16. Backward: L‾=1\bar L=1, e‾=L‾⋅2e=−8\bar e=\bar L\cdot 2e=-8; u‾=e‾⋅1=−8\bar u=\bar e\cdot 1=-8; w‾=u‾⋅3=−24\bar w=\bar u\cdot 3=-24. A negatív előjel azt mutatja, hogy ww növelése növeli a predikciót, ezáltal a −4-4 hibát nullához közelíti, tehát a veszteség csökken. Ellenőrzés a zárt formulával: 2(3w−7)⋅32(3w-7)\cdot 3 a w=1w=1 helyen 2(−4)⋅3=−242(-4)\cdot 3=-24.

2. megoldás. A függvény a példa forward és backward lépéseit rögzíti:

def grad_linear_squared(x, y, w):
    u = w * x
    a = u - y
    L = a * a
    bar_L = 1.0
    bar_a = bar_L * (2.0 * a)
    bar_u = bar_a * 1.0
    bar_w = bar_u * x
    return u, a, L, bar_w

u, a, L, bar_w = grad_linear_squared(2.0, 5.0, 1.0)
print(u, a, L, bar_w)   # szemlelteto elvart: 2.0 -3.0 9.0 -12.0

Az ellenőrző invariant: bar_w == 2*(w*x - y)*x. A (2,5,1)(2,5,1) bemenetre 2(2−5)⋅2=−122(2-5)\cdot 2 = -12. A numerikus differenciával való összehasonlítás ismét 10−810^{-8} tolerancián belül marad.

3. megoldás. A kolléga a bar_a * 1.0 értéket, azaz −12-12-t kap, mert a a‾\bar a-ból csak a uu-ba haladt, de a u=wxu=wx szorzat ww-szerinti helyi deriváltját, x=4x=4-et, elfelejtette. Ez a szorzat-tag elvesztése; a helyes érték 2(wx−y)x=2(−6)⋅4=−482(wx-y)x=2(-6)\cdot 4=-48. Numerikus kontroll: a forward pass mindkét w-értéken fut, a központi differencia −48-48-at közelít; a −12-12 négyszeres eltérése azonnal jelzi a hiányzó tényezőt.

4. megoldás. Gráf: u=wxu=wx, p=u+bp=u+b, e=p−ye=p-y, L=e2L=e^2. Forward: u=3u=3, p=4p=4, e=2e=2, L=4L=4. Backward: L‾=1\bar L=1; e‾=2e=4\bar e=2e=4; p‾=e‾=4\bar p=\bar e=4; y‾=−e‾=−4\bar y=-\bar e=-4; b‾=p‾⋅1=4\bar b=\bar p\cdot 1=4; u‾=p‾⋅1=4\bar u=\bar p\cdot 1=4; w‾=u‾⋅x=12\bar w=\bar u\cdot x=12; x‾=u‾⋅w=4\bar x=\bar u\cdot w=4. A paraméterek gradiense tehát (w‾,b‾)=(12,4)(\bar w,\bar b)=(12,4). Zárt képlet: 2(wx+b−y)x=122(wx+b-y)x=12 és 2(wx+b−y)=42(wx+b-y)=4; a kettő egyezik. A bb növelése túlbecslést okoz, ezért a veszteség nő, amit a pozitív b‾\bar b mutat.

11.3.7 Gyakori tévedés, visszautalás, önellenőrzés

Tévedés: az automatikus differenciálás (autodiff) nem véges differencia, és nem ígéri, hogy a derivált minden pontban létezik. A véges differencia (L(w+h)−L(w−h))/(2h)(L(w+h)-L(w-h))/(2h) egy numerikus közelítés; csonkolási hibája van, és a lépésköz túl kicsire választásával a lebegőpontos kerekítés felerősödik. A láncszabályos gráf ezzel szemben analitikus szabályokat alkalmaz, ezért a négyzetes példán a képlet pontosan adja a gradiens szerkezetét. Ellenpélda a második félmondatra: f(w)=|w|f(w)=|w| a 00-ban nem differenciálható. A gráf ott nem tud egyértelmű helyi deriváltat adni; a bal oldali meredekség −1-1, a jobb oldali +1+1. Egy autodiff implementáció vagy hibát jelez, vagy valamelyik féloldali értéket választja konvencióként, de ez már nem a derivált.

Visszautalás. A Parciális derivált és gradiens alfejezetben a gradiens a parciális deriváltak vektoraként jelent meg; most ennek egyetlen lánc menti elemi összetevőjét építettük fel. A Vetítés és legkisebb négyzetek legkisebb négyzetes normálegyenlet gradiense 𝐗T(𝐗𝐰−𝐲)\mathbf X^T(\mathbf X\mathbf w-\mathbf y); ennek egyetlen mintára eső darabja a most levezetett 2x(wx−y)2x(wx-y) alak. A CTE, window és előzményből számolt feature CTE-láncához hasonlóan itt is számítási gráfról van szó: a forward fázisból származó köztes értékek nélkül a backward fázis nem végezhető el, ahogyan a CTE-lépések sorrendje is meghatározza a lekérdezést.

Önellenőrzés. Nézze végig, hogy a backward pass miért L‾=1\bar L=1-gyel indul; miért szorozzuk az egyes lépésekben a helyi deriváltat a fentről kapott gradienssel; hol használtuk a forward passban elmentett aa értéket; és mi történne, ha egy köztes csomópontnak több kimenő éle lenne — a gradiensek akkor összegződnének a bejövő éleken. Ha ezekre tud válaszolni, a láncszabály gráfját érti.

11.3.8 Rövid összefoglaló

A láncszabály az összetett függvény változási rátáit lépésenkénti helyi deriváltak szorzatává alakítja. A számítási gráfban a forward pass eltárolja a köztes értékeket, a backward pass pedig a kimeneti L‾=1\bar L=1 gradiensből visszafelé szorozva adja a paraméterek szerinti gradienst. A lineáris predikció négyzetes hibájára L=(wx−y)2L=(wx-y)^2 esetén dL/dw=2(wx−y)xdL/dw=2(wx-y)x; a w=1,x=4,y=10w=1,\ x=4,\ y=10 pontban ez −48-48, és a numerikus kontroll tolerancián belül egyezik. Az autodiff ezt a mechanizmust gépesíti, de nem véges differencia, és csak differenciálható pontokban érvényes. A következő alfejezetben ezt a gradienst optimalizáló lépéssé alakítjuk.