A 10.2. Affín réteg és aktiváció backward egységben levezettük a lineáris réteg és a ReLU backwardját: , , , illetve a mentett maszkkal való szorzást. Itt azt a kérdést tesszük fel, amit minden kézzel írt backward után fel kell tenni: honnan tudjuk, hogy a levezetés nem tartalmaz előjel-, index- vagy skálahibát? A független eszköz a numerikus differenciálás. Induló becslés: helyes implementáció esetén egy sima ponton, float64 számítással és jól választott mellett a relatív eltérés alatt várható; egy előjelhiba viszont nagyságrendű relatív hibát ad, mert . A ReLU töréspontja a 9.3. Expresszivitás és approximációs állítások alfejezetben tárgyalt szakaszonként affin reprezentációban is kulcsszerepet játszott; itt a gradiensellenőrzés határfeltételeként tér vissza.
Legyen a modell összes paraméterének lapított oszlopvektora, a vizsgált veszteség. A backward a analitikus közelítése. A középső differencia (central difference) az -edik koordinátában
ahol a standard bázisvektor, a perturbáció. A forward differencia
A két becslés közötti eltérést abszolút és relatív hibával mérjük:
ahol stabilizáló nevező, például . Nullközeli gradiensre a relatív hiba értelmetlen, ezért ott abszolút kontrollt használunk. A tolerancia az az előre rögzített küszöb, amely alatt egy ponton az egyezést elfogadjuk; a laborban és . Ez heurisztikus protokoll, nem bizonyítás: egyetlen pont ellenőrzése nem igazolja a backward helyességét minden bemenetre. Töréspontnak (kink) nevezzük azt a pontot, ahol a függvény folytonos, de a klasszikus derivált nem létezik vagy ugrik; a ReLU nullpontja ilyen.
A Taylor-közelítésből levezetjük a középső differencia előnyét.
Lemma. Legyen . Ekkor
ahol . Ha , a forward differenciára
Bizonyítás. A Taylor-tétel Lagrange-maradéktaggal:
ahol és . A két sort kivonva
-nal osztva
A hibatag abszolút értéke legfeljebb , ahol a harmadik derivált maximuma. A forward esetben , tehát a hibatag . Ezzel mindkét állítást beláttuk.
A lemma értelme: a két Taylor-sor második deriváltas tagja kiejti egymást, ezért a középső differencia hibájú, a forward csak . Az előny ára két függvénykiértékelés egy helyett.
A kerekítési kompromisszum külön jelzendő, és nem éles tétel, hanem egyszerű hibamodell. Ha a kiértékelt loss értékek abszolút kerekítési hibája legfeljebb , akkor a számláló hibája legfeljebb , és -nal osztva járulékot kapunk. A teljes hiba hozzávetőlegesen
aminek minimuma . float64 esetén ; ha a loss nagyságrendileg és is , akkor , ezért a gyakorlati tartomány és között van. float32 esetén , így körülbelül -ra tolódik; ez az egyik oka annak, hogy a gradiensellenőrzést float64-ben végezzük.
Legyen , , . A pontos derivált . A forward differencia:
hiba . A középső differencia:
hiba . Mivel konstans, a lemma korlátja , amit a tényleges hiba itt pontosan elér. A forward korláthoz a lemma a intervallumon vett maximumot kéri: , így , és a korlát . A tényleges hiba ; a korlát érvényes, de nem éles egyenlőség. Ha a maximum helyett a derivált -beli értékét () írnánk, adódna, ami kisebb a tényleges hibánál: a pontbeli derivált csak elsőrendű közelítést ad, nem felső korlátot. A példa mutatja a másodrendű előnyt. Ugyanez minimális kódban:
import numpy as np
f = lambda x: x**3
x0, eps = 2.0, 1e-2
print((f(x0+eps)-f(x0))/eps) # 12.0601, számított érték
print((f(x0+eps)-f(x0-eps))/(2*eps)) # 12.0001, számított értékA kimenet a fenti kézi számításból adódik; a kódot a labor részeként lehet futtatni.
A labor a 10.2. Affín réteg és aktiváció backward két rétegű MLP-jét használja: bemenet, rejtett egység, kimenet, batch. A vizsgálati loss , ahol . A loss függvényeként sima, a paraméterek függvényeként azonban a ReLU miatt csak szakaszonként sima: ahol egy elem előjelet vált, a gradiensnek töréspontja van. A középső differencia ezért csak akkor hasonlítható a backwardhoz, ha a perturbációk mellett az aktív ReLU-maszk változatlan; a labor ezt koordinátánként ellenőrzi, a töréspontot átlépő koordinátákat külön számolja, és kizárja a hibamaximumból. A labor három paraméterskála () és öt epszilon mellett hasonlítja össze a backward gradienst a numerikus középső differenciával.
import numpy as np
rng = np.random.default_rng(20261020)
def linear_forward(X, W, b):
return X @ W.T + b
def linear_backward(G, X, W):
dW = G.T @ X
db = G.sum(axis=0)
dX = G @ W
return dX, dW, db
def relu_forward(Z):
return np.maximum(0.0, Z), Z
def relu_backward(dA, cache):
return dA * (cache > 0)
def unpack(theta, d, h, K):
nW1, nb1, nW2 = d*h, h, h*K
W1 = theta[:nW1].reshape(h, d)
b1 = theta[nW1:nW1+nb1]
W2 = theta[nW1+nb1:nW1+nb1+nW2].reshape(K, h)
b2 = theta[nW1+nb1+nW2:]
return W1, b1, W2, b2
def loss_and_grad(theta, X, d, h, K):
W1, b1, W2, b2 = unpack(theta, d, h, K)
Z1 = linear_forward(X, W1, b1)
A1, cache = relu_forward(Z1)
Z2 = linear_forward(A1, W2, b2)
N, Kk = Z2.shape
loss = np.mean(Z2**2)
G2 = 2.0/(N*Kk) * Z2
dA1, dW2, db2 = linear_backward(G2, A1, W2)
dZ1 = relu_backward(dA1, cache)
_, dW1, db1 = linear_backward(dZ1, X, W1)
grad = np.concatenate([dW1.ravel(), db1, dW2.ravel(), db2])
return loss, grad
def relu_mask(theta, X, d, h, K):
W1, b1, _, _ = unpack(theta, d, h, K)
return linear_forward(X, W1, b1) > 0 # aktív ReLU-k mintája
def numerical_grad(theta, X, d, h, K, eps):
p = theta.size
g = np.zeros_like(theta)
kink = np.zeros(p, dtype=bool) # töréspontot átlépő koordináták
ref = relu_mask(theta, X, d, h, K)
for i in range(p):
e = np.zeros_like(theta)
e[i] = 1.0
lp = loss_and_grad(theta + eps*e, X, d, h, K)[0]
lm = loss_and_grad(theta - eps*e, X, d, h, K)[0]
g[i] = (lp - lm) / (2*eps)
kink[i] = (np.any(relu_mask(theta + eps*e, X, d, h, K) != ref)
or np.any(relu_mask(theta - eps*e, X, d, h, K) != ref))
return g, kink
d, h, K, N = 3, 4, 2, 2
X = rng.normal(size=(N, d)).astype(np.float64)
epsilons = [1e-3, 1e-5, 1e-7, 1e-9, 1e-11]
for sigma in [0.1, 1.0, 10.0]:
theta = (sigma * rng.normal(size=d*h + h + K*h + K)).astype(np.float64)
loss, grad = loss_and_grad(theta, X, d, h, K)
assert grad.shape == theta.shape == (d*h + h + K*h + K,)
assert np.isfinite(grad).all()
for eps in epsilons:
num, kink = numerical_grad(theta, X, d, h, K, eps)
ok = ~kink # csak a sima tartományon hasonlítunk
abs_err = np.max(np.abs(num - grad)[ok])
rel_err = np.max((np.abs(num - grad) /
np.maximum(np.maximum(np.abs(num), np.abs(grad)), 1e-12))[ok])
print(f"sigma={sigma:4.1f} eps={eps:8.0e} kink={int(kink.sum()):2d} "
f"max_abs={abs_err:8.2e} max_rel={rel_err:8.2e}")A futtatási parancs a lab/n039_gradcheck
munkakönyvtárból:
python gradcheck_lab.pyA lab-profilban (NumPy 2.3.5, float64) mért kimenet:
sigma= 0.1 eps= 1e-03 kink= 0 max_abs=1.96e-15 max_rel=1.95e-12
sigma= 0.1 eps= 1e-05 kink= 0 max_abs=3.26e-13 max_rel=2.59e-10
sigma= 0.1 eps= 1e-07 kink= 0 max_abs=1.23e-11 max_rel=4.32e-08
sigma= 0.1 eps= 1e-09 kink= 0 max_abs=1.94e-09 max_rel=1.06e-06
sigma= 0.1 eps= 1e-11 kink= 0 max_abs=1.98e-07 max_rel=1.66e-04
sigma= 1.0 eps= 1e-03 kink= 0 max_abs=2.67e-12 max_rel=1.51e-11
sigma= 1.0 eps= 1e-05 kink= 0 max_abs=2.14e-10 max_rel=1.13e-09
sigma= 1.0 eps= 1e-07 kink= 0 max_abs=4.14e-08 max_rel=3.56e-07
sigma= 1.0 eps= 1e-09 kink= 0 max_abs=4.54e-06 max_rel=3.90e-05
sigma= 1.0 eps= 1e-11 kink= 0 max_abs=3.46e-04 max_rel=1.90e-03
sigma=10.0 eps= 1e-03 kink= 0 max_abs=1.38e-08 max_rel=3.74e-11
sigma=10.0 eps= 1e-05 kink= 0 max_abs=8.74e-07 max_rel=2.16e-09
sigma=10.0 eps= 1e-07 kink= 0 max_abs=1.18e-04 max_rel=6.12e-07
sigma=10.0 eps= 1e-09 kink= 0 max_abs=9.29e-03 max_rel=5.86e-05
sigma=10.0 eps= 1e-11 kink= 0 max_abs=1.34e+00 max_rel=4.51e-03
Az értelmezés: ezen a seeden egyetlen perturbáció sem lépte át a
ReLU-töréspontot (kink=0), így minden koordináta
összehasonlítható. A hiba itt a legnagyobb
-nál
a legkisebb, és
csökkenésével nagyjából
ütemben nő: ez a kerekítési tag
().
Csonkolási hiba gyakorlatilag nem látszik, mert az aktív tartományon
belül egyetlen paraméterkoordináta mentén a loss legfeljebb másodfokú
polinom
(
minden egyes súlyban affin, a loss
-ben
négyzetes), a középső differencia pedig másodfokú polinomra pontos. A
nagyobb paraméterskála nagyobb abszolút hibát ad a nagyobb loss és
gradiens miatt, a relatív hiba kevésbé érzékeny a skálára. Más seed vagy
nagyobb
mellett megjelenhetnek töréspontot átlépő koordináták; ezeket a
kink oszlop jelzi, és náluk a backward és a numerikus
becslés eltérése helyes kód mellett is nagy lehet.
F1 (fogalmi, nehézség 1). Előfeltétel: 4.1. Függvény, határérték és derivált derivált, 10.2. Affín réteg és aktiváció backward VJP-k. Definiálja a forward és a középső differencia gradiensbecslést, adja meg a pontossági rendjüket, és magyarázza el, miért a középső differenciát használjuk a gradiensellenőrzésnél. Átadandó: definíció, Taylor-maradéktagok, költség- és hibarend-összehasonlítás.
F2 (levezetési, nehézség 2). Előfeltétel: Taylor-tétel, trigonometrikus azonosság. Adott , , . Számítsa ki a középső differenciát, a pontos -t, a tényleges abszolút hibát, és a lemmából adódó felső korlátot. Hasonlítsa össze a forward differencia várható hibájával. Átadandó: azonosság vagy Taylor-sor, számértékek, korlát.
F3 (implementációs, nehézség 2). Előfeltétel: 10.2. Affín réteg és aktiváció backward. Írjon teljes függvényt, amely egy , , MLP-re batch-szel, seed 1-gyel generált float64 paraméterekkel ellenőrzi a backward gradienst középső differenciával mellett. Átadandó: kód, futtatási parancs, a kapott max abszolút hiba és shape-ellenőrzés.
F4 (hibakeresési, nehézség 2). Előfeltétel: 10.2. Affín réteg és aktiváció backward, lebegőpontos ismeretek. Egy hallgató a gradiensellenőrzést float32 paraméterekkel, -vel, és a loss-hoz adott konstanssal futtatja; az eltérés nagy. Mi a hiba oka, mi a következménye, és hogyan javítja? Átadandó: ok, következmény, javítás, az ellenőrző futtatás feltételei.
F5 (kísérleti, nehézség 3). Előfeltétel: 6.3. Sztochasztikus és minibatch gradiens, 10.2. Affín réteg és aktiváció backward. Tervezzen kis kísérletet annak mérésére, hogyan függ az optimális a paraméterskálától. Adja meg a protokollt, a baseline-t, a metrikát és az érvényességi korlátot. Átadandó: terv, döntési szabály, és indoklás, hogy a relatív hiba miért alkalmasabb a skálaösszehasonlításra.
F1. A forward differencia , a középső differencia a kétoldali képlet. A lemma szerint a forward hibája , a középsőé . A középső differencia nagyobb költségű: függvénykiértékelés a forward helyett, de a gradiensellenőrzésnél a – hibarend helyett – érhető el. Ezért a central a standard választás.
F2. A pontos derivált . A azonossággal
esetén , így . A tényleges abszolút hiba kb. . A lemma korlátja: , tehát ; a tényleges hiba ezen belül van. A forward differencia hibája első rendben , mintegy három nagyságrenddel nagyobb.
F3. A teljes megoldás:
import numpy as np
rng = np.random.default_rng(1)
def linear_forward(X, W, b):
return X @ W.T + b
def linear_backward(G, X, W):
return G @ W, G.T @ X, G.sum(axis=0)
def relu_forward(Z):
return np.maximum(0.0, Z), Z
def relu_backward(dA, cache):
return dA * (cache > 0)
d, h, K, N = 2, 3, 1, 1
X = rng.normal(size=(N, d)).astype(np.float64)
p = d*h + h + K*h + K
theta = rng.normal(size=p).astype(np.float64)
def unpack(theta):
nW1, nb1, nW2 = d*h, h, K*h
W1 = theta[:nW1].reshape(h, d)
b1 = theta[nW1:nW1+nb1]
W2 = theta[nW1+nb1:nW1+nb1+nW2].reshape(K, h)
b2 = theta[nW1+nb1+nW2:]
return W1, b1, W2, b2
def loss_and_grad(theta):
W1, b1, W2, b2 = unpack(theta)
Z1 = linear_forward(X, W1, b1)
A1, cache = relu_forward(Z1)
Z2 = linear_forward(A1, W2, b2)
loss = np.mean(Z2**2)
G2 = 2.0/(N*K) * Z2
dA1, dW2, db2 = linear_backward(G2, A1, W2)
dZ1 = relu_backward(dA1, cache)
_, dW1, db1 = linear_backward(dZ1, X, W1)
return loss, np.concatenate([dW1.ravel(), db1, dW2.ravel(), db2])
def numerical(theta, eps):
g = np.zeros_like(theta)
for i in range(theta.size):
e = np.zeros_like(theta)
e[i] = 1.0
g[i] = (loss_and_grad(theta + eps*e)[0] -
loss_and_grad(theta - eps*e)[0]) / (2*eps)
return g
loss, grad = loss_and_grad(theta)
num = numerical(theta, 1e-6)
print("grad shape:", grad.shape, "p:", p)
print("max abs error:", np.max(np.abs(grad - num)))Futtatás: python f3_gradcheck.py. A várható max abszolút
hiba
alatti, mert a függvény sima és float64; a konkrét érték a seedtől függ.
A shape-ellenőrzés:
,
a gradiens vektor hossza 13.
F4. Ok: három együtt ható tényező. A float32 kerekítési egysége ; az esetén a kerekítési járulék -re nő. A konstans a loss-ban a gradiensre analitikusan nincs hatással, de a numerikus kivonás abszolút hibáját a nagy függvényérték arányában növeli. Következmény: a central difference kerekítési zajt differenciál, ezért a backward és a numerikus gradiens látszólag eltér. Javítás: float64, , és a konstanst elhagyni, mert a gradiens invariáns a konstansra. Visszaellenőrzés: ugyanazon a ponton a max abszolút hiba alá esik.
F5. Protokoll: rögzített MLP, seed 20261020, batch; paraméterskálák ; -rács , ; minden kombinációban a labor central difference és backward közötti max abszolút és max relatív hiba. Az optimális epszilon az, ahol a max abszolút hiba minimális. A baseline a backward gradiens; a kérdés a numerikus approximáció hibaprofilja, nem modellösszehasonlítás. A relatív hiba azért alkalmasabb a skálaösszehasonlításra, mert normál a gradiens nagyságával; nagyobb skálán az abszolút hiba növekedhet, de a relatív hiba stabil marad, amíg a kerekítés nem dominál. Várható korlát: a csonkolási/kerekítési modell szerint az abszolút optimum a skálával eltolódik, a relatív optimum közelítőleg azonos. Érvényességi korlát: az eredmény egyetlen MLP-pontra vonatkozik, nem bizonyít általános tulajdonságot, és nem helyettesíti a sztochasztikus gradiens zajának vizsgálatát a 6.3. Sztochasztikus és minibatch gradiens alfejezetből.
A leggyakoribb hamis riasztás a ReLU nullpontjában adódik. Legyen , és a backward konvenció (mint a 10.2. Affín réteg és aktiváció backward alfejezetben). A central difference -nál:
A numerikus és a választott szubgradiens eltér, miközben mindkettő a ReLU szubgradiens-intervallumának eleme; ez nem implementációs hiba. A gradiensellenőrzés pontját ezért úgy kell választani, hogy minden ReLU pre-aktiváció legalább néhányszorosa legyen az -nak a nullától távol. Ha mégis töréspont közelében kell ellenőrizni, a törésponti konvenciót külön definiálni kell a numerikus összehasonlításhoz.
ravel alapértelmezett C sorrendű, ezért a
numerikus perturbáció ugyanabban a sorrendben lapít.A középső differencia a gradiens független numerikus becslése, amelynek csonkolási hibája , szemben a forward differencia hibájával. A Taylor-tétel Lagrange-maradéktagjával levezettük a pontos korlátot. Az epszilon választása kompromisszum: kicsi epszilonnál a kerekítési, nagynál a csonkolási hiba nő; float64-ben a gyakorlati tartomány –. Az abszolút és relatív hibát stabil nevezővel mérjük. A ReLU töréspontjában a numerikus differencia eltérhet a választott szubgradienstől, ezért sima pontot kell választani. A labor több epszilon és paraméterskála mellett ellenőrzi a backwardot; a kimenet trendje szemléltetett, a kód külön futtatandó.
A 10.4. Tanítási lépés és tanuló MLP egységben a most ellenőrzött backwardot használjuk a tanítási lépéshez: . A numerikus ellenőrzés nem helyettesíti a tanulási konvergencia vizsgálatát, de bizalmat ad a lépés gradienséhez.