Ebben az alfejezetben a 10.1. Számítási gráf és lokális derivált alfejezetben felépített visszaterjesztési gépezet két konkrét építőkövét vezetjük le: az affin réteg és a ReLU-aktiváció backwardját. Az előismeret a lokális VJP, a topologikus sorrend és a gradiensösszegzési lemma. Nem használunk sem PyTorch autogradot, sem kész optimalizálót; minden képletet NumPy-műveletként is megadunk.
Induló kérdés: egy elemű minibatchre számolt átlagolt veszteség után a biasgradiens a bejövő gradiens oszlopainak összege vagy átlaga? Mielőtt tovább olvasna, becsülje meg: ha , és a bejövő upstream gradiens minden sora azonos, mekkora a helyes db a sorösszeghez képest?
Az affin réteg a könyv konvenciója szerint
ahol a batch, a súly, a bias, a pre-aktiváció. Egy megfigyelés tehát az mátrix egy sora, a vektort a NumPy soronként szórja ki (broadcast).
A backward bemenete a -re érkező upstream gradiens , ahol a skalár veszteség. A levezetendő gradiens alakja mindig megegyezik a paraméter vagy bemenet alakjával, a könyv oszlop-VJP-konvenciója szerint.
Lemma (affin réteg VJP-je). Legyen a fenti alakokkal, és legyen differenciálható. Ekkor mellett
Bizonyítás. A skalár láncszabályt koordinátánként alkalmazzuk. Mivel
a szerinti parciális derivált , ezért
A biasra , így
ami éppen a oszlopösszege. Végül , tehát
ami a három állítást bizonyítja.
A levezetés saját kidolgozás, a mátrixalakú láncszabály általános konvencióját követi; a B01 általános mátrixkalkulus hátterét használtuk, de a konkrét képleteket ebben az alakban magunk ellenőriztük.
Alakellenőrzés: alakja , egyezik -vel; alakja , egyezik -szel; a biasgradiens . Azt is érdemes kimondani, hogy konkrét értéke nem jelenik meg egyik gradiensben sem, mert a transzláció Jacobi-mátrixa az identitás; a csak a forward értékén keresztül hat a további rétegek gradiensére.
A a forwardban példányban használatos: minden batchsorhoz ugyanazt a vektort adjuk. A 10.1. Számítási gráf és lokális derivált elágazási lemma szerint a közös paraméter gradiense a különböző ágak hozzájárulásainak összege. Egy ág egy sor, ahonnan érkezik, tehát
ahol
a csupa egyes oszlopvektor. Ez a db = G.sum(axis=0)
művelet.
Általános szabályként: ha egy forward műveletben egy kisebb alakú tenzort a batchdimenzió mentén sugároztunk ki, akkor a backward bejövő gradienst a kibővített tengelyek mentén összegezni kell, hogy visszakapjuk az eredeti alakot. Itt a 0. tengely a kibővített batchtengely.
A batch mean a reduction szintjén dől el, nem a biasgradiens képletében. Ha
A ekkor is , tehát a biasgradiens automatikusan tartalmazza az skálát. A sum reduction esetén , így minden gradiens pontosan -szerese az átlagos változatnak. A képlet tehát nem változik, csak a bejövő skálája; ezt a gyakori tévedésnél ismét elővesszük.
A 9.2. MLP és nemlinearitás alfejezetben bevezetett ReLU elemenkénti művelet: . A differenciálható minden pontban, és
Az pontban klasszikus derivált nincs; ott a szubderivált a intervallum. A NumPy és a PyTorch implementáció egyetlen értéket választ konvencióként, nevezetesen -t. Ennek megfelelően a backward
ahol az elemenkénti szorzat, az indikátor pedig a mentett pre-aktivációból képzett maszk. Elemenkénti aktiváció esetén a Jacobi-mátrix diagonális, ezért a VJP éppen ez az elemenkénti szorzat.
A törésponti konvenció miatt a nulla pre-aktivációjú elemhez tartozó gradiens a fenti szabály szerint nulla. Ez nem azt jelenti, hogy a nullpontbeli derivált nulla volna, hanem hogy az optimalizáló implementáció egy szubderiváltat választ. A 10.3. Gradiensellenőrzés és numerikus hibamérce gradiensellenőrzésénél ezért mindig nullától távoli vizsgálati pontot használunk; a labor adatait úgy választjuk, hogy minden pre-aktiváció pozitív legyen, így a töréspont nem zavarja a numerikus összevetést.
Legyen , , ,
és érkezzen a bejövő gradiens
Aktor
A alakja , a alakja , a alakja ; mindegyik a megfelelő paraméter alakjával egyezik. A értéke egyik eredményben sem szerepel, ahogy a lemma állítja.
A labor egy
bemenetű,
rejtett neuronú,
kimenetű MLP-t épít fel: affin réteg, ReLU, affin réteg, majd
batch-átlagolt fél-MSE loss. A bemenet determinisztikus, ezért nincs
RNG-seed; minden tenzor float64, eszköz CPU. A teljes
futtatható kód:
# lab_n038.py
# Munkakönyvtár: a fejezet lab könyvtára.
# Futtatás: python lab_n038.py
import numpy as np
def relu(x):
return np.maximum(0.0, x)
def linear_forward(X, W, b):
Z = X @ W.T + b
cache = (X, W) # b a backwardhoz nem kell
return Z, cache
def relu_forward(Z):
H = relu(Z)
cache = Z # a maszkot a pre-aktivaciobol kepezzuk
return H, cache
def linear_backward(G, cache):
X, W = cache
dW = G.T @ X
db = G.sum(axis=0) # biasgradiens: batch szerinti osszeg
dX = G @ W
return dX, dW, db
def relu_backward(dH, cache):
Z = cache
dZ = dH * (Z > 0) # ReLU-maszk; a nullponti konvencio 0
return dZ
# Determinisztikus kis bemenet, float64 CPU.
X = np.array([[1.,2.,3.,4.],
[5.,6.,7.,8.],
[9.,10.,11.,12.]], dtype=np.float64) # (3,4)
W1 = np.array([[0.1,0.2,0.3,0.4],
[0.5,0.6,0.7,0.8],
[0.9,1.0,1.1,1.2],
[1.3,1.4,1.5,1.6],
[1.7,1.8,1.9,2.0]], dtype=np.float64) # (5,4)
b1 = np.array([0.01,-0.02,0.03,-0.04,0.05], dtype=np.float64)
W2 = np.array([[0.1,0.2,0.3,0.4,0.5],
[-0.1,-0.2,0.1,0.2,0.3]], dtype=np.float64) # (2,5)
b2 = np.array([0.0,0.1], dtype=np.float64)
y = np.array([[1.,0.],
[0.,1.],
[1.,1.]], dtype=np.float64) # (3,2)
# Forward.
Z1, cache1 = linear_forward(X, W1, b1) # (3,5)
H1, rcache = relu_forward(Z1) # (3,5)
Z2, cache2 = linear_forward(H1, W2, b2) # (3,2)
# Loss: batch-mean, felenkenti MSE. L = 1/(2NK) * sum (Z2-y)^2
N, K = y.shape
L = 0.5 * np.mean((Z2 - y) ** 2)
# Backward; a mean reduction skalaja az upstream G2-ben van.
G2 = (Z2 - y) / (N * K)
dH1, dW2, db2 = linear_backward(G2, cache2)
dZ1 = relu_backward(dH1, rcache)
dX, dW1, db1 = linear_backward(dZ1, cache1)
print("L=", L)
print("Z1", Z1.shape, "H1", H1.shape, "Z2", Z2.shape)
print("dW1", dW1.shape, "db1", db1.shape, "dX", dX.shape)
print("dW2", dW2.shape, "db2", db2.shape)
# Fuggetlen numerikus ellenorzes centralis differenciaval.
def loss_of(params):
A, c, B, d = params
return 0.5 * np.mean((relu(X @ A.T + c) @ B.T + d - y) ** 2)
base_params = [W1.copy(), b1.copy(), W2.copy(), b2.copy()]
analytical = [dW1, db1, dW2, db2]
eps = 1e-6
max_err = 0.0
for p, g in zip(base_params, analytical):
pf = p.reshape(-1)
gf = g.reshape(-1)
for idx in range(pf.size):
old = pf[idx]
pf[idx] = old - eps
lm = loss_of(base_params)
pf[idx] = old + eps
lp = loss_of(base_params)
pf[idx] = old
num = (lp - lm) / (2.0 * eps)
max_err = max(max_err, abs(num - gf[idx]))
print(f"max_abs_grad_err={max_err:.3e}")
print("max_abs_grad_err < 1e-8:", max_err < 1e-8)A fenti kód az alfejezet képleteiből összeállított, futtatásra kész
munkamenet; az itt közölt kimeneti szerkezet számított illusztráció, nem
a jelen kéziratírás során mért futás. A várt alakok: Z1, H1
,
Z2
,
dW1
,
db1
,
dX
,
dW2
,
db2
.
A választott
mellett minden pre-aktiváció pozitív, ezért a ReLU-töréspont nem
befolyásolja a numerikus gradienst. A véges differencia középső lépése
,
a várt maximális abszolút eltérés sima ponton
alatti; az átadandó laboreredmény a fenti kimenet és a
max_abs_grad_err < 1e-8 logikai igazságérték.
F1 (fogalmi, nehézség 1, előfeltétel: 10.1.–10.2.). Magyarázza el, miért és miért nem a átlag, amikor a veszteség batch-átlag. Adjon -re olyan -t, amellyel a két képlet különböző eredményt ad. Átadandó: magyarázat, a különbség konkrét számértéke.
F2 (levezetési, nehézség 2, előfeltétel: a lemma). Legyen
Számítsa ki , és értékét az affin VJP-lemma alapján, és ellenőrizze mindhárom alakot. Mutassa meg, hogy értéke nem jelenik meg az eredményben. Átadandó: minden mátrixművelet, a három eredmény és alakellenőrzés.
F3 (implementációs, nehézség 2, előfeltétel: NumPy).
Implementálja a linear_backward függvényt a fenti labor
szerint, futtassa az F2 bemenetén, és nyomtassa ki a három gradienst.
Átadandó: futtatható kód, dtype és az F2-vel egyező három
kimenet.
F4 (hibakeresési, nehézség 2). A következő hibás backwardot kapja:
def linear_backward_wrong(G, cache):
X, W = cache
dW = X.T @ G
db = G.sum(axis=0)
dX = W.T @ G
return dX, dW, dbHol a hiba, mi a következménye, és hogyan ellenőrizné a javítást? Átadandó: a hiba lokalizálása, a helyes két sor, valamint egy shape- vagy numerikus teszt, amely megfogja a hibát.
F5 (kísérleti/transzfer, nehézség 2). Tervezzen kis kontrollkísérletet a labor adatain: ugyanazt a forwardot, ugyanazt a lossfüggvényt, de egyszer sum, egyszer mean reductionnel. A változó tényező csak a reduction; a metrika a biasgradiens sum/mean aránya. Mi a várt arány, milyen numerikus toleranciát ír elő, és mi a következtetés érvényességi határa? Átadandó: protokoll, várt arány, tolerancia, egy érvényességi korlát.
F1. A vektort a forward minden batchsorhoz hozzáadja, tehát a paraméter különböző ágon jelenik meg a számítási gráfban. A 10.1. Számítási gráf és lokális derivált gradiensösszegzési lemma szerint a közös paraméter gradiense az ágak hozzájárulásainak összege, minden ágból érkezik. A mean reduction nem a biasgradiens képzésénél lép be, hanem már a bejövő skálájában: . Ha tehát gradiensét kaptuk, a további átlagolás dupla -es skálázást adna. Legyen , ; a helyes , a téves átlag , a kettő aránya .
F2. Számolás:
Az alakok sorrendben , , , mindegyik a megfelelő objektum alakja. Mivel és nem függ a bias konstanstól, valóban nem jelenik meg.
F3. A labor linear_backward függvényét
használva az F2 bemenetén dW == [[-1,0],[10,14]],
db == [1,4], dX == [[2,2],[-1,6]]; a dtype
float64. Az egyezőséget np.allclose ellenőrzi
alapértelmezett, szigorúbb rtol=1e-9, atol=1e-12
mellett.
F4. A hibás dW = X.T @ G a helyes
irányú szorzás transzponáltja: a forward
,
ezért a súlygradiens
,
nem
.
A hibás dX = W.T @ G szintén felcseréli a sor-oszlop
irányt; a lemma szerint
.
A következmény kettős: egyrészt a hibás alak sok esetben nem
vagy
,
másrészt ha
vagy
miatt az alak véletlenül stimmel, a számérték akkor is hibás. A
javítás:
dW = G.T @ X
dX = G @ WVisszaellenőrzés: shape-assert mindkét értékre, majd a labor középső differenciás vizsgálata; a hibás változat a numerikus gradienssel nem egyezik, a javított alatti abszolút eltérést ad.
F5. Protokoll: rögzített laborbemenet , egyetlen változó a loss aggregálása, sum vagy mean. Metrika: minden elemének sum/mean hányadosa, valamint ugyanez -re. A várt arány pontosan , mert a differenciálás lineáris, és . Numerikus tolerancia: minden hányados legyen float64, az osztás numerikus kerekítése miatt. Érvényességi korlát: az állítás csak a loss lineáris aggregálására vonatkozik; ha a reduction mellé például osztálysúlyozás vagy label smoothing is lép, a két bejövő gradiens aránya már nem konstans , hanem elemenként eltérhet.
A leggyakoribb tévedés a biasgradiens összegének és az átlagolt loss
skálájának összekeverése. Legyen
,
a bejövő átlagolt gradiens
.
A helyes
.
Aki a „batch-átlag” szó miatt G.mean(axis=0)-t használ,
-et
kap. Ez nem kicsi numerikus zaj, hanem pontosan kétszeres relatív hiba:
a bias frissítése fele akkora lépést tesz, mint kellene. Ha viszont a
forward loss sum reductionnel készült, a bejövő gradiens már ennek
megfelelően nagyobb, de a backwardban akkor is sum(axis=0)
kell; a reduction a
előállításánál dönt el, nem a bias összegzésénél. Az ellenőrzés
egyszerű:
esetén az összeg és az átlag egybeesik, így a hibás kód azonnal látszik
egy egyelemű batch numerikus gradiensével.
Az affin réteg backwardja a bejövő mellett , és . A lemmát koordinátánkénti láncszabállyal bizonyítottuk, és minden alakot ellenőriztünk. A biasgradiens a broadcast miatt batch szerinti összeg; a mean loss skálája már a bejövő -ben van, ezért a backward képlet reduction-független összeget használ. A ReLU backwardja a mentett pre-aktivációból képzett maszkkal szoroz, a nullpontban nulla konvencióval. A labor két rétegből álló NumPy-MLP-t épít, és minden paraméter gradiensét középső differenciával ellenőrzi alatti abszolút hibakorláttal.
A 10.3. Gradiensellenőrzés és numerikus hibamérce egységben ezt a moduláris VJP-készletet teljes gradiensellenőrzési protokollá alakítjuk: tárgyaljuk az választását, az abszolút/relatív hiba mércéjét és a ReLU-töréspont kikerülését. A 10.4. Tanítási lépés és tanuló MLP ezután a tanítási lépést és egy valóban tanuló MLP-t mutat be.
<a id=“forrasok”></a>