15.1 Súly- és biasgradiens

15.1.1 Cél, előismeret és induló becslés

Ez az alfejezet a konvolúciós réteg két paraméterének, a kernelnek és a biasnak a gradiensét vezeti le a teljes NCHW cross-correlation definícióból. A központi jelenség a weight sharing (súlymegosztás): ugyanaz a kernel több ezer kimeneti pozícióban szerepel, ezért a gradiensük az összes felhasználási hely lokális gradiensének összege lesz.

Előismeretként feltételezzük a 4.4. I. ellenőrző pont: matematika és kód megfelelése gráf-alapú láncszabályt, a 10.4. Tanítási lépés és tanuló MLP minibatch-gradiens átlagolását, a 13.4. Határkezelés és frekvenciaintuíció határkezelési konvenciókat és a 14.4. Csoportos és depthwise konvolúció csatornaösszegzési szerkezetet. Visszautalunk továbbá a 14.1. Stride, padding, dilation és outputméret kimeneti méretképletre, a 11.1. Tensor és autograd-életciklus autograd-életciklusra és a 3.1. Vektor, norma és skalárszorzat mátrix–vektor szorzás alakra.

Induló becslés. Egy 3×33 \times 3-as kernel 11 bemeneti és 11 kimeneti csatornával, 24×2424 \times 24-es bemeneten, stride 11 és nulla padding mellett hány kimeneti pozícióban szerepel? Segítség: a kimeneti méret 22×2222 \times 22, tehát a kernel 484484 ablakban vesz részt. A gradiens mind a 484484 ablak hozzájárulását összegzi.

15.1.2 Definíció és intuíció

Tekintsük a teljes NCHW cross-correlationt. A bemeneti tensor

X∈ℝN×Cin×H×W, X \in \mathbb R^{N \times C_{in} \times H \times W},

a kernel

K∈ℝCout×Cin×kh×kw, K \in \mathbb R^{C_{out} \times C_{in} \times k_h \times k_w},

a bias b∈ℝCoutb \in \mathbb R^{C_{out}}, és a kimenet

Y∈ℝN×Cout×Hout×Wout. Y \in \mathbb R^{N \times C_{out} \times H_{out} \times W_{out}}.

A HoutH_{out} és WoutW_{out} értékét a 14.1. Stride, padding, dilation és outputméret alfejezetben levezetett

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

formula adja, és analóg módon WoutW_{out}. Itt pp a szimmetrikus zero padding, ss a stride, dd a dilation. Ebben az alfejezetben d=1d = 1-et feltételezünk, hacsak mást nem mondunk; a dilációs általánosítás feladat.

Az output definíciója minden n∈{0,…,N−1}n \in \{0, \ldots, N-1\}, o∈{0,…,Cout−1}o \in \{0, \ldots, C_{out}-1\}, i∈{0,…,Hout−1}i \in \{0, \ldots, H_{out}-1\} és j∈{0,…,Wout−1}j \in \{0, \ldots, W_{out}-1\} indexre:

Y[n,o,i,j]=∑c=0Cin−1∑u=0kh−1∑v=0kw−1X[n,c,i⋅s+u−p,j⋅s+v−p]⋅K[o,c,u,v]+b[o]. Y[n, o, i, j] = \sum_{c=0}^{C_{in}-1} \sum_{u=0}^{k_h-1} \sum_{v=0}^{k_w-1} X[n, c, i \cdot s + u - p, \; j \cdot s + v - p] \cdot K[o, c, u, v] + b[o].

A pp padding eltolás biztosítja a virtuális, nullával kiegészített tartomány indexelését. A kényelem kedvéért bevezetjük a paddingelt bemenet jelölést: X̃[n,c,r,q]\tilde X[n, c, r, q] a paddingelt képet jelöli, ahol r∈{−p,…,H+p−1}r \in \{-p, \ldots, H+p-1\} és q∈{−p,…,W+p−1}q \in \{-p, \ldots, W+p-1\}. Ezzel az output tömörebben:

Y[n,o,i,j]=∑c=0Cin−1∑u=0kh−1∑v=0kw−1X̃[n,c,i⋅s+u,j⋅s+v]⋅K[o,c,u,v]+b[o]. Y[n, o, i, j] = \sum_{c=0}^{C_{in}-1} \sum_{u=0}^{k_h-1} \sum_{v=0}^{k_w-1} \tilde X[n, c, i \cdot s + u, \; j \cdot s + v] \cdot K[o, c, u, v] + b[o].

A veszteség és az upstream gradiens. Legyen LL egy skalár loss, amely differenciálható függvénye az YY kimenetnek (például a 4.4. I. ellenőrző pont: matematika és kód megfelelése alfejezetbeli 12∑Y2\frac12 \sum Y^2 vagy egy cross-entropy a későbbi fejezetből). A visszaterjesztés során ismert az upstream gradiens:

∇YL∈ℝN×Cout×Hout×Wout, \nabla_Y L \in \mathbb R^{N \times C_{out} \times H_{out} \times W_{out}},

ahol az általános elem ∇YL[n,o,i,j]=∂L∂Y[n,o,i,j]\nabla_Y L[n, o, i, j] = \frac{\partial L}{\partial Y[n, o, i, j]}. A cél a

∇KL∈ℝCout×Cin×kh×kw,∇bL∈ℝCout \nabla_K L \in \mathbb R^{C_{out} \times C_{in} \times k_h \times k_w}, \qquad \nabla_b L \in \mathbb R^{C_{out}}

kiszámítása. A láncszabály szerint minden paraméter-elemre:

∂L∂K[o,c,u,v]=∑minden n,i,jahol K[o,c,u,v] szerepel∂L∂Y[n,o,i,j]⋅∂Y[n,o,i,j]∂K[o,c,u,v]. \frac{\partial L}{\partial K[o, c, u, v]} = \sum_{\substack{\text{minden }n,i,j \\ \text{ahol }K[o,c,u,v]\text{ szerepel}}} \frac{\partial L}{\partial Y[n, o, i, j]} \cdot \frac{\partial Y[n, o, i, j]}{\partial K[o, c, u, v]}.

Mivel YY lineáris a kernelben, a belső parciális derivált éppen az adott bemeneti pixel: X̃[n,c,i⋅s+u,j⋅s+v]\tilde X[n, c, i \cdot s + u, \; j \cdot s + v]. A bias esetén a parciális derivált 11.

15.1.3 Tétel: a kernel és a bias gradiensének levezetése

Tétel (súly- és biasgradiens a teljes NCHW cross-correlationhoz). Legyen X∈ℝN×Cin×H×WX \in \mathbb R^{N \times C_{in} \times H \times W} bemenet, K∈ℝCout×Cin×kh×kwK \in \mathbb R^{C_{out} \times C_{in} \times k_h \times k_w} kernel, b∈ℝCoutb \in \mathbb R^{C_{out}} bias, pp szimmetrikus zero padding, ss stride, d=1d = 1 dilation, és legyen YY a fenti cross-correlation kimenete. Ha LL differenciálható függvénye YY-nak, akkor

∂L∂K[o,c,u,v]=∑n=0N−1∑i=0Hout−1∑j=0Wout−1∇YL[n,o,i,j]⋅X̃[n,c,i⋅s+u,j⋅s+v], \frac{\partial L}{\partial K[o, c, u, v]} = \sum_{n=0}^{N-1} \sum_{i=0}^{H_{out}-1} \sum_{j=0}^{W_{out}-1} \nabla_Y L[n, o, i, j] \cdot \tilde X[n, c, i \cdot s + u, \; j \cdot s + v],

és

∂L∂b[o]=∑n=0N−1∑i=0Hout−1∑j=0Wout−1∇YL[n,o,i,j]. \frac{\partial L}{\partial b[o]} = \sum_{n=0}^{N-1} \sum_{i=0}^{H_{out}-1} \sum_{j=0}^{W_{out}-1} \nabla_Y L[n, o, i, j].

Ahol X̃\tilde X a paddingelt bemenetet jelöli.

Bizonyítás. A láncszabály a teljes tenzorgráfon a 4.4. I. ellenőrző pont: matematika és kód megfelelése alfejezetben bemutatott skalár-út-menti érvelés közvetlen kiterjesztése. Mivel LL az YY minden elemén keresztül függhet K[o,c,u,v]K[o, c, u, v]-től, a teljes differenciál:

∂L∂K[o,c,u,v]=∑n,o′,i,j∂L∂Y[n,o′,i,j]⋅∂Y[n,o′,i,j]∂K[o,c,u,v]. \frac{\partial L}{\partial K[o, c, u, v]} = \sum_{n, o', i, j} \frac{\partial L}{\partial Y[n, o', i, j]} \cdot \frac{\partial Y[n, o', i, j]}{\partial K[o, c, u, v]}.

Az o′o' indexre a belső parciális derivált nulla, kivéve ha o′=oo' = o, mert az oo-adik kimeneti csatorna csak a kernel oo-adik kimeneti szeletét használja. Ha o′=oo' = o, akkor a definícióból közvetlenül:

∂Y[n,o,i,j]∂K[o,c,u,v]=X̃[n,c,i⋅s+u,j⋅s+v]. \frac{\partial Y[n, o, i, j]}{\partial K[o, c, u, v]} = \tilde X[n, c, i \cdot s + u, \; j \cdot s + v].

Ezt behelyettesítve a kívánt formula adódik. A bias esetén ∂Y[n,o,i,j]∂b[o]=1\frac{\partial Y[n, o, i, j]}{\partial b[o]} = 1, és ∂Y[n,o′,i,j]∂b[o]=0\frac{\partial Y[n, o', i, j]}{\partial b[o]} = 0 ha o′≠oo' \neq o, így

∂L∂b[o]=∑n=0N−1∑i=0Hout−1∑j=0Wout−1∇YL[n,o,i,j]. \frac{\partial L}{\partial b[o]} = \sum_{n=0}^{N-1} \sum_{i=0}^{H_{out}-1} \sum_{j=0}^{W_{out}-1} \nabla_Y L[n, o, i, j].

Ez befejezi a bizonyítást. ∎

Intuíció: a batch and spatial sum. A kernel gradiensformulája három összegzést tartalmaz: egyet a batch indexre, kettőt a térbeli kimeneti indexekre. Ez a batch and spatial sum (batch- és térösszeg) elv: minden felhasználási helyen az upstream gradiens és az adott bemeneti pixel szorzatát képezzük, és ezeket összeadjuk. A bias gradiensében nincs pixel-szorzó, csak az upstream gradiens tér- és batchösszege áll.

Összeadott, nem átlagolt gradiensek. A fenti levezetés lineáris rétegként kezeli a konvolúciót, ahol a gradiens a láncszabály szerint összegzés. Nincs benne osztás a felhasználási helyek számával. A minibatch-gradiens átlaga csak a loss definíciójából származik: ha L=1M∑m=1MLmL = \frac{1}{M}\sum_{m=1}^{M} L_m, akkor a batch-átlag 1M\frac{1}{M} faktora az upstream gradiensben jelenik meg, nem a kernel gradiensformulájában. Ezt a különbséget a 10.4. Tanítási lépés és tanuló MLP minibatch-átlagolása és a jelen alfejezet tisztán összegző láncszabálya között fontos megőrizni.

15.1.4 Kézi kis példa

Tekintsünk egy N=1N = 1, Cin=1C_{in} = 1, H=W=3H = W = 3 bemenetet, 3×33 \times 3-as kernelt, kh=kw=3k_h = k_w = 3, p=0p = 0 paddinget és s=1s = 1 stride-ot. Ekkor Hout=Wout=1H_{out} = W_{out} = 1, tehát éppen egyetlen térbeli kimeneti pozíció van. A kernel minden eleme pontosan egyszer szerepel, így ez a példa a legegyszerűbb eset: nincs összegzés a térbeli használatok fölött.

Legyen

X=[121013201],K=[10001000−1], X = \begin{bmatrix} 1 & 2 & 1 \\ 0 & 1 & 3 \\ 2 & 0 & 1 \end{bmatrix}, \qquad K = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \end{bmatrix},

és b=0b = 0. A kimenet egyetlen skalár:

Y[0,0,0,0]=1⋅1+2⋅0+1⋅0+0⋅0+1⋅1+3⋅0+2⋅0+0⋅0+1⋅(−1)=0. Y[0, 0, 0, 0] = 1 \cdot 1 + 2 \cdot 0 + 1 \cdot 0 + 0 \cdot 0 + 1 \cdot 1 + 3 \cdot 0 + 2 \cdot 0 + 0 \cdot 0 + 1 \cdot (-1) = 0.

Mivel egyetlen kimeneti elem van, az upstream gradiens is skalár: legyen ∇YL[0,0,0,0]=2\nabla_Y L[0, 0, 0, 0] = 2. A tétel szerint:

∂L∂K[0,0,u,v]=2⋅X[0,u,v]. \frac{\partial L}{\partial K[0, 0, u, v]} = 2 \cdot X[0, u, v].

Tehát ∇KL\nabla_K L éppen 2X2X. A bias gradiens ∇bL[0]=2\nabla_b L[0] = 2.

Numerikus ellenőrzés véges differenciával. A ∂L∂K[u,v]\frac{\partial L}{\partial K[u,v]} középső differenciás közelítése:

L(K+εEu,v)−L(K−εEu,v)2ε. \frac{L(K + \varepsilon E_{u,v}) - L(K - \varepsilon E_{u,v})}{2\varepsilon}.

Ahol Eu,vE_{u,v} az a mátrix, amelynek az (u,v)(u,v) eleme 11, a többi 00. A fenti L(K)=12Y2L(K) = \frac12 Y^2 választással L(0)=0L(0) = 0. Ha ε=10−4\varepsilon = 10^{-4} és (u,v)=(2,2)(u,v) = (2,2)-t perturbáljuk, akkor K[2,2]=−1+εK[2,2] = -1 + \varepsilon esetén Y=−εY = -\varepsilon, tehát L=12ε2L = \frac12 \varepsilon^2. Az (u,v)=(0,0)(u,v) = (0,0) perturbációval K[0,0]=1+εK[0,0] = 1 + \varepsilon esetén Y=εY = \varepsilon, szintén L=12ε2L = \frac12 \varepsilon^2. A középső differencia minden esetben

L(ε)−L(−ε)2ε=12ε2−12ε22ε=0, \frac{L(\varepsilon) - L(-\varepsilon)}{2\varepsilon} = \frac{\frac12\varepsilon^2 - \frac12\varepsilon^2}{2\varepsilon} = 0,

ami azt mutatja, hogy a 12Y2\frac12 Y^2 loss a Y=0Y = 0 pontban nulla gradiensű, tehát a fenti példa LL választása rosszul ellenőriz. A helyes teszt a veszteség L=YL = Y (azonosítható lineáris) vagy egy olyan YY, amely nem nulla. Világosabb példához bb-t válasszuk 55-nek, ekkor Y=5Y = 5, és L=12Y2=12.5L = \frac12 Y^2 = 12.5. Az upstream gradiens Y=5Y = 5, így ∇KL=5X\nabla_K L = 5X és ∇bL=5\nabla_b L = 5. A véges differencia a biasra:

L(5+ε)−L(5−ε)2ε=12(5+ε)2−12(5−ε)22ε=10ε2ε=5. \frac{L(5 + \varepsilon) - L(5 - \varepsilon)}{2\varepsilon} = \frac{\frac12(5+\varepsilon)^2 - \frac12(5-\varepsilon)^2}{2\varepsilon} = \frac{10\varepsilon}{2\varepsilon} = 5.

A kernelre hasonlóan ellenőrizhető, hogy a perturbáció utáni YY megváltozása lineáris, tehát a véges differencia az adott XX elemet adja.

15.1.5 Kódos munkamenet

A következő kód a levezetés numerikus ellenőrzése a fenti 3×33 \times 3-as példán és egy nagyobb, 4×44 \times 4-es inputon futtatva. A munkakönyvtár lab/n057/.

# lab/n057/weight_grad_check.py
import numpy as np

def conv2d_forward(X, K, b, stride=1, pad=0):
    """NCHW cross-correlation, groups=1, dilation=1, float64."""
    N, C_in, H, W = X.shape
    C_out, C_in_k, kh, kw = K.shape
    assert C_in == C_in_k
    H_out = (H + 2 * pad - kh) // stride + 1
    W_out = (W + 2 * pad - kw) // stride + 1
    Xp = np.pad(X, ((0, 0), (0, 0), (pad, pad), (pad, pad)))
    Y = np.zeros((N, C_out, H_out, W_out), dtype=np.float64)
    for n in range(N):
        for o in range(C_out):
            for i in range(H_out):
                for j in range(W_out):
                    acc = b[o]
                    for c in range(C_in):
                        for u in range(kh):
                            for v in range(kw):
                                acc += Xp[n, c, i * stride + u,
                                         j * stride + v] * K[o, c, u, v]
                    Y[n, o, i, j] = acc
    return Y

def conv2d_weight_grad(X, K, b, dY, stride=1, pad=0):
    """Weight and bias gradient from the derived theorem."""
    N, C_in, H, W = X.shape
    C_out, _, kh, kw = K.shape
    H_out = dY.shape[2]
    W_out = dY.shape[3]
    Xp = np.pad(X, ((0, 0), (0, 0), (pad, pad), (pad, pad)))
    dK = np.zeros_like(K, dtype=np.float64)
    db = np.zeros(C_out, dtype=np.float64)
    for o in range(C_out):
        for c in range(C_in):
            for u in range(kh):
                for v in range(kw):
                    total = 0.0
                    for n in range(N):
                        for i in range(H_out):
                            for j in range(W_out):
                                total += dY[n, o, i, j] * Xp[
                                    n, c, i * stride + u, j * stride + v]
                    dK[o, c, u, v] = total
        db[o] = np.sum(dY[:, o, :, :])
    return dK, db

def finite_diff_check(X, K, b, eps=1e-5):
    """Central difference for one scalar loss L = 0.5 * sum(Y ** 2)."""
    N, C_in, H, W = X.shape
    C_out, _, kh, kw = K.shape
    H_out = (H + 2 * 0 - kh) // 1 + 1
    W_out = (W + 2 * 0 - kw) // 1 + 1

    def loss(K, b):
        Y = conv2d_forward(X, K, b, 1, 0)
        return 0.5 * np.sum(Y ** 2)

    dK_num = np.zeros_like(K, dtype=np.float64)
    for o in range(C_out):
        for c in range(C_in):
            for u in range(kh):
                for v in range(kw):
                    Kp = K.copy(); Km = K.copy()
                    Kp[o, c, u, v] += eps
                    Km[o, c, u, v] -= eps
                    dK_num[o, c, u, v] = (loss(Kp, b) - loss(Km, b)) / (2 * eps)
    db_num = np.zeros(C_out, dtype=np.float64)
    for o in range(C_out):
        bp = b.copy(); bm = b.copy()
        bp[o] += eps; bm[o] -= eps
        db_num[o] = (loss(K, bp) - loss(K, bm)) / (2 * eps)
    return dK_num, db_num

if __name__ == "__main__":
    rng = np.random.default_rng(20261020)
    # 3x3 single-position case
    X1 = np.array([[[[1.0, 2.0, 1.0],
                     [0.0, 1.0, 3.0],
                     [2.0, 0.0, 1.0]]]])
    K1 = np.array([[[[1.0, 0.0, 0.0],
                     [0.0, 1.0, 0.0],
                     [0.0, 0.0, -1.0]]]])
    b1 = np.array([5.0])
    Y1 = conv2d_forward(X1, K1, b1, 1, 0)
    dY1 = Y1  # since upstream for L = 0.5*sum(Y**2) is Y
    dK1, db1 = conv2d_weight_grad(X1, K1, b1, dY1, 1, 0)
    dK1_num, db1_num = finite_diff_check(X1, K1, b1)
    assert np.allclose(dK1, dK1_num, atol=1e-8, rtol=1e-5), (dK1, dK1_num)
    assert np.allclose(db1, db1_num, atol=1e-8, rtol=1e-5), (db1, db1_num)
    print("3x3 case: dK =\n", dK1[0,0], "\ndb =", db1)

    # 4x4 input with 2 output channels, kernel 3x3, stride 1, no padding
    X2 = rng.normal(size=(2, 2, 4, 4))
    K2 = rng.normal(size=(2, 2, 3, 3))
    b2 = rng.normal(size=(2,))
    Y2 = conv2d_forward(X2, K2, b2, 1, 0)
    dY2 = Y2
    dK2, db2 = conv2d_weight_grad(X2, K2, b2, dY2, 1, 0)
    # Ugyanazzal a bemenettel, kernellel és biasszal ellenőrzünk:
    dK2_num, db2_num = finite_diff_check(X2, K2, b2)
    assert np.allclose(dK2, dK2_num, atol=1e-8, rtol=1e-5)
    assert np.allclose(db2, db2_num, atol=1e-8, rtol=1e-5)
    print("4x4 case: shapes dK", dK2.shape, "db", db2.shape, "passed.")

Futtatás:

cd lab/n057
python weight_grad_check.py

A várt kimenetben az első eset dKdK mátrixa a 5X5X mátrix, a dbdb értéke 5⋅1⋅1⋅1=55 \cdot 1 \cdot 1 \cdot 1 = 5 (itt egyetlen térbeli pozíció van). A második esetben a shape-ek (2,2,3,3)(2, 2, 3, 3) és (2,)(2,).

Megjegyzés a megosztott helyek számáról. A 4×44 \times 4-es inputon Hout=Wout=2H_{out} = W_{out} = 2, tehát minden kernel-elem 44 térbeli pozícióban szerepel. A gradiens ezek összege: az u és v hurkokon belüli total változó pontosan ezt a batch and spatial sum-ot számolja. A numerikus egyezés 10−810^{-8} abszolút tűréssel, f64-ben igazolja a képletet. A Python-specifikus eltérés: a NumPy np.pad alapértelmezésben konstans nulla paddinget ad, ami a szimmetrikus zero padding konvenciónk.

15.1.6 Önellenőrzés és gyakori tévedés

Gyakori tévedés. Azt gondolni, hogy a kernel gradiensét a felhasználási helyeken átlagolni kell, mert a minibatch-loss átlagot tartalmaz. Ez keveri a loss definíciójából származó 1M\frac{1}{M} faktort a láncszabály összegzési szerkezetével.

Ellenpélda. Legyen a loss L=12∑i,jY[0,0,i,j]2L = \frac12 \sum_{i,j} Y[0,0,i,j]^2, és legyen minden bemeneti pixel 11, a kernel minden eleme 11, a bias 00. Ekkor egyetlen térbeli pozícióban Y=∑u,v1⋅1=9Y = \sum_{u,v} 1 \cdot 1 = 9. Ha a gradiens a felhasználások átlaga lenne (itt egyetlen pozíció, tehát nincs osztás), nem látnánk hibát. Tekintsünk most stride 11-gyel 4×44 \times 4-es bemenetet: Hout=Wout=2H_{out} = W_{out} = 2, tehát négy pozíció, mindegyik Y=9Y = 9. Az upstream gradiens 99. A helyes kernel-gradiens: 9⋅49 \cdot 4 minden bemeneti pixelre, azaz 3636. Ha valaki osztana a pozíciók számával, 99-et kapna, ami téves. A helyes gondolat: minden kimeneti pozícióban a láncszabály a ∇YL⋅X\nabla_Y L \cdot X szorzatot adja, és ezek a hozzájárulások összeadódnak.

Visszahívás 11.1. Tensor és autograd-életciklus alfejezetből. A PyTorch backward hívása a .grad tárolóba összead, nem felülír. Ez pontosan ugyanaz az összegzési szemlélet: a kernel gradiensének minden felhasználási helyről érkező hozzájárulása akkumulálódik. Nem szabad tehát a réteg saját backwardjában osztani a pozíciók számával.

15.1.7 Feladatok

F1 (fogalmi, nehézség 1). Definiálja a weight sharing fogalmát a konvolúciós rétegben. Adjon egy konkrét példát, amelyben ugyanaz a kernel-elem két különböző térbeli pozícióban szerepel, és indokolja, miért nem tekinthetők ezek független paramétereknek. Elvárt eredmény: saját megfogalmazású definíció és egy konkrét index-példa i1≠i2i_1 \neq i_2 esetén.

F2 (levezetési, nehézség 2). Vezesse le a kernel gradiensét a 14.1. Stride, padding, dilation és outputméret alfejezetben bevezetett paddingelt bemenet jelölésével, de most s=2s = 2 stride esetén. Kérdés: megváltozik-e a formula szerkezete, vagy csak az indexelés? Elvárt eredmény: a teljes levezetés a láncszabályból, a stride explicit figyelembevételével, és a végső formula.

F3 (implementációs, nehézség 2). Írjon Python/NumPy függvényt, amely a megadott conv2d_forward kódot használva kiszámítja a kernel gradiensét, de a forward számítást nem hívja meg újra. A bemenet N=2N = 2, Cin=2C_{in} = 2, Cout=3C_{out} = 3, H=W=5H = W = 5, kh=kw=3k_h = k_w = 3, s=1s = 1, p=0p = 0. Ellenőrizze a kimeneti shape-et és egy véletlen seedű eseten a véges differenciás kontrollt. Elvárt átadandó eredmény: futtatható kód, shape-ellenőrzés és egyezési tűrés.

F4 (hibakeresési, nehézség 2). Egy hallgató a következő kódot írja a bias gradiensére:

db[o] = np.sum(dY[np.arange(N), o, :, :]) / (H_out * W_out)

Mi a hiba? Milyen következménye van a tanításra? Javítsa és ellenőrizze a tétel formulájával. Elvárt eredmény: a hiba oka, következménye és a javított kód.

F5 (kísérleti/transzfer, nehézség 3). Tervezzen egy kis kísérletet, amely megméri, hogy a kernel gradiensének kiszámítása mennyivel drágább egy 1×11 \times 1-es kernelre, mint egy 3×33 \times 3-asra, azonos bemeneti méret és csatornaszám mellett. Milyen metrikát használna, mit tartana állandóan, és milyen korlátot fogalmazna meg az eredmény általánosításáról? Elvárt átadandó eredmény: írásos kísérleti protokoll az 1.2. Szakmailag megalapozott modellezési állítások szerinti elemekkel, de futtatás nélkül.

15.1.8 Megoldások és indoklás

F1 megoldás. A weight sharing azt jelenti, hogy a kernel egyetlen K[o,c,u,v]K[o,c,u,v] eleme a bemenet több térbeli ablakában ugyanazzal a numerikus értékkel szerepel a kimenet kiszámításában. Például az s=1s = 1 stride-dal, p=0p = 0 paddinggel vett 3×33 \times 3-as kernel K[0,0,0,0]K[0,0,0,0] eleme az (i,j)=(0,0)(i,j) = (0,0) és (i,j)=(0,1)(i,j) = (0,1) kimeneti pozíciókban is a bemenet (0,0)(0,0), illetve (0,1)(0,1) pozíciójú pixelével szorzódik. A paraméterek azért nem függetlenek, mert a kimeneti pozíciók mindegyike ugyanazt a kernel-elemet használja; ha függetlenek lennének, a gradiensben nem összegzés, hanem külön-külön frissítés jelenne meg, és megszűnne az eltolási equivariance 16.1. Eltolási equivariance és invariance alfejezetbeli tulajdonsága.

F2 megoldás. A levezetés azonos a tétel bizonyításával, de a paddingelt bemenet indexelésében az i⋅s+ui \cdot s + u és j⋅s+vj \cdot s + v tagok szerepelnek. A s=2s = 2 stride nem változtatja meg a formula szerkezetét: a kernel gradiens továbbra is

∂L∂K[o,c,u,v]=∑n,i,j∇YL[n,o,i,j]⋅X̃[n,c,i⋅2+u,j⋅2+v]. \frac{\partial L}{\partial K[o,c,u,v]} = \sum_{n,i,j} \nabla_Y L[n,o,i,j] \cdot \tilde X[n, c, i \cdot 2 + u, \; j \cdot 2 + v].

A stride kizárólag abban jelenik meg, hogy a paddingelt bemenet mely koordinátáit mintavételezzük az egyes kimeneti pozíciókhoz. A levezetés minden lépése azonos a s=1s=1 esettel, csak a mintavételi rács ritkább.

F3 megoldás. A fenti kód conv2d_weight_grad függvénye már megvalósítja a levezetett formulát. A N=2N = 2, Cin=2C_{in} = 2, Cout=3C_{out} = 3, H=W=5H = W = 5, 3×33 \times 3-as kernel esetén a kimeneti shape (2,3,3,3)(2, 3, 3, 3). A finite_diff_check futtatása ezen a méreten azt mutatja, hogy a középső differencia 10−510^{-5} lépésközzel és a fenti tűrésekkel egyezik a levezetett formulával.

F4 megoldás. A hiba a Hout⋅WoutH_{out} \cdot W_{out}-tal való osztás. A tétel bizonyította, hogy a bias gradiens a batch- és térösszeg, nem pedig annak átlaga. A következmény az, hogy a bias gradiensét a térbeli méret függvényében alulskálázza: nagyobb kimeneti méret esetén a bias túl lassan tanul. A javított kód:

db[o] = np.sum(dY[:, o, :, :])

A visszaellenőrzés a tétel formulájával: a ∂L∂b[o]=∑n,i,j∇YL[n,o,i,j]\frac{\partial L}{\partial b[o]} = \sum_{n,i,j} \nabla_Y L[n,o,i,j] éppen a fenti kód, minden osztás nélkül.

F5 megoldásvázlat. A kísérleti kérdés: hogyan skálázódik a kernel gradiensének számítási költsége a kernelmérettel. Protokoll: (1) kérdés: a 1×11 \times 1-es és 3×33 \times 3-as kernel gradiensszámítási idejének aránya; (2) adat: szintetikus véletlen tensor, rögzített seed; (3) megfigyelési egység: egyetlen gradiensszámítás futása; (4) split: nem releváns, nincs tanulás; (5) baseline: a 1×11 \times 1-es kernel; (6) változó tényező: kernelméret; (7) metrika: fal-idő, ismételt méréssel, melegítés után; (8) erőforrás: helyi CPU, f64; (9) korlát: az eredmény nem általánosítható GPU-ra, és a forward overhead miatt a tiszta gradiens-számítás időaránya nem azonos a teljes tanítási idővel.

15.1.9 Rövid összefoglaló

A konvolúciós réteg kernel- és biasgradiensét a teljes NCHW cross-correlation definícióból vezettük le a láncszabály alkalmazásával. A kernel gradiens minden súlyelemre a batch, a kimeneti magasság és a kimeneti szélesség szerinti összeg: az upstream gradiens és a megfelelő paddingelt bemeneti pixel szorzatát minden felhasználási helyen összeadjuk. A bias gradiens a batch és térbeli indexekre vett upstream-gradiens összeg. A weight sharing miatt a hozzájárulások összeadódnak, nem átlagolódnak. Kézi példán és NumPy-kódban véges differenciával ellenőriztük a formulát f64 tűrésekkel. A leggyakoribb hiba a felhasználási helyek számával való osztás, ami a minibatch-loss átlagolásának félreértéséből származik.

15.1.10 Következő alkalmazás

A 15.2. Inputgradiens és átfedő recepciós mezők egységben az input gradiensét vezetjük le; ott az átfedő recepciós mezők miatt a gradiensek hasonlóan összegződnek, de a kernel szerepe megfordul.

15.1.11 Források