A 15.2. Inputgradiens és átfedő recepciós
mezők egységben indexszinten levezettük, hogy a konvolúciós réteg
bemeneti gradiense az upstream gradiensből scatter-add művelettel áll
elő. Most ezt a műveletet egyetlen matematikai objektumként vizsgáljuk:
a forward konvolúció lineáris operátor, a backwardban számolt bemeneti
gradiens pedig ennek az operátornak az adjungáltja, azaz
mátrixreprezentációban a transzponáltja. A könyvtári
ConvTranspose2d nem az inverz, hanem éppen ezt az adjungált
utat valósítja meg. A [D11] könyvtári leírását a következőkben saját
mátrixos levezetéssel vetjük össze, és különválasztjuk az adjungált
műveletet az inverz leképezéstől.
Induló kérdés: ha egy 4 hosszú 1D jelet egy 2 hosszú kernel valid, stride=1 cross-correlationje 3 hosszúvá képez, akkor a 3 hosszú gradiensből a 4 hosszú bemeneti gradienst a kernel megfordításával vagy a kernel eredeti sorrendjével, de a mátrixtranszponált oszlopaiból kapjuk? A válasz a mátrixtranszponált: a szummákban az indexeltolás és a kernel együtthatóinak párosítása változik meg, nem a kernel mint vektor sorrendje.
Legyen és a standard euklideszi skalárszorzattal: . Egy leképezés lineáris operátor, ha minden és esetén
Rögzített bázisban egy mátrixszal reprezentálható; a továbbiakban ugyanazt a jelet használjuk a leképezésre és a mátrixára.
Definíció (adjungált). A leképezés a adjungáltja, ha minden , esetén
Valós mátrix esetén az adjungált a mátrixtranszponált: , mert . Komplex terekben a konjugált transzponált lenne az adjungált; ebben a fejezetben minden mennyiség valós, így a transzponált és az adjungált azonosítható.
Tétel (az adjungált egyértelműsége). Ha és is teljesíti az (1) feltételt, akkor .
Bizonyítás. Minden esetén válasszuk az vektort. Ekkor
tehát , azaz , minden -ra.
A neurális hálózatok nyelvén az adjungált a vektor–Jacobi-szorzat szerepét tölti be. Ha skalárfüggvény és , akkor a láncszabály szerint
A 4.3. Jacobi-mátrix, láncszabály és VJP alfejezetben bevezetett VJP éppen ezt a transzponált alkalmazást jelenti; a 15.2. Inputgradiens és átfedő recepciós mezők alfejezetben levezetett képlet ennek a konvolúciós mátrixra specializált, indexelt alakja.
A 15.2. Inputgradiens és átfedő recepciós mezők és [D10] jelölése szerint legyen a bemeneti képbatch , a kimenet , a kernel pedig . Ha a bias , akkor a cross-correlation minden egyes kimeneti eleme a bemenet lineáris kombinációja:
Itt a kernel sor- és oszlopindexe, a stride, a dilation, a padding. Az a bemenet virtuális nullakiterjesztése: , ha és , különben . (A paddingen kívüli virtuális elemek hozzájárulása így nulla.) Vektorizáljuk a tensort a memóriabeli, NCHW-sorrendű lapítással: . A fenti összeg egy mátrixot határoz meg, amelyre . A mátrix sorai a kernel együtthatóit tartalmazzák a megfelelő bemeneti pozíciókban, a padding miatt pedig nulla hozzájárulásokat. A backward bemeneti gradiense ezért
amely pontosan a 15.2. Inputgradiens és átfedő recepciós mezők alfejezetben levezetett scatter-add: minden -elem a kernel súlyával visszaszóródik az őt létrehozó bemeneti pozíciókra, és az átfedések gradienshozzájárulásai összegződnek.
A transposed convolution (transzponált konvolúció) a diszkrét jelfeldolgozásban az az operátor, amely a forward cross-correlation lineáris leképezésének adjungáltját valósítja meg. A „transposed” szó a leképezés mátrixának transzponáltjára utal, nem a kernel valamely geometriai megfordítására, és nem is egy általános inverzre.
A PyTorch ConvTranspose2d modul ennek a diszkrét
operátornak a paraméterezett rétege. Csatornaszempontból a forward
út megfordul: a gradiensirányú modul bemenete
,
kimenete
csatornát kezel. Két dolgot kell elválasztani. Az
operátormátrix transzponálódik:
helyett
.
Az API súlytenzorának tárolási konvenciója viszont nem
cserél dimenziót: a Conv2d(C_in, C_out) súlya
,
a ConvTranspose2d(in_channels=C_out, out_channels=C_in)
súlya pedig
— ugyanaz az alak. Az adjungált modulba ezért a forward súlya csere
nélkül, változatlanul másolható; a transzponálást a modul műveleti
definíciója végzi el. A bias nem része a lineáris adjungált
azonosságnak; az affin
leképezés Jacobi-mátrixa továbbra is
,
ezért a bemeneti gradiensben a bias nem jelenik meg, a bias saját
gradiense pedig az upstream elemek összege.
Többcsatornás ellenőrzés: egy csatornás, stride-os, paddinges forward és a csatornás transzponált modul ugyanazzal a súllyal, az adjungált skalárszorzat-azonossággal:
import torch
torch.manual_seed(20261020)
dt = torch.float64
fwd = torch.nn.Conv2d(2, 3, kernel_size=(2, 3), stride=2, padding=1, bias=False, dtype=dt)
adj = torch.nn.ConvTranspose2d(3, 2, kernel_size=(2, 3), stride=2, padding=1, bias=False, dtype=dt)
print("Conv2d súly:", tuple(fwd.weight.shape)) # (C_out, C_in, kh, kw) = (3, 2, 2, 3)
print("ConvTranspose2d súly:", tuple(adj.weight.shape)) # (in_channels, out_channels/groups, kh, kw) = (3, 2, 2, 3)
with torch.no_grad():
adj.weight.copy_(fwd.weight) # ugyanaz a tensor, csere nélkül
x = torch.randn(1, 2, 7, 6, dtype=dt) # C: R^(2*7*6) -> R^(3*H'*W')
y_shape = fwd(x).shape
y = torch.randn(y_shape, dtype=dt)
ct_y = adj(y, output_size=x.shape[-2:]) # C^T y, a bemenet alakjára
lhs = (fwd(x) * y).sum() # <Cx, y>
rhs = (x * ct_y).sum() # <x, C^T y>
print("<Cx,y> =", lhs.item(), " <x,C^T y> =", rhs.item())
assert torch.allclose(lhs, rhs, rtol=1e-12, atol=1e-12)A lab-profilban (PyTorch 2.14.1+cpu, float64) mindkét súlytenzor
alakja (3, 2, 2, 3), és a két skalárszorzat
,
gépi pontossággal egyezik. Az output_size argumentum
rögzíti a transzponált kimenet méretét (lásd az
output_padding alább); stride esetén enélkül a kimeneti
alak nem egyértelmű.
Ezt a fogalmat élesen el kell választani az inverztől. Az inverzhez olyan kellene, amelyre és . Ilyen csak akkor létezhet, ha és teljes rangú. A konvolúciós forward általában dimenziót változtat vagy ranghiányos, ezért az adjungált legfeljebb a skalárszorzatos irányítást őrzi meg, az adatot általában nem állítja vissza.
Tekintsünk egy 4 elemű bemenetet, , és egy 2 elemű kernelt . A valid, stride=1, padding=0 cross-correlation kimenete három elemű:
A leképezés mátrixa
Lemma (valid cross-correlation adjungáltja). A (3) mátrix adjungáltja a transzponált
azaz elemei
Bizonyítás. Tetszőleges , esetén a forward mátrixszorzást beírva:
Csoportosítsunk komponensei szerint:
Mivel ez minden párra fennáll, a (4) képlet éppen az adjungált definícióját teljesíti, és a fenti tétel miatt az adjungált egyértelmű.
A (4) alak a scatter-add: a gradiens például súllyal az pozícióra és súllyal az pozícióra hullik vissza, mert a forwardban mindkét bemeneti elem részt vett a előállításában, a , a együtthatóval. Így a mátrixtranszponált oszlopszerkezete közvetlenül az átfedő recepciós mezők elvét kódolja.
A transzponált konvolúció egyik leggyakoribb felhasználási módja a térbeli felbontás növelése egy downsampling után. A mátrixtranszponált eleve a megfelelő irányú méretet adja: ha , akkor . Az alak-visszaállítás tehát nem extra tulajdonság, hanem a transzponált dimenzióinak következménye; az értékek visszaállítása ellenben csak a teljes rangú négyzetes esetben lenne lehetséges.
A stride felső egészrészes osztást vezet be a forward oldalon. A 14.3. Paraméterszám, MAC és aktivációmemória alfejezetben és a [D10]-ben használt képlet szerint
A padlófüggvény miatt több különböző
is adhatja ugyanazt a
értéket. A transzponált oldalon ezért nem mindig dönthető el
egyértelműen, mekkora legyen a kimeneti méret. A PyTorch
ConvTranspose2d ezt az output_padding
paraméterrel teszi eldönthetővé: a szokásos jelöléssel, ha a
transzponált bemenete
méretű,
Az output_padding tehát csak a méretválasztást egészíti
ki; a mátrixtranszponált értékszerkezetén nem változtat. A megadott
kimeneti képlet mellett a ConvTranspose2d konstruktorának
output_padding-korlátait is teljesíteni kell; ezeket a
választott stride és dilation paraméterekkel
együtt ellenőrizzük [D11].
Következmény (inverz limit). Legyen . A 3.3. Vetítés, legkisebb négyzetek és kondicionáltság alfejezetben tanult rangfeltétel szerint pontosan akkor invertálható, ha teljes oszloprangú, és pontosan akkor, ha teljes sorrangú. A két feltétel független: például -ra invertálható, miközben a sorrang , így szinguláris. Kétoldali inverzhez ( és ) négyzetes (), teljes rangú mátrix kell. A még ekkor sem általában : ehhez , vagyis ortogonális kellene, ami egy konvolúciós operátornál kivételes eset.
Legyen , . A forward:
Válasszuk az upstream gradienst értékűnek; ez a [JELOLESEK] kivételes kézi konvenciójában egyszerűen az vektor, de az adjungált azonosság tetszőleges -re külön is ellenőrizhető. A (4) képlet:
Ellenőrzés a skalárszorzatos azonossággal:
A két oldal egyezik, így a transzponált mátrix teljesíti az adjungált feltételt.
Minimális NumPy-ellenőrzés:
# n059_manual_example.py
import numpy as np
k = np.array([2.0, -1.0])
x = np.array([1.0, 3.0, 0.0, 2.0])
C = np.zeros((3, 4))
C[0, 0:2] = k
C[1, 1:3] = k
C[2, 2:4] = k
y = C @ x # [-1, 6, -2]
g = np.array([-1.0, 6.0, -2.0])
dx = C.T @ g # [-2, 13, -10, 2]
lhs = float(y @ g) # 41
rhs = float(x @ dx) # 41
print("y =", y)
print("C^T g =", dx)
print("adjoint difference =", abs(lhs - rhs))A labor célja, hogy az 1D valid cross-correlation explicit mátrixának
transzponáltját összehasonlítsa a PyTorch conv_transpose1d
függvényével. A conv_transpose1d a forward 1D konvolúció
adjungált útját adja; egycsatornás esetben a súly alakja megegyezik, így
a mátrixos és a könyvtári eredmény közvetlenül összevethető. A teszt
determinisztikus, véletlen generálást nem használ; a seed megadása a
laborprotokoll részeként mégis rögzíti a környezetet.
# labor/n059_adjoint_check.py
import numpy as np
import torch
import torch.nn.functional as F
def make_valid_cc_matrix(n_in: int, k: np.ndarray) -> np.ndarray:
"""1D valid, stride=1, padding=0 cross-correlation matrix."""
k = np.asarray(k, dtype=np.float64)
n_out = n_in - k.size + 1
C = np.zeros((n_out, n_in), dtype=np.float64)
for i in range(n_out):
C[i, i:i + k.size] = k
return C
torch.manual_seed(0) # nincs véletlen input; a seed a napló része
dtype = torch.float64
device = torch.device("cpu")
k = np.array([2.0, -1.0])
n_in = 4
C = make_valid_cc_matrix(n_in, k)
# upstream gradiens a forward kimeneti terében
g = np.array([-1.0, 6.0, -2.0])
manual = C.T @ g # (4,)
g_t = torch.tensor(g, dtype=dtype, device=device).view(1, 1, -1)
w_t = torch.tensor(k, dtype=dtype, device=device).view(1, 1, -1)
# stride=1, padding=0, dilation=1, output_padding=0 explicit
library = F.conv_transpose1d(
g_t, w_t, bias=None, stride=1, padding=0,
output_padding=0, groups=1, dilation=1,
).cpu().numpy().reshape(-1)
max_abs_diff = float(np.max(np.abs(manual - library)))
print("C^T g manual :", manual)
print("conv_transpose :", library)
print("max_abs_diff :", max_abs_diff)
print("PASS" if np.allclose(manual, library, atol=1e-12, rtol=0) else "FAIL")A kód futtatási parancsa a tankönyvi munkakönyvtárból:
python labor/n059_adjoint_check.py
A várt, számított kimenet:
C^T g manual : [-2. 13. -10. 2.]
conv_transpose : [-2. 13. -10. 2.]
max_abs_diff : 0.0
PASS
Ez a numerikus egyezés arra a kicsi esetre igazolja, hogy a könyvtári
művelet a várt mátrixtranszponáltat alkalmazza; ez nem általános
bizonyítás, hanem a [D05] irányelve szerinti független numerikus
kontroll egy konfigurációra. A [D11] API-szakaszának teljes ellenőrzése
után a labor a 2D ConvTranspose2d-re, valamint stride>1
és output_padding esetekre is kiterjesztendő; jelen kimenet
számított várt érték, nem mért futtatás.
F1 (fogalmi, nehézség 1). Definiálja külön-külön a mátrixtranszponáltat, az alak-visszaállítást és az inverzet. Adjon egy konkrét mátrixot, amelyre , és mutassa meg, hogy mérete miért árulhatja el már az inverz hiányát. Átadandó: három definíció és egy konkrét mátrix a számítással.
F2 (levezetési/bizonyítási, nehézség 2). Legyen a bemenet , a kernel , stride=2, padding=0, 1D valid cross-correlation. Írja fel a mátrixot, a mátrixot, és tetszőleges, majd a konkrét , párral igazolja a skalárszorzatos adjungált azonosságot. Mutassa meg, hogy nem invertálható, és adjon meg egy nemnulla vektort a nullterében. Átadandó: teljes mátrixok, azonosságellenőrzés, rang-indoklás, nulltérvektor.
F3 (implementációs, nehézség 2). Írjon függvényt,
amely tetszőleges 1D kernelre felépíti a valid cross-correlation
mátrixot, majd a transzponált mátrixszorzást a
F.conv_transpose1d kimenetével hasonlítja össze. Ellenőrzés
két különböző kernelre, legalább
abszolút toleranciával, float64 típuson. Átadandó:
futtatható kód, függvény, két teszteset és PASS kimenet.
F4 (hibakeresési, nehézség 2). Adott az alábbi
érvelés: „Mivel a réteg neve ConvTranspose2d, ezért az
általa számított kimenet a megfelelő Conv2d inverze, tehát
stride=2 downsampling után az eredeti képet adja vissza.” Lokalizálja a
hibát, adjon 1D ellenpéldát két különböző bemenettel, amelyek forwardja
azonos, és ezért a transzponált kimenetük is azonos. Írja le a javított
állítást és annak ellenőrzését. Átadandó: hibaelemzés, ellenpélda,
javítás.
F5 (kísérleti/kutatói transzfer, nehézség 3).
Tervezzen kis kontrollált kísérletet, amely eldönti, hogy egy rögzített
ConvTranspose2d konfiguráció pusztán alak-visszaállítást
vagy numerikusan is inverzet ad. Használjon 5×5-ös bemenetet, 3×3-ast
kernelt, stride=2, padding=1, output_padding=1 értéket.
Adja meg a nullhipotézist, a metrikát, a baseline-t, az ismétlésszámot
és a következtetés érvényességi korlátját. Futtatás nem kötelező.
Átadandó: legfeljebb egy oldalas protokoll.
F1 megoldás. A mátrixtranszponált a sorok és oszlopok felcserélése: . Az alak-visszaállítás csak annyit jelent, hogy a értelmezési tartományának dimenziójába képez; ez a mátrixméretből következik, nem garantál értékazonosságot. Az inverz a kétoldali azonosságot teljesítő mátrix: és .
Konkrét példa a fenti (3) mátrix választással:
Mivel mérete , a mérete , de rangja legfeljebb , ezért nem lehet az identitás. Például a nullterében van, mert , így .
F2 megoldás. A forward: , ; az elem a stride miatt nem járul hozzá. A mátrix:
Általánosan:
A konkrét párral , ezért , míg , tehát . Az azonosság fennáll.
A mátrix:
Rangja 2, kisebb, mint 5, tehát szinguláris. A nulltérnek eleme például , mert , így . Alakellenőrzés: mérete , mérete , a szorzat .
F3 megoldás.
# tasks/n059_f3.py
import numpy as np
import torch
import torch.nn.functional as F
def make_valid_cc_matrix(n_in: int, k: np.ndarray) -> np.ndarray:
k = np.asarray(k, dtype=np.float64)
n_out = n_in - k.size + 1
C = np.zeros((n_out, n_in), dtype=np.float64)
for i in range(n_out):
C[i, i:i + k.size] = k
return C
def check_adjoint(k, n_in=8):
C = make_valid_cc_matrix(n_in, k)
n_out = C.shape[0]
g = np.arange(1.0, n_out + 1.0)
manual = C.T @ g
g_t = torch.tensor(g, dtype=torch.float64).view(1, 1, -1)
w_t = torch.tensor(k, dtype=torch.float64).view(1, 1, -1)
library = F.conv_transpose1d(
g_t, w_t, stride=1, padding=0, output_padding=0
).numpy().reshape(-1)
return C, manual, library
for k in ([2.0, -1.0], [0.5, 0.25, 0.5]):
C, manual, library = check_adjoint(np.array(k))
ok = np.allclose(manual, library, atol=1e-13, rtol=0)
print("kernel ", k)
print("C shape ", C.shape)
print("max_abs_diff", float(np.max(np.abs(manual - library))))
print("PASS" if ok else "FAIL")Futtatás: python tasks/n059_f3.py. Mindkét kernelre a
várt kimenet PASS, max_abs_diff a
nagyságrendben, így a
tolerancia elegendő.
F4 megoldás. A hiba a „transposed convolution =
inverz” azonosításban van. A ConvTranspose2d a forward
lineáris operátor adjungáltja, nem az inverze; a stride=2 downsampling
nem injektív, ezért inverz nem is létezhet. 1D ellenpélda a F2 feladat
mátrixával:
és
esetén
,
tehát a transzponált kimenet mindkettőre
.
Egyetlen dekódolt vektor nem állíthatja vissza mindkét bemenetet.
Javított állítás: a réteg a forward adjungáltját, vagy másként az
upsampling irányú gradiens-szórást számolja; rekonstrukcióhoz tanított
dekódert, invertálható architektúrát vagy kiegészítő loss-t kell
használni. Ellenőrzés: rangszámítás vagy a fenti kétinputos teszt.
F5 megoldásminta. Kérdés: a megadott konfiguráció érték szerint is inverze-e a forwardnak? Nullhipotézis: a réteg kimenete alakú, és ez nem egyezik -szel; az inverz hipotézis akkor lenne tartható, ha a hiba numerikusan nulla lenne. Metrika: , valamint külön shape-ellenőrzés. Adat: 20 darab, seedelt, -beli 5×5-ös kép, NCHW float64. Baseline: identitás kimenet, . Protokoll: minden x-re kiszámítjuk a forward és a transzponált kimenetet; ha minden esetben, az inverz hipotézist a tesztelt konfigurációban elfogadjuk, különben elvetjük. Várható értelmezés: a ranghiány miatt nem nulla, így a modul csak alakot állít vissza. Korlát: a numerikus mérés egy konfigurációra vonatkozik; az általános érvénytelenséget a mátrix rangja bizonyítja lineáris algebrából, a mérés csak demonstrál.
A leggyakoribb tévedés, hogy a transposed convolutiont
dekonvolúciónak vagy inverznek
nevezik, és elvárják, hogy egy veszteséges forward után visszaadja az
eredeti képet. A fenti
példában a forward 4 elemű jelet 3 eleművé képez. A
mátrixnak nemtriviális nulltere van:
esetén
.
Ha a transzponált konvolúció inverz lenne, akkor a nulla forward
kimenetből ezt a nemnulla
-et
kellene visszaállítania; de lineáris leképezés nullát nullába visz,
tehát ez lehetetlen. Az alak lehet helyes, a pixelértékek nem. A
tanulság: a ConvTranspose2d a mátrixtranszponáltat
számolja, és a skalárszorzat-azonosságot őrzi, nem a rekonstrukciós
azonosságot.
Ellenőrző kérdések: (1) A (3) mátrix esetén mi a és mérete, és miért nem lehet ? (2) Írja fel a (4) képlet egy tetszőleges -ra, és ellenőrizze, hogy a komponens két helyre is szóródik; melyik forward együtthatók miatt? (3) Egy forward után miért adhat a transzponált réteg alakot úgy, hogy közben az értékek nem állnak vissza? A válaszok a fenti levezetésekben találhatók.
Visszahívás 3.3. Vetítés, legkisebb négyzetek és kondicionáltság alfejezetből: a normálegyenlet mátrixa csak teljes oszloprang esetén invertálható; itt a oszloprangja kisebb, mint a bemeneti dimenzió, ezért szingularitása ugyanaz a jelenség. Visszahívás 14.3. Paraméterszám, MAC és aktivációmemória alfejezetből: a paraméterszám és a MAC a kimeneti felbontáshoz kötődik, ezért az adjungált réteg költségét is a tényleges kimeneti méretből kell számolni, nem az „inverz” elnevezésből. Visszahívás 11.3. Dataset és DataLoader a split után alfejezetből: a transzponált konvolúció is NCHW batch-szerződésű, a batchdimenziót érintetlenül hagyja; a split- és transzformációs határok a tesztkód szintetikus tenzorain nem íródnak felül.
A konvolúciós forward lineáris operátor; a backward bemeneti
gradiense az operátor adjungáltja, amely valós mátrixreprezentációban a
mátrixtranszponált alkalmazása. Kis 1D valid cross-correlation mátrixon
levezettük a skalárszorzatos adjungált azonosságot, és megmutattuk, hogy
a transzponált a scatter-add gradiensszórást adja. A
ConvTranspose2d ezt az adjungált utat valósítja meg, nem
általános inverzet. Az output_padding csak a padlófüggvény
miatt nem egyértelmű kimeneti méretet választja. Numerikus labor egy 1D
mátrixtranszponáltat hasonlít a könyvtári művelethez.
A 15.4. Független gradiensellenőrzési
protokoll egységben a teljes konvolúciós réteg forward és backward
implementációját független numerikus gradiensellenőrzés alá vetjük. Az
itteni mátrixos adjungált azonosítás adja az analitikus
referenciaértéket, és a laborban használt float64
toleranciaszint lesz a hibamérce alapja. A ReLU töréspontját és a
nemsima pontokat külön elkerülő ellenőrzési protokollban a most
levezetett lineáris rész pontos gradiense nélkülözhetetlen.