17.1 LeNet és a lokális megosztott reprezentáció

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

Ez az egység a klasszikus konvolúciós hálózatok kiindulópontját, a LeNet-családot és azon belül a LeNet-5 szerkezetét vizsgálja. A 12.4. III. ellenőrző pont: önállóan tanuló kis háló egységből a teljes tanítási ciklust, a 14.4. Csoportos és depthwise konvolúció egységből a csatornakapcsolatok korlátozását, a 16.4. IV. ellenőrző pont: a CNN-operátor teljes megértése egységből pedig a CNN-operátor alak-, gradiens- és szimmetriavizsgálatát ismerjük. Itt a cél az, hogy egy konkrét réteggráfon mutassuk meg: hogyan kapcsolódnak a térbeli szűrők, a csatornabővítés, az alulmintavételezés és az osztályozófej. Induló kérdés: egy 32×32-es bemenetre 5×5-ös kernel, stride 1 és padding 0 mellett hány térbeli kimeneti elem adódik, és miért beszélünk mégis „konvolúciós” rétegről akkor, amikor a keretrendszer keresztkorrelációt számol?

17.1.2 2. A LeNet-5 réteggráfja és három alapkomponense

A [P01] tanulmány a LeNet-5 hálózatot a kézírásos dokumentumfelismerésre vezette be. Az eredeti architektúrát a cikk II.B szakasza (7–8. o., 2. ábra, I. táblázat, (5)–(7) egyenlet) írja le; az alábbi táblázat ebből készült, a jobb oldali oszlopok a labor saját, egyszerűsített TinyLeNet-Glyph változatát mutatják, amelyet a 4. szakasz vezet le. A saját változat nem az eredeti reprodukciója.

Réteg Eredeti LeNet-5 [P01] Param. [P01] TinyLeNet-Glyph (labor) Param.
bemenet 1×32×321\times32\times32; háttér −0,1-0{,}1, előtér 1,1751{,}175 (átlag kb. 0, variancia kb. 1) – 1×24×241\times24\times24, [0,1][0,1] –
C1 6 térkép, 5×55\times5 konvolúció, kimenet 6@28×286@28\times28 156 Conv2d(1,6,5), ReLU, 6@20×206@20\times20 156
S2 2×22\times2, nem átfedő; a négy bemenet összege ×\times tanulható együttható ++ tanulható bias, majd szigmoid 12 MaxPool2d(2), paraméter nélkül 0
C3 16 térkép, 5×55\times5, részleges kötés az I. táblázat szerint 1516 Conv2d(6,16,5) teljes kötés, ReLU, 16@6×616@6\times6 2416
S4 mint S2, 16@5×516@5\times5 32 MaxPool2d(2), 16@3×316@3\times3 0
C5 120 térkép, 5×55\times5 konvolúció minden S4-térképen, kimenet 1×11\times1 48 120 Linear(144,120), ReLU 17 400
F6 84 egység, teljes kötés C5-höz 10 164 Linear(120,84), ReLU 10 164
kimenet 10 euklideszi RBF-egység, yi=∑j(xj−wij)2y_i=\sum_j(x_j-w_{ij})^2, kézzel választott, kezdetben rögzített ±1\pm1 kódokkal – Linear(84,3) logit, cross-entropy 255

Az eredeti hálóban a C1–F6 rétegek súlyozott összegét a skálázott tangens hiperbolikus függvény nyomja össze: xi=f(ai)x_i=f(a_i), f(a)=Atanh(Sa)f(a)=A\tanh(Sa), A=1,7159A=1{,}7159 ([P01] (5)–(6) egyenlet; SS választását a cikk A. függeléke indokolja). A cikk szerint a teljes háló 340 908 kapcsolatot, de a súlymegosztás miatt csak mintegy 60 000 szabad paramétert tartalmaz.

A LeNet-5 szokásos rétegsora:

X∈[0,1]1×32×32→C1→S2→C3→S4→C5→F6→kimenet.X\in[0,1]^{1\times 32\times 32}\to C1 \to S2 \to C3 \to S4 \to C5 \to F6 \to \text{kimenet}.

Itt C1C1 egy 5×5-ös, 6 kimeneti csatornájú szűrőréteg, S2S2 egy 2×2-es alulmintavételező réteg, C3C3 16 csatornát állít elő, S4S4 ismét alulmintavételez, C5C5 az 5×5-ös bemenetet 120 csatornára képezi, F6F6 pedig 84 rejtett egységet tartalmazó affin réteg, amelyet a kimenet követ. A mai keretrendszerekben C5C5 megvalósítható egyszerű affin rétegként is, mert a bemeneti térbeli kiterjedés itt már 5×5.

Konvolúciós réteg mint keresztkorreláció. A 13.1. Kép mint diszkrét többcsatornás jel NCHW-indexelését és a 13.2. Konvolúció és cross-correlation definícióját használjuk. Egyetlen output elem:

On,o,i,j=bo+∑c=0Cin−1∑u=0k−1∑v=0k−1Xn,c,i+u,j+vKo,c,u,v.O_{n,o,i,j}=b_o+\sum_{c=0}^{C_{in}-1}\sum_{u=0}^{k-1}\sum_{v=0}^{k-1}X_{n,c,i+u,j+v}\,K_{o,c,u,v}.

Ez kernelmegfordítás nélküli keresztkorreláció. A „konvolúciós réteg” elnevezés a szakirodalmi hagyományból jön; a tanulás szempontjából a megfordítás nem lényeges, mert a KK súlyokat amúgy is tanuljuk. A LeNet-ben a térbeli szűrés után nemlineáris aktiváció, az eredeti hálóban a tanh-családhoz tartozó telítődő függvény következik, nem a mai ReLU.

Subsampling réteg. A „subsampling” a LeNet kontextusában nem azonos a modern max-poolinggal. Az eredeti SS-rétegek a 2×2-es cellát tanulható súllyal és eltolással összegzik, majd telítődő aktivációt alkalmaznak; ez tehát tanulható paramétereket tartalmaz. A modern max-pool ezzel szemben paraméter nélküli, cellánkénti maximumot vesz:

Pmax(X)n,c,i,j=maxu∈{0,1}maxv∈{0,1}Xn,c,si+u,sj+v,P_{max}(X)_{n,c,i,j}=\max_{u\in\{0,1\}}\max_{v\in\{0,1\}}X_{n,c,si+u,sj+v},

ahol a LeNet-szerű beállításban s=2s=2. A két művelet nem reprodukciós értelemben sem felcserélhető: más a gradiens útja, és más a paraméterszám.

Classifier head. A konvolúciós rétegek által előállított feature mapokat a háló vége lapítja, majd affin rétegekre bízza az osztályozást. A LeNet-5 esetében ez a C5→F6→kimenetC5\to F6\to\text{kimenet} szakasz. A modern változatokban a kimenet előtti affin réteg véges logitokat ad; a softmax és a cross-entropy kapcsolatát a 7.2. Softmax és többosztályos cross-entropy tárgyalta, és a könyv főútja szerint a loss logitokat vár, nem softmaxolt valószínűséget.

17.1.3 3. Történeti változatok: az eredeti LeNet-5 és a mai „LeNet-szerű” háló

Az eredeti LeNet-5 leírása és a mai könyvtári példák közötti eltérés gyakran elmosódik. A három legfontosabb különbség:

  1. Az eredeti háló 32×32-es bemenetet használ, és a 28×28-as számjegyet középre helyezi; a laborváltozatunk 24×24-es GlyphLab-bemenettel dolgozik.
  2. Az eredeti háló tanh-szerű aktivációt és tanulható súlyozású subsamplinget használ; a modern változat gyakran ReLU-t és max-poolingot.
  3. A C3C3 réteg az eredeti LeNet-5-ben nem teljes konvolúció ([P01] I. táblázat): az első hat C3-térkép három szomszédos S2-térképből, a következő hat négy szomszédosból, a következő három négy nem szomszédosból kap bemenetet, az utolsó mind a hatból. Paraméterszáma így 6⋅(3⋅25+1)+6⋅(4⋅25+1)+3⋅(4⋅25+1)+(6⋅25+1)=456+606+303+151=15166\cdot(3\cdot25+1)+6\cdot(4\cdot25+1)+3\cdot(4\cdot25+1)+(6\cdot25+1)=456+606+303+151=1516. A cikk indoklása kettős: a kapcsolatok száma kezelhető marad, és a szimmetriatörés különböző jellemzők tanulására kényszeríti a térképeket. A laborváltozat teljesen konvolúciós C3C3-at használ (6⋅16⋅25+16=24166\cdot16\cdot25+16=2416 paraméter).

A „historical variant” kifejezés tehát nem csupán címke: a változat megnevezéséhez pontosan rögzíteni kell a bemeneti méretet, az aktivációt, a pooling típusát, a csatornakötést és a kimeneti réteg szerkezetét.

17.1.4 4. Levezetés: a választott TinyLeNet-Glyph minden alakja és paramétere

A laborhoz választott saját változat:

A térbeli kimeneti méret a 14.1. Stride, padding, dilation és outputméret formulájával:

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

A számítás lépésről lépésre:

Réteg Kimeneti alak Paraméterképlet Paraméterszám
C1 6×20×20 6⋅(1⋅5⋅5)+66\cdot(1\cdot5\cdot5)+6 156
P1 6×10×10 0 0
C2 16×6×6 16⋅(6⋅5⋅5)+1616\cdot(6\cdot5\cdot5)+16 2416
P2 16×3×3 0 0
F1 120 144⋅120+120144\cdot120+120 17 400
F2 84 120⋅84+84120\cdot84+84 10 164
F3 3 84⋅3+384\cdot3+3 255

A bias-tag minden kimeneti csatornán, illetve kimeneti egységen külön számít. Az összeg:

156+2416+17400+10164+255=30391.156+2416+17400+10164+255=30\,391.

Ez nem a LeNet-5 eredeti paraméterszáma; ez a saját laborváltozaté. A fenti levezetés típusa egyszerű alak- és paraméterszámítás, nem tételbizonyítás; az ellenőrzés módja a képletek indexhatár-vizsgálata és a későbbi kód.

17.1.5 5. Labor: TinyLeNet-Glyph ellenőrzése a GlyphLab adatúton

A labor nem tanít, hanem a modult és az adatbetöltést ellenőrzi. Munkakönyvtár: lab/. Az alábbi kód futtatható, de a jelen szövegben a kimenet jelölt szemléltetés, mert a kiírt értékek képletből származnak; a hallgató a python tiny_lenet_check.py paranccsal futtassa.

# lab/tiny_lenet_check.py
import numpy as np
import torch
import torch.nn as nn

torch.manual_seed(20261020)
device = torch.device("cpu")
dtype = torch.float32

class TinyLeNetGlyph(nn.Module):
    """LeNet-szeru, de nem az 1998-as LeNet-5 pontos reprodukcioja:
    teljes konvolucios C3, ReLU, modern max-pool, harom GlyphLab-osztaly."""
    def __init__(self, in_channels=1, num_classes=3):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(in_channels, 6, kernel_size=5, stride=1, padding=0),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(16 * 3 * 3, 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, num_classes),
        )

    def forward(self, x):
        return self.classifier(self.features(x))

images = np.load("data/images.npy").astype(np.float32)[:4]  # (4,1,24,24)
x = torch.from_numpy(images).to(device=device, dtype=dtype)
model = TinyLeNetGlyph().to(device=device, dtype=dtype)

with torch.no_grad():
    logits = model(x)  # a CrossEntropyLoss logitsot var, softmax nelkul

n_params = sum(p.numel() for p in model.parameters())
assert logits.shape == (4, 3), logits.shape
assert n_params == 30391, n_params
assert torch.isfinite(logits).all()

print("logits shape:", tuple(logits.shape))
print("parameter count:", n_params)
print("first-batch logits finite:", bool(torch.isfinite(logits).all()))

Ellenőrzés: a shape-nak (4,3)(4,3)-nek, a paraméterszámnak 3039130\,391-nek kell lennie; a Flatten a features kimenet (N,16,3,3)(N,16,3,3) lapításából (N,144)(N,144)-et ad. A seed a súlyok inicializálását rögzíti, de a konkrét logit-értékeket a futtatási naplónak kell rögzítenie; a könyv itt nem közöl előre nem mért számokat.

17.1.6 6. Feladatok F1–F5

F1 (fogalmi, nehézség 1). Magyarázd el, miért nem ugyanaz a művelet az eredeti LeNet-5 S2/S4 alulmintavételező rétege és a modern MaxPool2d(2,2). Adj egy konkrét, 2×2-es számpéldát, amelyen a maximum és a tanulható súlyozott összeg különböző viselkedését megmutatod; jelöld, melyik melyik.

F2 (levezetési, nehézség 2). Egy új változat specifikációja: input 1×1×24×241\times1\times24\times24; conv1: 6 csatorna, 5×5, stride 1, padding 0; MaxPool 2×2, stride 2; conv2: 16 csatorna, 3×3, stride 1, padding 1; MaxPool 2×2, stride 2; fc1: 400→120; fc2: 120→84; kimenet: 84→3. Vezesd le minden réteg kimeneti alakját és a teljes paraméterszámot. Mutasd meg, miért tartja meg a padding=1 a 10×10-es térbeli méretet a 3×3-as kernelnél.

F3 (implementációs, nehézség 1). Implementáld a TinyLeNetGlyph modult a szöveg 4. szakaszának specifikációja szerint úgy, hogy a paraméterszám 3039130\,391 legyen. Futtass rajta egy (2,1,24,24)(2,1,24,24)-es véletlen float32 tensort, és ellenőrizd a kimeneti alakot. A megoldáshoz add meg a futtatható kódot.

F4 (hibakeresési, nehézség 2). Az alábbi töredékben a második poolingréteg már szerepel, de a Linear(576,120) bemeneti méretét nem igazították hozzá. Lokalizáld az alakhibát, írd le a futtatási következményt, javítsd a kódot, és add meg azt az assertet, amellyel a javítást visszaellenőrzöd.

self.features = nn.Sequential(
    nn.Conv2d(1, 6, 5), nn.ReLU(), nn.MaxPool2d(2, 2),
    nn.Conv2d(6, 16, 5), nn.ReLU(), nn.MaxPool2d(2, 2),
)
self.fc = nn.Linear(16 * 6 * 6, 120)  # hibás: a bemenet valójában 16*3*3

F5 (kísérleti/transzfer, nehézség 3). Tervezz kísérletet annak eldöntésére, hogy a GlyphLabon tanított kis LeNet-típusú modell első konvolúciós rétegének szűrői nagyobb térbeli lokalitással bírnak-e, mint ugyanazon seed melletti véletlen inicializálású súlyok. Add meg a hipotézist, a scene_id-alapú felosztást, a baseline-t, a lokalitás kvantitatív definícióját, a választási protokollt és az érvényességi határt.

17.1.7 7. Megoldások és indoklás

F1 megoldás. Az eredeti SS-réteg a 2×2-es cella értékeit tanulható ww súllyal és bb eltolással összegzi, majd telítődő aktivációt alkalmaz; a max-pool ezzel szemben a maxmax-ot veszi, és nincs tanulható paramétere. Példa: X=(1230)X=\begin{pmatrix}1&2\\3&0\end{pmatrix}. A max-pool kimenete 33; egy w=0,5,b=0w=0{,}5,\ b=0 súlyozott összeg kimenete 0,5(1+2+3+0)=30{,}5(1+2+3+0)=3, ami itt véletlenül egyezik, ezért jobb ellenpélda: X=(11110)X=\begin{pmatrix}1&1\\1&10\end{pmatrix}-nél a max=1010, az átlagos összeg 13⋅0,5=6,513\cdot0{,}5=6{,}5. Az aktiváció és a gradiens útja is eltérő. Pontosabb formula a teljes [P01] cikkből ellenőrizendő.

F2 megoldás. A HoutH_{out}-képletet használjuk. C1: 24−5+1=2024-5+1=20, alak 6×20×206\times20\times20. P1: ⌊(20−2)/2⌋+1=10\lfloor(20-2)/2\rfloor+1=10, alak 6×10×106\times10\times10. C2: 10+2⋅1−3=910+2\cdot1-3=9, ⌊9/1⌋+1=10\lfloor 9/1\rfloor+1=10, tehát a padding éppen a kernel okozta 2 pixeles csökkenést kompenzálja: 2p=k−12p=k-1, ezért a térbeli méret 10×10 marad; alak 16×10×1016\times10\times10. P2: ⌊(10−2)/2⌋+1=5\lfloor(10-2)/2\rfloor+1=5, alak 16×5×516\times5\times5; flatten 400400. A paraméterek:

Összesen: 156+880+48120+10164+255=59575156+880+48\,120+10\,164+255=59\,575. Alakellenőrzés: a lapítás 16⋅5⋅5=40016\cdot5\cdot5=400 valóban fc1 bemenete; az osztályozó minden ponton NN-soros marad.

F3 megoldás. A teljes kód az 5. szakaszban megadott TinyLeNetGlyph; a kis különbség a batch méret. A modul után:

import torch
torch.manual_seed(20261020)
x = torch.randn(2, 1, 24, 24, dtype=torch.float32)
model = TinyLeNetGlyph()
out = model(x)
assert out.shape == (2, 3)
assert sum(p.numel() for p in model.parameters()) == 30391

A paraméterszám egzakt egész, ezért tűrésre nincs szükség; a shape-nál ugyanígy.

F4 megoldás. A második MaxPool2d(2,2) után a konvolúciós rész kimenete 16×3×316\times3\times3, vagyis lapítva 144144 elem. A hibás Linear(576,120) ezzel szemben 576576 bemeneti elemet vár, ezért (4,1,24,24)(4,1,24,24) batch esetén a továbbítás RuntimeError: mat1 and mat2 shapes cannot be multiplied kivételt eredményez. A javított rétegrész:

self.features = nn.Sequential(
    nn.Conv2d(1, 6, 5), nn.ReLU(), nn.MaxPool2d(2, 2),
    nn.Conv2d(6, 16, 5), nn.ReLU(), nn.MaxPool2d(2, 2),
)
self.fc = nn.Linear(16 * 3 * 3, 120)

Ellenőrzés a modul forward metódusában alkalmazott flatten után: x = torch.randn(4,1,24,24); h = self.features(x).flatten(1); assert h.shape == (4,144); assert self.fc(h).shape == (4,120). A teljes modell kimenete (4,3)(4,3).

F5 megoldásminta. Hipotézis: a tanított modell első rétegének szűrői kevésbé véletlenszerűen oszlanak el a 5×5-ös kernelben, mint a nem tanított baseline. Protokoll: a GlyphLab metadata.csv scene_id oszlopa szerint 120 train/30 val/30 test jelenetet rögzítünk; a modell csak az images.npy-t kapja. Baseline: azonos architektúra azonos seeddel inicializálva, tanítás nélkül. Változó tényező: a train spliten futtatott, például 20 epochos, Adam-léptetésű tanítás; a kiválasztáshoz val loss-t használunk, a testet nem érintjük. Lokalitás: minden 5×5-ös KK szűrőre

r(K)=∑(u,v):∥(u,v)−(2,2)∥∞≤1Ku,v2∥K∥F2,r(K)=\frac{\sum_{(u,v):\|(u,v)-(2,2)\|_\infty\le 1}K_{u,v}^2}{\|K\|_F^2},

vagyis a középső 3×3-as mag által hordozott energia aránya. A metrika a hat szűrő mediánja. A bizonytalanságbecsléshez több, jelenetszinten újramintázott tanítóhalmazon újratanított modell–baseline párt használunk; egyetlen rögzített modellnél a tesztjelenetek újramintázása nem változtatná a szűrőstatisztikát, ezért abból nem származna értelmes intervallum. Átadandó: a tanított és baseline mediánok nyers értékei, valamint az újratanítások páros különbségének intervalluma. Érvényességi határ: a GlyphLab három orientációt tartalmaz, ezért az eredmény nem igazol általános „élreprezentációs” állítást; a lokalitásnövekedés egyetlen toy-adathalmazra vonatkozik.

17.1.8 8. Tipikus tévedés ellenpéldával

A leggyakoribb tévedés, hogy egy modern, LeNet-szerű modellt automatikusan „az eredeti LeNet-5 reprodukciójának” neveznek. Ellenpélda: a fenti TinyLeNetGlyph 24×24-es bemenetet, ReLU-t, max-poolingot, teljesen konvolúciós C3-at és 3 osztályos logit-kimenetet használ; az eredeti LeNet-5 ezzel szemben 32×32-es bemenetet, telítődő aktivációt, tanulható súlyozású subsamplinget, C3-nál nem teljes csatornakötést és más kimeneti felépítést alkalmaz. Ha valaki a laborváltozat 3039130\,391 paraméterét az 1998-as cikk paraméterszámának állítaná be, téves összehasonlítást tenne, mert a C3-kötési tábla eltérése miatt a két háló súlymátrixa szerkezetileg sem azonos. A korrekt megfogalmazás: „LeNet-típusú, modernizált változat”.

Önellenőrzés: a HoutH_{out}-képletbe a padding, stride és kernel értékek visszahelyettesítése; a paraméterszámok kézi összegzése a sum(p.numel()) ellenőrzéssel szemben; annak ellenőrzése, hogy a lapítás mérete mindig a feature map térfogata. Korábbi fogalom visszahívása: a 16.1. Eltolási equivariance és invariance szerint a konvolúciós réteg eltolási ekvivarianciát ad, de invarianciát önmagában nem; a LeNet-ben az invariancia felé az alulmintavételező rétegek lépnek. Az 5.1. Esemény, feltétel és véletlen változó tanulsága szerint a végső softmax kimenet normalizált valószínűségeloszlás, a három logit önmagában nem az.

17.1.9 Rövid összefoglaló

A LeNet-5 a konvolúciós hálózatok történeti alapváltozata: a P01 tanulmány a kézírásos dokumentumfelismerésre vezette be. A réteggráf három komponenst kapcsol össze: térbeli keresztkorrelációs rétegeket, 2×2-es alulmintavételező rétegeket és affin osztályozófejet. Az eredeti LeNet-5 32×32 bemenetet, telítődő aktivációt, súlyozott subsamplinget, C3-nál részleges csatornakötést és más kimenetet használ; a mai könyvtárakban gyakori max-poolos, ReLU-s, teljes konvolúciós változat ettől eltér, ezért nem nevezhető pontos reprodukciónak. A laborban megvalósított TinyLeNet-Glyph 24×24-es GlyphLab-bemenetre épül, 30 391 tanulható paramétere van, és logitokat ad három osztályra.

17.1.10 Következő alkalmazás

A következő, 17.2. AlexNet és a kísérleti kontextus egység az AlexNet hálózatot vizsgálja: ott a méretnövekedés, a kétutas GPU-elrendezés és a ReLU-alapú gyorsabb tanítás hoz új tervezési döntést.

17.1.11 Felhasznált források