A 18.1. Reziduális blokk és identitásút alfejezetben levezettük a reziduális blokk rövidítését, a 18.3. Pre-activation, kis batch és alternatív normalizálás alfejezetben pedig a normalizáló rétegek tengelyválasztását és a pre-aktivációs elrendezést. Ebben az egységben azt a kérdést tesszük mérhetővé, amelyet a képlet önmagában nem dönt el: vajon a skip kapcsolat ténylegesen mérhető taníthatósági előnyt ad-e egy kis, ellenőrzött feladaton, vagy csak az adott recept szerencséje? Az ehhez szükséges eszköz az ablatív vizsgálat.
Előismeret: a 17.4. Architektúra összehasonlítása kontrollált keretben alfejezetből a kontrollált összehasonlítás kerete, a 6.4. Momentum és adaptív optimizálók alfejezetből az AdamW frissítési szabálya, a 18.3. Pre-activation, kis batch és alternatív normalizálás alfejezetből a BatchNorm train/eval viselkedése és a blokkok sorrendje. Induló becslés: ha a plain háló minden rétege kicsiny Jacobi-normával továbbítja a hibát, a korai rétegek gradiense eltűnik; a reziduális út ezt a szorzást megkerüli. A kérdés az, hogy ez a lokális érvelés a tényleges tanítási görbén is látszik-e.
Definíció (abláció). Az ablatív vizsgálat (ablation study) olyan kísérlet, amelyben egy architektúra vagy tanítási recept egyetlen komponensét távolítjuk el vagy cseréljük ki, a többi döntést változatlanul hagyva, és a változatokat ugyanazon a protokollon mérjük össze.
Definíció (matched recipe). A matched recipe olyan kontroll, amelyben a vizsgált tényezőn kívül minden tanítási döntés azonos: ugyanaz az adat és split, súlyinicializálás, seed-készlet, optimizer és súlycsökkentés, tanulási ráta és ütemezés, batchméret, epochszám, checkpoint-választási szabály és végső tesztelési eljárás. A 17.4. Architektúra összehasonlítása kontrollált keretben ottani lemmájának közvetlen alkalmazásaként a költségkeretet a kutatási kérdéshez kell igazítani: mivel a shortcut paramétermentes összeadás, a paraméterkeret itt automatikusan egyezik; emellett a számítási keret (MAC) is közel azonos, mert az összeadás elhanyagolható a konvolúciók mellett.
A vizsgált faktor tehát egy bináris változó:
A plain blokk
a reziduális blokk pedig
ahol a blokk bemeneti aktivációja, a blokk konvolúciós és normalizációs paramétereit tartalmazza, és a feltételek biztosítják, hogy az összeadás definiált legyen. A GlyphLab-hálóban a térbeli méret és a csatornaszám blokkon belül állandó, ezért az identitás-shortcut közvetlenül használható; vetítő-shortcutra most nincs szükség.
A kísérleti egység a GlyphLab scene_id-ja: egy jelenet két nézete ugyanabba a splitbe kerül, a bootstrap és az aggregálás ezért scene-szintű (5.4. Függő képek, seedek és resampling). A splitet a szerződés rögzíti: 120/30/30 jelenet train/val/test, osztályonként kiegyenlítve.
A kísérlet előtt rögzítjük a cáfolható hipotéziseket:
A H1 egy lokális lineáris modellből vezethető le. Legyen a háló mélysége , a blokk kimenete , a skalár veszteség gradiense pedig , amelynek alakja megegyezik alakjával. A láncszabály szerint
ahol a blokk Jacobi-mátrixa. A plain blokkra . Olyan pontokban, ahol a ReLU-k aktív mintázata rögzített, a blokk lokálisan affin leképezés; a konvolúció linearitása (15.1. Súly- és biasgradiens) miatt a Jacobi-norma a súlyokból számítható, de ehhez most elég annyi, hogy létezik felső korlát minden rétegre.
Lemma (eltűnő gradiens a plain láncban, lokális lineáris közelítés). Ha minden rétegre , akkor a plain hálóra
Bizonyítás. Indukcióval: , amit -szer alkalmazva kapjuk az állítást.
A reziduális blokkra , ezért differenciálható pontban
tehát a backward lépés
Az upstream gradiens tehát változatlanul megjelenik a kimenő gradiensben: az identitásút minden blokkban hozzáadja a head felől érkező hibát. A teljes szorzat
ahol a szorzat minden tagja tartalmazza az összeadandót. Ha , akkor ez a szorzat alakú; a head-gradiens nem kényszerül kizárólag kicsiny Jacobi-tényezőkön áthaladni. Ez azonban nem ad a reziduális normára erős alsó korlátot, mert a tagok elvben kiolthatják egymást. Az állítás státusza: lokális lineáris közelítésből levezetett heurisztikus mechanizmus, nem globális konvergencia- vagy optimalizálási tétel. A H1 mérési célja éppen az, hogy ezt a mechanizmust empirikusan ellenőrizze, nem pedig az, hogy általános tételként igazolja.
Legyen , skalár közelítésben differenciálható pontban, , és a head-gradiens . A plain láncra
a reziduálisra
Ugyanazokkal a blokkparaméterekkel a korai réteg gradiense a reziduális esetben több mint harmincszorosa a plain értéknek. A példa a lemmát illusztrálja, de nem helyettesíti: valódi hálóban a ReLU-k aktív mintázata lépésenként változik, és a Jacobi-norma feltételét nem ellenőriztük.
A labor a GlyphLab háromosztályos orientációfeladatát használja (13.1. Kép mint diszkrét többcsatornás jel, 25.1. Teljes képosztályozó adatfolyam). A bemenet float32 NCHW , a cél int64 ; a modell logitsot ad, CrossEntropyLoss előtt nincs softmax ([D07]).
Protokoll (a futás előtt rögzítendő). Változó
faktor:
.
Rögzített: GlyphLab split, seedek
,
AdamW
,
(6.4. Momentum és adaptív optimizálók), batch
64, 40 epoch, best checkpoint = legkisebb val loss, a teszt csak a
checkpoint kiválasztása után, a clean és corrupt párosítás szerint a 38.3. Végső teszt és páros korrupció alfejezethez
hasonlóan. A BatchNorm train alatt batch-statisztikát, eval alatt futó
buffert használ; a mérést model.eval() módban végezzük
([D12]).
Az architektúra: stem Conv(, , pad=1), öt blokk, head AdaptiveAvgPool2d(1) és Linear(). Minden blokk két Conv(, , pad=1, bias=False) réteget, két BatchNorm2d(16) réteget és ReLU-kat tartalmaz; a reziduális változat a második BN után hozzáadja a bemenetet, majd ReLU következik (poszt-aktivációs blokk a 18.3. Pre-activation, kis batch és alternatív normalizálás jelölésével). A paraméterszám mindkét változatra:
| Rész | Számítás | Paraméter |
|---|---|---|
| Stem Conv | 160 | |
| Egy blokk | 4640 | |
| Head Linear | 51 | |
| Öt blokkal összesen | 23 411 |
A labor kódja a lab/ munkakönyvtárban:
# lab/n072_resablation.py
# Futtatás: python lab/n072_resablation.py --data data --epochs 40 --seeds 0 1 2
import argparse, json, pathlib
import numpy as np
import torch
import torch.nn as nn
def make_block(ch, residual):
class Block(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(ch, ch, 3, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(ch)
self.conv2 = nn.Conv2d(ch, ch, 3, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(ch)
self.residual = residual
def forward(self, x):
h = self.bn1(self.conv1(x)).relu()
h = self.bn2(self.conv2(h))
if self.residual:
h = h + x
return h.relu()
return Block()
class Net(nn.Module):
def __init__(self, blocks=5, ch=16, residual=True, num_classes=3):
super().__init__()
self.stem = nn.Conv2d(1, ch, 3, padding=1)
self.blocks = nn.Sequential(*[make_block(ch, residual) for _ in range(blocks)])
self.head = nn.Sequential(
nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(ch, num_classes))
def forward(self, x):
return self.head(self.blocks(self.stem(x).relu()))
def grad_norm(modules):
sq = 0.0
for m in modules:
for p in m.parameters():
if p.grad is not None:
sq += p.grad.detach().float().pow(2).sum().item()
return float(np.sqrt(sq))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--data", type=pathlib.Path, default="data")
ap.add_argument("--epochs", type=int, default=40)
ap.add_argument("--seeds", nargs="+", type=int, default=[0, 1, 2])
ap.add_argument("--device", default="cpu")
args = ap.parse_args()
torch.manual_seed(20261020) # adatbetöltés determinizmusa
imgs = np.load(args.data / "images.npy").astype(np.float32)
labels = np.load(args.data / "labels.npy").astype(np.int64)
meta = np.genfromtxt(args.data / "metadata.csv", delimiter=",", dtype=None,
names=True, encoding="utf-8")
split = meta["split"]
X = torch.from_numpy(imgs)
y = torch.from_numpy(labels)
device = torch.device(args.device)
loss_fn = nn.CrossEntropyLoss()
report = {}
for seed in args.seeds:
torch.manual_seed(seed)
train_idx = [i for i, s in enumerate(split) if s == "train"]
val_idx = [i for i, s in enumerate(split) if s == "val"]
# egyszerű batch-képzés görgetéssel, a labban saját DataLoader is használható
history = []
for residual in (False, True):
model = Net(residual=residual).to(device)
opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
best_val = float("inf")
for ep in range(args.epochs):
model.train()
order = torch.randperm(len(train_idx))
for st in range(0, len(train_idx), 64):
idx = order[st:st+64]
xb, yb = X[train_idx[idx]].to(device), y[train_idx[idx]].to(device)
opt.zero_grad()
loss = loss_fn(model(xb), yb)
loss.backward()
if ep == 0 and st == 0:
g0 = grad_norm([model.stem, model.blocks[0]])
gL = grad_norm([model.blocks[-1], model.head])
history.append({"step": 0, "stem_first_block_norm": g0,
"last_block_head_norm": gL})
opt.step()
model.eval()
with torch.no_grad():
xv, yv = X[val_idx].to(device), y[val_idx].to(device)
val_loss = loss_fn(model(xv), yv).item()
if val_loss < best_val:
best_val = val_loss
report[f"seed{seed}_{'residual' if residual else 'plain'}"] = {
"best_val_loss": best_val, "first_step_norms": history}
out = args.data.parent / "n072_report.json"
out.write_text(json.dumps(report, indent=2))
print(out)
if __name__ == "__main__":
main()A kód szándékosan minimális: a DataLoader bevezetése és
a legjobb checkpoint tényleges mentése, valamint a tesztkiértékelés a 11.4. Egy teljes PyTorch tanítási iteráció és 12.2. Checkpoint és folytathatóság receptjét
követi. A labor átadandó eredménye: a
n072_report.json nyers epochonkénti és lépésenkénti adatai,
valamint a tesztkiértékelés a kiválasztott checkpointokon. Az alábbi
mezők szemléltető adatok, nem mérési jegyzőkönyv:
{"seed0_plain": {"best_val_loss": 0.41, "first_step_norms":
[{"step": 0, "stem_first_block_norm": 0.017, "last_block_head_norm": 5.2}]},
"seed0_residual": {"best_val_loss": 0.28, "first_step_norms":
[{"step": 0, "stem_first_block_norm": 0.17, "last_block_head_norm": 5.1}]}}A tényleges futást a tanulói környezetben kell elvégezni; az itt megadott számok nem tekinthetők mért eredménynek.
Az értékelés három szinten történik. (1) A H1-et a
stem_first_block_norm mezők arányával ellenőrizzük; a
várakozás a szemléltetett irány, de a mérés lehet ellentétes is. (2) A
H2-höz a seedek feletti best-val-loss eloszlást hasonlítjuk össze, nem a
legjobb seedet válogatjuk. (3) A végső tesztkiértékelés után közöljük a
clean teszt pontosságát és szükség esetén a corrupt páros
különbséget.
A negatív eredmény kifejezetten megengedett: ha a GlyphLab feladat 24×24 pixelen öt blokkal is könnyű, a két változat val lossza nem különül el. Ez nem cáfolja a [P06] nagyléptékű eredményét, és nem igazolja, hogy a skip minden feladaton hasznos; mindössze annyit állít, hogy ezen az adaton, ezzel a recepttel a különbség nem kimutatható. A következtetés érvényességi köre ezért a protokollal együtt rögzített: GlyphLab, 360 kép, 23 411 paraméter, CPU, három seed.
A leggyakoribb hiba az, hogy a kis ablatív kísérlet eredményét az eredeti ResNet-publikáció reprodukciójaként vagy általános architektúraigazságként olvassuk. A [P06] olvasott 3.1–3.2 szakasza a blokkot definiálja; a cikk állításai azonban ImageNet/COCO léptékű, nagyságrendekkel nagyobb hálókra és adatokra vonatkoznak. Ellenpélda: ha a fenti laborban a plain háló véletlenül jobb val losszt ér el, az nem azt jelenti, hogy „a skip kapcsolat hibás”, hanem hogy a mérési környezetben a H2 nem igazolódott. A skála és az adat különbözősége miatt a két állítás nem ütközik.
F1 (fogalmi, nehézség 1). Magyarázza el, miért nem ablatív vizsgálat, ha a plain hálót -mal, a reziduálist pedig -szal tanítjuk, és közben azt állítjuk, hogy a skip gyorsította a tanulást. Adjon egy olyan ellenőrzést, amely ezt a hibát kizárja.
F2 (levezetési, nehézség 2). Számítsa ki a fenti labor hálójának paraméterszámát, és igazolja, hogy a plain és reziduális változat paraméterkerete egyezik. Ezután a skalár példában mellett adja meg a plain és a reziduális értékét, és ellenőrizze a lemma exponenciális becslését.
F3 (implementációs, nehézség 2). Írjon egy
norm_probe függvényt, amely egy Net modellen
az első forward-backward lépés után visszaadja a stem, az első blokk, az
utolsó blokk és a head gradiensnormáit. Az input alakja
legyen, dtype float32, device CPU; a függvény a modell paraméterein
iterál, és None gradiensre
-t
ad.
F4 (hibakeresési, nehézség 2). A laborváltozat korai
verziója a --residual kapcsolót beolvasta, de a
Net(residual=False) hívás helyett mindig
Net(residual=True)-ot konstruált. Mi a hiba következménye a
H2 kiértékelésére, és milyen naplóbejegyzéssel lehet ezt a hiba
detektálása után visszamenőleg kimutatni?
F5 (kísérleti/transzfer, nehézség 3). Tervezzen depth-sweep kísérletet 3, 5 és 7 blokkal annak eldöntésére, hogy a skip előnye a mélységgel nő-e a GlyphLabon. Adja meg a nullhipotézist, a rögzített erőforráskeretet, a metrikát, a seed- és scene-szintű kiértékelést, valamint a következtetés legalább egy érvényességi korlátját.
F1 megoldás. Az eltérő tanulási ráta két faktor
együttes változtatása, ezért a mért különbséget nem lehet kizárólag a
skipnek tulajdonítani. Az ellenőrzés: ugyanazt a seed-készletet és AdamW
konfigurációt mindkét ágon futtatjuk, a kód pedig a manifestbe írja az
lr, a residual és a modell-paraméterszám
értékét; a manifestet a kiértékelés előtt összevetjük.
F2 megoldás. A stem: . Egy blokkban a Conv bias=False, ezért súlyonként, két konvolúció ; a két BatchNorm2d párja ; összesen blokkonként . Öt blokk , a head . Összesen . A shortcut add-nak nincs paramétere, tehát a budget egyezik. A skalár példában plain: ; reziduális: . A lemma a plain esetre , , az egyenlőség teljesül, mert a példa lineáris.
F3 megoldás.
def norm_probe(model, xb, yb):
loss_fn = torch.nn.CrossEntropyLoss()
model.train()
opt = torch.optim.AdamW(model.parameters(), lr=1e-3)
opt.zero_grad()
loss_fn(model(xb), yb).backward()
def norm(modules):
s = 0.0
for m in modules:
for p in m.parameters():
if p.grad is not None:
s += p.grad.detach().pow(2).sum().item()
return s ** 0.5
return {
"stem": norm([model.stem]),
"first_block": norm([model.blocks[0]]),
"last_block": norm([model.blocks[-1]]),
"head": norm([model.head]),
}
x = torch.randn(2, 1, 24, 24, dtype=torch.float32, device=torch.device("cpu"))
y = torch.tensor([0, 1], dtype=torch.int64)
model = Net(residual=True)
print(norm_probe(model, x, y))Alakellenőrzés: a bemenet NCHW ; a head logitja , a CrossEntropyLoss int64 célt vár. A kimeneti gradiensek skalárok, így a visszaadott értékek mindig nemnegatív valós számok.
F4 megoldás. Ha mindkét ág reziduális, a H2
összehasonlítás triviálisan azonos eloszlást mér, és a plain-ként
naplózott eredmény valójában a reziduális modellé. A naplójavítás: a
konstrukciókor a config JSON-ba be kell írni a residual
kapcsoló tényleges értékét, és minden futás elején kiírni a modell
state_dict kulcsainak számát és a blokkok
Block.residual attribútumait. Visszaellenőrzés: ha a
konfiguráció residual=false, de a blokkok attribútuma
True, a hiba egyértelműen kimutatható a regenerált
manifestből.
F5 megoldás. Nullhipotézis: a skip nélküli és a skipes háló best-val-loss különbsége nem változik a blokkszám függvényében. Rögzített keret: csatorna, AdamW , három seed, 40 epoch, batch 64; a paraméterszám a mélységgel nő, ezért a kérdéshez a mélységi keret a releváns, és a MAC-értéket blokkonként expliciten naplózni kell. Metrika: a seedekre átlagolt best-val-loss különbség és a clean test accuracy; a bizonytalanság scene-szintű bootstrap CI a val predikciókra. A tesztet csak a három mélység és a két skip-változat best checkpointjainak kiválasztása után értékeljük. Érvényességi korlát: a GlyphLab kis mérete miatt a 7 blokkos háló is csak 32 ezer paraméteres marad, így az eredmény nem vihető át mély ImageNet-osztályú hálókra.
A képletekben az indexhatárok ellenőrizve:
,
a head felől érkező gradiens. Az add a reziduális blokkban
csak akkor definiált, ha a csatorna- és térbeli alak egyezik; a laborban
a stride 1 és a pad 1 ezt biztosítja. A plain és reziduális
paraméterszám levezetése a bias=False konvenciót követi; a BN
paraméterei mindkét ágban azonosak. A gradiensnormák a paraméterek
L2-normái, a reduction sum a paraméterek négyzetösszege felett; a loss
átlagolása a batchre vonatkozik, a gradiensnorma mérése ebből a skálából
származó torzítást hordozhat, ezért a labornak azonos batchméretet kell
használnia. Visszahívás a 17.4. Architektúra
összehasonlítása kontrollált keretben alfejezetből: az egyeztetendő
keretet a kérdéshez választjuk; a 18.3.
Pre-activation, kis batch és alternatív normalizálás alfejezetből: a
pre-aktivációs blokk a shortcutra nem tesz normalizálást; a 6.4. Momentum és adaptív optimizálók alfejezetből:
az AdamW súlycsökkentése decoupled, ezért az L2-tagot nem a lossba
írjuk.
Az ablatív vizsgálat egyetlen tényezőt változtat, a matched recipe minden más döntést rögzít: adat, split, seed, optimizer, ütemezés, checkpoint- és tesztszabály. A skip kapcsolat hatását a plain és a reziduális blokk azonos paraméterkeretű összehasonlításával izoláljuk. A backward levezetés szerint a plain lánc a Jacobi-normák szorzatával skáláz, a reziduális láncban az identitás az upstream gradienst változatlanul továbbítja. A labor ötblokkos GlyphLab-hálón méri a best-val-loss eloszlást és az első lépés gradiensnormáit; a mérési állítás érvényessége erre a kis környezetre korlátozódik, és a negatív eredmény is közölhető.
A 19.1. Depthwise separable és pointwise réteg a depthwise separable konvolúciót vezeti be: ott ismét ablációval választjuk szét a spatial és a pointwise rész hatását, és a most bevezetett matched-recipe naplózást használjuk a paraméter/MAC-keret igazolására.