Ez a VI. ellenőrző pont: a 24.1.–24.3. hibadiagnózis- és profilereszközeit egyetlen, átadható csomaggá zárjuk. A cél nem új réteg tanítása, hanem annak bizonyítása, hogy a hallgató az adatot, tanítási receptet, mérési protokollt, metrikákat és hibakezelést együtt tudja lektorálható módon kezelni. Előismeret: a 24.2. CPU/GPU profiling és helyes időmérés wall time, warmup, adatbetöltési és memóriafogalmai, a 23.4. Hibaszeletek és alkalmazási határ szelet- és páros tesztelési szerződése, a 20.4. V. ellenőrző pont: indokolt CNN-választás architektúra-döntési lapja, valamint a 12.4. III. ellenőrző pont: önállóan tanuló kis háló checkpoint- és gradiensegyeztetési eljárása.
Értékelés. A közös 0–4 rubrikát, a PASS-szabályt és a hibatípusonkénti visszautakat az 1.4. Tanulási diagnózis és kísérleti napló „Tanulási főút és ellenőrző pontok” szakasza adja meg; itt csak az eltérés: a riport futási jegyzéke itt a fő leadandó; a súlypont a D3 és a D4.
Induló kérdés: egy CPU-n tanított, 2979 paraméteres kis CNN vajon „megbízhatóbb-e”, mint egy GPU-n tanított nagyobb háló? Becslés: a paraméterszám önmagában nem megbízhatósági mérce; először a modell loss- és alakszerződését, azután a kísérleti protokollt kell ellenőrizni. A GPU jelenléte gyorsíthat, de nem igazol helyes modellválasztást.
A laborhoz a 20.4. V. ellenőrző pont: indokolt CNN-választás döntési lapjából a következőket választjuk: saját, kisméretű classic CNN (scratch), CPU-erőforrás, GlyphLab-adat. A modell:
A pontos alak- és paraméterszerződés:
| Réteg | Bemeneti alak | Kimeneti alak | Paraméterszám |
|---|---|---|---|
| Conv1: 1→8, , pad 1 | |||
| ReLU, MaxPool | 0 | ||
| Conv2: 8→16, , pad 1 | |||
| ReLU, MaxPool | 0 | ||
| Flatten | 0 | ||
| Linear: 576→3 |
Összesen paraméter. A kötelező NCHW-sorrend miatt minden térbeli alakot -ként adunk meg; a batchméret kívül áll.
A tanítási loss-szerződés a [D07] keretjegyzet szerint: a modell
logitsot ad, a cél int64 osztályindex, nn.CrossEntropyLoss
előtt nem alkalmazunk külön softmaxot. Legyen
a logitmátrix és
a cél; a mean reduction veszteség
A softmaxos nevező minden -re az összes osztályon összegez; a célindex csak a számlálóban jelenik meg. A kimenet skalár.
Analitikus kontroll (lemma). Ha , akkor tetszőleges célra .
Levezetés: minden -re , tehát a softmax-kimenet
Így minden megfigyelésre , és a batchátlag ugyanennyi. GlyphLab esetén , tehát a kontrollérték . Ezt a labor sanity checkje numerikusan, abszolút tűréssel ellenőrzi.
A lemma csak a loss-modult teszteli, a modellszerkezetet nem. Ezért a labor tartalmaz egy második, zero-init alakkontrollt: a modell minden súlyát és biasát nullára állítjuk. Ekkor minden Conv-réteg kimenete azonosan nulla tetszőleges bemenetre, mert a konvolúció lineáris része nulla, a bias nulla; ezt ReLU, MaxPool és Flatten nullán változatlanul hagyja; a Linear-réteg pedig -t ad. Ezért a modell kimenete alakú, azonosan nulla. Ez a kontroll a teljes forward alakszerződést bizonyítja, de nem bizonyítja a tanulás működését: a nulla inicializálás nem töri meg a szimmetriát, ezért tanításra nem használjuk. A labor a kontroll után a rögzített seeddel generált alapértelmezett állapotot automatikusan visszaállítja.
A tanításhoz a torch.manual_seed(20261020) hívással
rögzítjük a véletlensúly-inicializálást. A 21.1.
Inicializálás és varianciaterjedés alfejezetben levezetett
varianciafeltételt itt csak visszahívjuk: ReLU előtti konvolúciónál a
cél gyakorlat az, hogy a súly varianciája
nagyságrendű legyen, ahol
A mi modellünkben az első Conv , a másodiké , a lineáris rétegé . A labor a könyvtár alapértelmezett inicializálását használja, és a gyakorlati ellenőrzést nem a kezdeti súlyokra, hanem arra alapozza, hogy a seed, az architektúra és a betöltött checkpoint együtt megismételhető legyen. A [D02] szerint az azonos seed nem garantál platformok és verziók közötti bitazonos egyezést; ezért a jegyzőkönyv a seedet, a könyvtárverziót és az adatmanifesztet is rögzíti. A seed tehát szükséges, de nem elégséges hordozhatósági garancia.
A labor AdamW-t használ
decoupled weight decayjel. Az ütemező StepLR,
step_size=10 és gamma=0.5 paraméterrel,
azaz
A 20 epochos futásban az 1–10. epoch lépésköze , a 11–20. epoché . Az ütemezés heurisztika: a viszonylag nagy kezdeti ráta gyors felfutást, a csökkentés kisebb validációs ingadozást céloz. Hogy az ütemezés valóban ezt teszi-e, nem elméleti állítás, hanem a validációs loss epochonkénti naplója dönti el; a labor minden epoch falidejét és veszteségét menti.
Az augmentáció definíciója szigorú:
olyan címkemegőrző transzformáció, amelyre a feladat értelmezése szerint
teljesül. A GlyphLab adatszerződés erre konkrét korlátot ad: a
-os
forgatás a vertical és horizontal osztályt
felcseréli; a diagonal osztály csak az egyik ferde irányt
tartalmazza, ezért tükrözés vagy forgatás új célértelmezést igényel.
Ezért a geometriai augmentáció ebben az alapkísérletben
nem címkemegőrző, és nem használjuk.
Címkemegőrző lehetőség az intenzitástérbeli perturbáció, például valamely mellett, mert az alakzat orientációját geometriailag nem változtatja. A jelen ellenőrző labor mégis augmentáció nélkül fut: az ellenőrzőpont feladata a minimális, reprodukálható alapvonal rögzítése. Az augmentáció hatását az F5 feladat külön kísérletként tervezi, hogy a két változtatás ne keveredjen.
A főút két tesztkörnyezetet különböztet meg: a clean tesztet és a
data/corrupted_test.npy fájlt, amely ugyanazon 60
scene/view pár zajos, fényességgradienssel módosított változata. A
corrupt fájl sorrendje a metadata teszt sorainak sorrendje; ezért a
célok páronként egyeznek. A labor három fő metrikát rögzít:
clean_acc: a clean teszt 60 képén mért pontosság;corrupt_acc: a corrupt teszt ugyanazon 60 páron mért
pontossága;matched_agreement: azon párok aránya, ahol a
befagyasztott modell ugyanazt az osztályt adja a clean és corrupt
képre.A 23.4. Hibaszeletek és alkalmazási határ
alfejezetben kimondott elvet követjük: a corrupt tesztet kizárólag a
legjobb, validációs loss alapján kiválasztott checkpoint betöltése után
értékeljük; utólagos ráhangolás nincs. Az osztályszintű bontást a
jegyzőkönyv per_class_clean és
per_class_corrupt mezőkben tárolja, mert a 60 képes
összpontosság elfedi az osztályszintű hibakoncentrációt. A scene-szintű
bootstrapet itt nem számoljuk; a jelen labor egyetlen seeddel jellemzi a
kiválasztott receptet, és nem becsli a training-seedek szórását.
A CPU-főúton nincs aszinkron GPU-ütemező, ezért a 24.2. CPU/GPU profiling és helyes időmérés
CUDA-szinkronizációs szabálya nem lép be: a
time.perf_counter() hívások közvetlenül a processz
falidejét mérik. Az első epochot warmupként kezeljük, mert a
fájlrendszer-gyorsítótár, a szálindítás és a Python-importok
torzíthatják; a jegyzőkönyv minden epoch falidejét külön menti, így a
steady-state elkülöníthető. A labor CPU-n, workers=0
DataLoaderrel fut, ezért az adatbetöltés a fő szálon történik; a labor
egyetlen next(iter(train_loader)) hívással méri az első
batch betöltésének falidejét is. A batch szintű aktivációmemória
becslése az alakszerződésből adódik:
esetén a legnagyobb köztes tensor a
térkép,
float32 elem. A torch.profiler részletes operátorbontását
ez a CPU-labor nem használja; itt a mérési protokoll és a falidő
összehasonlíthatósága a cél.
A checkpoint minimális szerződése a következő mezőket tartalmazza:
model: a state_dict, amely a betöltő
oldalon deklarált, azonos architektúrához tartozik;config: modellnév, csatornaszám, osztályszám, bemeneti
alak és seed;epoch, val_loss,
val_acc;data_manifest mutató a
data/manifest.json fájlra;parameter_count: betöltés utáni
konzisztenciaellenőrzéshez.A betöltés torch.load(..., weights_only=True) módon,
majd model.load_state_dict(...) strict módban történik. A
strict mód garantálja a kulcskészlet és alakok egyezését, de a
hibakezelés akkor teljes, ha ezután egy rögzített bemeneti batchre a
kimeneti alakot és a végességet is ellenőrizzük. A checkpoint önmagában
nem helyettesíti a seedet, az adatverziót és a tanítási konfigurációt;
ezért a labor ezeket a runlog.json fájlban együtt
menti.
Státusz. E kézirat írásakor a kódot nem futtattuk,
ezért a labor numerikus kimenetét nem állítjuk mért eredménynek. Az
alábbi kód a runlog.json futásjegyzéket írja; a tényleges
számok a hallgató helyi futtatásából származnak. A kódban szereplő
0.0 értékű JSON-mező a séma illusztrációja.
Munkakönyvtár: lab/. Futtatás:
cd lab && python n096_checkpoint.py# lab/n096_checkpoint.py
from pathlib import Path
import json, time, math
import numpy as np
import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset
BASE = Path(__file__).resolve().parent
DATA = BASE / "data"
SEED = 20261020
DEVICE = torch.device("cpu")
DTYPE = torch.float32
EPOCHS = 20
BATCH = 16
LR = 1e-3
WEIGHT_DECAY = 1e-4
STEP_SIZE = 10
GAMMA = 0.5
K = 3
torch.manual_seed(SEED)
np.random.seed(SEED)
class TinyCNN(nn.Module):
def __init__(self, in_ch=1, num_classes=K):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(in_ch, 8, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(8, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Flatten()
)
self.fc = nn.Linear(16 * 6 * 6, num_classes)
def forward(self, x):
return self.fc(self.features(x))
def count_params(model):
return sum(p.numel() for p in model.parameters())
def evaluate(model, loader, criterion=None):
model.eval()
total_loss = 0.0
correct = 0
n = 0
with torch.inference_mode():
for xb, yb in loader:
xb = xb.to(DEVICE, dtype=DTYPE)
yb = yb.to(DEVICE)
logits = model(xb)
if criterion is not None:
total_loss += criterion(logits, yb).item() * xb.shape[0]
correct += (logits.argmax(dim=1) == yb).sum().item()
n += xb.shape[0]
return correct / n, (total_loss / n if criterion is not None else None)
def predict_all(model, loader):
model.eval()
preds, ys = [], []
with torch.inference_mode():
for xb, yb in loader:
xb = xb.to(DEVICE, dtype=DTYPE)
yb = yb.to(DEVICE)
preds.append(model(xb).argmax(dim=1).cpu().numpy())
ys.append(yb.cpu().numpy())
return np.concatenate(preds), np.concatenate(ys)
def per_class_acc(preds, labels):
out = []
for c in range(K):
mask = labels == c
out.append(float(np.mean(preds[mask] == c)) if mask.sum() else None)
return out
def main():
criterion = nn.CrossEntropyLoss()
# 1) loss-sanity: zero logits -> ln K
z0 = torch.zeros(4, K, dtype=DTYPE, device=DEVICE)
y0 = torch.tensor([0, 1, 2, 0], device=DEVICE)
l0 = criterion(z0, y0).item()
assert abs(l0 - math.log(K)) < 1e-6, (l0, math.log(K))
print(f"[sanity] zero-logit loss = {l0:.6f} (expected {math.log(K):.6f})")
model = TinyCNN().to(DEVICE, dtype=DTYPE)
# 2) zero-init forward contract
x_dummy = torch.randn(2, 1, 24, 24, dtype=DTYPE, device=DEVICE)
default_state = {k: v.detach().clone() for k, v in model.state_dict().items()}
with torch.no_grad():
for p in model.parameters():
p.zero_()
z_zero = model(x_dummy)
assert z_zero.shape == (2, K) and torch.allclose(z_zero, torch.zeros_like(z_zero)), z_zero
model.load_state_dict(default_state)
print(f"[sanity] zero-init output {tuple(z_zero.shape)}, all zero: {bool((z_zero == 0).all())}")
# 3) adat a metadata splitje szerint
meta = np.loadtxt(DATA / "metadata.csv", delimiter=",", dtype=str, skiprows=1)
indices = meta[:, 0].astype(np.int64)
split = meta[:, 4]
images = np.load(DATA / "images.npy").astype(np.float32)
labels = np.load(DATA / "labels.npy").astype(np.int64)
def make_loader(split_name, shuffle=False):
idx = indices[split == split_name]
x = torch.from_numpy(images[idx]).to(DEVICE, dtype=DTYPE)
y = torch.from_numpy(labels[idx]).to(DEVICE)
ds = TensorDataset(x, y)
gen = torch.Generator().manual_seed(SEED) if shuffle else None
return DataLoader(ds, batch_size=BATCH, shuffle=shuffle, generator=gen)
train_loader = make_loader("train", shuffle=True)
val_loader = make_loader("val")
test_loader = make_loader("test")
t = time.perf_counter()
next(iter(train_loader))
data_load_s = time.perf_counter() - t
optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=STEP_SIZE, gamma=GAMMA)
run = {
"config": {"seed": SEED, "device": str(DEVICE), "dtype": str(DTYPE),
"epochs": EPOCHS, "batch": BATCH, "lr": LR,
"weight_decay": WEIGHT_DECAY,
"schedule": {"step_size": STEP_SIZE, "gamma": GAMMA}},
"parameter_count": count_params(model),
"data_load_s": data_load_s,
"epochs": []
}
best = {"val_loss": float("inf")}
total_start = time.perf_counter()
for ep in range(1, EPOCHS + 1):
model.train()
t0 = time.perf_counter()
total_loss = 0.0
n = 0
for xb, yb in train_loader:
xb = xb.to(DEVICE, dtype=DTYPE)
yb = yb.to(DEVICE)
optimizer.zero_grad(set_to_none=True)
loss = criterion(model(xb), yb)
loss.backward()
optimizer.step()
total_loss += loss.item() * xb.shape[0]
n += xb.shape[0]
train_loss = total_loss / n
val_acc, val_loss = evaluate(model, val_loader, criterion)
scheduler.step()
wall = time.perf_counter() - t0
run["epochs"].append({"epoch": ep, "train_loss": train_loss,
"val_loss": val_loss, "val_acc": val_acc,
"wall_s": wall})
print(f"epoch {ep:02d} train {train_loss:.4f} val {val_loss:.4f} acc {val_acc:.4f} {wall:.3f}s")
if val_loss < best["val_loss"]:
best = {"epoch": ep, "val_loss": val_loss, "val_acc": val_acc}
torch.save({
"model": model.state_dict(),
"config": {"name": "TinyCNN", "in_ch": 1, "num_classes": K,
"input_shape": [1, 24, 24], "seed": SEED},
"epoch": ep,
"val_loss": val_loss,
"val_acc": val_acc,
"data_manifest": "data/manifest.json",
"parameter_count": count_params(model)
}, BASE / "best.pt")
ckpt = torch.load(BASE / "best.pt", map_location="cpu", weights_only=True)
model.load_state_dict(ckpt["model"])
model.eval()
clean_preds, clean_ys = predict_all(model, test_loader)
clean_acc = float(np.mean(clean_preds == clean_ys))
corrupt_np = np.load(DATA / "corrupted_test.npy").astype(np.float32)
test_idx = indices[split == "test"]
corrupt_x = torch.from_numpy(corrupt_np).to(DEVICE, dtype=DTYPE)
corrupt_y = torch.from_numpy(labels[test_idx]).to(DEVICE)
corrupt_loader = DataLoader(TensorDataset(corrupt_x, corrupt_y),
batch_size=BATCH, shuffle=False)
corrupt_preds, corrupt_ys = predict_all(model, corrupt_loader)
corrupt_acc = float(np.mean(corrupt_preds == corrupt_ys))
matched_agreement = float(np.mean(clean_preds == corrupt_preds))
run["best"] = best
run["test"] = {
"clean_acc": clean_acc,
"corrupt_acc": corrupt_acc,
"matched_agreement": matched_agreement,
"per_class_clean": per_class_acc(clean_preds, clean_ys),
"per_class_corrupt": per_class_acc(corrupt_preds, corrupt_ys)
}
run["total_wall_s"] = time.perf_counter() - total_start
with open(BASE / "runlog.json", "w", encoding="utf-8") as f:
json.dump(run, f, indent=2)
print(json.dumps(run["test"], indent=2))
if __name__ == "__main__":
main()A kód által létrehozott runlog.json szerkezete:
{
"config": {"seed": 20261020, "device": "cpu", "dtype": "torch.float32",
"epochs": 20, "batch": 16, "lr": 0.001,
"weight_decay": 0.0001,
"schedule": {"step_size": 10, "gamma": 0.5}},
"parameter_count": 2979,
"data_load_s": 0.0,
"epochs": [{"epoch": 1, "train_loss": 0.0, "val_loss": 0.0,
"val_acc": 0.0, "wall_s": 0.0}],
"best": {"epoch": 0, "val_loss": 0.0, "val_acc": 0.0},
"test": {"clean_acc": 0.0, "corrupt_acc": 0.0, "matched_agreement": 0.0,
"per_class_clean": [0.0, 0.0, 0.0],
"per_class_corrupt": [0.0, 0.0, 0.0]},
"total_wall_s": 0.0
}A fenti 0.0 értékek helykitöltők, nem mért futásjegyzék.
A tényleges jegyzőkönyvet a futtatás hozza létre.
F1 (fogalmi, nehézség 1). Mondja ki a labor
loss-szerződésének három elemét: reduction, logits vagy valószínűség,
target típusa. Adjon ellenpéldát arra, amikor a softmax előzetes
alkalmazása más numerikus eredményt ad, mint a szerződéses
CrossEntropyLoss logits-bemenettel.
F2 (levezetési, nehézség 2). Bizonyítsa be, hogy a
labor TinyCNN modellje minden súly és bias nullázása esetén
tetszőleges
bemenetre
kimenetet ad. Pontosan adja meg, mit mutat és mit nem mutat ez a
kontroll.
F3 (implementációs, nehézség 2). Írjon
lab/check_best.py szkriptet, amely a best.pt
betöltése után ellenőrzi: (a) a config mező név- és
alakszerződését; (b) a state_dict kulcskészletét; (c) egy
rögzített torch.randn(4,1,24,24) bemenetre a kimeneti
alakot és végességet. Adja meg a futtatási parancsot.
F4 (hibakeresési, nehézség 2). A hallgatótárs a tanítási ciklusban minden epoch után a clean test pontosságát használja a legjobb epoch kiválasztására, majd a betöltött modellen a corrupt tesztet értékeli. Lokalizálja a hibát, írja le a következményét, és javítsa a protokollt.
F5 (kísérleti/transzfer, nehézség 3). Tervezzen egy
kísérletet, amely az intenzitás-augmentáció
(g(x)=clamp(ux),
)
hatását méri a GlyphLab clean és corrupt tesztjén úgy, hogy a corrupt
tesztet nem használja modellválasztásra. Adja meg a protokollt, a
baseline-t, a metrikát és legalább két érvényességi korlátot.
F1 megoldás. A szerződés: (1) a loss mean reduction,
azaz a batch megfigyeléseinek átlaga; (2) a bemenet nyers logits, mert a
CrossEntropyLoss magában végzi a log-softmaxot; (3) a
target int64 osztályindex, egy megfigyeléshez egy skalár.
Ellenpélda: legyen
,
logit
és helyes cél
.
A szerződéses loss:
.
Ha előbb softmaxot alkalmazunk,
,
majd ezt logitnak tekintve a loss
.
A dupla softmax tehát számottevően nagyobb, értelmetlen értéket ad.
F2 megoldás. Rétegről rétegre: a Conv-réteg
súllyal és
biassal bármely bemenetre
kimenetet ad, mert a kernel-összeg minden tagja
.
A ReLU nullpontbeli értéke konvenció szerint
,
a nn.ReLU() implementációja itt
-t
ad; a MaxPool nulla bemeneten nullát; a Flatten az azonosan nulla tensor
alakját
-ra
rendezi, értéke nulla; a Linear
.
Ezért a kimenet
nulla. A bizonyítás a forward kompozíció alakszerződését igazolja
bármely inputra. Nem mutatja, hogy a modell nemtriviális gradienseket
ad, és nem mutatja a tanulás sikerét; a zero init nem töri meg a
szimmetriát, ezért tanításra alkalmatlan. A labor ezért csak sanity
checkként használja, majd visszaállítja a seedelt alapállapotot.
F3 megoldás.
# lab/check_best.py
from pathlib import Path
import torch
from n096_checkpoint import TinyCNN, DEVICE, DTYPE
BASE = Path(__file__).resolve().parent
ckpt = torch.load(BASE / "best.pt", map_location="cpu", weights_only=True)
cfg = ckpt["config"]
assert cfg["name"] == "TinyCNN"
assert cfg["in_ch"] == 1 and cfg["num_classes"] == 3
assert cfg["input_shape"] == [1, 24, 24]
model = TinyCNN(cfg["in_ch"], cfg["num_classes"]).to(DEVICE, dtype=DTYPE)
model.load_state_dict(ckpt["model"])
expected_keys = {
"features.0.weight", "features.0.bias",
"features.3.weight", "features.3.bias",
"fc.weight", "fc.bias"
}
assert set(ckpt["model"].keys()) == expected_keys, set(ckpt["model"].keys())
x = torch.randn(4, 1, 24, 24, dtype=DTYPE, device=DEVICE)
with torch.inference_mode():
y = model(x)
assert y.shape == (4, 3)
assert torch.isfinite(y).all()
print("check OK:", tuple(y.shape), sum(p.numel() for p in model.parameters()))Futtatás: cd lab && python check_best.py. Az
elvárt kimenet check OK: (4, 3) 2979. A kontroll szigorú: a
strict load_state_dict önmagában is hibát dob, ha kulcs
vagy alak eltér; a kimeneti alakellenőrzés a 24×24 bemeneti séma és a
háromosztályos kimenet együttes tesztje. A tolerancia itt szükségtelen,
mert alak- és végességellenőrzésről van szó, nem lebegőpontos
egyezésről.
F4 megoldás. A hiba a modellválasztás
adatszivárgása: a clean test a tanítási ciklus során befolyásolja az
epochválasztást, ezért a clean test többé nem független teszt. A
következmény kettős: a clean és corrupt teszten mért pontosság
szelekciós torzítást kap, és a választott epoch általában optimistább,
mint a tényleges generalizáció. A korrekt protokoll: a legjobb epochot
kizárólag val_loss alapján kell választani; a clean testet
csak a befagyasztott checkpoint betöltése után, egyszer szabad
értékelni; a corrupt tesztet ugyancsak csak ezután, páros clean-korrupt
összehasonlítással. A hibás futtatásjegyzék nem javítható utólag az
epochok újbóli értékelésével, a tanítást újra kell indítani.
F5 megoldás. Protokoll: (1) baseline = a fenti
TinyCNN augmentáció nélkül; kezelés = ugyanaz a modell,
ugyanaz a seed, de minden training batchre
skalárral szorzott, [0,1]-re vágott képek. (2) A
training/val/test split a GlyphLab szerződéses 120/30/30 scene marad; a
val loss alapján mindkét modellnél külön epochválasztás. (3) A
kiértékelés a befagyasztott modelleken clean és corrupt teszten
clean_acc, corrupt_acc,
matched_agreement és osztályszintű bontás. (4) A két modell
különbségét páros scene/view szinten számoljuk, mert a view-k
összetartoznak. Érvényességi korlát: egyetlen seed, ezért az eredmény a
training-véletlent nem választja el a kezelés hatásától; a corrupt
eloszlás csak a GlyphLab-féle zajt és fényességgradienst reprezentálja,
természetes képi shiftre nem vihető át. Emellett az
intenzitástranszformáció csak akkor címkemegőrző, ha a feladatszerződés
az orientációt a fényességtől függetlenül definiálja; ha a későbbi
adatverzió ezt megváltoztatja, a protokollt újra kell validálni.
Gyakori tévedés: „a GPU gyorsabb, tehát a kísérlet helyesebb.” Ellenpélda: az egyik hallgató GPU-n 10 epochot futtat nagy batchszel, és közben a modellválasztáshoz a test accuracy-t használja; a másik CPU-n 20 epochot futtat a fenti val-loss protokollal. A GPU-s futás gyorsabb, de szelekciós torzítást visz a tesztbe, így a mért pontossága nem független általánosítási becslés. A nagyobb batch a lépésszámot is megváltoztatja, és ha a learning-rate ütemezést nem igazítják hozzá, a két konfiguráció más optimalizálási pályát jár be. A GPU jelenléte tehát számítási erőforrás, nem tudományos érvényességi garancia.
Önellenőrző kérdések: szerepel-e a loss-szerződésben a mean reduction
és az int64 target? A corrupt tesztet tényleg csak a legjobb validációs
checkpoint betöltése után értékeli a kód? A runlog.json
tartalmazza-e a seedet, az adatmanifesztet és az ütemezést? A checkpoint
betöltése strict módban történt-e? Visszahívás: a 24.2. CPU/GPU profiling és helyes időmérés
alfejezetben tanított warmup-szabály szerint itt az első epoch falidejét
a steady-state átlagtól külön kezeljük; a 23.4.
Hibaszeletek és alkalmazási határ páros tesztelési elve szerint a
corrupt fájl sorrendje a clean teszt sorrendjével párosul.
A VI. ellenőrző pont egyetlen csomagba zárja a kis CNN alak- és
loss-szerződését, a seedelt inicializálást, a StepLR-ütemezést, a
címkemegőrző augmentáció korlátját, a clean/corrupt metrikákat, a
CPU-falidejű profilmérést és a szigorú checkpoint-betöltést. Az
analitikus kontroll belátja, hogy nulla logitokra a cross-entropy
,
a zero-init modell pedig minden bemenetre nulla kimenetet ad; ez a
forward-szerződést igazolja, a tanulást nem. A labor teljes CPU-kódot
ad, amely runlog.json futásjegyzéket ír; a kéziratban nem
állítunk mért eredményt. A clean és corrupt teszt kizárólag a validációs
loss alapján kiválasztott checkpointon értékelhető.
A 25.1. Teljes képosztályozó adatfolyam teljes képosztályozó adatfolyamot épít: az itteni checkpoint- és metrikaszerződést kiterjeszti bináris, többosztályos és multilabel célokra, és a betanított modellt új felhasználási felületre illeszti.