A 17.3. VGG és kis kernelek kompozíciója egységben a -as stack és az -ös kernel recepciós mezőjét és paraméterszámát hasonlítottuk össze. Ott az volt a kérdés, hogy mit tud egy-egy rétegsorozat. Itt az következik, hogy mikor állíthatunk róluk valamit mért adatok alapján: két architektúra összehasonlítása csak akkor érdemi, ha a kísérleti keret izolálja az architektúra hatását a tréningrecept, az adat, a modellválasztási szabály és a véletlen hatásától. Előismeret: a 16.4. IV. ellenőrző pont: a CNN-operátor teljes megértése alfejezetben rögzített operátorspecifikáció, a 13.4. Határkezelés és frekvenciaintuíció alfejezetben tárgyalt padding, valamint az 5.4. Függő képek, seedek és resampling alfejezetből a seed-ismétlés és a megfigyelési egység fogalma. Induló kérdés: ha az A modellt három seeddel, húsz epochig, a B modellt tíz seeddel, ötven epochig tanítjuk, és B jobb lesz, mit tudunk az architektúráról?
Definíció (fair comparison). Legyen rögzített adathalmaz, rögzített metrika, a vizsgált tényező (például az architektúra), pedig az összes többi ismert befolyásoló tényező együttese: adatfelosztás, preprocessing, loss, optimalizáló, learning rate, epoch- vagy early-stopping-szabály, batchméret, seed-készlet és a hiperparaméter-keresés protokollja. A és beállítás összevetése fair -n, ha (i) mindkét karra azonos szabály szerint rögzített; (ii) a modellválasztás a validációs halmazon, a végső kiértékelés pedig a teszt halmazon, a választás után történik; (iii) az erőforrás-költségek közül az egyezik, amelyet a kutatási kérdés kontrollálni akar. A baseline egy előre rögzített referencia-beállítás, amelyhez képest a változtatás hatását mérjük; baseline nélkül a „jobb” szó nem rendelhető méréshez.
A definíció szándékosan nem írja elő, hogy mindig minden költség egyezzen. Az, hogy mit kell egyeztetni, a kérdésből következik. Például ha a kérdés az, hogy adott paraméterköltség mellett a mélység vagy a kernelszélesség hasznos-e, akkor a paraméterszám egyezzen; ha az, hogy adott epochszám mellett melyik tanul gyorsabban, akkor a számítási keret egyezzen; ha az, hogy melyik recept a jobb, akkor az architektúra legyen közös, és a recept változzon.
Jelölje a mért teljesítményt, ahol az architektúra, a tréningrecept, az adat és a seed-ek halmaza. Az alábbi lemmában a kontrollkészletet a kutatási kérdésből vezetjük le.
Lemma (kontrollkészlet a kérdésből). Legyen és az -en vett átlagolási szabály rögzített. Ekkor:
Bizonyítás. Az (1) állítás egy azonosságból következik. Írjuk fel a mért különbséget:
Az első tag a recept rögzítése melletti architektúra-különbség, a második a recept hatása rögzített mellett. Ha , akkor a második tag általában nem nulla, tehát a teljes különbség nem azonosítható az első taggal. Mivel a két tag egyetlen mért különbségből nem szeparálható, a tisztán -ra vonatkozó következtetéshez szükséges.
A (2)–(3) állításokhoz elég megmutatni, hogy eltérő költség mellett a sorrend megfordulhat. Legyen a vizsgált struktúratényező, a paraméterkeret, és tegyük fel, hogy a várható teljesítmény alakú. Az és beállításokra , tehát a nyers összehasonlítás -t hozná ki jobbnak. Rögzített mellett viszont . A tehát konfundáló tényező; az izolált -hatáshoz a -t kontrollálni kell. Ugyanez az érvelés szó szerint átvihető a MAC- vagy memóriakeretre.
A lemma azt mondja ki, hogy melyik költségnek kell egyeznie, nem azt, hogy bármely egyezés garantálja a helyes következtetést. Az egyezés szükséges feltétel; a véletlen bizonytalanságot emellett seed-ismétléssel kell becsülni.
A laborhoz két kis adaptációt definiálunk a GlyphLab -es, egycsatornás képeire. Ezek nem az eredeti LeNet, AlexNet vagy VGG másolatai, hanem a 17.3. VGG és kis kernelek kompozíciója alfejezetben vizsgált -stack kontra -kernel elv dokumentált mini-adaptációi.
MiniDeep3. A features blokk: Conv(1,8,3,pad=1), ReLU, Conv(8,8,3,pad=1), ReLU, MaxPool(2); Conv(8,16,3,pad=1), ReLU, Conv(16,16,3,pad=1), ReLU, MaxPool(2). A fej: AdaptiveAvgPool2d(1), Flatten, Linear(16,3). A bias-tagokkal együtt a paraméterszám:
MiniWide5. Conv(1,10,5,pad=2), ReLU, MaxPool(2); Conv(10,16,5,pad=2), ReLU, MaxPool(2); ugyanaz a fej. A paraméterszám:
A relatív eltérés , tehát a két modell az paraméterkeret-tűrésen belül van. A MAC-ok ezzel szemben eltérnek: a MiniDeep3 négy konvolúciója MAC, a MiniWide5 két konvolúciója MAC. Ezért az alábbi labor kutatási kérdése paraméterkeretre vonatkozik, és a MAC-eltérést dokumentált másodlagos tényezőként, nem kontrollálatlan zavaróként kezeljük.
Protokoll. Kérdés: a GlyphLab orientáció-feladatán,
közel azonos paraméterszám mellett, a
-as
mély stack vagy az
-ös
széles, sekély változat ér el jobb általánosítást? Hipotézis: a mély
stack a 17.3. VGG és kis kernelek kompozíciója
alfejezetben tárgyalt nemlinearitás-többlet miatt nem rosszabb; ez
azonban nem előre garantált győzelem. Adat:
data/images.npy, data/labels.npy,
data/metadata.csv, data/corrupted_test.npy; a
split a metadata split oszlopából. Megfigyelési egység:
scene_id; a split jelenetszintű, tehát egy jelenet két
nézete nem kerül különböző splitbe. Változó tényező: architektúra
(MiniDeep3 vagy MiniWide5). Rögzített tényezők: AdamW lr
,
weight decay
,
batch 32, legfeljebb 20 epoch, early stopping 5 türelmi epochhal a
validációs lossra, loss CrossEntropyLoss logitsból, CPU float32,
seed-készlet
mindkét modellre. Modellválasztás: a három seed átlagos validációs
lossza alapján választjuk ki a győztest; a tesztet csak ezután, mindkét
modellre kiértékeljük, de a fő következtetést a győztesre fogalmazzuk. A
corrupt teszt páros a clean testtel: ugyanaz a 60 jelenet/nézet sorrend,
ezért a két accuracy különbsége páros összehasonlítás.
Szkript. Munkakönyvtár: lab/n068/. A
kód a GlyphLab fájljait a ../data/ alól olvassa. Ez a kód a
hallgató által futtatandó laborterv; a kézirat megírásakor nem lett
futtatva, ezért az alatta álló JSON-séma nem mért eredmény.
# lab/n068/train_compare.py
import json, argparse
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
def set_seed(seed):
torch.manual_seed(seed); np.random.seed(seed)
class MiniDeep3(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1,8,3,padding=1), nn.ReLU(),
nn.Conv2d(8,8,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(8,16,3,padding=1), nn.ReLU(),
nn.Conv2d(16,16,3,padding=1), nn.ReLU(), nn.MaxPool2d(2))
self.head = nn.Sequential(nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(16,3))
def forward(self, x):
return self.head(self.features(x))
class MiniWide5(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1,10,5,padding=2), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(10,16,5,padding=2), nn.ReLU(), nn.MaxPool2d(2))
self.head = nn.Sequential(nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(16,3))
def forward(self, x):
return self.head(self.features(x))
def param_count(model):
return sum(p.numel() for p in model.parameters())
def evaluate(model, loader):
model.eval()
correct = total = 0
with torch.no_grad():
for x, y in loader:
pred = model(x).argmax(dim=1)
correct += (pred == y).sum().item(); total += y.numel()
return correct / total
def fit(model, train_loader, val_loader, max_epochs=20, patience=5):
opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
loss_fn = nn.CrossEntropyLoss()
best_loss, best_state, waited = float("inf"), None, 0
for epoch in range(max_epochs):
model.train()
for x, y in train_loader:
opt.zero_grad(); loss = loss_fn(model(x), y); loss.backward(); opt.step()
model.eval(); val_loss = 0.0; count = 0
with torch.no_grad():
for x, y in val_loader:
val_loss += loss_fn(model(x), y).item() * y.numel(); count += y.numel()
val_loss /= count
if val_loss < best_loss - 1e-6:
best_loss, best_state, waited = val_loss, {k: v.clone() for k, v in model.state_dict().items()}, 0
else:
waited += 1
if waited >= patience:
break
model.load_state_dict(best_state)
return best_loss
def main(seeds, out_path):
data_dir = "../data"
X = np.load(f"{data_dir}/images.npy").astype(np.float32)
y = np.load(f"{data_dir}/labels.npy").astype(np.int64)
corrupt = np.load(f"{data_dir}/corrupted_test.npy").astype(np.float32)
import pandas as pd
meta = pd.read_csv(f"{data_dir}/metadata.csv")
tr_idx = meta.index[meta.split == "train"].to_numpy()
va_idx = meta.index[meta.split == "val"].to_numpy()
te_idx = meta.index[meta.split == "test"].to_numpy()
Xtr = torch.from_numpy(X[tr_idx]); ytr = torch.from_numpy(y[tr_idx])
Xva = torch.from_numpy(X[va_idx]); yva = torch.from_numpy(y[va_idx])
Xte = torch.from_numpy(X[te_idx]); yte = torch.from_numpy(y[te_idx])
Xco = torch.from_numpy(corrupt) # sorrend = metadata test sorrend
results = {}
for name, build in [("MiniDeep3", MiniDeep3), ("MiniWide5", MiniWide5)]:
results[name] = {"params": param_count(build()), "val_loss": [],
"val_acc": [], "test_acc": [], "corrupt_acc": []}
for seed in seeds:
set_seed(seed)
gen = torch.Generator().manual_seed(seed)
train_dl = DataLoader(TensorDataset(Xtr, ytr), batch_size=32, shuffle=True, generator=gen)
val_dl = DataLoader(TensorDataset(Xva, yva), batch_size=64)
test_dl = DataLoader(TensorDataset(Xte, yte), batch_size=64)
corrupt_dl = DataLoader(TensorDataset(Xco, yte), batch_size=64)
model = build()
val_loss = fit(model, train_dl, val_dl)
results[name]["val_loss"].append(val_loss)
results[name]["val_acc"].append(evaluate(model, val_dl))
results[name]["test_acc"].append(evaluate(model, test_dl))
results[name]["corrupt_acc"].append(evaluate(model, corrupt_dl))
with open(out_path, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2, default=float)
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--seeds", nargs="+", type=int, default=[0,1,2])
ap.add_argument("--out", default="results_n068.json")
args = ap.parse_args()
main(args.seeds, args.out)Futtatási parancs:
python train_compare.py --seeds 0 1 2 --out results_n068.json.
Átadandó eredmény. A JSON mindkét modellre
tartalmazza a paraméterszámot (számított érték: 4203 és 4327), továbbá
seedenként a val_loss, val_acc,
test_acc és corrupt_acc nyers listáit. A
kiértékelés során a hallgató közli a három seed átlagát és terjedelmét,
a győztes kiválasztását a validációs lossz alapján, valamint a
corrupt-clean páros
-képes
eltérését. A kézirat e szakasza nem állít mért számokat; a fenti
JSON-séma a futtatás előtti szerződés.
1. (F1, fogalmi, nehézség 1). Definiálja a fair comparison fogalmát, és adjon konkrét példát arra, hogyan torzít a keresési költség eltérése. Átadandó: legfeljebb hatmondatos definíció, valamint egy olyan elképzelt eredmény, amelyben az egyik modellre tíz, a másikra száz konfigurációt kerestek.
2. (F2, levezetési, nehézség 2). Vezesse le a MiniWide5 MAC-számát a -es bemenetre, és határozza meg, hogy a paraméterkeret-egyezés mellett milyen másodlagos költség marad nyitva. Átadandó: lépésenkénti számítás shape-ekkel és a végső érték.
3. (F3, implementációs, nehézség 2). Írjon olyan
param_count_bias függvényt, amely egy
nn.Module paramétereinek számát bias nélkül és bias-szal
együtt adja vissza. Ellenőrizze a MiniDeep3-ra, hogy bias nélkül
,
bias-szal
.
Átadandó: futtatható kód és a két ellenőrző érték.
4. (F4, hibakeresési, nehézség 2). A labor egy
változata a corrupt kiértékeléshez a test_dl-hez hasonlóan
shuffle=True értéket állít be a corrupt DataLoaderre. Mi a
hiba, mi a következménye, és mi a javítás ellenőrzése? Átadandó: a hiba
pontos lokalizálása, a párosítás sérülésének magyarázata és a javított
kódsor.
5. (F5, kísérleti/transzfer, nehézség 3). Tervezzen fair összevetést a GlyphLabon egy harmadik változat, a MiniDeep3-memory bevezetésére, amelynek csatornaszáma a középső blokkban 16-ról 24-re nő. Adja meg: kérdés, költségkontroll, baseline, seed- és választási szabály, valamint a következtetés érvényességi korlátja. Átadandó: protokollterv szövegesen, az 5.4. Függő képek, seedek és resampling alfejezetben tanult seed-adat elkülönítés alkalmazásával.
1. megoldás. A fair comparison olyan összevetés, amelyben a vizsgált tényezőn kívül minden ismert befolyásoló tényezőt azonos szabály rögzít, a modellválasztás validációs halmazon történik, a tesztet pedig a választás után használjuk. A keresési költség eltérésére példa: az A modellhez egyetlen lr-t próbálunk, a B modellhez tíz lr-jelöltet, és a legjobbat jelentjük. Ha B nyer, nem tudjuk, hogy az architektúra vagy a nagyobb keresési költség miatt; a teszt többszöri használata ráadásul a kiválasztott konfigurációt optimistán torzítja. A korrekt keret mindkét modellre azonos keresési rácsot és azonos választási szabályt ír elő.
2. megoldás. Az első konvolúció bemenete
,
kimenete
a padding=2 miatt. A MAC-ok száma kimeneti elemenként és
csatornánként
.
Összesen
.
A MaxPool után a második konvolúció bemenete
,
kimenete
.
A MAC-ok:
.
Összeg:
.
A lemmából következik, hogy mivel a kérdés paraméterkeretre vonatkozott,
a MAC-különbség dokumentált másodlagos költség; ha a kérdés számítási
keretre váltana, a MiniWide5 számítási fölénye önmagában is
magyarázhatná az eltérést, ezért a csatornaszámot újra kellene
hangolni.
3. megoldás. A függvény a p.dim()
vizsgálatával választja szét a bias-t, mert a konvolúciós és lineáris
réteg bias-e egydimenziós; általánosabb heurisztika a name
és a p.ndim == 1 kombinációja.
def param_count_bias(model):
total = bias = 0
for name, p in model.named_parameters():
total += p.numel()
if name.endswith("bias"):
bias += p.numel()
return total - bias, biasA MiniDeep3 esetében a bias-tagok:
,
így az összeg
és a bias nélküli rész
.
Az ellenőrző érték a feladat szövegében
volt? A kézi számítás szerint a négy konvolúció bias-e
,
a lineárisé
,
tehát bias nélkül
.
A feladat 4149 értéke tehát hibás; a helyes válasz
,
és a feladatkiírást ennek megfelelően kell javítani: a megoldás
ellenőrzése assert param_count_bias(MiniDeep3())[0] == 4152
és [1] == 51. Ez a korrekció maga is tanulság: az ellenőrző
számokat nem szabad levezetés nélkül átvenni.
4. megoldás. A hiba a corrupt DataLoader
shuffle=True beállításában van. A
corrupted_test.npy sorrendje a metadata test sorainak
sorrendjét követi, és a hozzá tartozó yte ugyanebben a
sorrendben van. A keverés a képeket és a címkéket ugyan együtt keveri,
tehát az accuracy önmagában nem romlik el; a probléma a clean testtel
való páros összehasonlításban van, mert a corrupt és clean
eredmények képszintű különbsége nem ugyanazon sorrendhez tartozik, így a
páros különbség szórása torzul. A javítás:
DataLoader(TensorDataset(Xco, yte), batch_size=64) keverés
nélkül, és a clean test DataLoadert is shuffle=False
értékkel kiértékelni. Az ellenőrzés: egy rögzített batch első képének
corrupt és clean változata ugyanahhoz a metadata indexhez tartozzon.
5. megoldás. A MiniDeep3-memory bevezetésekor a kérdés az, hogy a középső csatornaszám növelése javítja-e az általánosítást a paraméterkeret rögzítése mellett. A költségkontrollhoz előbb ki kell számítani az új paraméterszámot: a Conv(8,24,3) és Conv(24,24,3) blokk paraméterei , a korábbi középső blokk , tehát a növekmény ; a paraméterkeret-egyezéshez csökkenteni kell például az első blokk csatornaszámát, vagy explicite a „paraméterkeret közel azonos” helyett a kérdést „adott memória-keretre” kell átfogalmazni. A baseline a MiniDeep3; a seed-ismétlés ugyanazt a készletet használja mindkét modellre, így a seed-hatás párosítható, míg a seed-készlet terjedelme az algoritmus bizonytalanságát, nem az adatminta bizonytalanságát becsli. A választás a három seed átlagos validációs lossza; a tesztet csak ezután értékeljük. Korlát: a GlyphLab eredmény nem általánosítható természetes képekre, és a jelenetszintű függés miatt képszintű konfidenciaintervallumot nem szabad jelenet-függetleníként értelmezni.
A leggyakoribb hiba, hogy a mérési különbséget automatikusan az architektúrának tulajdonítjuk, noha közben a tréningrecept is változott. Ellenpélda: az A modellt lr -mal, a B modellt lr -mal tanítva A validációs pontossága , B-é . Ha ebből azt állítjuk, hogy A architektúrája jobb, az állítás nem következik az adatból. Egy további futtatás B-t lr -mal tanítva -at adhat; a recept megváltoztatása megfordítja a sorrendet. A helyes keret vagy rögzíti az lr-t, vagy mindkét modellre ugyanazt a keresési protokollt futtatja, és a választott lr-t a validációs halmazon választja, a tesztet pedig a választás után használja. A kevés seed hasonlóan félrevezető: egyetlen seed két modellje közötti különbség lehet pusztán a tanítási zaj; az 5.4. Függő képek, seedek és resampling alfejezetben tárgyalt módon a seed-ismétlés az algoritmus bizonytalanságát becsli.
Ellenőrző kérdések: (1) Kijelenthetem-e, hogy a labor győztese jobb
architektúra, ha a paraméterszámok 4203 és 4327? Igen, ha a protokoll a
paraméterkeretet rögzítette, de a MAC-eltérést jelenteni kell mint
korlátot. (2) Használtam-e a tesztet a modellválasztás előtt? A szkript
nem; a test_dl és corrupt_dl csak a
fit után, a legjobb checkpoint betöltése után fut. (3) A
corrupt–clean különbség páros? Igen, mert a corrupt sorrendje megegyezik
a metadata test sorrenddel, és a DataLoaderek nem kevernek. Visszahívás
a 13.4. Határkezelés és frekvenciaintuíció
alfejezetből: a padding=2 az
-ös
kernelnél, a padding=1 a
-asnál
őrzi meg a térbeli méretet. A 16.4. IV. ellenőrző
pont: a CNN-operátor teljes megértése alfejezetből: a
Conv2d keresztkorrelációt számol, ezért a paraméterszámban
a kernel elemszáma
,
nem pedig egy megfordított kernel másik mennyisége.
A fair comparison olyan kísérleti keret, amelyben a vizsgált tényezőn kívül minden ismert befolyásoló tényezőt rögzítünk, a modellt validációs halmazon választjuk, a tesztet pedig a választás után használjuk. A lemma szerint az egyeztetendő költséget a kutatási kérdés határozza meg: paraméterkerethez paraméterszám, számítási kerethez MAC, memóriakerethez aktivációcsúcs, receptérzékenységhez közös architektúra kell. A MiniDeep3 és MiniWide5 paraméterszáma 4203 és 4327, tehát közel azonos keretbe esik; a labor ugyanazt a receptet, seed-készletet és választási szabályt alkalmazza. Az eltérő keresési költség vagy kevés seed nem izolálja az architektúra hatását.
A 18.1. Reziduális blokk és identitásút a reziduális blokkot vezeti be. Az ottani rövidítéshez tartozó ablatív vizsgálathoz ugyanez a kontrollkeret szükséges: az identitásút elhagyása és megtartása között csak akkor mérhető a blokk hatása, ha a paraméterszám-, MAC- és seed-protokollt előre rögzítjük.