Ez a X. ellenőrző pont: a teljes tananyag matematikai, implementációs és kísérleti teljesítményét egyetlen új, korábban nem tárgyalt feladaton kell önállóan bemutatni. A 40.3. Tudományos kézirat és forrásaudit forrásauditja, a 39.4. Alkalmazási riport és átadás átadási szerződése, a 36.4. IX. ellenőrző pont: lektorálható modellállítás lektorálható csomagja és a 28.4. VII. ellenőrző pont: CNN alkalmazási szerződés alkalmazási szerződése együtt adja azt a keretet, amelyben a hallgató a feladatkiírástól a futtatható modellig és az érvényességi korlátig minden lépést maga végez. Előismeret: a CNN-operátor alakszabályai 14.1.–14.4., az MSE-regressziós veszteség 7.3. Bináris, multilabel és regressziós cél, a gradiensellenőrzés 15.4. Független gradiensellenőrzési protokoll, a best-checkpoint-választás 24.4. VI. ellenőrző pont: megbízható CNN-training és a scene-szintű mintavételi egység 5.4. Függő képek, seedek és resampling.
É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 a hét komponensű projektcsomag, és a PASS része a szóbeli védés is; mind az öt dimenzió súlyos.
Induló kérdés: elegendő-e a korábbi orientáció-osztályozó fejének kicserélése, ha az új cél egy folytonos geometriai mennyiség? Becslés: nem. A target típusa, a veszteség, a metrika, az inference-kimenet normalizálása és a hiba értelmezése mind más szerződést követel; csak a CNN-gerinc (backbone) marad újrafelhasználható.
Feladat: a GlyphLab-képekből becsüljük meg az előtér függőleges kiterjedését, azaz a maszk befoglaló téglalapjának magasságát:
A bemenet továbbra is egyetlen zajos nézet
,
,
NCHW alakban, float32 típusban. A cél regressziós, nem
osztályozási: a
pozitív egész, de a hálózat valós kimenetet ad. A két nézet ugyanahhoz a
jelenethez tartozik, ezért a
célérték jelenetenként közös; a független mintavételi egység a
scene_id. A felosztás az eredeti szerződést követi:
120/30/30 jelenet train/val/test, osztályonkénti rétegzéssel; a jelenet
mindkét nézete azonos splitbe kerül. A tesztet sem modell-, sem
hiperparaméter-választásra nem használjuk.
A hallgatónak hét átadandó komponenst kell előállítania:
problem, mathematical model, implementation, data, evaluation,
inference, report. A problem egy féloldalas
feladatleírás a fenti szerződéssel; a data a meglévő
képekhez a metadata.csv ymin, ymax oszlopaiból képzett
thickness.npy célállomány, amelynek SHA-256 lenyomatát a
futásnapló rögzíti; a többi komponenst alább részletezzük.
A paraméterű modell
ahol nyers, aktiváció nélküli kimenet. A veszteség átlagolt MSE:
Az inference-szerződés a nyers kimenetet lezárja:
A kerekítés tie-szabálya a torch.round viselkedése: a
pontosan
értékek a páros egészre kerülnek (round half to even); az
utófeldolgozás verziója
clip(round_half_to_even(t), 1, 24) v1, és ezt az
inference-szerződés rögzíti.
A levezetési kötelezettség a vizsgafeladatban egy új tételalkalmazás: a kimeneti lineáris réteg gradiensének pontos, alakhelyes levezetése a VJP-konvencióban.
1. lemma (MSE-gradiens a végső lineáris rétegen). Legyen , ahol , az -től függő rögzített jellemzővektor, . Az elemű batchre vett átlagolt MSE veszteségre
Bizonyítás. A láncszabály skalárkoordinátánként: . A 4.3. Jacobi-mátrix, láncszabály és VJP VJP-konvenciójában a upstream a lineáris rétegen -szel, -nel terjed vissza. Itt az „input” egy sora , így
Az alakellenőrzés: skalár, sorvektor, az összeg , ami megegyezik alakjával. A keresztkorreláció-rétegre ez a lemma nem vonatkozik változatlanul; ott az átfedő patch-ek súlygradienseit a 15.1. Súly- és biasgradiens szabálya szerint összegezni kell. ∎
A konvolúciós kimeneti méret tételét 14.1. Stride, padding, dilation és outputméret
alfejezetből idézzük vissza:
.
A vizsgafeladat azt kéri, hogy a hallgató alkalmazza ezt a tételt a
saját architektúrájára, és adjon ellenpéldát olyan
konfigurációra, ahol a képlet nem pozitív kimenetet ad. Ez utóbbi a
Conv2d számára érvénytelen beállítás, tehát a tétel
feltételeinek megsértését mutatja.
A levezetési kötelezettség kézi kontrollja egy 1D keresztkorrelációs rétegen, az átlagos MSE-vel. Legyen input , kernel , bias , padding , stride , dilation . A kimenet hossza , tehát . Cél . A veszteség
A kernelgradiens az átfedő inputpatch-ekkel: (pozíció 0) esetén
esetén
A két derivált független ellenőrzése középső véges differenciával (): és , vagyis és .
Egy lépésnyi, tanulási rátájú gradienslépést csak a kernelre teszünk; a bias -n marad (a bias frissítése külön döntés, és ez a kontroll kizárólag a kernellépést vizsgálja). Ekkor
a frissítés iránya az adott pontban a negatív gradiens. A csökkenést azonban külön számolással kell ellenőrizni: az új kernel kimenete , így az új MSE
amely nagyobb a kezdeti -nél. Ez a példa megmutatja, hogy egy túl nagy tanulási ráta a helyes gradiens ellenére is növelheti a veszteséget. A súlygradiens-összegzés (15.1. Súly- és biasgradiens) független megerősítése nem maga a kézi képlet, hanem a fenti véges differenciás kontroll, amely a képlettől függetlenül, csak a veszteségfüggvény kiértékeléséből számol.
A labor egyetlen, újrafuttatható szkriptet ad:
lab/n160_thickness.py. Munkakönyvtár: a labor főkönyvtára.
Bemenet: lab/data/images.npy,
lab/data/metadata.csv. A targetet a script a
ymax-ymin oszlopokból determinisztikusan állítja elő, és a
munkakönyvtárhoz képest data/thickness.npy néven menti. A
metadata sorait nem a CSV sorrendje, hanem az index mező
köti az images.npy soraihoz: az align_metadata
ellenőrzi, hogy az index egyedi és pontosan a
tartományt fedi le, majd index szerint rendez; a script egy véletlenül
permutált CSV-sorrenddel is lefuttatja, és ugyanazt az eredményt
követeli meg. A scene_split jelenetenként ellenőrzi a közös
splitet, a két nézetazonosítót (0 és 1) és a közös targetet. Az állomány
SHA-256 lenyomatát a futásnaplóba kell menteni.
# lab/n160_thickness.py
import hashlib, json
from pathlib import Path
import numpy as np
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
torch.manual_seed(20261020)
np.random.seed(20261020)
DEVICE = torch.device("cpu")
DTYPE = torch.float32
DATA = Path("data")
class ThicknessNet(nn.Module):
"""Kis CNN osztályozó gerincből regressziós fejjel. NCHW 1x24x24 -> [N,1]."""
def __init__(self) -> None:
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 8, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(2), # 24 -> 12
nn.Conv2d(8, 16, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(2), # 12 -> 6
)
self.head = nn.Sequential(
nn.Flatten(), # 16*6*6 = 576
nn.Linear(576, 32), nn.ReLU(),
nn.Linear(32, 1),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.head(self.features(x))
class GlyphThickness(Dataset):
"""images.npy + thickness.npy párosítás a metadata.csv scene_id splitje után."""
def __init__(self, images: np.ndarray, target: np.ndarray, idx: np.ndarray) -> None:
self.x = torch.tensor(images[idx], dtype=DTYPE)
self.t = torch.tensor(target[idx], dtype=DTYPE).unsqueeze(1) # [N,1]
def __len__(self) -> int:
return len(self.x)
def __getitem__(self, i: int) -> tuple[torch.Tensor, torch.Tensor]:
return self.x[i], self.t[i]
POSTPROCESS = "clip(round_half_to_even(t), 1, 24) v1" # torch.round: x.5 -> páros egész
def align_metadata(rows: np.ndarray, n_images: int) -> np.ndarray:
"""A metadata sorait az `index` mező szerint az images.npy soraihoz igazítja."""
index = rows["index"].astype(np.int64)
assert len(index) == n_images, "a metadata sorszáma eltér a képtömbétől"
assert np.array_equal(np.sort(index), np.arange(n_images)), "az index nem 0..N-1 permutációja"
return rows[np.argsort(index)] # ezután az i. sor az i. képé
def scene_split(meta: np.ndarray, target: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Scene-szintű indexfelosztás; egy jelenet minden nézete azonos splitbe kerül."""
ids, splits = meta["scene_id"], meta["split"]
assert set(np.unique(splits)) == {"train", "val", "test"}
for sid in np.unique(ids):
rows = ids == sid
assert len(set(splits[rows])) == 1, "Egy jelenet több splitben szerepel"
assert sorted(meta["view_id"][rows]) == [0, 1], "a jelenet nézetei hiányosak"
assert len(set(target[rows])) == 1, "a két nézet célja eltér"
return tuple(np.flatnonzero(splits == name) for name in ("train", "val", "test"))
def predict_final(model: nn.Module, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
"""Átadási függvény: nyers kimenet és az inference-szerződés szerinti végső predikció."""
model.eval()
with torch.inference_mode():
raw = model(x).squeeze(1)
return raw, torch.clamp(torch.round(raw), 1, 24)
def main() -> None:
torch.set_num_threads(1) # CPU-n szálszámfüggetlen eredmény
images = np.load(DATA / "images.npy") # (360,1,24,24)
rows = np.genfromtxt(DATA / "metadata.csv", delimiter=",", names=True,
dtype=None, encoding="utf-8")
meta = align_metadata(rows, len(images))
shuffled = rows[np.random.default_rng(0).permutation(len(rows))]
assert np.array_equal(align_metadata(shuffled, len(images)), meta) # sorrendfüggetlen
thickness = (meta["ymax"] - meta["ymin"]).astype(np.float32)
target_path = DATA / "thickness.npy"
np.save(target_path, thickness)
print("thickness_sha256=" + hashlib.sha256(target_path.read_bytes()).hexdigest())
tr_idx, va_idx, te_idx = scene_split(meta, thickness)
assert (len(tr_idx), len(va_idx), len(te_idx)) == (240, 60, 60)
assert images.shape == (360, 1, 24, 24) and thickness.shape == (360,)
assert np.all((thickness >= 1) & (thickness <= 24))
model = ThicknessNet().to(DEVICE)
opt = torch.optim.Adam(model.parameters(), lr=3e-3)
loss_fn = nn.MSELoss()
tr_loader = DataLoader(GlyphThickness(images, thickness, tr_idx),
batch_size=16, shuffle=True)
va_x = torch.tensor(images[va_idx], dtype=DTYPE)
best_va, best_epoch, best_state = float("inf"), -1, None
for epoch in range(12):
model.train()
for xb, tb in tr_loader:
opt.zero_grad()
loss = loss_fn(model(xb), tb)
loss.backward()
opt.step()
va_raw, _ = predict_final(model, va_x)
va_mae = float(np.abs(va_raw.numpy() - thickness[va_idx]).mean()) # választó metrika: nyers val MAE
if va_mae < best_va:
best_va, best_epoch = va_mae, epoch
best_state = {k: v.clone().cpu() for k, v in model.state_dict().items()}
ckpt = Path("checkpoints") / "n160_thickness_best.pt"
ckpt.parent.mkdir(parents=True, exist_ok=True)
assert best_state is not None
torch.save(best_state, ckpt)
ckpt_sha = hashlib.sha256(ckpt.read_bytes()).hexdigest()
# átadás: a visszatöltött modell végső predikciói és a teljes inference-szerződés
delivered = ThicknessNet().to(DEVICE)
delivered.load_state_dict(torch.load(ckpt, map_location=DEVICE, weights_only=True))
te_x = torch.tensor(images[te_idx], dtype=DTYPE)
raw, final = predict_final(delivered, te_x)
model.load_state_dict(best_state)
assert torch.equal(predict_final(model, te_x)[1], final) # memóriabeli == visszatöltött
out = Path("runs") / "n160"
out.mkdir(parents=True, exist_ok=True)
np.savez(out / "test_predictions.npz", index=te_idx, raw=raw.numpy(), final=final.numpy(),
target=thickness[te_idx])
contract = {"input": "float32 NCHW (N,1,24,24), [0,1]", "raw_output": "float32 (N,)",
"final_output": "egész 1..24", "postprocess": POSTPROCESS,
"selection_metric": "nyers val MAE (legkisebb)", "best_epoch": best_epoch,
"checkpoint": str(ckpt), "checkpoint_sha256": ckpt_sha,
"claim_refers_to": "final_test_mae (az átadott predikció)"}
(out / "inference_contract.json").write_text(json.dumps(contract, indent=2, ensure_ascii=False))
saved = np.load(out / "test_predictions.npz") # a riport az artifactból számol
raw_test_mae = float(np.abs(saved["raw"] - saved["target"]).mean())
final_test_mae = float(np.abs(saved["final"] - saved["target"]).mean())
train_median = float(np.median(thickness[tr_idx]))
baseline_test_mae = float(np.abs(thickness[te_idx] - train_median).mean())
print(f"best_epoch={best_epoch} best_val_mae_raw={best_va:.4f}")
print(f"raw_test_mae={raw_test_mae:.4f} final_test_mae={final_test_mae:.4f} "
f"baseline_test_mae={baseline_test_mae:.4f}")
if __name__ == "__main__":
main()Futtatási parancs: python n160_thickness.py. A
checkpointot a nyers validációs MAE választja (ezt a
tanítás előtt rögzítjük), az alkalmazási állítás viszont az átadott,
utófeldolgozott predikcióra, azaz a végső teszt MAE-re
vonatkozik; a script a kettőt külön közli. A visszatöltött checkpointból
az átadási függvény (predict_final) állítja elő a teszt
nyers és végső predikcióit; a script ellenőrzi, hogy ez bitre egyezik a
memóriában lévő legjobb állapot végső predikciójával, a predikciókat a
runs/n160/test_predictions.npz, a szerződést a
runs/n160/inference_contract.json fájlba menti, és a teszt
MAE-ket már a mentett artifactból számolja. Futási jegyzőkönyv a
lab-profilban (PyTorch 2.14.1+cpu, egy szál; két futás és
OMP_NUM_THREADS=4 mellett is azonos):
thickness_sha256=cd9b71e47e517fb9acf96a0838f4f3a7503e12a34423147972973932a9ae7066
best_epoch=11 best_val_mae_raw=0.8662
raw_test_mae=0.9153 final_test_mae=0.8833 baseline_test_mae=2.6000
Ez egyetlen seed eredménye; a seed-szórás külön mérés (36.2. Ismétlések és eredménybizonytalanság). A
checkpoint SHA-256 lenyomata a szerződésfájlban szerepel. A modell
paraméterszáma: első konvolúció
;
második
;
lineáris
;
kimeneti
;
összesen
.
A kimeneti térkép ellenőrzése: 24→24 (Conv,
),
24→12 (Pool 2), 12→12 (Conv,
),
12→6 (Pool 2), csatorna 16, flatten
,
ami illeszkedik a Linear(576,32) bemenetéhez.
Az értékelési metrika képenkénti átlagos abszolút hiba (mean absolute error, MAE) pixelben, valamint a jeleneten belüli két nézet predikciójának különbsége:
A triviális baseline a train-célok mediánja
,
mert az állandó becslések közül a medián minimalizálja az átlagos
abszolút eltérést. A hozzá tartozó teszt MAE az abszolút predikciós hiba
mértéke, nem a szórás. MSE-re optimalizált állandó baseline esetén a
train-célok átlaga a megfelelő választás. A jelenetpáron belüli
kiegyensúlyozatlanság miatt a képenkénti bootstrap nem
használható szignifikancia-állításra: a resampling egysége a
scene_id 5.4. Függő képek, seedek és
resampling, és a CI feltételes, ismételt mintavételi értelmezését
[S02] rögzíti.
Az inference-szerződés: bemenet float32 NCHW
kép; nyers kimenet
,
végső kimenet
a clip(round_half_to_even(t), 1, 24) v1 utófeldolgozással.
A predikciós artifact a teszt indexét, a nyers és a végső predikciót és
a targetet, a szerződésfájl a sémát, az utófeldolgozás verzióját, a
választó metrikát, a best epochot és a checkpoint hash-ét tartalmazza. A
fenti MAE-képlet a végső predikcióra vonatkozik; a nyers MAE
diagnosztikai érték, mert a kerekítés és a levágás mindkét irányba
változtathatja a hibát. A tanítási veszteség MSE, de a best checkpointot
kizárólag a nyers val MAE alapján választjuk; a tesztet csak a
fagyasztott súlyok betöltése után, egyszer értékeljük.
A report a 36.4. IX. ellenőrző pont: lektorálható modellállítás hat komponensét követi: a bizonyítást (1. lemma és a kimeneti méret tétel alkalmazása), a source trace-t (images.npy, thickness.npy, metadata.csv, szkript és checkpoint hash-e), a kódot, a run manifestet (seed 20261020, float32, CPU, epochszám, nyers validációs MAE, nyers és végső teszt MAE, utófeldolgozás-verzió), a páros clean–corrupt vagy view–view eredményt és a korlátokat. A korlátszakasz kötelező eleme: a GlyphLab szintetikus adat, a MAE elméleti alsó határa nulla az egészértékű célok mellett is; pozitív elkerülhetetlen hiba csak a feladat és a megfigyelések bizonytalanságának külön indoklásával állítható, és a regresszió nem kalibrált valószínűségi predikció.
Rövid szóbeli védési kérdések:
thickness.npy determinisztikusan, a
metaadatokból számolható, miért fontosabb az inputfájlok hash-e és a
transzformáció kódjának verziója, mint egy nem használt generálási
seed?round művelet
differenciálhatónak, és miért helyes azt csak az inference-szerződésbe
tenni?Gyakori tévedés és ellenpélda. A leggyakoribb hiba a belső értékelési skála akkreditált jeggyé alakítása. Ellenpélda: ha egy projekt mind az öt dimenzióban 4 pontot kap, az azt jelenti, hogy az adott, a feladatkiírásban rögzített feltételek között önállóan helyes és ellenőrizhető; ez nem bizonyítja, hogy a modell új adaton vagy éles környezetben is 4-es teljesítményt ad. A rubrika a lektori visszacsatolás belső eszköze, nem helyettesíti a 36.3. Szakirodalmi reprodukció és attribúció alfejezetben megkövetelt forrásauditot. Hasonló kategóriahiba: a teszt MAE-t két tizedesjegy pontossággal közölni egyetlen seedből, és ezt a modell várható hibájának nevezni; a seed-szórás külön mérés 36.2. Ismétlések és eredménybizonytalanság.
F1 (fogalmi, nehézség 1). Nevezze meg az orientáció-osztályozó és a vastagság-regresszió szerződésének négy különböző komponensét (target, loss, output, metrika), és adjon mindegyikre egy-egy pontos értéktartományt.
F2 (levezetési, nehézség 2). Alakítsa alkalmazott
tétellé a 14.1. Stride, padding, dilation és
outputméret kimeneti méretképletét a saját architektúrájára, és
adjon ellenpéldát olyan
ötösre, amelyre a képlet nem pozitív egész kimenetet ad; indokolja,
miért érvénytelen ez a Conv2d hívásra.
F3 (implementációs, nehézség 2). Egészítse ki a
labor kódját egy rövid önteszttel, amely (a)
véletlen bemenetre ellenőrzi, hogy a kimenet alakja
,
(b) a végső Linear(32,1) réteg súlyára vonatkozó analitikus
gradienst összehasonlítja az autograd gradienssel float64 pontossággal,
abszolút toleranciával.
F4 (hibakeresési, nehézség 1). A következő hibás eljárásban azonosítsa a hibát, következményét és javítását: „A 360 képből véletlenszerűen választunk 240/60/60 képet train/val/test részre, és a modellt MSE-vel tanítjuk.”
F5 (kísérlettervezési, nehézség 3). Tervezzen kísérletet a kérdésre: „Javítja-e a harmadik konvolúciós réteg hozzáadása a teszt MAE-t?” Adja meg a protokollt, a baseline-t, a változó tényezőt, a scenestratified splitet, a metrikát, a seed- és erőforrás-kezelést, valamint a következtetés érvényességi határát.
F1. Osztályozás: target
int64, loss CrossEntropyLoss logitsból, output logit
,
metrika accuracy. Regresszió: target
,
loss MSE, output
,
metrika MAE pixelben. A round/clip csak
inference-utófeldolgozás, nem a tanítási cél része.
F2. A modell két konvolúciójára
,
így
;
a poolra a MaxPool2d(2) alapértelmezései érvényesek:
,
,
,
és ceil_mode=False, azaz lefelé kerekítés. Ekkor a számláló
,
tehát
,
másodszor
.
Páratlan bemeneten látszik, miért nem
a számláló:
,
,
ceil_mode=False mellett
,
a hibás
viszont 3-at adna; páros
-ra
a két alak a lefelé kerekítés miatt egybeesik, ezért ott a hiba rejtve
marad. ceil_mode=True mellett
(PyTorch 2.14.1-ben ellenőrizve). Ellenpélda:
esetén a képlet
,
tehát a kimeneti méret nem pozitív. A Conv2d ilyen
konfigurációra hibát ad, mert az effektív kernelméret
meghaladja a bemenet méretét; a szükséges pozitív kimeneti méret
feltétele sérül. A törtnek nem kell egésznek lennie: például
,
,
,
,
mellett
,
és az érvényes kimenet
.
F3. A megoldás vázlata:
def self_test() -> None:
model = ThicknessNet().double() # float64 kontroll
x = torch.randn(2, 1, 24, 24, dtype=torch.float64, requires_grad=True)
y = model(x)
assert y.shape == (2, 1), y.shape
t = torch.tensor([[3.0], [7.0]], dtype=torch.float64)
loss = torch.nn.functional.mse_loss(y, t) # mean reduction
a = model.head[2](model.head[1](model.head[0](model.features(x)))).detach() # (2,32)
g = (2.0 / x.shape[0]) * (y.detach() - t) # (2,1)
grad_W_analytical = g.T @ a # (1,32)
loss.backward()
grad_W_autograd = model.head[3].weight.grad # (1,32)
assert grad_W_analytical.shape == grad_W_autograd.shape == (1, 32)
torch.testing.assert_close(grad_W_analytical, grad_W_autograd, atol=1e-6, rtol=0)Itt a model.head[1] a Linear(576,32) réteg,
a model.head[2] a ReLU, ezért az
aktiváció alakja
.
Az upstream
alakja
,
így
alakja
,
azonos a model.head[3].weight súlyéval. Az explicit
alakkontroll megelőzi a véletlen broadcastingből eredő téves sikeres
tesztet. A batch-átlag
szorzója a lemma szerint.
F4. A hiba a megfigyelési egység figyelmen kívül
hagyása. Következmény: egy jelenet két nézete különböző splitbe
kerülhet; ugyanaz a geometriai tartalom a trainben és a tesztben is
megjelenik, a teszt MAE optimistán torzított lesz. Javítás: a splitet a
scene_id-re kell csoportosítani, azaz az 1.3. Adatfelosztás és a megfigyelési egység
alfejezetben rögzített scene-stratified eljárást kell használni;
visszaellenőrzés: minden scene_id pontosan egy splitben
szerepeljen.
F5. Lehetséges terv. Kérdés: harmadik konvolúciós réteg hatása. A réteg hozzáadása a paraméterszámot szükségszerűen növeli; ez a beavatkozás része, nem zavaró tényező (36.1. Állításból kísérleti terv), a kapacitásillesztett kontroll (azonos paraméterkeret más elrendezésben) külön, opcionális kérdés. Protokoll: azonos vetőmag, azonos CPU, azonos float32 dtype; a baseline a jelenlegi kétkonvolúciós modell; a változat egy harmadik, csatornás, , réteg a második pool előtt. Megfigyelési egység: jelenetpár. Split: 120/30/30 scene, ötszörös rétegzett ismétlés. Metrika: a már véglegesen kiválasztott két modell teszt-MAE-je és jeleneten belüli mediánja. A konfiguráció és a best checkpoint kiválasztása kizárólag validációs MAE szerint történik, és csak ezután következik az egyszeri, páros tesztkiértékelés. Következtetési határ: a GlyphLab szintetikus adat; az eredmény nem általánosítható természetes képekre, és a seed-szórás miatt egyetlen futtatásból nem vonunk le általános architektúra-következtetést. A nyers MAE-ket és a bizonytalanságot külön kell jelenteni.
A X. ellenőrző pont egy új, regressziós feladaton bizonyítja az önálló munkavégzést: a GlyphLab-képből a maszk befoglaló magasságát becsüljük. A szerződés hét komponense – probléma, matematikai modell, implementáció, adat, értékelés, inference, jelentés – együtt adja a lektorálható csomagot. A levezetési kötelezettség az MSE-gradiens végső lineáris rétegre vonatkozó alakhelyes VJP-je és a konvolúciós kimeneti képlet egy érvénytelen konfigurációval vett ellenpéldája. A labor kis CPU-s PyTorch-szkriptet ad; a közölt jegyzőkönyv egyetlen seed lab-profilbeli futása, a nyers és a végső teszt MAE külön. A belső 0–4 rubrika lektori eszköz, nem akkreditált osztályzat.
Az elvégzett ellenőrző pont lezárja a tananyag főútját. A hallgató a fenti csomagot a saját adatán, új céllal is felhasználhatja: a hét komponens sablonja független a GlyphLabtól, amennyiben a target, a split és a metrika szerződését újradefiniálja.