15.3 Transposed convolution mint adjungált

15.3.1 Cél, előismeret és induló kérdés

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.

15.3.2 Lineáris operátor, adjungált és mátrixtranszponált

Legyen V=ℝdinV=\mathbb R^{d_{in}} és W=ℝdoutW=\mathbb R^{d_{out}} a standard euklideszi skalárszorzattal: ⟨a,b⟩=a⊤b\langle a,b\rangle=a^\top b. Egy C:V→WC:V\to W leképezés lineáris operátor, ha minden x1,x2∈Vx_1,x_2\in V és α,β∈ℝ\alpha,\beta\in\mathbb R esetén

C(αx1+βx2)=αC(x1)+βC(x2).C(\alpha x_1+\beta x_2)=\alpha C(x_1)+\beta C(x_2).

Rögzített bázisban CC egy dout×dind_{out}\times d_{in} 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 C*:W→VC^*:W\to V leképezés a CC adjungáltja, ha minden x∈Vx\in V, y∈Wy\in W esetén

⟨Cx,y⟩W=⟨x,C*y⟩V.\langle Cx,y\rangle_W=\langle x,C^*y\rangle_V. \tag{1}

Valós mátrix esetén az adjungált a mátrixtranszponált: C*=C⊤C^*=C^\top, mert (Cx)⊤y=x⊤C⊤y(Cx)^\top y=x^\top C^\top y. 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 C*C^* és C†C^\dagger is teljesíti az (1) feltételt, akkor C*=C†C^*=C^\dagger.

Bizonyítás. Minden y∈Wy\in W esetén válasszuk az x=(C*−C†)yx=(C^*-C^\dagger)y vektort. Ekkor

0=⟨x,C*y⟩−⟨x,C†y⟩=⟨Cx,y⟩−⟨Cx,y⟩=∥x∥2,0=\langle x,C^*y\rangle-\langle x,C^\dagger y\rangle =\langle Cx,y\rangle-\langle Cx,y\rangle =\|x\|^2,

tehát x=0x=0, azaz C*y=C†yC^*y=C^\dagger y, minden yy-ra. ◼\blacksquare

A neurális hálózatok nyelvén az adjungált a vektor–Jacobi-szorzat szerepét tölti be. Ha L=g(y)L=g(y) skalárfüggvény és y=Cxy=Cx, akkor a láncszabály szerint

∇xL=C⊤∇yL.\nabla_x L=C^\top\nabla_y L. \tag{2}

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 dXdX képlet ennek a konvolúciós CC mátrixra specializált, indexelt alakja.

15.3.3 A Conv2d mint lineáris operátor

A 15.2. Inputgradiens és átfedő recepciós mezők és [D10] jelölése szerint legyen a bemeneti képbatch X∈ℝN×Cin×H×WX\in\mathbb R^{N\times C_{in}\times H\times W}, a kimenet Y∈ℝN×Cout×Hout×WoutY\in\mathbb R^{N\times C_{out}\times H_{out}\times W_{out}}, a kernel pedig K∈ℝCout×Cin×kh×kwK\in\mathbb R^{C_{out}\times C_{in}\times k_h\times k_w}. Ha a bias b=0b=0, akkor a cross-correlation minden egyes kimeneti eleme a bemenet lineáris kombinációja:

yn,o,i,j=∑c=0Cin−1∑r=0kh−1∑t=0kw−1Ko,c,r,tx‾n,c,ish+rdh−ph,jsw+tdw−pw.y_{n,o,i,j}=\sum_{c=0}^{C_{in}-1}\sum_{r=0}^{k_h-1}\sum_{t=0}^{k_w-1} K_{o,c,r,t}\,\bar x_{n,c,\;i s_h+r d_h-p_h,\;j s_w+t d_w-p_w}.

Itt r,tr,t a kernel sor- és oszlopindexe, sh,sws_h,s_w a stride, dh,dwd_h,d_w a dilation, ph,pwp_h,p_w a padding. Az x‾\bar x a bemenet virtuális nullakiterjesztése: x‾n,c,u,v=xn,c,u,v\bar x_{n,c,u,v}=x_{n,c,u,v}, ha 0≤u<H0\le u<H és 0≤v<W0\le v<W, különben 00. (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: vec(X)∈ℝNCinHW\operatorname{vec}(X)\in\mathbb R^{N C_{in}HW}. A fenti összeg egy C(K)C(K) mátrixot határoz meg, amelyre vec(Y)=C(K)vec(X)\operatorname{vec}(Y)=C(K)\operatorname{vec}(X). 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

vec(dX)=C(K)⊤vec(dY),\operatorname{vec}(dX)=C(K)^\top\operatorname{vec}(dY),

amely pontosan a 15.2. Inputgradiens és átfedő recepciós mezők alfejezetben levezetett scatter-add: minden dYdY-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.

15.3.4 Transposed convolution mint adjungált

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 Cin→CoutC_{in}\to C_{out} út megfordul: a gradiensirányú modul bemenete CoutC_{out}, kimenete CinC_{in} csatornát kezel. Két dolgot kell elválasztani. Az operátormátrix transzponálódik: C(K)∈ℝdout×dinC(K)\in\mathbb R^{d_{out}\times d_{in}} helyett C(K)⊤∈ℝdin×doutC(K)^\top\in\mathbb R^{d_{in}\times d_{out}}. Az API súlytenzorának tárolási konvenciója viszont nem cserél dimenziót: a Conv2d(C_in, C_out) súlya (Cout,Cin,kh,kw)(C_{out},C_{in},k_h,k_w), a ConvTranspose2d(in_channels=C_out, out_channels=C_in) súlya pedig (in_channels,out_channels,kh,kw)=(Cout,Cin,kh,kw)(\text{in\_channels},\text{out\_channels},k_h,k_w)=(C_{out},C_{in},k_h,k_w) — 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 x↦Cx+bx\mapsto Cx+b leképezés Jacobi-mátrixa továbbra is CC, 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 2→32\to3 csatornás, stride-os, paddinges forward és a 3→23\to2 csatornás transzponált modul ugyanazzal a súllyal, az adjungált ⟨Cx,y⟩=⟨x,C⊤y⟩\langle Cx,y\rangle=\langle x,C^\top y\rangle 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 3,7917631382168…3{,}7917631382168{\ldots}, 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 C−1:W→VC^{-1}:W\to V kellene, amelyre C−1C=IdinC^{-1}C=I_{d_{in}} és CC−1=IdoutCC^{-1}=I_{d_{out}}. Ilyen csak akkor létezhet, ha din=doutd_{in}=d_{out} és CC 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.

15.3.5 Levezetés: explicit 1D operátormátrix és adjungált azonosság

Tekintsünk egy 4 elemű bemenetet, x=(x0,x1,x2,x3)⊤∈ℝ4x=(x_0,x_1,x_2,x_3)^\top\in\mathbb R^4, és egy 2 elemű kernelt k=(k0,k1)⊤k=(k_0,k_1)^\top. A valid, stride=1, padding=0 cross-correlation kimenete három elemű:

y0=k0x0+k1x1,y1=k0x1+k1x2,y2=k0x2+k1x3.y_0=k_0x_0+k_1x_1,\qquad y_1=k_0x_1+k_1x_2,\qquad y_2=k_0x_2+k_1x_3.

A leképezés mátrixa

C=(k0k1000k0k1000k0k1)∈ℝ3×4.C= \begin{pmatrix} k_0&k_1&0&0\\ 0&k_0&k_1&0\\ 0&0&k_0&k_1 \end{pmatrix}\in\mathbb R^{3\times4}. \tag{3}

Lemma (valid cross-correlation adjungáltja). A (3) mátrix adjungáltja a transzponált

C⊤=(k000k1k000k1k000k1)∈ℝ4×3,C^\top= \begin{pmatrix} k_0&0&0\\ k_1&k_0&0\\ 0&k_1&k_0\\ 0&0&k_1 \end{pmatrix}\in\mathbb R^{4\times3},

azaz C⊤yC^\top y elemei

(C⊤y)0=k0y0,(C⊤y)1=k1y0+k0y1,(C⊤y)2=k1y1+k0y2,(C⊤y)3=k1y2.(C^\top y)_0=k_0y_0,\quad (C^\top y)_1=k_1y_0+k_0y_1,\quad (C^\top y)_2=k_1y_1+k_0y_2,\quad (C^\top y)_3=k_1y_2. \tag{4}

Bizonyítás. Tetszőleges x∈ℝ4x\in\mathbb R^4, y∈ℝ3y\in\mathbb R^3 esetén a forward mátrixszorzást beírva:

⟨Cx,y⟩=y0(k0x0+k1x1)+y1(k0x1+k1x2)+y2(k0x2+k1x3).\langle Cx,y\rangle =y_0(k_0x_0+k_1x_1)+y_1(k_0x_1+k_1x_2)+y_2(k_0x_2+k_1x_3).

Csoportosítsunk xx komponensei szerint:

=x0(k0y0)+x1(k1y0+k0y1)+x2(k1y1+k0y2)+x3(k1y2)=⟨x,C⊤y⟩.=x_0(k_0y_0)+x_1(k_1y_0+k_0y_1)+x_2(k_1y_1+k_0y_2)+x_3(k_1y_2)=\langle x,C^\top y\rangle.

Mivel ez minden x,yx,y 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ű. ◼\blacksquare

A (4) alak a scatter-add: a y1y_1 gradiens például k0k_0 súllyal az x1x_1 pozícióra és k1k_1 súllyal az x2x_2 pozícióra hullik vissza, mert a forwardban mindkét bemeneti elem részt vett a y1y_1 előállításában, x1x_1 a k0k_0, x2x_2 a k1k_1 együtthatóval. Így a mátrixtranszponált oszlopszerkezete közvetlenül az átfedő recepciós mezők elvét kódolja.

15.3.6 Alak-visszaállítás, output_padding és inverz limit

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 C:V→WC:V\to W, akkor C⊤:W→VC^\top:W\to V. 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

Hout=⌊H+2p−d(k−1)−1s⌋+1.H_{out}=\left\lfloor\frac{H+2p-d(k-1)-1}{s}\right\rfloor+1.

A padlófüggvény miatt több különböző HH is adhatja ugyanazt a HoutH_{out} é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 HinTH_{in}^{T} méretű,

HoutT=(HinT−1)s−2p+d(k−1)+1+output_padding.H_{out}^{T}=(H_{in}^{T}-1)s-2p+d(k-1)+1+\text{output\_padding}. \tag{5}

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 C∈ℝdout×dinC\in\mathbb R^{d_{out}\times d_{in}}. A 3.3. Vetítés, legkisebb négyzetek és kondicionáltság alfejezetben tanult rangfeltétel szerint C⊤C∈ℝdin×dinC^\top C\in\mathbb R^{d_{in}\times d_{in}} pontosan akkor invertálható, ha CC teljes oszloprangú, és CC⊤∈ℝdout×doutCC^\top\in\mathbb R^{d_{out}\times d_{out}} pontosan akkor, ha CC teljes sorrangú. A két feltétel független: például C=(100100)C=\begin{pmatrix}1&0\\0&1\\0&0\end{pmatrix}-ra C⊤C=I2C^\top C=I_2 invertálható, miközben a sorrang 2<32<3, így CC⊤CC^\top szinguláris. Kétoldali inverzhez (C−1C=IC^{-1}C=I és CC−1=ICC^{-1}=I) négyzetes (din=doutd_{in}=d_{out}), teljes rangú mátrix kell. A C⊤C^\top még ekkor sem általában C−1C^{-1}: ehhez C⊤C=IC^\top C=I, vagyis ortogonális CC kellene, ami egy konvolúciós operátornál kivételes eset.

15.3.7 Eredeti kézi számpélda

Legyen k=(2,−1)⊤k=(2,-1)^\top, x=(1,3,0,2)⊤x=(1,3,0,2)^\top. A forward:

y=Cx=(2⋅1+(−1)⋅32⋅3+(−1)⋅02⋅0+(−1)⋅2)=(−16−2).y=Cx= \begin{pmatrix} 2\cdot1+(-1)\cdot3\\ 2\cdot3+(-1)\cdot0\\ 2\cdot0+(-1)\cdot2 \end{pmatrix} = \begin{pmatrix} -1\\6\\-2 \end{pmatrix}.

Válasszuk az upstream gradienst g=∇yL=(−1,6,−2)⊤g=\nabla_y L=(-1,6,-2)^\top értékűnek; ez a [JELOLESEK] kivételes kézi konvenciójában egyszerűen az yy vektor, de az adjungált azonosság tetszőleges gg-re külön is ellenőrizhető. A (4) képlet:

C⊤g=(2(−1)(−1)(−1)+2⋅6(−1)⋅6+2(−2)(−1)(−2))=(−213−102).C^\top g= \begin{pmatrix} 2(-1)\\ (-1)(-1)+2\cdot6\\ (-1)\cdot6+2(-2)\\ (-1)(-2) \end{pmatrix} = \begin{pmatrix} -2\\13\\-10\\2 \end{pmatrix}.

Ellenőrzés a skalárszorzatos azonossággal:

⟨Cx,g⟩=(−1)2+62+(−2)2=1+36+4=41,\langle Cx,g\rangle=(-1)^2+6^2+(-2)^2=1+36+4=41,

⟨x,C⊤g⟩=1(−2)+3⋅13+0(−10)+2⋅2=−2+39+4=41.\langle x,C^\top g\rangle=1(-2)+3\cdot13+0(-10)+2\cdot2=-2+39+4=41.

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))

15.3.8 Labor: mátrixtranszponált és könyvtári művelet egybevetése

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.

15.3.9 Feladatok

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 CC mátrixot, amelyre C⊤C≠IC^\top C\neq I, és mutassa meg, hogy C⊤CC^\top C 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 x∈ℝ5x\in\mathbb R^5, a kernel k=(1,1)⊤k=(1,1)^\top, stride=2, padding=0, 1D valid cross-correlation. Írja fel a CC mátrixot, a C⊤C^\top mátrixot, és tetszőleges, majd a konkrét x=(1,2,−1,0,3)⊤x=(1,2,-1,0,3)^\top, y=(4,−2)⊤y=(4,-2)^\top párral igazolja a skalárszorzatos adjungált azonosságot. Mutassa meg, hogy C⊤CC^\top C 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 10−1310^{-13} 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.

15.3.10 Megoldások és indoklás

F1 megoldás. A mátrixtranszponált C⊤C^\top a sorok és oszlopok felcserélése: (C⊤)ij=Cji(C^\top)_{ij}=C_{ji}. Az alak-visszaállítás csak annyit jelent, hogy C⊤C^\top a CC é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 C−1C^{-1} a kétoldali azonosságot teljesítő mátrix: C−1C=IC^{-1}C=I és CC−1=ICC^{-1}=I.

Konkrét példa a fenti (3) mátrix k=(2,−1)k=(2,-1) választással:

C=(2−10002−10002−1),C⊤C=(4−200−25−200−25−200−21).C= \begin{pmatrix} 2&-1&0&0\\ 0&2&-1&0\\ 0&0&2&-1 \end{pmatrix}, \qquad C^\top C= \begin{pmatrix} 4&-2&0&0\\ -2&5&-2&0\\ 0&-2&5&-2\\ 0&0&-2&1 \end{pmatrix}.

Mivel CC mérete 3×43\times4, a C⊤CC^\top C mérete 4×44\times4, de rangja legfeljebb 33, ezért nem lehet az identitás. Például x=(1,2,4,8)⊤x=(1,2,4,8)^\top a CC nullterében van, mert Cx=0Cx=0, így C⊤Cx=0≠xC^\top Cx=0\neq x.

F2 megoldás. A forward: y0=x0+x1y_0=x_0+x_1, y1=x2+x3y_1=x_2+x_3; az x4x_4 elem a stride miatt nem járul hozzá. A mátrix:

C=(1100000110),C⊤=(1010010100).C= \begin{pmatrix} 1&1&0&0&0\\ 0&0&1&1&0 \end{pmatrix}, \qquad C^\top= \begin{pmatrix} 1&0\\ 1&0\\ 0&1\\ 0&1\\ 0&0 \end{pmatrix}.

Általánosan:

⟨Cx,y⟩=y0(x0+x1)+y1(x2+x3)=x0y0+x1y0+x2y1+x3y1=⟨x,C⊤y⟩.\langle Cx,y\rangle =y_0(x_0+x_1)+y_1(x_2+x_3) =x_0y_0+x_1y_0+x_2y_1+x_3y_1 =\langle x,C^\top y\rangle.

A konkrét párral Cx=(3,−1)⊤Cx=(3,-1)^\top, ezért ⟨Cx,y⟩=3⋅4+(−1)(−2)=14\langle Cx,y\rangle=3\cdot4+(-1)(-2)=14, míg C⊤y=(4,4,−2,−2,0)⊤C^\top y=(4,4,-2,-2,0)^\top, tehát ⟨x,C⊤y⟩=1⋅4+2⋅4+(−1)(−2)+0(−2)+3⋅0=14\langle x,C^\top y\rangle=1\cdot4+2\cdot4+(-1)(-2)+0(-2)+3\cdot0=14. Az azonosság fennáll.

A C⊤CC^\top C mátrix:

C⊤C=(1100011000001100011000000).C^\top C= \begin{pmatrix} 1&1&0&0&0\\ 1&1&0&0&0\\ 0&0&1&1&0\\ 0&0&1&1&0\\ 0&0&0&0&0 \end{pmatrix}.

Rangja 2, kisebb, mint 5, tehát szinguláris. A nulltérnek eleme például v=(0,0,0,0,1)⊤v=(0,0,0,0,1)^\top, mert Cv=0Cv=0, így C⊤Cv=0C^\top Cv=0. Alakellenőrzés: CC mérete 2×52\times5, C⊤C^\top mérete 5×25\times2, a szorzat 5×55\times5.

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 10−1610^{-16} nagyságrendben, így a 10−1310^{-13} 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 CC mátrixával: x=(1,0,0,0,0)⊤x=(1,0,0,0,0)^\top és x′=(2,−1,0,0,0)⊤x'=(2,-1,0,0,0)^\top esetén Cx=Cx′=(1,0)⊤Cx=Cx'=(1,0)^\top, tehát a transzponált kimenet mindkettőre C⊤(1,0)⊤=(1,1,0,0,0)⊤C^\top(1,0)^\top=(1,1,0,0,0)^\top. 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 A⊤AxA^\top A x alakú, és ez nem egyezik xx-szel; az inverz hipotézis akkor lenne tartható, ha a hiba numerikusan nulla lenne. Metrika: e=∥A⊤Ax−x∥2/∥x∥2e=\|A^\top A x-x\|_2/\|x\|_2, valamint külön shape-ellenőrzés. Adat: 20 darab, seedelt, [−1,1][-1,1]-beli 5×5-ös kép, NCHW float64. Baseline: identitás kimenet, e=0e=0. Protokoll: minden x-re kiszámítjuk a forward és a transzponált kimenetet; ha e<10−12e<10^{-12} 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 ee 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.

15.3.11 Tipikus tévedés és ellenpélda

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 k=(2,−1)k=(2,-1) példában a forward 4 elemű jelet 3 eleművé képez. A CC mátrixnak nemtriviális nulltere van: x=(1,2,4,8)⊤x=(1,2,4,8)^\top esetén Cx=0Cx=0. Ha a transzponált konvolúció inverz lenne, akkor a nulla forward kimenetből ezt a nemnulla xx-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.

15.3.12 Önellenőrzés és visszahívás

Ellenőrző kérdések: (1) A (3) mátrix esetén mi a CC és C⊤C^\top mérete, és miért nem lehet C⊤C=IC^\top C=I? (2) Írja fel a (4) képlet egy tetszőleges yy-ra, és ellenőrizze, hogy a y1y_1 komponens két helyre is szóródik; melyik forward együtthatók miatt? (3) Egy 4→34\to3 forward után miért adhat a transzponált réteg 3→43\to4 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 A⊤AA^\top A mátrixa csak teljes oszloprang esetén invertálható; itt a CC oszloprangja kisebb, mint a bemeneti dimenzió, ezért C⊤CC^\top C 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.

15.3.13 Rövid összefoglaló

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.

15.3.14 Következő alkalmazás

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.

15.3.15 Források