10.3 Gradiensellenőrzés és numerikus hibamérce

10.3.1 Cél és induló kérdés

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: dW=G⊤XdW=G^\top X, db=∑nGn,:db=\sum_n G_{n,:}, dX=GWdX=GW, illetve a mentett Z>0Z>0 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 ε\varepsilon mellett a relatív eltérés 10−610^{-6} alatt várható; egy előjelhiba viszont 22 nagyságrendű relatív hibát ad, mert a−(−a)=2aa-(-a)=2a. 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.

10.3.2 Definíciók és hibamérték

Legyen θ∈ℝp\theta\in\mathbb R^p a modell összes paraméterének lapított oszlopvektora, L:ℝp→ℝL:\mathbb R^p\to\mathbb R a vizsgált veszteség. A backward gbwd∈ℝpg_{\mathrm{bwd}}\in\mathbb R^p a ∇θL\nabla_\theta L analitikus közelítése. A középső differencia (central difference) az ii-edik koordinátában

gnum,i(ε)=L(θ+εei)−L(θ−εei)2ε,g_{\mathrm{num},i}^{(\varepsilon)}=\frac{L(\theta+\varepsilon e_i)-L(\theta-\varepsilon e_i)}{2\varepsilon},

ahol ei∈ℝpe_i\in\mathbb R^p a standard bázisvektor, ε>0\varepsilon>0 a perturbáció. A forward differencia

g+,i(ε)=L(θ+εei)−L(θ)ε.g_{+,i}^{(\varepsilon)}=\frac{L(\theta+\varepsilon e_i)-L(\theta)}{\varepsilon}.

A két becslés közötti eltérést abszolút és relatív hibával mérjük:

Eabs=maxi|gbwd,i−gnum,i(ε)|,E_{\mathrm{abs}}=\max_i\,|g_{\mathrm{bwd},i}-g_{\mathrm{num},i}^{(\varepsilon)}|,

Erel=maxi|gbwd,i−gnum,i(ε)|max(|gbwd,i|,|gnum,i(ε)|,δ),E_{\mathrm{rel}}=\max_i\frac{|g_{\mathrm{bwd},i}-g_{\mathrm{num},i}^{(\varepsilon)}|} {\max\!\big(|g_{\mathrm{bwd},i}|,\ |g_{\mathrm{num},i}^{(\varepsilon)}|,\ \delta\big)},

ahol δ\delta stabilizáló nevező, például 10−1210^{-12}. 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 Eabs<10−8E_{\mathrm{abs}}<10^{-8} és Erel<10−6E_{\mathrm{rel}}<10^{-6}. 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.

10.3.3 A középső differencia hibája

A Taylor-közelítésből levezetjük a középső differencia előnyét.

Lemma. Legyen f∈C3([x0−ε,x0+ε])f\in C^3([x_0-\varepsilon,x_0+\varepsilon]). Ekkor

|Dcf(x0)−f′(x0)|≤ε26maxξ∈[x0−ε,x0+ε]|f‴(ξ)|,|D_c f(x_0)-f'(x_0)|\le \frac{\varepsilon^2}{6} \max_{\xi\in[x_0-\varepsilon,x_0+\varepsilon]}|f'''(\xi)|,

ahol Dcf(x0)=f(x0+ε)−f(x0−ε)2εD_c f(x_0)=\frac{f(x_0+\varepsilon)-f(x_0-\varepsilon)}{2\varepsilon}. Ha f∈C2([x0,x0+ε])f\in C^2([x_0,x_0+\varepsilon]), a forward differenciára

|D+f(x0)−f′(x0)|≤ε2maxξ∈[x0,x0+ε]|f″(ξ)|.|D_+ f(x_0)-f'(x_0)|\le \frac{\varepsilon}{2} \max_{\xi\in[x_0,x_0+\varepsilon]}|f''(\xi)|.

Bizonyítás. A Taylor-tétel Lagrange-maradéktaggal:

f(x0+ε)=f(x0)+εf′(x0)+ε22f″(x0)+ε36f‴(ξ+),f(x_0+\varepsilon)=f(x_0)+\varepsilon f'(x_0)+\frac{\varepsilon^2}{2}f''(x_0) +\frac{\varepsilon^3}{6}f'''(\xi_+),

f(x0−ε)=f(x0)−εf′(x0)+ε22f″(x0)−ε36f‴(ξ−),f(x_0-\varepsilon)=f(x_0)-\varepsilon f'(x_0)+\frac{\varepsilon^2}{2}f''(x_0) -\frac{\varepsilon^3}{6}f'''(\xi_-),

ahol ξ+∈[x0,x0+ε]\xi_+\in[x_0,x_0+\varepsilon] és ξ−∈[x0−ε,x0]\xi_-\in[x_0-\varepsilon,x_0]. A két sort kivonva

f(x0+ε)−f(x0−ε)=2εf′(x0)+ε36(f‴(ξ+)+f‴(ξ−)).f(x_0+\varepsilon)-f(x_0-\varepsilon)=2\varepsilon f'(x_0) +\frac{\varepsilon^3}{6}\big(f'''(\xi_+)+f'''(\xi_-)\big).

2ε2\varepsilon-nal osztva

Dcf(x0)=f′(x0)+ε212(f‴(ξ+)+f‴(ξ−)).D_c f(x_0)=f'(x_0)+\frac{\varepsilon^2}{12} \big(f'''(\xi_+)+f'''(\xi_-)\big).

A hibatag abszolút értéke legfeljebb ε212⋅2M3=ε26M3\frac{\varepsilon^2}{12}\cdot 2M_3=\frac{\varepsilon^2}{6}M_3, ahol M3M_3 a harmadik derivált maximuma. A forward esetben f(x0+ε)=f(x0)+εf′(x0)+ε22f″(ξ+)f(x_0+\varepsilon)=f(x_0)+\varepsilon f'(x_0)+\frac{\varepsilon^2}{2}f''(\xi_+), tehát a hibatag ε2f″(ξ+)\frac{\varepsilon}{2}f''(\xi_+). Ezzel mindkét állítást beláttuk. ▫\square

A lemma értelme: a két Taylor-sor második deriváltas tagja kiejti egymást, ezért a középső differencia O(ε2)O(\varepsilon^2) hibájú, a forward csak O(ε)O(\varepsilon). 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 η\eta, akkor a számláló hibája legfeljebb 2η2\eta, és 2ε2\varepsilon-nal osztva η/ε\eta/\varepsilon járulékot kapunk. A teljes hiba hozzávetőlegesen

E(ε)≈ε26M3+ηε,E(\varepsilon)\approx \frac{\varepsilon^2}{6}M_3+\frac{\eta}{\varepsilon},

aminek minimuma ε*=(3η/M3)1/3\varepsilon_*=(3\eta/M_3)^{1/3}. float64 esetén η∼10−16∥L∥∞\eta\sim10^{-16}\|L\|_\infty; ha a loss nagyságrendileg 11 és M3M_3 is 11, akkor ε*∼7⋅10−6\varepsilon_*\sim7\cdot10^{-6}, ezért a gyakorlati tartomány 10−510^{-5} és 10−710^{-7} között van. float32 esetén η∼10−7\eta\sim10^{-7}, így ε*\varepsilon_* körülbelül 10−310^{-3}-ra tolódik; ez az egyik oka annak, hogy a gradiensellenőrzést float64-ben végezzük.

10.3.4 Kézi példa

Legyen f(x)=x3f(x)=x^3, x0=2x_0=2, ε=10−2\varepsilon=10^{-2}. A pontos derivált f′(2)=12f'(2)=12. A forward differencia:

D+=2.013−230.01=0.1206010.01=12.0601,D_+=\frac{2.01^3-2^3}{0.01}=\frac{0.120601}{0.01}=12.0601,

hiba 0.06010.0601. A középső differencia:

Dc=2.013−1.9930.02=8.120601−7.8805990.02=12.0001,D_c=\frac{2.01^3-1.99^3}{0.02} =\frac{8.120601-7.880599}{0.02}=12.0001,

hiba 0.00010.0001. Mivel f‴=6f'''=6 konstans, a lemma korlátja ε26⋅6=ε2=10−4\frac{\varepsilon^2}{6}\cdot6=\varepsilon^2=10^{-4}, amit a tényleges hiba itt pontosan elér. A forward korláthoz a lemma a [2;2,01][2;\ 2{,}01] intervallumon vett maximumot kéri: f″(x)=6xf''(x)=6x, így max|f″|=6⋅2,01=12,06\max|f''|=6\cdot2{,}01=12{,}06, és a korlát 0,012⋅12,06=0,0603\frac{0{,}01}{2}\cdot12{,}06=0{,}0603. A tényleges hiba 0,0601≤0,06030{,}0601\le0{,}0603; a korlát érvényes, de nem éles egyenlőség. Ha a maximum helyett a derivált x0x_0-beli értékét (f″(2)=12f''(2)=12) írnánk, 0,060{,}06 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ék

A kimenet a fenti kézi számításból adódik; a kódot a labor részeként lehet futtatni.

10.3.5 Labor

A labor a 10.2. Affín réteg és aktiváció backward két rétegű MLP-jét használja: d=3d=3 bemenet, h=4h=4 rejtett egység, K=2K=2 kimenet, N=2N=2 batch. A vizsgálati loss L=1NK∑n,kZ2,nk2L=\frac{1}{NK}\sum_{n,k}Z_{2,nk}^2, ahol Z2=A1W2⊤+b2Z_2=A_1W_2^\top+b_2. A loss Z2Z_2 függvényeként sima, a paraméterek függvényeként azonban a ReLU miatt csak szakaszonként sima: ahol egy Z1Z_1 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 θ±εei\theta\pm\varepsilon e_i 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 (σ∈{0.1,1.0,10.0}\sigma\in\{0.1,1.0,10.0\}) é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.py

A 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 ε=10−3\varepsilon=10^{-3}-nál a legkisebb, és ε\varepsilon csökkenésével nagyjából 1/ε1/\varepsilon ütemben nő: ez a kerekítési tag (η/ε\eta/\varepsilon). 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 (Z2Z_2 minden egyes súlyban affin, a loss Z2Z_2-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 ε\varepsilon 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.

10.3.6 Feladatok

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 f(x)=sinxf(x)=\sin x, x0=π/4x_0=\pi/4, ε=10−3\varepsilon=10^{-3}. Számítsa ki a középső differenciát, a pontos f′(x0)f'(x_0)-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 d=2d=2, h=3h=3, K=1K=1 MLP-re N=1N=1 batch-szel, seed 1-gyel generált float64 paraméterekkel ellenőrzi a backward gradienst középső differenciával ε=10−6\varepsilon=10^{-6} 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, ε=10−12\varepsilon=10^{-12}-vel, és a loss-hoz adott 10610^6 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 ε\varepsilon 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.

10.3.7 Megoldások és indoklás

F1. A forward differencia g+,i=(L(θ+εei)−L(θ))/εg_{+,i}=(L(\theta+\varepsilon e_i)-L(\theta))/\varepsilon, a középső differencia a kétoldali képlet. A lemma szerint a forward hibája O(ε)O(\varepsilon), a középsőé O(ε2)O(\varepsilon^2). A középső differencia nagyobb költségű: 2p2p függvénykiértékelés a forward p+1p+1 helyett, de a gradiensellenőrzésnél a 10−210^{-2}–10−310^{-3} hibarend helyett 10−410^{-4}–10−610^{-6} érhető el. Ezért a central a standard választás.

F2. A pontos derivált f′(π/4)=cos(π/4)=2/2≈0.707106781f'(\pi/4)=\cos(\pi/4)=\sqrt2/2\approx0.707106781. A sin(a+b)−sin(a−b)=2cosasinb\sin(a+b)-\sin(a-b)=2\cos a\sin b azonossággal

Dc=sin(x0+ε)−sin(x0−ε)2ε=cos(x0)sinεε.D_c=\frac{\sin(x_0+\varepsilon)-\sin(x_0-\varepsilon)}{2\varepsilon} =\cos(x_0)\frac{\sin\varepsilon}{\varepsilon}.

ε=10−3\varepsilon=10^{-3} esetén sinε/ε≈1−ε2/6=0.9999998333\sin\varepsilon/\varepsilon\approx1-\varepsilon^2/6=0.9999998333, így Dc≈0.707106781⋅0.9999998333=0.707106663D_c\approx0.707106781\cdot0.9999998333=0.707106663. A tényleges abszolút hiba kb. 1.18⋅10−71.18\cdot10^{-7}. A lemma korlátja: |f‴|=|−cosx|≤1|f'''|=|-\cos x|\le1, tehát ε2/6=1.67⋅10−7\varepsilon^2/6=1.67\cdot10^{-7}; a tényleges hiba ezen belül van. A forward differencia hibája első rendben ε2|f″(x0)|=10−32sin(π/4)≈3.54⋅10−4\frac{\varepsilon}{2}|f''(x_0)|=\frac{10^{-3}}{2}\sin(\pi/4)\approx3.54\cdot10^{-4}, 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 10−1010^{-10} alatti, mert a függvény sima és float64; a konkrét érték a seedtől függ. A shape-ellenőrzés: p=2⋅3+3+1⋅3+1=13p=2\cdot3+3+1\cdot3+1=13, a gradiens vektor hossza 13.

F4. Ok: három együtt ható tényező. A float32 kerekítési egysége ∼10−7\sim10^{-7}; az ε=10−12\varepsilon=10^{-12} esetén a kerekítési járulék η/ε∼10−7/10−12=105\eta/\varepsilon\sim10^{-7}/10^{-12}=10^5-re nő. A 10610^6 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, ε=10−6\varepsilon=10^{-6}, és a konstanst elhagyni, mert a gradiens invariáns a konstansra. Visszaellenőrzés: ugyanazon a ponton a max abszolút hiba 10−910^{-9} alá esik.

F5. Protokoll: rögzített MLP, seed 20261020, N=8N=8 batch; paraméterskálák {0.1,1,10}\{0.1,1,10\}; ε\varepsilon-rács 10−k10^{-k}, k=2,…,10k=2,\dots,10; 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.

10.3.8 Tipikus tévedés: ReLU a töréspontban

A leggyakoribb hamis riasztás a ReLU nullpontjában adódik. Legyen f(z)=max(0,z)f(z)=\max(0,z), és a backward konvenció f′(0)=0f'(0)=0 (mint a 10.2. Affín réteg és aktiváció backward alfejezetben). A central difference z=0z=0-nál:

Dc=f(ε)−f(−ε)2ε=ε−02ε=12.D_c=\frac{f(\varepsilon)-f(-\varepsilon)}{2\varepsilon} =\frac{\varepsilon-0}{2\varepsilon}=\frac12.

A numerikus és a választott szubgradiens eltér, miközben mindkettő a ReLU [0,1][0,1] 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 ε\varepsilon-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.

10.3.9 Önellenőrzés

  1. A central difference előjelét f(x)=x2f(x)=x^2, x0=1x_0=1 ellenőrzi: ((1+ε)2−(1−ε)2)/(2ε)=2((1+\varepsilon)^2-(1-\varepsilon)^2)/(2\varepsilon)=2 minden ε>0\varepsilon>0-ra, ami a pontos derivált. Ha a számlálóban a kivonás sorrendje felcserélődne, −2-2 lenne.
  2. A labor G2=2NKZ2G_2=\frac{2}{NK}Z_2 skálája tartalmazza a mean-reduction 1/N1/N-jét; a linear backward dW=G⊤XdW=G^\top X ezután reduction-független, mert az összegzés explicit.
  3. A gradiens vektor hossza dh+h+Kh+Kdh+h+Kh+K; a laborban 3⋅4+4+2⋅4+2=263\cdot4+4+2\cdot4+2=26. A ravel alapértelmezett C sorrendű, ezért a numerikus perturbáció ugyanabban a sorrendben lapít.
  4. A relatív hiba stabil nevezője nullgradiens esetén nem robban: ha |gbwd|=|gnum|=0|g_{\mathrm{bwd}}|=|g_{\mathrm{num}}|=0, akkor δ=10−12\delta=10^{-12} miatt a tört 0/10−12=00/10^{-12}=0.

10.3.10 Rövid összefoglaló

A középső differencia a gradiens független numerikus becslése, amelynek csonkolási hibája O(ε2)O(\varepsilon^2), szemben a forward differencia O(ε)O(\varepsilon) 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 10−510^{-5}–10−710^{-7}. 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ó.

10.3.11 Következő alkalmazás és források

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: θ←θ−αgbwd\theta\leftarrow\theta-\alpha g_{\mathrm{bwd}}. A numerikus ellenőrzés nem helyettesíti a tanulási konvergencia vizsgálatát, de bizalmat ad a lépés gradienséhez.