34.3 Pruning és struktúra

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

A 34.2. Kvantálás és hibatovábbterjedés egységben a kvantálás a tárolandó súly bitszélességét csökkentette, miközben az egyes rétegek alakja és műveletszáma változatlan maradt. A pruning ezzel szemben a paraméterek számát akarja csökkenteni: a kérdés az, hogy egy súly elhagyása ténylegesen kisebb futási költséget jelent-e. Az induló kérdés: van egy lineáris rétegünk, amelynek súlymátrixában a belépések 70 százaléka nulla; a torch.nn.Linear ugyanannyi szorzást végez-e ekkor, mint a nulla nélküli változaton? A válasz a szokásos sűrű futási úton igen: a sűrű Linear kernel a nulla súlypozíciókat is bejárja, ugyanannyi szorzást végez. Kevesebb számítás csak akkor lesz, ha a nullák mintázatát ténylegesen kihasználó ritka kernel fut, vagy a pruning a tensor dimenzióját is csökkenti (például egész csatornák eltávolításával). Ezért kell szétválasztanunk a nullázott súlyt és a ténylegesen kihagyott számítást. A témához tartozik az unstructured/structured pruning, a ritkasági arány, a csatorna-eltávolítás, az újratanítás és a kernel-támogatás kérdése is.

Előismeret: a tenzoralakok és paraméterszámlálás a 14.3. Paraméterszám, MAC és aktivációmemória egységből, a PyTorch nn.Module regisztrációja a 11.2. nn.Module és paraméterregisztráció egységből, valamint a kvantálásból származó numerikus hibakezelés a 34.2. Kvantálás és hibatovábbterjedés egységből. A 33.3. torch.export és ONNX export-ellenőrzés gondolata abban tér vissza, hogy egy pruned modellt is ugyanúgy exportálni és ellenőrizni kell; a 30.3. Negatív nélküli módszerek és collapse egységből a „kontroll nélkül nem állítunk javulást” elve érvényes; a 22.3. Zajos és hiányos címke egység pedig arra emlékeztet, hogy a tanítási és értékelési kiírásokat a pruning után sem szabad összekeverni.

34.3.2 2. Ritkaság, pruning és kernel-támogatás

Legyen W∈ℝm×dW\in\mathbb R^{m\times d} egy teljesen összekötött réteg súlymátrixa. A strukturálatlan pruning (unstructured pruning) elemenkénti maszkot rendel a súlyhoz: M∈{0,1}m×dM\in\{0,1\}^{m\times d}, és a használt súly

W̃=M⊙W, \widetilde W=M\odot W,

ahol ⊙\odot elemenkénti szorzás. A ritkaság (sparsity) a nullázott elemek aránya:

ρ=#{(i,j):Mij=0}md=1−∑i,jMijmd. \rho=\frac{\#\{(i,j): M_{ij}=0\}}{md} =1-\frac{\sum_{i,j}M_{ij}}{md}.

Ha ρ=0.7\rho=0.7, a súlytároló héttizede elvileg elhagyható, de a tényleges megtakarítás attól függ, mit tud a futtató kernel. A kernel-támogatás fogalma: egy ritkítási mintázatot akkor támogat a futási út, ha a minta alapján a végrehajtott szorzások és memóriamozgatások száma is csökken. A sűrű (dense) mátrixszorzó implementáció ezt nem teszi meg automatikusan: bejárja mind a mdmd súlypozíciót, akár nulla az ott tárolt érték, akár nem.

A strukturált pruning (structured pruning) nem elemeket, hanem teljes tensorszeleteket távolít el: egy lineáris rétegnél kimeneti neuront, konvolúciós rétegnél pedig egy teljes bemeneti vagy kimeneti csatornát. A channel removal a konvolúciós súly K∈ℝCout×Cin×kh×kwK\in\mathbb R^{C_{out}\times C_{in}\times k_h\times k_w} valamelyik csatornaindexét törli a tensorból, nem nullává teszi. Ekkor az új súly alakja kisebb, tehát az általános dense konvolúciós kernel is kevesebb tényleges szorzást végez. A strukturált pruning azonban a hálózat függvényét általában megváltoztatja, ezért fine-tune, azaz a megmaradt súlyokból kiinduló további tanítás követi.

A PyTorch torch.nn.utils.prune modulja strukturálatlan maszkolást valósít meg: a maszk külön bufferként jelenik meg a paraméter mellett, és a forward kiértékelés a maszkolt súlyt használja. Ez önmagában nem csökkenti a dense kernel műveletszámát.

34.3.3 3. Csatorna eltávolítás mint strukturált pruning: alak- és paraméterlemma

Legyen L1L_1 egy Conv2d réteg, amely C0C_0 bemeneti csatornából C1C_1 kimeneti csatornába képez kh×kwk_h\times k_w kernelmérettel, és legyen L2L_2 egy második Conv2d, amely C1C_1 bemeneti csatornából C2C_2 kimeneti csatornába képez k′h×k′wk'_h\times k'_w kernelmérettel. Mindkét réteg legyen biasszal ellátott. A ⋆\star művelet a JELOLESEK szerinti térbeli cross-correlation, nem matematikai konvolúció.

Lemma (csatornatörlés ekvivalenciája nulla fogyasztósúly esetén). Ha valamely j∈{0,…,C1−1}j\in\{0,\dots,C_1-1\} indexre K2[q,j,⋅,⋅]=0K_2[q,j,\cdot,\cdot]=0 minden q∈{0,…,C2−1}q\in\{0,\dots,C_2-1\} kimeneti csatornára, akkor a jj-edik kimeneti csatorna törlése L1L_1-ből és a jj-edik bemeneti csatorna törlése L2L_2-ből a kompozíció függvényét pontosan változatlanul hagyja.

Bizonyítás. Jelölje Y=L1(x)Y=L_1(x) és Y′=L′1(x)Y'=L'_1(x) a törlés előtti és utáni közbülső aktivációt. Y′Y' csatornái éppen YY csatornái a jj index kihagyásával, mert a megmaradó csatornák súlyai és biasai változatlanok. A qq-adik kimenetre

Zq=b2[q]+∑c≠jK2[q,c]⋆Yc+K2[q,j]⋆Yj⏟=0. Z_q=b_2[q]+\sum_{c\neq j}K_2[q,c]\star Y_c+\underbrace{K_2[q,j]\star Y_j}_{=0}.

A feltétel miatt a harmadik tag azonosan nulla, így a c≠jc\neq j indexek összege pontosan a törlés utáni L′2(Y′)L'_2(Y') értékét adja. Ezzel az állítást beláttuk. A bizonyítás kulcsa, hogy a törölt csatornát a fogyasztó oldalán is töröltük; ha a fogyasztósúly nem nulla, az ekvivalencia nem áll fenn.

Állítás (paraméterszám-csökkenés egy csatorna eltávolításakor). A fenti két réteg paraméterszáma a törlés előtt

P=C1(C0khkw+1)+C2(C1k′hk′w+1), P=C_1(C_0k_hk_w+1)+C_2(C_1k'_hk'_w+1),

a törlés után pedig

P′=(C1−1)(C0khkw+1)+C2((C1−1)k′hk′w+1), P'=(C_1-1)(C_0k_hk_w+1)+C_2\bigl((C_1-1)k'_hk'_w+1\bigr),

feltéve, hogy C1≥2C_1\ge 2. A különbség

Δ=P−P′=C0khkw+1+C2k′hk′w. \Delta=P-P'=C_0k_hk_w+1+C_2k'_hk'_w.

Bizonyítás. Az L1L_1 súlyának alakja C1×C0×kh×kwC_1\times C_0\times k_h\times k_w, minden kimeneti csatornához C0khkwC_0k_hk_w súly és 11 bias tartozik, ezért C1(C0khkw+1)C_1(C_0k_hk_w+1). A jj-edik kimeneti szelet törlése után a kimeneti méret C1−1C_1-1, a biasvektor egy elemmel rövidebb. Az L2L_2 súlyának alakja C2×C1×k′h×k′wC_2\times C_1\times k'_h\times k'_w; a jj-edik bemeneti szelet törlése a középső tengely hosszát csökkenti C1−1C_1-1-re, a biasvektor hossza pedig változatlan marad, mert a kimeneti csatornák száma ugyanaz. A kivonást elvégezve

Δ=C1(C0khkw+1)−(C1−1)(C0khkw+1)+C2(C1k′hk′w+1)−C2((C1−1)k′hk′w+1)=C0khkw+1+C2k′hk′w. \begin{aligned} \Delta &=C_1(C_0k_hk_w+1)-(C_1-1)(C_0k_hk_w+1)\\ &\quad +C_2(C_1k'_hk'_w+1)-C_2((C_1-1)k'_hk'_w+1)\\ &=C_0k_hk_w+1+C_2k'_hk'_w. \end{aligned}

A törlés során a közbülső aktiváció alakja ugyanígy N×C1×H×WN\times C_1\times H\times W alakról N×(C1−1)×H×WN\times(C_1-1)\times H\times W alakra változik; a térbeli H,WH,W dimenziók nem változnak, ha a padding, stride és dilation beállítások azonosak maradnak.

34.3.4 4. Kézi példa: 1×1 csatornaeltávolítás

Használjunk a 19.2. MobileNet és inverted residual egységben megismert pontonkénti rétegeket, hogy minden szám kézzel követhető legyen. Legyen C0=2C_0=2, C1=3C_1=3, C2=2C_2=2, kh=kw=k′h=k′w=1k_h=k_w=k'_h=k'_w=1. A bemenet legyen az egyelemű 1×2×1×11\times2\times1\times1 alakú tensor, X0,0=1X_{0,0}=1 és X0,1=2X_{0,1}=2. Az L1L_1 súlya és biása:

W1[0,:]=[1,0],b1[0]=0;W1[1,:]=[0,2],b1[1]=1;W1[2,:]=[−1,1],b1[2]=0. W_1[0,:]=[1,0],\ b_1[0]=0;\qquad W_1[1,:]=[0,2],\ b_1[1]=1;\qquad W_1[2,:]=[-1,1],\ b_1[2]=0.

Ekkor Y1[0]=1Y_1[0]=1, Y1[1]=0⋅1+2⋅2+1=5Y_1[1]=0\cdot1+2\cdot2+1=5, Y1[2]=−1+2=1Y_1[2]=-1+2=1. Az L2L_2 úgy legyen megválasztva, hogy az 11-es bemeneti csatorna fogyasztósúlya nulla legyen:

W2[0,:]=[2,0,−1],b2[0]=0;W2[1,:]=[1,0,3],b2[1]=−2. W_2[0,:]=[2,0,-1],\ b_2[0]=0;\qquad W_2[1,:]=[1,0,3],\ b_2[1]=-2.

A kimenet

Z0=2⋅1+0⋅5−1⋅1=1,Z1=1⋅1+0⋅5+3⋅1−2=2. Z_0=2\cdot1+0\cdot5-1\cdot1=1,\qquad Z_1=1\cdot1+0\cdot5+3\cdot1-2=2.

Töröljük most az 11-es közbülső csatornát. A megtartott indexek 00 és 22, az új L′1L'_1 súlya 2×2×1×12\times2\times1\times1 alakú:

W′1[0,:]=[1,0],b′1[0]=0;W′1[1,:]=[−1,1],b′1[1]=0, W'_1[0,:]=[1,0],\ b'_1[0]=0;\qquad W'_1[1,:]=[-1,1],\ b'_1[1]=0,

ezért Y′=[1,1]Y'=[1,1]. Az új L′2L'_2 súlyában az eredeti 2×3×1×12\times3\times1\times1 súly középső bemeneti oszlopát töröljük:

W′2[0,:]=[2,−1],b′2[0]=0;W′2[1,:]=[1,3],b′2[1]=−2. W'_2[0,:]=[2,-1],\ b'_2[0]=0;\qquad W'_2[1,:]=[1,3],\ b'_2[1]=-2.

A kimenet

Z′0=2⋅1−1⋅1=1,Z′1=1⋅1+3⋅1−2=2, Z'_0=2\cdot1-1\cdot1=1,\qquad Z'_1=1\cdot1+3\cdot1-2=2,

tehát a törlés előtti kimenettel pontosan egyezik, ahogy a lemma állítja. A paraméterszám a törlés előtt P=3(2⋅1+1)+2(3⋅1+1)=9+8=17P=3(2\cdot1+1)+2(3\cdot1+1)=9+8=17, utána P′=2(2+1)+2(2+1)=6+6=12P'=2(2+1)+2(2+1)=6+6=12, a különbség 55. A harmadik szakasz képlete szerint Δ=C0khkw+1+C2k′hk′w=2⋅1+1+2⋅1=5\Delta=C_0k_hk_w+1+C_2k'_hk'_w=2\cdot1+1+2\cdot1=5, ami egyezik.

34.3.5 5. Ellenőrizhető PyTorch-munkamenet

A következő példa a fenti számítást a PyTorch Conv2d rétegeivel ellenőrzi. A példa az olvasó által futtatható; a kódban kommentként jelölt kimenetek a kézi példából számított értékek, nem előre elvégzett futási eredmények.

# munkakönyvtár: lab/
# python n135_prune_example.py
import torch
import torch.nn as nn

torch.manual_seed(0)
x = torch.tensor([[[[1.0]], [[2.0]]]], dtype=torch.float32)

def make_pair():
    l1 = nn.Conv2d(2, 3, 1)
    l2 = nn.Conv2d(3, 2, 1)
    with torch.no_grad():
        l1.weight.copy_(torch.tensor([
            [[[1.0]], [[0.0]]],
            [[[0.0]], [[2.0]]],
            [[[-1.0]], [[1.0]]]
        ], dtype=torch.float32))
        l1.bias.copy_(torch.tensor([0.0, 1.0, 0.0]))
        l2.weight.copy_(torch.tensor([
            [[[2.0]], [[0.0]], [[-1.0]]],
            [[[1.0]], [[0.0]], [[3.0]]]
        ], dtype=torch.float32))
        l2.bias.copy_(torch.tensor([0.0, -2.0]))
    return l1, l2

def remove_channel(l1, l2, j):
    keep = [c for c in range(l1.out_channels) if c != j]
    l1p = nn.Conv2d(l1.in_channels, l1.out_channels - 1, l1.kernel_size,
                    stride=l1.stride, padding=l1.padding, bias=True)
    l2p = nn.Conv2d(l2.in_channels - 1, l2.out_channels, l2.kernel_size,
                    stride=l2.stride, padding=l2.padding, bias=True)
    with torch.no_grad():
        l1p.weight.copy_(l1.weight[keep])
        l1p.bias.copy_(l1.bias[keep])
        l2p.weight.copy_(l2.weight[:, keep])
        l2p.bias.copy_(l2.bias)
    return l1p, l2p

l1, l2 = make_pair()
l1p, l2p = remove_channel(l1, l2, 1)
z = l2(l1(x))     # számított: [[[1.0]], [[2.0]]]
zp = l2p(l1p(x))  # számított: ugyanaz
torch.testing.assert_close(z, zp, rtol=1e-7, atol=1e-7)
print(z.flatten().tolist())  # [1.0, 2.0]

A remove_channel függvény ténylegesen új Conv2d objektumokat hoz létre, és a megtartott indexekkel másolja át a súlyokat; nem maszkol. A törlés után a paraméterszám a sum(p.numel() for p in l1.parameters()) hívásokkal is ellenőrizhető: 9-ből 6 és 8-ból 6 lesz.

34.3.6 6. Labor: ritkítási specifikáció, pontosság- és memóriakontroll

A labor egy kis GlyphLab-CNN-en két modellt hasonlít össze: egy tanított baseline-t és egy strukturáltan pruned változatot. Az alábbi eredmények közül a paraméterszámok a pythonbeli alakokból számított értékek; a pontosság és futásidő mérését a protokoll szerint kell elvégezni, így azokat itt üres mezőként vagy jelölt szemléltetésként adjuk meg.

# lab/n135_prune_lab.py (vázlat)
import torch
import torch.nn as nn

class TinyGlyphCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 8, 3, padding=1)
        self.conv2 = nn.Conv2d(8, 16, 3, padding=1)
        self.pool = nn.MaxPool2d(2)
        self.head = nn.Linear(16 * 12 * 12, 3)

    def forward(self, x):
        x = torch.relu(self.conv1(x))
        x = torch.relu(self.conv2(x))
        x = self.pool(x)
        return self.head(torch.flatten(x, 1))

def channel_l1(weight):
    # weight: (C_out, C_in, kh, kw)
    return weight.abs().sum(dim=(1, 2, 3))

def remove_pair(conv1, conv2, drop1, drop2):
    keep1 = [c for c in range(conv1.out_channels) if c not in drop1]
    keep2 = [c for c in range(conv2.out_channels) if c not in drop2]
    c1p = nn.Conv2d(conv1.in_channels, conv1.out_channels - len(drop1),
                    conv1.kernel_size, stride=conv1.stride,
                    padding=conv1.padding, bias=True)
    c2p = nn.Conv2d(conv2.in_channels - len(drop1),
                    conv2.out_channels - len(drop2),
                    conv2.kernel_size, stride=conv2.stride,
                    padding=conv2.padding, bias=True)
    with torch.no_grad():
        c1p.weight.copy_(conv1.weight[keep1])
        c1p.bias.copy_(conv1.bias[keep1])
        c2p.weight.copy_(conv2.weight[keep2][:, keep1])
        c2p.bias.copy_(conv2.bias[keep2])
    return c1p, c2p

A ritkítási specifikáció: a baseline tanítása után a conv1 nyolc kimeneti csatornájából kettőt, a conv2 tizenhat kimeneti csatornájából négyet törlünk; mindkét törlést a train-állapotból számított legkisebb csatorna-ℓ1\ell_1-normák alapján választjuk, a val- vagy test-eloszlás megtekintése nélkül. Az új fej bemeneti mérete 12⋅12⋅12=172812\cdot12\cdot12=1728 lesz; a régi head súlyát nem lehet átmásolni, mert a bemeneti reprezentáció csatornaszáma megváltozott, ezért a fejet újra kell inicializálni.

A paraméterszám:

Modell Súlyok és biask Összesített
baseline conv1 80, conv2 1168, fej 6915 8163
pruned conv1 60, conv2 660, fej 5187 5907

A pruned változat paraméterszáma 27.6%27.6\%-kal kisebb. A memóriaellenőrzéshez a paraméter-bájtok összege sum(p.numel() * p.element_size() for p in model.parameters()); a baseline esetén 8163⋅4=326528163\cdot4=32652 bájt, a pruned esetén 5907⋅4=236285907\cdot4=23628 bájt. Az aktivációs memóriát a torch.profiler profile_memory=True kapcsolójával lehet mérni; a pontos mezőneveket a helyi dokumentációból kell ellenőrizni ([D17] státuszát lásd a forrásoknál).

A futtatási parancs:

python n135_prune_lab.py --images data/images.npy --labels data/labels.npy \
  --metadata data/metadata.csv --epochs 10 --drop1 2 --drop2 4 --seed 20261020

A protokoll: Tanuljuk a baseline-t train-en, maximálisan 20 epochig, Adam tanulóval, 0.001 tanulási rátával, 32-es batchcsel; a legjobb állapotot a val veszteség választja ki. A pruned modellt a kiválasztott baseline állapotból másoljuk, a fenti csatornák törlésével, majd 10 epochig finomhangoljuk. A zárótesztet egyszer, a protokoll rögzítése után értékeljük. A nem futtatott pontosságértékeket a laborjelentésben MÉRENDŐ mező jelöli; a paraméter- és alakkontroll tételszerű, a pontossághatás viszont empirikus és seedfüggő.

34.3.7 7. Gyakori tévedés: nullázott dense súly és futtatott művelet

A strukturálatlan ritkítás gyakori félreértése, hogy a nullázás már gyorsítja a hálózatot. Tekintsük a torch.nn.Linear réteget, amely az N×dN\times d bemenetből Z=XW⊤+bZ=XW^\top+b kimenetet számít. Ha a WW mátrixban nullákat helyezünk el, a dense mátrixszorzó kernel továbbra is végigjárja a teljes m×dm\times d súlyterületet; nem létezik a futásidőben automatikusan kiértékelt maszkfeltétel a kernel belsejében. A nullázott dense súly tehát nem csökkenti a futtatott dense műveletek számát, csak a tárolt értéket változtatja.

Ellenpélda: legyen W=[1023]W=\begin{bmatrix}1&0\\2&3\end{bmatrix}, azaz a súly 25 százaléka nulla. Egy ℝ2\mathbb R^2-beli bemenetre a kimenet y1=1x1+0x2+b1y_1=1x_1+0x_2+b_1, y2=2x1+3x2+b2y_2=2x_1+3x_2+b_2. A futtatott GEMM akkor is négy szorzást és a hozzájuk tartozó összeadásokat végzi, ha a második elem nulla; a kernel nem tudja kihagyni a pozíciót a dense reprezentációból. Ezzel szemben a strukturált törlés, például a fenti csatorna-eltávolítás, megváltoztatja a súlytenzor alakját, ezért az általános dense Conv2d kernel is kisebb dimenzióval és kevesebb tényleges szorzással fut. A maszkolt Python-reteg ráadásul a forwardban még egy elemenkénti szorzást is hozzáadhat, tehát rosszul megválasztott ritkítási módszerrel a pruned modell akár lassabb is lehet.

34.3.8 8. Feladatok

F1 (fogalmi, nehézség 1). Definiálja a strukturálatlan és a strukturált pruning különbségét. Adjon egy 4×34\times3 méretű súlymátrixon példát mindkettőre, és mondja meg, miért nem ad az unstructured nullázás automatikus gyorsulást egy sűrű GEMM-ben. Átadandó: két mátrix, maszkok, shape-változás, egy rövid indoklás.

F2 (levezetési, nehézség 2). Igazolja a paraméterszám-lemmát bias nélküli rétegekre, majd alkalmazza C0=3C_0=3, C1=4C_1=4, C2=5C_2=5, kh=kw=3k_h=k_w=3, k′h=k′w=1k'_h=k'_w=1 értékekkel: adja meg a két réteg súlyalakját, paraméterszámát törlés előtt és után. Átadandó: teljes levezetés és PP, P′P', Δ\Delta.

F3 (implementációs, nehézség 2). Írjon PyTorch-függvényt, amely két Conv2d rétegből ténylegesen eltávolítja a jj-edik közbülső csatornát, majd a 4. szakasz numerikus példáján ellenőrizze torch.testing.assert_close segítségével. Átadandó: futtatható kód, shape-kontroll, egyezési feltétel.

F4 (hibakeresési, nehézség 2). Adott a következő kód:

l1.weight.data[1] = 0
l1.bias.data[1] = 0
# a l2-t nem módosítjuk

Magyarázza el, miért hamis channel pruningnak nevezni ezt, és mi a kimenetre és a számítási költségre gyakorolt hatása. Adja meg a javítást. Átadandó: hiba ok, következmény, javított kódrészlet, ellenőrzési sor.

F5 (kísérlettervezési/transzfer, nehézség 3). Tervezzen GlyphLab-kísérletet a baseline, a 70 százalékos unstructured pruning és a 25 százalékos structured channel pruning összehasonlítására CPU-n. A cél annak eldöntése, hogy az azonos val-pontosságú tűréshatár mellett melyik változat ad rövidebb CPU forward időt. Írja le a splitet, a baseline-t, a metrikákat, a seed- és fine-tune-protokollt, valamint a következtetés korlátait. Átadandó: protokollterv és alternatív értelmezés.

34.3.9 9. Megoldások és indoklás

F1 megoldás. Az unstructured pruning az elemenkénti M⊙WM\odot W maszkkal nulláz, a mátrix alakja 4×34\times3 marad. Például

W=[201003450070],M=[101001110010]. W=\begin{bmatrix} 2&0&1\\ 0&0&3\\ 4&5&0\\ 0&7&0 \end{bmatrix},\qquad M=\begin{bmatrix} 1&0&1\\ 0&0&1\\ 1&1&0\\ 0&1&0 \end{bmatrix}.

A structured pruning teljes oszlopot töröl: ha a második oszlopot elhagyjuk, az eredmény 4×24\times2 alakú, és a sűrű mátrixszorzás tényleges mérete is csökken. A sűrű GEMM az unstructured nullás mátrixot teljes 4×34\times3-ként beolvassa, nullákkal együtt, ezért azok a pozíciók továbbra is részt vesznek a ciklusban.

F2 megoldás. Bias nélkül P=C1C0khkw+C2C1k′hk′wP=C_1C_0k_hk_w+C_2C_1k'_hk'_w. A törlés után P′=(C1−1)C0khkw+C2(C1−1)k′hk′wP'=(C_1-1)C_0k_hk_w+C_2(C_1-1)k'_hk'_w, tehát

Δ=C0khkw+C2k′hk′w. \Delta=C_0k_hk_w+C_2k'_hk'_w.

A konkrét értékekkel P=4⋅3⋅9+5⋅4⋅1=108+20=128P=4\cdot3\cdot9+5\cdot4\cdot1=108+20=128, P′=3⋅27+5⋅3=81+15=96P'=3\cdot27+5\cdot3=81+15=96, Δ=32\Delta=32. A súlyalakok: L1L_1: 4×3×3×34\times3\times3\times3, törlés után 3×3×3×33\times3\times3\times3; L2L_2: 5×4×1×15\times4\times1\times1, törlés után 5×3×1×15\times3\times1\times1. Ez a feladat megerősíti, hogy a +1+1-es tagok csak biasos esetben lépnek be.

F3 megoldás. Az 5. szakasz remove_channel függvénye teljes: a keep indexekkel átmásolja L1L_1 kimeneti és L2L_2 bemeneti szeleteit. A szükséges kontrollok:

assert l1p.weight.shape == (2, 2, 1, 1)
assert l2p.weight.shape == (2, 2, 1, 1)
assert sum(p.numel() for p in l1p.parameters()) == 6
assert sum(p.numel() for p in l2p.parameters()) == 6
torch.testing.assert_close(zp, z, rtol=1e-7, atol=1e-7)

Az első feltétel az alak-ellenőrzés: a törlés után mindkét 1×1 réteg 2×22\times2 súlyú. A numerikus egyezés itt a lemma feltételét ellenőrzi, nem pedig általánosan bizonyítja bármely réteg törlését.

F4 megoldás. A hiba az, hogy a kód a csatornát nullázza, nem törli. A jj csatorna kimenete azonosan nulla lesz, ezért az L2L_2 kimenete numerikusan megegyezhet a törölt változattal, de a L2L_2 súlyainak megfelelő bemeneti oszlopa továbbra is a memóriában és a kernelben él; a paraméterszám nem csökken, és a futott szorzások száma sem. A javítás az 5. szakasz függvénye: új Conv2d példányok létrehozása és a megtartott indexek átmásolása. Az ellenőrzés a paraméterszám és a assert_close együttes futtatása.

F5 megoldásminta. A független mintavételi egység a scene_id; a train/val/test split rögzített. A baseline a TinyGlyphCNN, a változók: no prune, unstructured 70%, structured 25%. A structured modellt a baseline legjobb val-checkpointjából vágjuk; az unstructuredt ugyanabból maszkoljuk. A tanulási lépések három seeddel ismétlődnek 2026102020261020, 2026102120261021, 2026102220261022, mert egyetlen seed nem hordozhatósági garancia. A metrika a val accuracy a fine-tune után, a végső teszten egyszer, a legjobb val-állapottal mérve; a futásidőt torch.utils.benchmark.Timer méri melegítéssel és ismétlésekkel, a memóriát a profiler. A következtetés csak a helyi CPU-környezetre és a GlyphLab-adatra érvényes; GPU-ra vagy természetes képekre nem általánosítható. A vártnál rosszabb pruned pontosság sem a structured pruning érvénytelenítése, hanem a fine-tune és a törlési kritérium hibájának jele lehet.

34.3.10 10. Önellenőrzés és visszahívás

Ellenőrző lista: a maszkolt súly alakja mindig azonos az eredetiével, a törölt súly alakja kisebb; a bias hossza csak a kimeneti tengely törlésekor csökken; a C1=1C_1=1 esetben a törlés üres csatornateret hagyna, ezért a lemma feltétele C1≥2C_1\ge2; a maradékcsatlakozásos blokkokban a csatorna törlése az összeadás mindkét ágát módosítja, ezért a 18.1. Reziduális blokk és identitásút blokkja külön feltételt igényel. A 34.2. Kvantálás és hibatovábbterjedés kvantálás visszahívása: ott az alak nem változik, de a számábrázolási hiba megjelenik; itt az alak változik, de a numerikus ekvivalencia csak nulla fogyasztósúly esetén garantált. A 33.3. torch.export és ONNX export-ellenőrzésből örökölt elv: az új modell viselkedését véges kontrollhalmazon is újra meg kell mérni.

34.3.11 Rövid összefoglaló

A pruning akkor csökkenti a számítást, ha ténylegesen törli a tensordimenziót vagy ha a kernel kifejezetten támogatja a ritka mintázatot. A strukturálatlan nullázás ritkaságot adhat a tárolásban, de nem csökkenti automatikusan a futott dense műveleteket. A strukturált csatorna-eltávolítás egy Conv2d kimeneti csatornáját és a következő réteg bemeneti csatornáját együtt törli; két rétegnél a paraméterszám csökkenése Δ=C0khkw+1+C2k′hk′w\Delta=C_0k_hk_w+1+C_2k'_hk'_w. Nulla fogyasztósúly esetén a törlés a függvényt pontosan megőrzi, általában azonban fine-tune-ra van szükség. A laborban a kiválasztást train-statisztika, a végső döntést val-mérés, a zárt tesztet egyszeri mérés igazolja.

34.3.12 Következő alkalmazás és ténylegesen használt források

A 34.4. Teacher–student desztilláció egység a tudásdesztillációt tárgyalja: ott a már pruned tanulóháló egy nagyobb teacher-lágy céljait közelíti, ami a pruninghoz képest független sűrítési út ([P26] az absztrakt és a rekord szintjén jelöli a keretet). A laborban használt profiler és benchmark vázlat a hivatalos PyTorch-oldalakra mutat, amelyek tartalma a jelen kézirat írásakor még nem volt megnyitva, ezért a konkrét API-mezőket a telepített 2.14-es verzióban ellenőrizni kell.

Felhasznált források: