16.4 IV. ellenőrző pont: a CNN-operátor teljes megértése

16.4.1 1. Cél, előismeret és induló kérdés

A IV. ellenőrző pont egyetlen szabályos specifikációs lapba foglalja a CNN-operátort: a keresztkorrelációt, a kimeneti alakot, a paramétereket, az adjungált (visszaterjesztett) műveletet, az eltolási szimmetriát és a határfeltételt. A hallgató a 13.1.–14.1. egységekből ismeri a diszkrét jelet és a lokális szűrőt, a 15.1.–15.2. alfejezetből a backwardot, a 16.1. Eltolási equivariance és invariance alfejezetből az equivariance fogalmát, a 16.3. Elméleti és effektív recepciós mező alfejezetből pedig a recepciós mező rekurzióját. Itt a feladat nem új tétel felépítése, hanem annak megmutatása, hogy ugyanaz a képlet egyszerre állja ki az alak-, gradiens- és szimmetriavizsgálatot. A 4.4. I. ellenőrző pont: matematika és kód megfelelése és 12.4. III. ellenőrző pont: önállóan tanuló kis háló módszertani leckéjét idézzük vissza: a numerikus egyezés nem helyettesíti a levezetést, és a checkpoint akkor teljes, ha a kézi számolás, a független f64 referencia és az autograd háromszögben minden hiba a megadott tűrés alatt van. Induló kérdés: egy 4×44\times4-es bemenetre és 2×22\times2-es kernelre hány kimeneti elem adódik, és miért nem nevezhető a Conv2d hívás matematikai konvolúciónak?

Értékelés. A közös 0–4 rubrikát, a PASS-szabályt és a hibatípusonkénti visszautakat az 1.4. Tanulási diagnózis és kísérleti napló „Tanulási főút és ellenőrző pontok” szakasza adja meg; itt csak az eltérés: a PASS (a) pontja a független float64 referencia és a könyvtári művelet egyezése; a súlypont a D1 és a D2.

16.4.2 2. A keresztkorrelációs operátor specifikációja

A bemeneti tenzor X∈ℝN×Cin×H×WX\in\mathbb R^{N\times C_{in}\times H\times W}, a súly K∈ℝCout×Cin×kh×kwK\in\mathbb R^{C_{out}\times C_{in}\times k_h\times k_w}, a bias b∈ℝCoutb\in\mathbb R^{C_{out}}, a stride ss, a szimmetrikus zero padding pp és a dilatáció dd pozitív egész. A programozói főút a térbeli keresztkorrelációt számolja, kernelmegfordítás nélkül. Legyen XpX_p a pp szélességű nullákkal kiegészített bemenet; az output Y∈ℝN×Cout×Hout×WoutY\in\mathbb R^{N\times C_{out}\times H_{out}\times W_{out}} eleme:

Y[n,o,i,j]=b[o]+∑c=0Cin−1∑u=0kh−1∑v=0kw−1Xp[n,c,is+ud,js+vd]⋅K[o,c,u,v],Y[n,o,i,j]\;=\;b[o]\;+\;\sum_{c=0}^{C_{in}-1}\sum_{u=0}^{k_h-1}\sum_{v=0}^{k_w-1}X_p\!\big[n,c,\;i\,s+u\,d,\;j\,s+v\,d\big]\cdot K[o,c,u,v],

ahol n∈[0,N)n\in[0,N), o∈[0,Cout)o\in[0,C_{out}), i∈[0,Hout)i\in[0,H_{out}), j∈[0,Wout)j\in[0,W_{out}). A kimeneti méret a kernel d(k−1)+1d(k-1)+1 effektív kiterjedésével:

Hout=⌊H+2p−d(kh−1)−1s⌋+1,Wout=⌊W+2p−d(kw−1)−1s⌋+1.H_{out}=\left\lfloor\frac{H+2p-d(k_h-1)-1}{s}\right\rfloor+1,\qquad W_{out}=\left\lfloor\frac{W+2p-d(k_w-1)-1}{s}\right\rfloor+1.

A paraméterszám Cout⋅(Cin⋅kh⋅kw+1)C_{out}\cdot(C_{in}\cdot k_h\cdot k_w+1): ennyi tanulható skalár tartozik a réteghez, függetlenül a kép méretétől — ez a súlymegosztás lényege. A groups=1 esetre szorítkozunk; a csoportosítás a 14.4. Csoportos és depthwise konvolúció egységben külön szerződést kapott.

Az adjungált a visszaterjesztés VJP-oldala. A L=12∑n,o,i,jY[n,o,i,j]2L=\tfrac12\sum_{n,o,i,j}Y[n,o,i,j]^2 céllal az upstream gradiens g=Yg=Y, és a láncszabályból:

∂L∂K[o,c,u,v]=∑n,i,jY[n,o,i,j]⋅Xp[n,c,is+ud,js+vd],\frac{\partial L}{\partial K}[o,c,u,v]=\sum_{n,i,j}Y[n,o,i,j]\cdot X_p[n,c,\,i s+u d,\,j s+v d],

∂L∂Xp[n,c,a,b]=∑o,u,v:is+ud=a,js+vd=bY[n,o,i,j]⋅K[o,c,u,v],\frac{\partial L}{\partial X_p}[n,c,a,b]=\sum_{o,u,v:\;i s+u d=a,\;j s+v d=b}Y[n,o,i,j]\cdot K[o,c,u,v],

majd d=1d=1 paddinggel a pp keret levágásával kapjuk ∂L/∂X\partial L/\partial X-et. A biasgradiens ∂L/∂b[o]=∑n,i,jY[n,o,i,j]\partial L/\partial b[o]=\sum_{n,i,j}Y[n,o,i,j]. A scatter–add szerkezet mutatja, hogy a backward a megfelelő lineáris operátor transzponáltja/adjungáltja — ezért kapcsolódik a transposed convolutionhoz, amely nem általános inverz.

16.4.3 3. Eltolási equivariance: bizonyított tétel és határ-ellenpélda

Tétel (stride-részcsoport szerinti equivariance, határmentes tartományon). Legyen CKC_K a bias nélküli keresztkorrelációs operátor ss stride-dal, pp paddinggel, d=1d=1 dilatációval, a Cin=Cout=1C_{in}=C_{out}=1, N=1N=1 esetben, és legyen az értelmezési tartomány a teljes ℤ2\mathbb Z^2 rács (véges tartójú bemenettel, nullakiterjesztéssel). Ekkor tetszőleges t=(t1,t2)∈ℤ2t=(t_1,t_2)\in\mathbb Z^2 eltolásra:

CK(SstX)=St(CKX),C_K\big(S_{s\,t}X\big)\;=\;S_t\big(C_K X\big),

ahol (SτX)[a,b]=X[a−τ1,b−τ2](S_\tau X)[a,b]=X[a-\tau_1,\,b-\tau_2] a koordinátaeltolás. Tehát az input sts\,t pixelnyi eltolása az outputon tt rácspontnyi eltolást okoz.

Bizonyítás. Pontonként alkalmazzuk a definíciót. Az output (i,j)(i,j) pozícióban:

CK(SstX)[i,j]=∑u,v(SstX)[is+u,js+v]⋅K[u,v]=∑u,vX[is+u−st1,js+v−st2]⋅K[u,v].C_K(S_{s\,t}X)[i,j]=\sum_{u,v}(S_{s\,t}X)[i s+u,\;j s+v]\cdot K[u,v]=\sum_{u,v}X[i s+u-s t_1,\;j s+v-s t_2]\cdot K[u,v].

Az indexek átrendezésével (i−t1)s+u(i-t_1)s+u és (j−t2)s+v(j-t_2)s+v adódik, ami éppen a deriválatlan output (i−t1,j−t2)(i-t_1,j-t_2) pozíciójának képlete, azaz St(CKX)[i,j]S_t(C_K X)[i,j]. Az egyenlőség minden pozícióban fennáll, mert a ℤ2\mathbb Z^2 rácson nincs kilépés a tartományból. ▫\square

Következmény (véges tartomány feltétele). Véges [0,H)×[0,W)[0,H)\times[0,W) képen zero paddinggel az equivariance csak akkor igaz, ha az eltolt bemenet minden, a forward által elért pozíciója az eredeti tartományba esik; más szóval a kernel támogatása nem lép ki a kép határán. Ha a szélső oszlopok nullákkal pótlódnak, a szélső outputokban az információvesztés aszimmetrikus lesz, és az egyenlőség megbomlik.

Ellenpélda (határon sérül az equivariance). Legyen $X=\begin{bsmallmatrix}1&2&3\\4&5&6\\7&8&9\end{bsmallmatrix}$ és $K=\begin{bsmallmatrix}1&0\\0&1\end{bsmallmatrix}$, s=1s=1, p=0p=0. A forward $Y=C_K X=\begin{bsmallmatrix}6&8\\12&14\end{bsmallmatrix}$. Az inputot jobbra tolva: $S_{(0,1)}X=\begin{bsmallmatrix}0&1&2\\0&4&5\\0&7&8\end{bsmallmatrix}$, így $C_K(S_{(0,1)}X)=\begin{bsmallmatrix}4&6\\7&12\end{bsmallmatrix}$. Az output eltolása viszont $S_{(0,1)}Y=\begin{bsmallmatrix}0&6\\0&12\end{bsmallmatrix}$. A két mátrix első oszlopa különbözik (4≠04\ne0 és 7≠07\ne0), tehát az equivariance a véges, pad nélküli képen nem teljesül. A hiba a bal szélen keletkezik, ahol az eltolt input nullái az eredeti 1,41,4 értékeket helyettesítik, miközben az output-eltolás a 6,126,12 értékeket egyszerűen kihúzza a nulltartományba.

16.4.4 4. Kézi példa: teljes forward és spot-check gradiens

Vegyük a 4×44\times4-es bemenetet és a $K=\begin{bsmallmatrix}1&2\\3&4\end{bsmallmatrix}$ kernelt s=1s=1, p=0p=0 mellett. A 2×22\times2-es ablakok szisztematikus szorzatösszege:

Y=[4454648494104124134144].Y=\begin{bmatrix}44&54&64\\84&94&104\\124&134&144\end{bmatrix}.

Például Y[1,1]=6⋅1+7⋅2+10⋅3+11⋅4=94Y[1,1]=6\cdot1+7\cdot2+10\cdot3+11\cdot4=94. Az L=12∑Y2L=\tfrac12\sum Y^2 célra g=Yg=Y, így a súlygradiensek a fenti scatter–add képletekből kézzel is ellenőrizhetők:

∂L∂K=[60966942948010326].\frac{\partial L}{\partial K}=\begin{bmatrix}6096&6942\\9480&10326\end{bmatrix}.

Egyetlen elem teljes kifejtésben: ∂L/∂K[0,0]=∑i,jY[i,j]X[i,j]=44⋅1+54⋅2+⋯+134⋅10+144⋅11=6096\partial L/\partial K[0,0]=\sum_{i,j}Y[i,j]\,X[i,j]=44\cdot1+54\cdot2+\cdots+134\cdot10+144\cdot11=6096. Az inputgradiens egy belső pontja ∂L/∂X[1,1]=Y[0,0]K[1,1]+Y[0,1]K[1,0]+Y[1,0]K[0,1]+Y[1,1]K[0,0]=44⋅4+54⋅3+84⋅2+94⋅1=600\partial L/\partial X[1,1] = Y[0,0]K[1,1]+Y[0,1]K[1,0]+Y[1,0]K[0,1]+Y[1,1]K[0,0] = 44\cdot4+54\cdot3+84\cdot2+94\cdot1 = 600, a bal felső saroké pedig ∂L/∂X[0,0]=Y[0,0]K[0,0]=44\partial L/\partial X[0,0]=Y[0,0]K[0,0]=44. A teljes mátrixot a labor kódja numerikusan ellenőrzi.

16.4.5 5. Labor: független f64 referencia, autograd és határkísérlet

A labor három független utat futtat: (i) saját NumPy forward/backward float64-ben, (ii) PyTorch conv2d autograd float64-ben, (iii) határkezelési hiba térképe. A kézi értékek az 1–2. pontból származó várt értékek; a futtatás során a np.testing.assert_allclose rögzíti őket 10−1210^{-12} abszolút tűréssel.

# lab/n064_checkpoint.py -- futtatás: python lab/n064_checkpoint.py
import numpy as np
import torch

torch.manual_seed(0); np.random.seed(0)

def cc_forward(X, K, b, stride=1, pad=0, dilation=1):
    """Referencia: cross-correlation, NCHW, groups=1, zero pad, float64."""
    N, C, H, W = X.shape
    O, Ck, kh, kw = K.shape
    assert C == Ck
    eff_h = dilation * (kh - 1) + 1
    eff_w = dilation * (kw - 1) + 1
    H_out = (H + 2 * pad - eff_h) // stride + 1
    W_out = (W + 2 * pad - eff_w) // stride + 1
    Xp = np.pad(X, ((0, 0), (0, 0), (pad, pad), (pad, pad)), mode="constant")
    Y = np.empty((N, O, H_out, W_out), dtype=np.float64)
    for n in range(N):
        for o in range(O):
            for i in range(H_out):
                for j in range(W_out):
                    total = b[o]
                    for c in range(C):
                        for u in range(kh):
                            for v in range(kw):
                                total += Xp[n, c, i * stride + u * dilation,
                                            j * stride + v * dilation] * K[o, c, u, v]
                    Y[n, o, i, j] = total
    return Y, Xp

def cc_backward_L2(Y, Xp, K, stride=1, dilation=1):
    """Backward a L = 0.5 * sum(Y**2) célra; upstream gradiens g = Y."""
    N, O, H_out, W_out = Y.shape
    Nc, C, Hp, Wp = Xp.shape          # Xp mérete megadja a paddinget is
    kh, kw = K.shape[2], K.shape[3]
    dK = np.zeros_like(K, dtype=np.float64)
    dXp = np.zeros_like(Xp, dtype=np.float64)
    db = np.zeros(O, dtype=np.float64)
    for n in range(N):
        for o in range(O):
            for i in range(H_out):
                for j in range(W_out):
                    g = Y[n, o, i, j]
                    db[o] += g
                    for c in range(C):
                        for u in range(kh):
                            for v in range(kw):
                                a = i * stride + u * dilation
                                b_idx = j * stride + v * dilation
                                dK[o, c, u, v] += g * Xp[n, c, a, b_idx]
                                dXp[n, c, a, b_idx] += g * K[o, c, u, v]
    return dK, db, dXp

# Kézi példa
X = np.arange(1, 17, dtype=np.float64).reshape(1, 1, 4, 4)
K = np.array([[[[1.0, 2.0], [3.0, 4.0]]]])
b = np.zeros((1,), dtype=np.float64)
Y_manual = np.array([[[[44.0, 54.0, 64.0],
                       [84.0, 94.0, 104.0],
                       [124.0, 134.0, 144.0]]]], dtype=np.float64)

Y_ref, Xp = cc_forward(X, K, b, stride=1, pad=0)
np.testing.assert_allclose(Y_ref, Y_manual, atol=1e-12)
dK, db, dXp = cc_backward_L2(Y_ref, Xp, K)
dK_manual = np.array([[[[6096.0, 6942.0], [9480.0, 10326.0]]]])
np.testing.assert_allclose(dK, dK_manual, atol=1e-10)
np.testing.assert_allclose(dXp[:, :, 1, 1], 600.0, atol=1e-10)
np.testing.assert_allclose(dXp[:, :, 0, 0], 44.0, atol=1e-10)

# PyTorch autograd-kontroll
Xt = torch.tensor(X, dtype=torch.float64, device="cpu", requires_grad=True)
Kt = torch.tensor(K, dtype=torch.float64, device="cpu", requires_grad=True)
bt = torch.tensor(b, dtype=torch.float64, device="cpu", requires_grad=True)
Yt = torch.nn.functional.conv2d(Xt, Kt, bt, stride=1, padding=0)
Lt = 0.5 * torch.sum(Yt ** 2)
Lt.backward()
np.testing.assert_allclose(Yt.detach().numpy(), Y_manual, atol=1e-12)
np.testing.assert_allclose(Kt.grad.numpy(), dK_manual, atol=1e-12)
np.testing.assert_allclose(dXp, Xt.grad.numpy(), atol=1e-12)  # paddinges input, itt nincs keret
np.testing.assert_allclose(bt.grad.numpy(), np.array([Y_ref.sum()]), atol=1e-12)
print("forward/backward: OK (f64, tűrés 1e-12)")

# Határkezelési kísérlet: 3x3-as identitás kernel, stride=1, pad=0
def shift_zero_2d(arr, dh, dw):
    out = np.zeros_like(arr)
    h, w = arr.shape[2], arr.shape[3]
    src_h0, src_h1 = max(0, -dh), min(h, h - dh)
    dst_h0, dst_h1 = max(0, dh), min(h, h + dh)
    src_w0, src_w1 = max(0, -dw), min(w, w - dw)
    dst_w0, dst_w1 = max(0, dw), min(w, w + dw)
    out[:, :, dst_h0:dst_h1, dst_w0:dst_w1] = arr[:, :, src_h0:src_h1, src_w0:src_w1]
    return out

Xb = np.arange(1, 10, dtype=np.float64).reshape(1, 1, 3, 3)
Kb = np.array([[[[1.0, 0.0], [0.0, 1.0]]]])
Yb, _ = cc_forward(Xb, Kb, np.zeros(1), stride=1, pad=0)
print("shift_err (Frobenius-norma, 3x3/2x2):")
for dh in range(-1, 2):
    row = []
    for dw in range(-1, 2):
        Xs = shift_zero_2d(Xb, dh, dw)
        Ys, _ = cc_forward(Xs, Kb, np.zeros(1), stride=1, pad=0)
        Ysh = shift_zero_2d(Yb, dh, dw)
        row.append(f"{np.linalg.norm(Ys - Ysh):.2f}")
    print(("dh=%+d: " % dh) + " ".join(row))

A határkísérlet várt kimenete a kézi számolásból: a Frobenius-hiba csak (dh,dw)=(0,0)(d_h,d_w)=(0,0)-ban 00; a többi nyolc eltolás hibája például (0,+1)(0,+1)-re 65≈8,06\sqrt{65}\approx8{,}06, (+1,0)(+1,0)-ra 13≈3,61\sqrt{13}\approx3{,}61, (−1,−1)(-1,-1)-re 181≈13,45\sqrt{181}\approx13{,}45. Ez a minta azt mutatja, hogy pad nélküli, kisméretű képen minden nem-nulla eltolás megsérti az equivarianciát a határokon. Nagyobb pad esetén a belső eltolások tartománya szélesebb lenne, de a legszélső outputok akkor is határérzékenyek. A stride s>1s>1 esetén a részcsoport-feltétel külön is megjelenik: csak az ss-szeres inputeltolások kommutálnak, a többiek még határmentes tartományon sem.

16.4.6 6. Feladatok (F1–F5)

F1 (fogalmi, nehézség 1). Definiálja a keresztkorreláció és a matematikai konvolúció különbségét 1D-ben. Adjon olyan x∈ℝ3x\in\mathbb R^3 jelet és K∈ℝ2K\in\mathbb R^2 kernelt, amelyre a keresztkorreláció [1,6][1,6] kimenetet ad, a valódi konvolúció pedig [1,2][1,2] kimenetet. Elvárt átadandó: definíciók, majd az ellenőrző számolás.

F2 (levezetési, nehézség 2). Vezesse le a biasgradiens és a súlygradiens képletét 1D-ben: x∈ℝ7x\in\mathbb R^7, K∈ℝ3K\in\mathbb R^3, s=2s=2, p=1p=1, d=1d=1, L=12∑Y2L=\tfrac12\sum Y^2. Adja meg HoutH_{out}-ot, majd ∂L/∂K[1]\partial L/\partial K[1] teljes összegző képletét az outputindexekkel. Elvárt átadandó: minden indexhatár, a padding indexeltolás, valamint egy Y[1]Y[1]-re vonatkozó láncszabályos felbontás láthatóan.

F3 (implementációs, nehézség 2). Egészítse ki a labor cc_forward függvényét úgy, hogy tetszőleges d≥1d\ge1 dilatáció mellett is adjon vissza az outputtal konzisztens paddinges bemenetet, majd írjon unit tesztet egy 5×55\times5-ös véletlen bemenetre numpy.random.RandomState(42) generátorral, a labor cc_backward_L2-vel és PyTorch autograddal L=12∑Y2L=\tfrac12\sum Y^2-re. Elvárt átadandó: teljes kód, float64, max(|a-b|) < 5e-12 a gradiensmátrixokra.

F4 (hibakeresési, nehézség 2). Az alábbi backward-vázlat a d=1d=1 esetben hibásan a kernel megfordítottját használja K_rev[o,c,u,v]=K[o,c,-u-1,-v-1] a dXp scatter–add ágában. Lokalizálja a hibát, mutassa meg következményét egy 3×33\times3, 2×22\times2-es eset számszerű gradiensével, és adja meg a javított sort. Elvárt átadandó: hiba oka, hibás gradienselem, javítás, autograd-ellenőrzés.

F5 (kísérleti/transzfer, nehézség 3). Tervezzen kísérletet a GlyphLab data/metadata.csv-re, amely azt méri, hogy egy stride-2-t használó, befagyasztott CNN-osztályozó test-pontossága hogyan változik a képek dhd_h-nyi, illetve sts\,t-nyi (stride-többszörösi) eltolására, zero padding nélkül. Fogalmazza meg a hipotézist, a metrikát, a megfigyelési egységet, a baseline-t, a seed-kezelést és az érvényességi korlátot. Elvárt átadandó: protokoll-szöveg, nem szükséges futtatott eredmény.

16.4.7 7. Megoldások és indoklás

F1 megoldás. A keresztkorreláció Y[i]=∑k=0L−1X[i+k]K[k]Y[i]=\sum_{k=0}^{L-1}X[i+k]K[k], tehát a kernel csúszik a jelen, azaz nem megfordítva. A konvolúció C[i]=∑k=0L−1X[i−k]K[k]C[i]=\sum_{k=0}^{L-1}X[i-k]K[k], ami a kernel időbeli megfordítását jelenti. Példa: X=[1,0,3]X=[1,0,3], K=[1,2]K=[1,2], pad=0, stride=1. Keresztkorreláció: Y[0]=1⋅1+0⋅2=1Y[0]=1\cdot1+0\cdot2=1, Y[1]=0⋅1+3⋅2=6Y[1]=0\cdot1+3\cdot2=6, tehát [1,6][1,6]. Konvolúció: C[0]=X[0]K[0]+X[−1]K[1]=1+0=1C[0]=X[0]K[0]+X[-1]K[1]=1+0=1, C[1]=X[1]K[0]+X[0]K[1]=0+2=2C[1]=X[1]K[0]+X[0]K[1]=0+2=2, tehát [1,2][1,2]. A két művelet a kernel megfordításában különbözik.

F2 megoldás. A kimenet hossza Hout=⌊(7+2−2−1)/2⌋+1=3+1=4H_{out}=\lfloor(7+2-2-1)/2\rfloor+1=3+1=4. A p=1p=1 paddinggel XpX_p indexei 0..80..8: Xp[0]=0X_p[0]=0, Xp[1..7]=X[1..7]X_p[1..7]=X[1..7], Xp[8]=0X_p[8]=0. A forward: Y[i]=∑u=02Xp[2i+u]K[u]Y[i]=\sum_{u=0}^{2}X_p[2i+u]K[u]. A súlygradiens ∂L/∂K[u]=∑i=03Y[i]Xp[2i+u]\partial L/\partial K[u]=\sum_{i=0}^{3}Y[i]\,X_p[2i+u]; konkrétan u=1u=1-re ∑i=03Y[i]Xp[2i+1]\sum_{i=0}^{3}Y[i]X_p[2i+1]. A lánc minden Y[i]Y[i]-re Y[i]=∑uXp[2i+u]K[u]Y[i]=\sum_u X_p[2i+u]K[u], és ∂L/∂K[u]=∑iY[i]⋅∂Y[i]/∂K[u]=∑iY[i]Xp[2i+u]\partial L/\partial K[u]=\sum_i Y[i]\cdot\partial Y[i]/\partial K[u]=\sum_i Y[i]X_p[2i+u]. A padding miatt például i=0i=0 esetén Xp[1]=X[0]X_p[1]=X[0], Xp[2]=X[1]X_p[2]=X[1], Xp[3]=X[2]X_p[3]=X[2]; az alsó indexeltolást a pp rögzíti.

F3 megoldásváz. A cc_forward dilatációval: az effektív kernelméret d(kh−1)+1d(k_h-1)+1, a cc_backward_L2 pedig ugyanazt az i⋅s+u⋅di\cdot s+u\cdot d indexelést használja, mert a dilation a forward és a backward indexelését ugyanúgy nyújtja. A unit teszt RandomState(42)-vel generál, a PyTorch conv2d(..., dilation=d) hívásával vet össze L_t = 0.5 * torch.sum(Y_t**2) céllal; a max|a−b|<5×10−12\max|a-b|<5\times10^{-12} ellenőrzés float64-ben a négy gradiens- és outputmátrixra vonatkozik. A paddinges input trükk kiküszöböli a határon kívüli indexelést, ezért a dXp közvetlenül XpX_p alakú, ahonnan a keret levágásával kapjuk ∂L/∂X\partial L/\partial X-et.

F4 megoldás. A hiba oka az adjungált és az inverz fogalmának összemosása: a backward scatter–add folyamat a forward lineáris operátor transzponáltja, amely ugyanazt a K kernelt használja, nem a megfordítottját. A KrevK_{rev} alkalmazása a matematikai konvolúció adjungáltját keveri a keresztkorreláció adjungáltjába. Következmény: a 3×33\times3-as, $K=\begin{bsmallmatrix}1&2\\3&4\end{bsmallmatrix}$ esetben például ∂L/∂X[1,1]=600\partial L/\partial X[1,1]=600 a helyes érték, míg a rev-kernel a kevert 44⋅4+54⋅1+84⋅3+94⋅244\cdot4+54\cdot1+84\cdot3+94\cdot2-szerű összeget adná, ami már az első tagon 176176 versus a valódi 176176? Számszerű kontroll: a rev esetben ∂L/∂X[1,1]=Y[0,0]Krev[0,0]+Y[0,1]Krev[0,1]+Y[1,0]Krev[1,0]+Y[1,1]Krev[1,1]=44⋅4+54⋅3+84⋅2+94⋅1=176+162+168+94=600\partial L/\partial X[1,1]=Y[0,0]K_{rev}[0,0]+Y[0,1]K_{rev}[0,1]+Y[1,0]K_{rev}[1,0]+Y[1,1]K_{rev}[1,1]=44\cdot4+54\cdot3+84\cdot2+94\cdot1=176+162+168+94=600-nak látszik, mert a 2×22\times2-es szimmetrikus példában a sarok-elemek felcserélődése másképp hat — ezért a kontrollhoz aszimmetrikus kernelt kell választani. Javítás: a dXp ágban töröljük a K_rev használatát, és a K[o,c,u,v] szerepel a scatter–add összegben; az autograd-összehasonlítás 10−1210^{-12} tűréssel igazolja a javítást.

F5 megoldásváz. Hipotézis: a stride-2 hálózat pontossága a stride-többszörösi eltolásokra a valid tartományban közel állandó, a nem-többszörösi 1-pixeles eltolásokra azonban a részcsoportfeltétel miatt nincs garantált ekvivariancia. Metrika: test-accuracy osztályonkénti bontásban. Megfigyelési egység: scene_id, nem kép, mert a két view függő. Split: az eredeti metadata.csv train/val/test azonosítóját használjuk; a modellt a train+val részen tanítjuk, a testet csak a végén értékeljük. Baseline: az eltolatlan test-accuracy ugyanazon a befagyasztott checkpointon. A kísérlet minden dh∈{-2,-1,0,1,2} eltolásra a korrupt-test párosításhoz hasonlóan ugyanazon scene/view sorrendet tartja. Seed: a tanítást három seeddel ismételjük, és a seedek közötti szórást külön jelentjük a CI-től. Érvényességi korlát: a 24×2424\times24-es GlyphLab nem természetes képi benchmark, és a zero pad nélküli eltolás adatvesztést okoz a határon, ezért a határon belüli kernel-támogatottságú esetekre szűkítve értelmezzük az eredményt.

16.4.8 8. Tipikus tévedés, önellenőrzés, visszahívás

A fejezet kulcsfigyelmeztetése: a könyvtárhívás neve nem azonos az általa megvalósított matematikai objektum pontos definíciójával. A torch.nn.functional.conv2d név ellenére térbeli keresztkorrelációt számol. Ellenpélda a 4×4-es bemenetünkön: az eredeti kernel $K=\begin{bsmallmatrix}1&2\\3&4\end{bsmallmatrix}$ a [0,0][0,0] pozícióban 4444-et ad, míg a matematikai konvolúció által megkövetelt flip $K_{rev}=\begin{bsmallmatrix}4&3\\2&1\end{bsmallmatrix}$ ugyanitt 4⋅1+3⋅2+2⋅5+1⋅6=264\cdot1+3\cdot2+2\cdot5+1\cdot6=26-ot ad. Tehát a conv2d nem flip-el; a név öröklött terminológia. Önellenőrzés: (i) a forward helyessége a három független csatorna — kézi számolás, NumPy referencia, autograd — egyezésén áll; (ii) az equivariance tételt csak a kimondott ℤ2\mathbb Z^2 tartományra és ss-szeres eltolásra állítjuk, a finite-grid ellenpélda a határfeltétel fontosságát rögzíti. Visszahívás: a 4.4. I. ellenőrző pont: matematika és kód megfelelése láncszabály-VJP és a 15.4. Független gradiensellenőrzési protokoll float64 centrális differencia protokollja itt a linearitás miatt közvetlenül alkalmazható; a 16.3. Elméleti és effektív recepciós mező recepciós mező rekurziója ugyanezt az indexeltolást használja, ami jól mutatja, hogy a jump és a kernel-nyúlás egyazon szomszédsági struktúra két leképezése.

16.4.9 9. Rövid összefoglaló

A IV. ellenőrző pont a CNN-operátort egységes specifikációként zárja. A keresztkorreláció Y[n,o,i,j]=b[o]+∑c,u,vXp[n,c,is+ud,js+vd]K[o,c,u,v]Y[n,o,i,j]=b[o]+\sum_{c,u,v}X_p[n,c,i s+u d,j s+v d]K[o,c,u,v], a kimeneti méret a pad-dilatáció-stride képletből adódik, a paraméterszám Cout(Cinkhkw+1)C_{out}(C_{in}k_hk_w+1). A backward a ugyanezen lineáris operátor adjungáltja, scatter–add szerkezettel. A bizonyított tétel szerint határmentes ℤ2\mathbb Z^2 rácson stride-ss esetén az input sts t eltolása az output tt eltolását adja; a 3×33\times3-as, pad nélküli ellenpélda mutatja, hogy véges képen a határon ez megsérül. A labor kézi értéket, float64 referenciát, PyTorch-autograd egyezést és a shift-hibatérképet együtt ellenőrzi.

16.4.10 Következő alkalmazás

A 17.1. LeNet és a lokális megosztott reprezentáció LeNet-től kezdve a klasszikus architektúrák ugyanezt a specifikációs lapot használják: a rétegméret, a paraméterszám és a lokális megosztott reprezentáció ott visszavezethető a most ellenőrzött operátor-definícióra.

16.4.11 Felhasznált források