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
-es
bemenetre és
-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.
A bemeneti tenzor , a súly , a bias , a stride , a szimmetrikus zero padding és a dilatáció pozitív egész. A programozói főút a térbeli keresztkorrelációt számolja, kernelmegfordítás nélkül. Legyen a szélességű nullákkal kiegészített bemenet; az output eleme:
ahol , , , . A kimeneti méret a kernel effektív kiterjedésével:
A paraméterszám
:
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 céllal az upstream gradiens , és a láncszabályból:
majd paddinggel a keret levágásával kapjuk -et. A biasgradiens . 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.
Tétel (stride-részcsoport szerinti equivariance, határmentes tartományon). Legyen a bias nélküli keresztkorrelációs operátor stride-dal, paddinggel, dilatációval, a , esetben, és legyen az értelmezési tartomány a teljes rács (véges tartójú bemenettel, nullakiterjesztéssel). Ekkor tetszőleges eltolásra:
ahol a koordinátaeltolás. Tehát az input pixelnyi eltolása az outputon rácspontnyi eltolást okoz.
Bizonyítás. Pontonként alkalmazzuk a definíciót. Az output pozícióban:
Az indexek átrendezésével és adódik, ami éppen a deriválatlan output pozíciójának képlete, azaz . Az egyenlőség minden pozícióban fennáll, mert a rácson nincs kilépés a tartományból.
Következmény (véges tartomány feltétele). Véges 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}$, , . 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 ( és ), 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 értékeket helyettesítik, miközben az output-eltolás a értékeket egyszerűen kihúzza a nulltartományba.
Vegyük a -es bemenetet és a $K=\begin{bsmallmatrix}1&2\\3&4\end{bsmallmatrix}$ kernelt , mellett. A -es ablakok szisztematikus szorzatösszege:
Például . Az célra , így a súlygradiensek a fenti scatter–add képletekből kézzel is ellenőrizhetők:
Egyetlen elem teljes kifejtésben: . Az inputgradiens egy belső pontja , a bal felső saroké pedig . A teljes mátrixot a labor kódja numerikusan ellenőrzi.
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
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 -ban ; a többi nyolc eltolás hibája például -re , -ra , -re . 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 esetén a részcsoport-feltétel külön is megjelenik: csak az -szeres inputeltolások kommutálnak, a többiek még határmentes tartományon sem.
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 jelet és kernelt, amelyre a keresztkorreláció kimenetet ad, a valódi konvolúció pedig 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: , , , , , . Adja meg -ot, majd teljes összegző képletét az outputindexekkel. Elvárt átadandó: minden indexhatár, a padding indexeltolás, valamint egy -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
dilatáció mellett is adjon vissza az outputtal konzisztens paddinges
bemenetet, majd írjon unit tesztet egy
-ös
véletlen bemenetre numpy.random.RandomState(42)
generátorral, a labor cc_backward_L2-vel és PyTorch
autograddal
-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
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
,
-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
-nyi,
illetve
-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.
F1 megoldás. A keresztkorreláció , tehát a kernel csúszik a jelen, azaz nem megfordítva. A konvolúció , ami a kernel időbeli megfordítását jelenti. Példa: , , pad=0, stride=1. Keresztkorreláció: , , tehát . Konvolúció: , , tehát . A két művelet a kernel megfordításában különbözik.
F2 megoldás. A kimenet hossza . A paddinggel indexei : , , . A forward: . A súlygradiens ; konkrétan -re . A lánc minden -re , és . A padding miatt például esetén , , ; az alsó indexeltolást a rögzíti.
F3 megoldásváz. A cc_forward
dilatációval: az effektív kernelméret
,
a cc_backward_L2 pedig ugyanazt az
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
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
alakú, ahonnan a keret levágásával kapjuk
-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
alkalmazása a matematikai konvolúció adjungáltját
keveri a keresztkorreláció adjungáltjába. Következmény: a
-as,
$K=\begin{bsmallmatrix}1&2\\3&4\end{bsmallmatrix}$
esetben például
a helyes érték, míg a rev-kernel a kevert
-szerű
összeget adná, ami már az első tagon
versus a valódi
?
Számszerű kontroll: a rev esetben
-nak
látszik, mert a
-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
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
-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.
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
pozícióban
-et
ad, míg a matematikai konvolúció által megkövetelt flip $K_{rev}=\begin{bsmallmatrix}4&3\\2&1\end{bsmallmatrix}$
ugyanitt
-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
tartományra és
-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.
A IV. ellenőrző pont a CNN-operátort egységes specifikációként zárja.
A keresztkorreláció
,
a kimeneti méret a pad-dilatáció-stride képletből adódik, a
paraméterszám
.
A backward a ugyanezen lineáris operátor adjungáltja, scatter–add
szerkezettel. A bizonyított tétel szerint határmentes
rácson
stride-
esetén az input
eltolása az output
eltolását adja; a
-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.
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.