40.4 X. ellenőrző pont: önálló neurális modell és bizonyíték

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

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ó.

40.4.2 2. A vizsgafeladat: új probléma specifikációja

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:

t=ymax−ymin,t∈{1,…,24}.t=\mathrm{ymax}-\mathrm{ymin},\qquad t\in\{1,\dots,24\}.

A bemenet továbbra is egyetlen zajos nézet x∈ℝ1×24×24x\in\mathbb R^{1\times 24\times 24}, x∈[0,1]x\in[0,1], NCHW alakban, float32 típusban. A cél regressziós, nem osztályozási: a tt 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 tt 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.

40.4.3 3. Matematikai modell és levezetési kötelezettség

A θ\theta paraméterű modell

fθ:ℝ1×24×24→ℝ,t̂=fθ(x),f_\theta:\mathbb R^{1\times 24\times 24}\to\mathbb R,\qquad \hat t=f_\theta(x),

ahol t̂\hat t nyers, aktiváció nélküli kimenet. A veszteség átlagolt MSE:

L(θ)=1N∑i=1N(fθ(xi)−ti)2.L(\theta)=\frac1N\sum_{i=1}^N\left(f_\theta(x_i)-t_i\right)^2.

Az inference-szerződés a nyers kimenetet lezárja:

t̂final=clip(round(t̂),1,24).\hat t_{\mathrm{final}}=\operatorname{clip}\left(\operatorname{round}(\hat t),\;1,\;24\right).

A kerekítés tie-szabálya a torch.round viselkedése: a pontosan x,5x{,}5 é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 fθ(x)=Wa+bf_\theta(x)=Wa+b, ahol W∈ℝ1×DW\in\mathbb R^{1\times D}, a=hϕ(x)∈ℝDa=h_\phi(x)\in\mathbb R^{D} az xx-től függő rögzített jellemzővektor, b∈ℝb\in\mathbb R. Az NN elemű batchre vett átlagolt MSE veszteségre

∇WL=2N∑i=1N(t̂i−ti)ai⊤∈ℝ1×D,∇bL=2N∑i=1N(t̂i−ti).\nabla_W L=\frac2N\sum_{i=1}^N\left(\hat t_i-t_i\right)a_i^\top\in\mathbb R^{1\times D},\qquad \nabla_b L=\frac2N\sum_{i=1}^N\left(\hat t_i-t_i\right).

Bizonyítás. A láncszabály skalárkoordinátánként: ∂L/∂t̂i=2(t̂i−ti)/N\partial L/\partial \hat t_i=2(\hat t_i-t_i)/N. A 4.3. Jacobi-mátrix, láncszabály és VJP VJP-konvenciójában a g=∇t̂L∈ℝN×1g=\nabla_{\hat t}L\in\mathbb R^{N\times 1} upstream a Z=XW⊤+bZ=XW^\top+b lineáris rétegen ∇WL=g⊤X\nabla_W L=g^\top X-szel, ∇bL=g⊤𝟏N\nabla_b L=g^\top \mathbf 1_N-nel terjed vissza. Itt az „input” XX egy sora ai⊤a_i^\top, így

∇WL=∑i=1Ngiai⊤=2N∑i(t̂i−ti)ai⊤,∇bL=∑igi=2N∑i(t̂i−ti).\nabla_W L=\sum_{i=1}^N g_i a_i^\top=\frac2N\sum_i(\hat t_i-t_i)a_i^\top,\qquad \nabla_b L=\sum_i g_i=\frac2N\sum_i(\hat t_i-t_i).

Az alakellenőrzés: gig_i skalár, ai⊤a_i^\top 1×D1\times D sorvektor, az összeg 1×D1\times D, ami megegyezik WW 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: Hout=⌊(H+2p−d(k−1)−1)/s⌋+1H_{out}=\lfloor (H+2p-d(k-1)-1)/s\rfloor+1. 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 (H,k,d,p,s)(H,k,d,p,s) 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.

40.4.4 4. Eredeti kézi kis példa

A levezetési kötelezettség kézi kontrollja egy 1D keresztkorrelációs rétegen, az átlagos MSE-vel. Legyen input x=[1,2,3,4]x=[1,2,3,4], kernel k=[1,−1]k=[1,-1], bias 00, padding 00, stride 11, dilation 11. A kimenet hossza ⌊(4+2⋅0−1⋅(2−1)−1)/1⌋+1=3\lfloor(4+2\cdot0-1\cdot(2-1)-1)/1\rfloor+1=3, tehát y=[1−2,2−3,3−4]=[−1,−1,−1]y=[1-2,2-3,3-4]=[-1,-1,-1]. Cél t=[1,0,0]t=[1,0,0]. A veszteség

L=13[(−2)2+(−1)2+(−1)2]=63=2,∇yL=23(y−t)=23[−2,−1,−1]=[−43,−23,−23].L=\frac13\left[(-2)^2+(-1)^2+(-1)^2\right]=\frac63=2,\qquad \nabla_y L=\frac23(y-t)=\frac23[-2,-1,-1]=\left[-\frac43,-\frac23,-\frac23\right].

A kernelgradiens az átfedő inputpatch-ekkel: k0k_0 (pozíció 0) esetén

∂L∂k0=∑o=02(∇yL)oxo+0=1⋅(−43)+2⋅(−23)+3⋅(−23)=−43−43−2=−143,\frac{\partial L}{\partial k_0}=\sum_{o=0}^2 (\nabla_y L)_o\, x_{o+0}=1\cdot\left(-\frac43\right)+2\cdot\left(-\frac23\right)+3\cdot\left(-\frac23\right)=-\frac43-\frac43-2=-\frac{14}{3},

k1k_1 esetén

∂L∂k1=∑o=02(∇yL)oxo+1=2⋅(−43)+3⋅(−23)+4⋅(−23)=−83−2−83=−223.\frac{\partial L}{\partial k_1}=\sum_{o=0}^2 (\nabla_y L)_o\, x_{o+1}=2\cdot\left(-\frac43\right)+3\cdot\left(-\frac23\right)+4\cdot\left(-\frac23\right)=-\frac83-2-\frac83=-\frac{22}{3}.

A két derivált független ellenőrzése középső véges differenciával (h=10−6h=10^{-6}): ∂L/∂k0≈−4,6666666663\partial L/\partial k_0\approx-4{,}6666666663 és ∂L/∂k1≈−7,3333333331\partial L/\partial k_1\approx-7{,}3333333331, vagyis −14/3-14/3 és −22/3-22/3.

Egy lépésnyi, η=0,3\eta=0{,}3 tanulási rátájú gradienslépést csak a kernelre teszünk; a bias 00-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

k←k−η∇kL=[1+0,3⋅143,−1+0,3⋅223]=[2,4,1,2],k\leftarrow k-\eta\nabla_kL=\left[1+0{,}3\cdot\tfrac{14}{3},\;-1+0{,}3\cdot\tfrac{22}{3}\right]=[2{,}4,\;1{,}2],

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 [2,4⋅1+1,2⋅2,2,4⋅2+1,2⋅3,2,4⋅3+1,2⋅4]=[4,8,8,4,12][2{,}4\cdot1+1{,}2\cdot2,\;2{,}4\cdot2+1{,}2\cdot3,\;2{,}4\cdot3+1{,}2\cdot4]=[4{,}8,\;8{,}4,\;12], így az új MSE

(4,8−1)2+8,42+1223=14,44+70,56+1443=2293≈76,33,\frac{(4{,}8-1)^2+8{,}4^2+12^2}{3}=\frac{14{,}44+70{,}56+144}{3}=\frac{229}{3}\approx76{,}33,

amely nagyobb a kezdeti 22-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.

40.4.5 5. Konkrét labor: teljes PyTorch projekt

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 0,…,N−10,\dots,N-1 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ó 1⋅8⋅3⋅3+8=801\cdot8\cdot3\cdot3+8=80; második 8⋅16⋅3⋅3+16=11688\cdot16\cdot3\cdot3+16=1168; lineáris 576⋅32+32=18464576\cdot32+32=18\,464; kimeneti 32⋅1+1=3332\cdot1+1=33; összesen 1974519\,745. A kimeneti térkép ellenőrzése: 24→24 (Conv, p=1p=1), 24→12 (Pool 2), 12→12 (Conv, p=1p=1), 12→6 (Pool 2), csatorna 16, flatten 16⋅6⋅6=57616\cdot6\cdot6=576, ami illeszkedik a Linear(576,32) bemenetéhez.

40.4.6 6. Értékelés, inference-szerződés és a jelentés

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:

MAEper-image=160∑i∈test|t̂final,i−ti|,Δscene=|t̂view1−t̂view2|.\mathrm{MAE}_{\mathrm{per\text{-}image}}=\frac1{60}\sum_{i\in\text{test}}|\hat t_{\mathrm{final},i}-t_i|,\qquad \Delta_{\mathrm{scene}}=\left|\hat t_{\mathrm{view\ 1}}-\hat t_{\mathrm{view\ 2}}\right|.

A triviális baseline a train-célok mediánja mtrainm_{\mathrm{train}}, 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 [0,1][0,1] kép; nyers kimenet t̂∈ℝ\hat t\in\mathbb R, végső kimenet t̂final∈{1,…,24}\hat t_{\mathrm{final}}\in\{1,\dots,24\} 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ó.

40.4.7 7. Szóbeli védési kérdések és gyakori tévedés

Rövid szóbeli védési kérdések:

  1. Miért nem használható a képenkénti véletlen split ebben a feladatban?
  2. Miért nem elég a val MAE önmagában annak eldöntésére, hogy a modell a geometriát tanulta-e, nem pedig a jelenet átlagos fényességét?
  3. Mivel a 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?
  4. Miért nem tekinthető a round művelet differenciálhatónak, és miért helyes azt csak az inference-szerződésbe tenni?
  5. Miért nem hivatalos egyetemi osztályzat a belső 0–4 rubrika?

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.

40.4.8 8. Feladatok (F1–F5)

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 (H,k,d,p,s)(H,k,d,p,s) ö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) 2×1×24×242\times1\times24\times24 véletlen bemenetre ellenőrzi, hogy a kimenet alakja 2×12\times1, (b) a végső Linear(32,1) réteg súlyára vonatkozó analitikus gradienst összehasonlítja az autograd gradienssel float64 pontossággal, 10−610^{-6} 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.

40.4.9 9. Megoldások és indoklás

F1. Osztályozás: target y∈{0,1,2}y\in\{0,1,2\} int64, loss CrossEntropyLoss logitsból, output logit ℝ3\mathbb R^3, metrika accuracy. Regresszió: target t∈{1,…,24}⊂ℝt\in\{1,\dots,24\}\subset\mathbb R, loss MSE, output t̂∈ℝ\hat t\in\mathbb R, 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 (H,k,d,p,s)=(24,3,1,1,1)(H,k,d,p,s)=(24,3,1,1,1), így Hout=⌊(24+2−2−1)/1⌋+1=⌊23⌋+1=24H_{out}=\lfloor(24+2-2-1)/1\rfloor+1=\lfloor23\rfloor+1=24; a poolra a MaxPool2d(2) alapértelmezései érvényesek: k=2k=2, s=k=2s=k=2, d=1d=1, p=0p=0 és ceil_mode=False, azaz lefelé kerekítés. Ekkor a számláló H+2p−d(k−1)−1=H−2H+2p-d(k-1)-1=H-2, tehát Hout=⌊(24−2)/2⌋+1=12H_{out}=\lfloor(24-2)/2\rfloor+1=12, másodszor ⌊(12−2)/2⌋+1=6\lfloor(12-2)/2\rfloor+1=6. Páratlan bemeneten látszik, miért nem H−1H-1 a számláló: H=5H=5, k=s=2k=s=2, ceil_mode=False mellett Hout=⌊(5−2)/2⌋+1=2H_{out}=\lfloor(5-2)/2\rfloor+1=2, a hibás ⌊(5−1)/2⌋+1\lfloor(5-1)/2\rfloor+1 viszont 3-at adna; páros HH-ra a két alak a lefelé kerekítés miatt egybeesik, ezért ott a hiba rejtve marad. ceil_mode=True mellett ⌈(5−2)/2⌉+1=3\lceil(5-2)/2\rceil+1=3 (PyTorch 2.14.1-ben ellenőrizve). Ellenpélda: (H,k,d,p,s)=(4,3,2,0,1)(H,k,d,p,s)=(4,3,2,0,1) esetén a képlet Hout=⌊(4−4−1)/1⌋+1=⌊−1⌋+1=0H_{out}=\lfloor(4-4-1)/1\rfloor+1=\lfloor-1\rfloor+1=0, tehát a kimeneti méret nem pozitív. A Conv2d ilyen konfigurációra hibát ad, mert az effektív kernelméret d(k−1)+1=5d(k-1)+1=5 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 H=6H=6, k=3k=3, d=1d=1, p=0p=0, s=2s=2 mellett (6−2−1)/2=1,5(6-2-1)/2=1{,}5, és az érvényes kimenet ⌊1,5⌋+1=2\lfloor1{,}5\rfloor+1=2.

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 aa aktiváció alakja (N,32)(N,32). Az upstream gg alakja (N,1)(N,1), így g⊤ag^\top a alakja (1,32)(1,32), 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 2/N2/N 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, 16→1616\to16 csatornás, k=3k=3, p=1p=1 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 Δscene\Delta_{\mathrm{scene}} 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.

40.4.10 10. Rövid összefoglaló

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.

40.4.11 11. Következő alkalmazás

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.

40.4.12 12. Források