38.1 Adat, split és előzetes protokoll

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

A Projekt II a GlyphLab teljes CNN-osztályozási kísérlete: az adat előkészítésétől a befagyasztott modell végső clean/korrupt tesztjéig. Ez az egység a projektet megnyitó döntéseket rögzíti, mielőtt bármilyen modellt tanítanánk: mi a megfigyelési egység, miért jelenetszintű a split, mi a baseline, mi az erőforráskeret, és milyen manifest igazolja az adat sértetlenségét. Előismeret: a checkpoint- és train/eval-szerződés a 12.4. III. ellenőrző pont: önállóan tanuló kis háló alfejezetből, az architektúra-döntési lap és a kis saját CNN választása a 20.4. V. ellenőrző pont: indokolt CNN-választás alfejezetből, az osztályarány és a mintavételezés a 21.4. Osztályarány, súlyozás és céleloszlás alfejezetből, a clean/korrupt teszt megkülönböztetése a 22.4. Augmentációs kísérlet és domainváltozás alfejezetből, a szeletek és modellkártya a 23.4. Hibaszeletek és alkalmazási határ alfejezetből, valamint a megbízható CNN-training csomagja a 24.4. VI. ellenőrző pont: megbízható CNN-training alfejezetből. Visszautalunk a konvolúciós operátor NCHW-szerződésére 37.1. Operátorspecifikáció és kézi referencia, a tényleges időmérés követelményére 34.1. Mért késleltetés, throughput és memória és a fél-nyílt dobozkoordinátákra 26.1. Dobozkoordináta, IoU és lokalizációs loss.

Induló kérdés. A 360 GlyphLab-képen egy jelenethez két zajos nézet tartozik. Mi történne, ha a képeket a jelenetekre tekintet nélkül véletlenszerűen osztanánk train/val/test részre? Becslés: a jelenetek nagyjából fele átszivárogna a részek között, tehát a mérés már nem az „új jelenet” hibáját közelítené. A becslést a 3. szakasz egzakt számpéldával és lemmával ellenőrzi.

38.1.2 2. Definíciók: jelenet, split, manifest és a projekt szerződése

Definíció (jelenet, nézet, kép). A GlyphLab adatban 𝒮={s1,…,s180}\mathcal S=\{s_1,\dots,s_{180}\} a 180 független jelenet halmaza. Minden s∈𝒮s\in\mathcal S jelenethez két nézet tartozik, Vs={0,1}V_s=\{0,1\}, ezért a képek halmaza:

ℐ={(s,v):s∈𝒮,v∈Vs},|ℐ|=360. \mathcal I=\{(s,v):s\in\mathcal S,\ v\in V_s\},\qquad |\mathcal I|=360.

A kép tenzora Xs,v∈ℝ1×24×24X_{s,v}\in\mathbb R^{1\times24\times24}, NCHW alakban, float32, [0,1][0,1] intenzitással; a cél ys,v∈{0,1,2}y_{s,v}\in\{0,1,2\}, ahol 0 vertical, 1 horizontal, 2 diagonal. A metadata index oszlopa a kép sorszáma a data/images.npy tömb első tengelyén; a view_id 0 vagy 1, a scene_id azonosítja a csoportot. A maszk és az xyxy doboz annotáció, nem osztályozóbemenet; a doboz nullától indexelt, fél-nyílt intervallum, ahogyan a 26.1. Dobozkoordináta, IoU és lokalizációs loss is rögzíti.

Definíció (jelenetszintű split). A projekt splitje a jelenetek osztályrétegzett partíciója:

𝒮=𝒮tr∪𝒮val∪𝒮test, \mathcal S=\mathcal S_{\mathrm{tr}}\cup\mathcal S_{\mathrm{val}}\cup\mathcal S_{\mathrm{test}},

ahol |𝒮tr|=120|\mathcal S_{\mathrm{tr}}|=120, |𝒮val|=30|\mathcal S_{\mathrm{val}}|=30, |𝒮test|=30|\mathcal S_{\mathrm{test}}|=30, és minden osztályból rendre 40, 10, 10 jelenet kerül a három részbe. A képindexek a splitből származnak:

Isplit={(s,v):s∈𝒮split,v∈{0,1}}. I_{\mathrm{split}}=\{(s,v):s\in\mathcal S_{\mathrm{split}},\ v\in\{0,1\}\}.

Így a train/val/test képszám 240/60/60, és minden osztályból 80/20/20 kép jut egy-egy részbe. Egy jelenet mindkét nézete ugyanabban a splitben van. A clean teszt a data/images.npy azon 60 sora, amely a metadata split=="test" sorainak felel meg; a corrupt teszt data/corrupted_test.npy sorrendje ugyanez a metadata-test-sorrend, ezért a jj-edik corrupt kép az jj-edik clean tesztkép rögzített zajos, fényességgradienssel ellátott változata. A párosítás a 22.4. Augmentációs kísérlet és domainváltozás alfejezetben bevezetett clean–corrupt szerződés.

Definíció (adatmanifest). A manifest a projekt adatjegyzéke: verzió, generátorseed, tömbalakok és dtype-ok, splitdarabszám, valamint a fájlok SHA-256 lenyomata. A hash a teljes fájlra vonatkozik, nem a tensor tartalmának külön reprezentációjára. Belső adatnál a jegyzék rögzíti az eredetet (saját generátor); külső adatnál az alábbi mezőket is kötelezővé tesszük: name, origin_url, license, license_url, download_date, preprocessing, sha256. A GlyphLab projektben külső adat nincs, így az external_data lista üres.

A megfigyelési egység az 1.3. Adatfelosztás és a megfigyelési egység értelmében az az objektum, amelynek az újrafelhasználásakor függetlenséget várunk. Itt ez a scene_id: a két nézet ugyanannak a geometriának két zajos megvalósítása, ezért a nézetek nem képezhetnek önálló, független megfigyelést a train/test-határon át.

38.1.3 3. Levezetési kötelezettség: miért a jelenetszintű split illik a használati helyzethez?

A megnevezett felhasználási helyzet (U1). A projekt kérdése zárt, háromosztályú felismerés: egy új, a tanításban nem látott jelenetből érkezik egyetlen zajos 24×2424\times24-es szürke nézet, és a modellnek meg kell adnia az orientációs osztályt. Ebből a helyzetből következik, hogy a tesztnek az új jelenet hibáját kell mérnie, nem pedig egy már látott jelenet második négyzetét. A jelenetszintű split pontosan ezt a célt reprezentálja; a képszintű split ezt megsérti.

Ellenpélda: nézetszivárgás egy memóriamodellel. Tegyük fel, hogy egy jelenet view 0 képe a trainben, view 1 képe a tesztben van. Egy hmemh_{\mathrm{mem}} modell, amely a tanítóképek minden pixelét és címkéjét eltárolja, a teszt view 1 képen a látott geometriát azonosíthatja, és a címkét visszaadhatja. Ez a modell a teszt held-out pontosságát felfelé tolja, miközben új jelenetekre nem általánosít. A hiba tehát nem a használati helyzet kockázatát becsli, hanem a modell memóriáját.

Számpélda: a kép-szintű felosztás várható szivárgása. Ha a 360 képet a jelenetre tekintet nélkül véletlenszerűen osztjuk 240/60/60 részre, egy rögzített jelenet két nézetének együttes hovatartozása három kedvező esemény összege: mindkettő train, mindkettő val, vagy mindkettő test. Mivel a két nézet konkrét kép, a valószínűség:

Psame=(2402)+(602)+(602)(3602)=240⋅239+60⋅59+60⋅59360⋅359=64440129240≈0,4986. P_{\mathrm{same}} =\frac{\binom{240}{2}+\binom{60}{2}+\binom{60}{2}}{\binom{360}{2}} =\frac{240\cdot239+60\cdot59+60\cdot59}{360\cdot359} =\frac{64440}{129240}\approx0{,}4986.

Annak valószínűsége, hogy a jelenet két nézete különböző splitbe kerül:

Pdiff=1−Psame≈0,5014. P_{\mathrm{diff}}=1-P_{\mathrm{same}}\approx0{,}5014.

A 180 jelenetre a várhatóan megsértett csoportok száma a várható érték linearitása miatt

180⋅Pdiff≈90,25. 180\cdot P_{\mathrm{diff}}\approx90{,}25.

Tehát a naiv felosztás várhatóan mintegy 90 jelenetet hasítana ketté, és a teszt már nem lenne „új jelenet” mérés.

Lemma (a jelenetszintű tesztbecslő torzítatlansága). Legyen hh a teljes tanítási eljárás kimenete, amely csak 𝒮tr∪𝒮val\mathcal S_{\mathrm{tr}}\cup\mathcal S_{\mathrm{val}} adataitól függ; legyen a 0/10/1 hiba ℓ(h,(s,v))=𝟏[h(Xs,v)≠ys,v]∈[0,1]\ell(h,(s,v))=\mathbf 1[h(X_{s,v})\neq y_{s,v}]\in[0,1]. Tegyük fel, hogy a tesztjelenetek a használati U1 eloszlásból származnak, a két nézet marginális képeloszlása azonos, és a tesztjelenetek függetlenek a tanítási előzménytől. Az empirikus teszthiba

err̂=160∑s∈𝒮test∑v∈{0,1}ℓ(h,(s,v)). \widehat{\mathrm{err}} =\frac{1}{60}\sum_{s\in\mathcal S_{\mathrm{test}}}\sum_{v\in\{0,1\}}\ell(h,(s,v)).

Ekkor 𝔼[err̂∣ℋ]=L(h)\mathbb E[\widehat{\mathrm{err}}\mid\mathcal H]=L(h), ahol ℋ\mathcal H a train/val előzmény, hh annak függvénye, és L(h)L(h) a U1 szerinti valódi várható hiba.

Bizonyítás. Az ℋ\mathcal H feltételre hh rögzített. A tesztjelenetek függetlensége miatt minden s∈𝒮tests\in\mathcal S_{\mathrm{test}} és v∈{0,1}v\in\{0,1\} esetén

𝔼[ℓ(h,(s,v))∣ℋ]=𝔼S′∼𝒟[ℓ(h,(S′,v))]=Lv(h). \mathbb E[\ell(h,(s,v))\mid\mathcal H] =\mathbb E_{S'\sim\mathcal D}[\ell(h,(S',v))]=L_v(h).

A nézetek marginális azonossága szerint L0(h)=L1(h)=L(h)L_0(h)=L_1(h)=L(h). A feltételes várható érték linearitásával:

𝔼[err̂∣ℋ]=160∑s∈𝒮test∑v=01L(h)=L(h). \mathbb E[\widehat{\mathrm{err}}\mid\mathcal H] =\frac{1}{60}\sum_{s\in\mathcal S_{\mathrm{test}}}\sum_{v=0}^{1}L(h) =L(h).

A teljes várható értéket véve 𝔼[err̂]=𝔼[L(h)]\mathbb E[\widehat{\mathrm{err}}]=\mathbb E[L(h)]: a becslő torzítatlan a tanítási eljárás feletti U1-kockázatra. ▫\square

A lemma két korlátját ki kell mondani. Egyrészt a nézetek közötti függetlenség nem feltétel a torzítatlansághoz, csak a marginálisok azonossága; ezért a 60 tesztkép nem 60 független megfigyelés. A konfidenciaintervallumot ezért jelenetszintű resamplinggel számoljuk [S02], nem képszintű binomiális formulával. Másrészt a lemma feltételezi, hogy a modell kiválasztása nem használta a tesztet; ha a teszt visszahat a választásra, a feltétel megsérül, és a 8. szakasz tipikus tévedéséhez jutunk.

38.1.4 4. Előzetes protokoll és baseline

A 36.4. IX. ellenőrző pont: lektorálható modellállítás lektorálható állításának mintájára a protokollt a kísérlet előtt kell lefagyasztani. A projekt fő kérdése:

A befagyasztott, saját kis CNN clean-teszt-pontossága meghaladja-e a lineáris baseline clean-teszt-pontosságát, és mekkora a clean–corrupt páros romlás?

A döntési táblázat:

Elem Befagyasztott döntés
Használati helyzet Új jelenetből egyetlen view, zárt 3 osztály
Megfigyelési egység scene_id
Split 120/30/30 jelenet, osztályrétegzett, minden view egy helyen
Modell A 38.2. Modell, training és best checkpoint GlyphCNN-je: két 3×33\times3-as konvolúciós blokk, 32 egységes rejtett réteg, 19 811 paraméter, scratch, CPU; egyetlen forrása a lab/glyph_models.py
Baseline Lineáris osztályozó: flatten 576 → 3 logit (1731 paraméter), ugyanazzal a tanítási recepttel és választási szabállyal; külön minimum-referencia a képfüggetlen prior (log3\log 3 loss, 1/31/3 accuracy)
Választási szabály Futásonként a legkisebb val_loss epoch-snapshot; architektúránként a három futás közül a legkisebb best val_loss; a választás csak train és val adatot használ
Teszt Egyszer, befagyasztott checkpoint után; clean, majd corrupt páros
Seedek 20261020, 20261021, 20261022 mindkét modellre; a seed-szórás a kísérlet bizonytalanságát mutatja
Elsődleges metrika Per-view clean accuracy, jelenet-bootstrap CI
Másodlagos Osztályonkénti recall; clean–corrupt páros accuracy-különbség

A baseline szándékosan a legegyszerűbb értelmes osztályozó. Az U1 képek osztályonkénti intenzitásmintázata lineárisan is megkülönböztethető lehet, ezért a lineáris modell azt a kérdést teszi fel, hogy a konvolúciós lokalitás és a nemlinearitás ad-e mérhető előnyt. A lineáris modell a 24⋅24=57624\cdot24=576 pixelt egyetlen vektorba lapítja, utána Z=Wx̃+b∈ℝ3Z=W\tilde x+b\in\mathbb R^3 logitot számol; ez 576⋅3+3=1731576\cdot3+3=1731 paraméter. A projekt CNN-je 19 811 paraméteres, vagyis több mint tízszer akkora; a két modell kapacitása tehát nem azonos, és az összevetés azt a kérdést dönti el, hogy a konvolúciós lokalitás, a nemlinearitás és a nagyobb kapacitás együtt ad-e mérhető előnyt. Ha a reprezentáció szerkezetét a kapacitástól is el akarjuk választani, egy kapacitásban illesztett változat (például a 24.4. VI. ellenőrző pont: megbízható CNN-training 2979 paraméteres TinyCNN-je) külön, előre deklarált harmadik karként vehető fel; ez a jelen protokollnak nem része.

A magasabb szintű protokollt a labor protocol_n149.json fájlként menti, protocol_version = 1 jelöléssel. Ha a modell vagy a baseline a végső teszt előtt megváltozik, az új protokoll új verziószámot kap, és a régi verzió a naplóban marad; a teszt megnyitása után a protokoll nem módosítható. Külső adat választása esetén ugyanez a fájl kötelezően tartalmazza az eredet- és licencjegyzéket; ennek hiánya a labort elfogadhatatlanná teszi.

38.1.5 5. Labor: előzetes protokoll és ellenőrzött adatmanifest

A labor célja kettős: egy futtatható ellenőrző és egy manifest-kimenet. Az alábbi kód nem helyettesíti a kezdőlabor ellenőrzött lab/ellenorzott_report.json jelentését; a futtatást a hallgató végzi el, és a kimenetet a projektnaplóhoz csatolja.

# lab/protocol_check.py
from pathlib import Path
import csv, hashlib, json
import numpy as np

HERE = Path(__file__).resolve().parent
DATA = HERE / "data"

FILE_NAMES = [
    "images.npy", "masks.npy", "labels.npy",
    "corrupted_test.npy", "metadata.csv",
]

def sha256(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()

def main() -> None:
    manifest = json.loads((DATA / "manifest.json").read_text(encoding="utf-8"))
    metadata = list(csv.DictReader((DATA / "metadata.csv").open(
        newline="", encoding="utf-8")))

    assert len(metadata) == 360, "a metadata.csv-nek 360 rekordot kell tartalmaznia"

    images = np.load(DATA / "images.npy")
    labels = np.load(DATA / "labels.npy")
    masks = np.load(DATA / "masks.npy")
    corrupt = np.load(DATA / "corrupted_test.npy")

    assert images.dtype == np.float32 and images.shape == (360, 1, 24, 24)
    assert ((images >= 0.0) & (images <= 1.0)).all()
    assert masks.dtype == np.uint8 and masks.shape == (360, 1, 24, 24)
    assert set(np.unique(masks)) <= {0, 1}
    assert labels.dtype == np.int64 and labels.shape == (360,)
    assert set(np.unique(labels)) <= {0, 1, 2}
    assert corrupt.dtype == np.float32 and corrupt.shape == (60, 1, 24, 24)
    assert np.isfinite(corrupt).all()

    # A metadata sora a képtömb ugyanazon indexét írja le.
    for i, row in enumerate(metadata):
        assert int(row["index"]) == i, "a metadata indexének a képindexszel kell egyeznie"
        assert int(row["label"]) == int(labels[i])
        assert row["split"] in {"train", "val", "test"}

    # Jelenetintegritás: minden scene-hez pontosan a két view tartozik,
    # egyetlen splitben, egyetlen címkével.
    rows_by_scene: dict[str, list[dict[str, str]]] = {}
    for row in metadata:
        rows_by_scene.setdefault(row["scene_id"], []).append(row)

    assert len(rows_by_scene) == 180
    for scene_id, rows in rows_by_scene.items():
        assert {row["view_id"] for row in rows} == {"0", "1"}
        splits = {row["split"] for row in rows}
        assert len(splits) == 1, f"a(z) {scene_id} jelenet több splitben van"
        assert len({row["label"] for row in rows}) == 1

    # Osztályrétegzett jelenetszámok: 40/10/10 minden osztályra.
    scene_split = {s: rows[0]["split"] for s, rows in rows_by_scene.items()}
    scene_label = {s: rows[0]["label"] for s, rows in rows_by_scene.items()}
    counts: dict[str, int] = {}
    for split in ("train", "val", "test"):
        for cls in ("0", "1", "2"):
            counts[f"{split}/class_{cls}"] = sum(
                1 for s in rows_by_scene
                if scene_split[s] == split and scene_label[s] == cls
            )
    assert counts == {
        "train/class_0": 40, "train/class_1": 40, "train/class_2": 40,
        "val/class_0": 10,   "val/class_1": 10,   "val/class_2": 10,
        "test/class_0": 10,  "test/class_1": 10,  "test/class_2": 10,
    }, counts

    test_meta = [r for r in metadata if r["split"] == "test"]
    assert len(test_meta) == 60 == corrupt.shape[0]
    # A corrupt sorrend a metadata test sorrendjét követi; ezt a manifest
    # és a kezdőlabor ellenőrzése rögzíti, a tömbtartalomból közvetlenül nem
    # párosítható vissza a clean képekhez.

    # Fájlhash-egyezés a manifesttel.
    file_hashes = manifest["sha256"]
    found_hashes = {name: sha256(DATA / name) for name in FILE_NAMES}
    for name in FILE_NAMES:
        assert found_hashes[name] == file_hashes[name], f"hash-eltérés: {name}"

    report = {
        "manifest_ok": True,
        "scene_integrity": True,
        "split_counts": counts,
        "corrupt_pairs_by": "metadata test order",
        "file_hashes": found_hashes,
        "external_data": [],
        "protocol_version": 1,
        "models": {"cnn": {"class": "GlyphCNN", "params": 19811},
                   "linear": {"class": "LinearBaseline", "params": 1731}},
        "seeds": [20261020, 20261021, 20261022],
        "param_budget": 20000,
    }
    (HERE / "protocol_n149.json").write_text(
        json.dumps(report, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )
    print("protocol_n149.json: minden ellenőrzés sikeres.")

if __name__ == "__main__":
    main()

Futtatás és ellenőrzés:

cd lab
python protocol_check.py

A manifest-séma a kezdőlabor data/manifest.json fájljából származik; ha a kulcsok eltérnek, a kód KeyError-ral áll meg, és a data/README szerinti sémát kell ellenőrizni. Az ellenőrzés nem használ RNG-t és nem igényel GPU-t; minden művelet CPU-n, fájlolvasás szintjén fut. A kimeneti protocol_n149.json tartalmazza a fájlonkénti SHA-256 lenyomatot és a splitdarabszámokat, tehát a projekt későbbi átadásakor az adat sértetlensége újra bizonyítható.

38.1.6 6. Modellköltségkeret

A költségkeret a 34.1. Mért késleltetés, throughput és memória alfejezetben tanult mérési szabályt vetíti vissza a tervezésre: a tényleges futás csak később mérhető, de a választásnak előre rögzített korlátokat kell teljesítenie.

Erőforrás Előzetes korlát Ellenőrzés ideje
Paraméterszám legfeljebb 20 000 modellépítéskor, kapacitásszámlálással
Epoch legfeljebb 30 seedenként training ciklus
Fal-idő legfeljebb 30 perc seedenként CPU-n 38.3. Végső teszt és páros korrupció alfejezetben mért, warmuppal
Inferencia batch-memória legfeljebb 50 MB profilerrel, batch-csúccsal
Test-hozzáférés egyetlen értékelés befagyasztott modellen protokoll-audit

A kiválasztott 19 811 paraméteres CNN float32 súlytárolása 19811⋅4B≈79,2KB19\,811\cdot4\ \text{B}\approx79{,}2\ \text{KB}; az AdamW két momentumával ez körülbelül 3⋅79,2KB≈237,7KB3\cdot79{,}2\ \text{KB}\approx237{,}7\ \text{KB} train-time állapot. Egy 32-es batch bemenete 32⋅1⋅24⋅24⋅4B≈73,7KB32\cdot1\cdot24\cdot24\cdot4\ \text{B}\approx73{,}7\ \text{KB}. A köztes aktivációk pontos csúcsát nem becsüljük előre: a 38.3. Végső teszt és páros korrupció profiler méri. A fal-időre itt csak a felső korlátot fagyasztjuk le; a kezdőlabor ellenőrzött jelentése a saját kis CPU-futtatását rögzíti, de abból más gépre nem általánosítunk futásidőt [D02].

A baseline és a CNN ugyanazt a 3 seedet, ugyanazt a train/val/test indexhalmazt és ugyanazt a választási szabályt kapja; az egyetlen szándékosan változtatott tényező az architektúra. Ez kontrollált összehasonlítás, nem utólag kiválasztott legjobb futtatás.

38.1.7 7. Tipikus tévedés: a tesztből visszacsatolt modellválasztás

A leggyakoribb projekt hiba az, hogy a hallgató megnézi a teszt-accuracyt, ezután módosítja az augmentációt vagy a learning rate-et, újra tanít, és az ugyanazon testen kapott jobb értéket jelenti. Az első tesztfutás pillanatában a teszt elveszítette held-out státuszát: az új modell kiválasztása már felhasználta a tesztet, ezért a jelentett pontosság lefelé torzított. A kiválasztási torzítás 22.4. Augmentációs kísérlet és domainváltozás alfejezetből ismert: több jelölt közül a tesztminimum várhatóan nem nagyobb a legjobb valódi hibánál, tehát a 83%-os jelentett pontosság nem általánosítási bizonyíték. A javítás új értékelési ciklus: a választást csak train/val végzi, a módosított modellhez új, még nem érintett tesztjelenetek vagy új felosztás kell, és a régi teszt a naplóban választási adatként szerepel.

38.1.8 8. Feladatok

F1 (fogalmi, nehézség 1). Definiálja a scene_id, view_id és index különbségét a GlyphLab metadata alapján. Magyarázza el, miért a jelenet a megfigyelési egység, és adjon egy konkrét helyzetet, amelyben a két nézet nem független. Elvárt átadandó: saját megfogalmazás, tenzoralak és a nemfüggetlenség egy példája.

F2 (levezetési, nehézség 2). A 180 jelenet 360 képét véletlenszerűen, jelenetre tekintet nélkül osztjuk 240/60/60 részre. Számolja ki egzaktul annak valószínűségét, hogy egy rögzített jelenet két nézete eltérő splitbe kerül, és adja meg a 180 jelenetre várhatóan megsértett csoportok számát. Elvárt: képlet, közbülső számok, értelmezés.

F3 (implementációs, nehézség 2). A metadata.csv soraiból írjon check_scene_splits(rows) függvényt, amely visszaad egy scene_id -> split leképezést, és ValueError-t dob, ha egy jelenet két különböző splitben jelenik meg. Bemenet a CSV DictReader listája; a kimenet hibatűrése nem releváns, mert a művelet egzakt. Elvárt: futtatható kód és két ellenőrző bemenet: egy hibátlan, egy hibás.

F4 (hibakeresési, nehézség 2). Egy hallgató a best checkpointot a test loss minimumánál választja, majd a testen 0,86 pontosságot jelent. Hol a hiba, mi a következménye a mérésre, és mi a minimális javítás? Elvárt: ok, következmény, javítás, visszaellenőrzés.

F5 (kísérleti/transzfer, nehézség 3). Tegyük fel, hogy a projekt új adatszerződésre vált: 900 külső jelenet, jelenetenként 3 nézet, 5 osztály, osztályarány 50/20/15/10/5%. Készítsen előzetes protokollvázlatot: megfigyelési egység, osztályrétegzett split, baseline, metrika, erőforráskorlát, valamint külső adathoz az eredet- és licencjegyzék kötelező mezői. Elvárt: indokolt terv, nem előre megadott győztes.

38.1.9 9. Megoldások és indoklás

F1 megoldás. A scene_id egy generált geometriai jelenetet azonosít; a view_id az adott jelenet két zajos nézetének 0/1 azonosítója; az index a teljes images.npy tömb sorindexe, tehát a kép tenzor 360×1×24×24360\times1\times24\times24 első tengelyén fut. A megfigyelési egység azért a jelenet, mert a két nézet közös geometriát és címkét hordoz: ha például egy jelenetben a ferde vonal vastagabb, a két nézet intenzitáskorrelációja erős, és a modell a train-view-ból a test-view-ra szivárogtathat geometriai információt. A nézetek nem független minták; ezért a bootstrap és a split csoportosítási egysége is a jelenet.

F2 megoldás. A jelenet két konkrét képe azonos splitben van, ha mindkettő train, mindkettő val vagy mindkettő test. A megfelelő valószínűségek:

Ptrain=240360⋅239359,Pval=60360⋅59359,Ptest=60360⋅59359. P_{\mathrm{train}}=\frac{240}{360}\cdot\frac{239}{359},\quad P_{\mathrm{val}}=\frac{60}{360}\cdot\frac{59}{359},\quad P_{\mathrm{test}}=\frac{60}{360}\cdot\frac{59}{359}.

Összegük:

Psame=57360+3540+3540129240=64440129240≈0,4986, P_{\mathrm{same}} =\frac{57360+3540+3540}{129240} =\frac{64440}{129240}\approx0{,}4986,

tehát Pdiff=1−Psame≈0,5014P_{\mathrm{diff}}=1-P_{\mathrm{same}}\approx0{,}5014. A várhatóan megsértett jelenetek száma 180⋅0,5014≈90,25180\cdot0{,}5014\approx90{,}25. Szélsőeset-ellenőrzés: ha minden kép egyetlen splitbe esne, Psame=1P_{\mathrm{same}}=1; ha egyetlen split sem tartalmazhatná mindkét nézetet, Pdiff=1P_{\mathrm{diff}}=1. A kapott 90,25 a 180 felénél kissé több, mert a train–val–test határok háromfelé osztanak, és a kis val/test részek ritkán tartják együtt a párokat.

F3 megoldás.

def check_scene_splits(rows):
    scene_to_split = {}
    for row in rows:
        s = row["scene_id"]
        split = row["split"]
        old = scene_to_split.setdefault(s, split)
        if old != split:
            raise ValueError(
                f"scene {s} több splitben szerepel: {old!r} és {split!r}"
            )
    return scene_to_split

Ellenőrzés:

ok = [
    {"scene_id": "0", "split": "train"},
    {"scene_id": "0", "split": "train"},
]
bad = [
    {"scene_id": "0", "split": "train"},
    {"scene_id": "0", "split": "test"},
]
assert check_scene_splits(ok) == {"0": "train"}
try:
    check_scene_splits(bad)
except ValueError as e:
    assert "több splitben" in str(e)
else:
    raise AssertionError("a hibás esethez ValueError kell")

Alakellenőrzés: a bemenet minden rekordja szöveges CSV-mezőket tartalmaz; a kimenet egy dict, amely 180 jelenetre pontosan 180 kulcsot ad, ha az adat érvényes.

F4 megoldás. A hiba oka: a modellválasztás a tesztet használja. Következmény: a teszt már nem held-out; a legjobb test-loss választása a teszt zajára is illeszkedik, ezért a jelentett 0,86 pontosság várhatóan optimista. A minimális javítás az új ciklus: a választás kizárólag a val_loss minimuma, a testhez a befagyasztott checkpoint egyszeri kiértékelése tartozik. Visszaellenőrzés: a projektnaplóban a teszt egyetlen predikciófuttatáshoz tartozzon, és a kiválasztási lépés ne hivatkozzon testmetrikára.

F5 megoldásminta. A megfigyelési egység a jelenet marad; három nézet esetén minden nézet ugyanabba a splitbe kerül, és a bootstrap szintén jelenetszintű. A split osztályrétegzett: az 5 osztály arányát mindhárom részben megtartjuk, ha a ritka osztály mérete engedi; a 5%-os osztályt legalább val/test jelenetszámhoz kell rögzíteni. A kiegyensúlyozatlanság miatt a baseline a többségi osztály prior-accuracyje, a fő metrika pedig macro-recall vagy osztályonkénti recall, nem csupán overall accuracy. A modellválasztás val_loss vagy val macro-recall szerint történik; a tesztet egyszer értékeljük. Erőforrás: CPU, legfeljebb 5000 paraméter, legfeljebb 30 epoch seedenként. Külső adathoz a manifest kötelező mezői: name, origin_url, license, license_url, download_date, preprocessing, sha256; ezek hiányában a kísérlet nem átadható. Alternatíva lehet az egy nézetre redukált alsó mérés: először view 0-n ellenőrizzük a csővezetéket, majd a teljes háromnézetes adaton futtatjuk a végső protokollt.

38.1.10 10. Önellenőrzés és korábbi fogalom visszahívása

Ellenőrizze a labor után, hogy (1) minden scene_id mindkét nézete azonos splitben van; (2) a protocol_n149.json hash-ei egyeznek a manifesttel; (3) a test split nem szerepel semmilyen választási döntésben; (4) a baseline és a CNN ugyanazt a jelenet-splitet és seed-sémát használja. Visszahívás: a 22.4. Augmentációs kísérlet és domainváltozás szerint a corrupt teszt csak a befagyasztott modell után értékelhető; a 26.1. Dobozkoordináta, IoU és lokalizációs loss szerint a doboz fél-nyílt xyxy, ezért a maszk befoglaló téglalapját 𝚡𝚖𝚊𝚡−𝚡𝚖𝚒𝚗\texttt{xmax}-\texttt{xmin} és 𝚢𝚖𝚊𝚡−𝚢𝚖𝚒𝚗\texttt{ymax}-\texttt{ymin} területtel számoljuk; a 34.1. Mért késleltetés, throughput és memória szerint a fal-időt csak warmup utáni, ismételt, tényleges mérésből szabad jelenteni.

38.1.11 Rövid összefoglaló

A Projekt II adatszerződése a GlyphLab 180 jelenetét és 360 nézetét jelenetszintű, osztályrétegzett 120/30/30 splitre bontja. A képszintű véletlen felosztás várhatóan körülbelül 90 jelenetet osztana meg; a lemmában beláttuk, hogy a jelenetszintű tesztbecslő torzítatlan a megnevezett U1 használati helyzetre, ha a modellválasztás nem érinti a tesztet. A labor manifest-ellenőrzőt és SHA-256-jegyzéket készít, baseline-ként 1731 paraméteres lineáris osztályozót rögzít, és CPU-költségkeretet fagyaszt le. A teszt egyszeri, befagyasztott kiértékeléshez tartozik; a teszt-visszacsatolás új ciklust igényel.

38.1.12 Következő alkalmazás

A 38.2. Modell, training és best checkpoint a befagyasztott modell pontos rétegsorát, a training ciklust és a best-checkpoint választást valósítja meg az itteni protokoll alapján; a 38.3. Végső teszt és páros korrupció végzi a clean és corrupt teszt páros kiértékelését.

38.1.13 Felhasznált források