1.3 Adatfelosztás és a megfigyelési egység

1.3.1 Cél, előismeret és induló kérdés

Az egység célja, hogy a programozói adatkezelést pontos megfigyelési egységgel, csoportos felosztással és ellenőrizhető szivárgásdefinícióval kapcsolja össze. Előismeretként csak az 1.2. Szakmailag megalapozott modellezési állítások állítástípusait és bizonyítékjegyzékét használjuk; haladó valószínűségszámításra nincs szükség.

Induló becslés. A GlyphLab adatbázis 360 darab 24×2424\times 24-es szürke képet tartalmaz, de ezek 180 jelenetből (scene) készültek, jelenetenként két zajos nézettel (view). Hány független megfigyelésünk van: 360 vagy 180? Ha valaki képszintű train/validation/test felosztást készít, pontosan milyen hibát követ el? Tippeljen, és a labor végén vesse össze a levezetett értékkel.

1.3.2 Definíciók és intuíció

Megfigyelési egység (observation unit). Az az elemi rekord, amelyről a modell egy előrejelzést ad. A GlyphLabban ez egy kép, tehát a modell bemenete egy x(i)∈ℝC×H×Wx^{(i)}\in\mathbb R^{C\times H\times W} tensor; a kezdőadatban C=1C=1, H=W=24H=W=24, float32, [0,1][0,1] intenzitás. A hozzá tartozó cél y(i)∈{0,1,2}y^{(i)}\in\{0,1,2\}, int64 osztályindex.

Scene és view. A scene az a rejtett, közös képződmény, amelyből a képek keletkeznek: ugyanaz a geometriai elrendezés, alakzatvastagság és pozíció. A view ennek egy zajos, önálló képi megfigyelése. A metadata.csv minden sorához scene_id és view_id tartozik; egy scene mindkét view-ja ugyanabba a splitbe kerül.

Csoport (group). Egy megfigyeléscsoport azon rekordok halmaza, amelyek közös rejtett véletlen forrásból származnak. Formálisan legyen g(i)g(i) az ii rekord csoportazonosítója; a GlyphLabban g(i)=scene_id(i)g(i)=\text{scene\_id}(i). Az érvényes felosztás feltétele:

ha g(i)=g(j), akkor split(i)=split(j).\text{ha } g(i)=g(j), \text{ akkor } \text{split}(i)=\text{split}(j).

A kép a predikció és a képenkénti metrika megfigyelési egysége, míg a scene_id a split és a csoportos bizonytalanságbecslés egysége. A scene_id tehát a független mintavételi egység: ha a tesztkészletből új jelenetekre akarunk következtetni, akkor a felosztást jelenetekre, nem képekre kell elvégezni.

Train/validation/test. A train a paraméterek illesztésére, a validation a modell- és hiperparaméterválasztásra, a test a rögzített protokoll végén, egyszeri, független értékelésre szolgál. A GlyphLab split 120, 30, 30 független jelenetet, azaz 240, 60, 60 képet tartalmaz. A két view nem növeli a független jelenetek számát.

Transzformáció. Az a leképezés, amely a nyers rekordból a modell bemenetét állítja elő: x(i)=T(r(i))x^{(i)}=T(r^{(i)}). Ha a transzformáció adaptív paramétereket tanul, például globális standardizálást vagy PCA-alapú redukciót, akkor ezeket csak a train felosztáson illesztjük; a teszt statisztikája nem szivároghat a trainbe. A GlyphLab corrupted_test.npy állománya a rögzített split után alkalmazott teszt-transzformáció, ezért nem befolyásolja a tanítást.

Adatszivárgás (data leakage). Két fő formája van. Az egyik a csoportszintű átszivárgás: ugyanazon scene képei különböző splitbe kerülnek, így a tesztben olyan rejtett jelenet szerepel, amelynek egy másik megfigyelését a train már látta. A másik a preprocessing-átszivárgás: a felosztás elvégzése előtt a teljes adaton számolunk statisztikát, alkalmazunk imputációt vagy választunk hiperparamétert. Mindkettő torzítja az általánosítási becslést.

1.3.3 Levezetés: miért nem független két zajos nézet?

A függetlenség formális valószínűségi definícióját az 5.1. Esemény, feltétel és véletlen változó egységben építjük fel; itt az elemi gyakorisági kritériummal dolgozunk: két megfigyelés akkor független, ha az együttes relatív gyakoriság a peremgyakoriságok szorzata. A következő lemmát saját, teljesen diszkrét generatív modellel bizonyítjuk.

Lemma (közös rejtett jelenetből származó nézetek függősége). Legyen S∈{a,b}S\in\{a,b\} rejtett jelenet, P(S=a)=P(S=b)=1/2P(S=a)=P(S=b)=1/2. Minden nézet egy zajos csatornán halad át: ha S=aS=a, a nézet aa értéket ad 0,80{,}8 valószínűséggel és bb értéket 0,20{,}2 valószínűséggel; ha S=bS=b, a szerepek felcserélődnek. A két csatorna zajai adott SS mellett függetlenek. Ekkor a két nézet X1X_1 és X2X_2 nem független.

Bizonyítás. Először a peremeloszlást számítjuk. Az SS feltételeit összegezve:

P(X1=a)=0,8⋅12+0,2⋅12=0,5,P(X_1=a)=0{,}8\cdot\tfrac12+0{,}2\cdot\tfrac12=0{,}5,

és ugyanígy P(X1=b)=0,5P(X_1=b)=0{,}5. A szimmetria miatt ugyanez igaz X2X_2-re.

Most az együttes eloszlást írjuk fel. Ha a két csatorna adott SS mellett független, akkor a helyes–helyes együttes valószínűség 0,8⋅0,80{,}8\cdot 0{,}8, a hibás–hibás 0,2⋅0,20{,}2\cdot 0{,}2. A két SS-érték együttesen:

P(X1=a,X2=a)=12(0,82+0,22)=12⋅0,68=0,34.P(X_1=a,X_2=a)=\tfrac12(0{,}8^2+0{,}2^2)=\tfrac12\cdot 0{,}68=0{,}34.

Ha X1X_1 és X2X_2 függetlenek lennének, akkor a peremek szorzatának kellene teljesülnie:

P(X1=a)P(X2=a)=0,5⋅0,5=0,25.P(X_1=a)P(X_2=a)=0{,}5\cdot 0{,}5=0{,}25.

Mivel 0,34≠0,250{,}34\neq 0{,}25, a két nézet nem független. ∎

A bizonyításban a feltételes függetlenség és a perem-összegzés szerepét érdemes külön látni: adott SS mellett a két zajdobás független, de a közös rejtett SS miatt a megfigyelések összekapcsolódnak. Ha a csatorna hibája 00 lenne, az együttes tömeg 0,5⋅(12+02)=0,50{,}5\cdot(1^2+0^2)=0{,}5 volna, tehát maximális függést kapnánk. Ha a csatorna teljesen informatív zajt adna, vagyis a kimenet független lenne SS-től, akkor a nézetek is függetlenné válnának; a függés forrása tehát a közös jel.

1.3.4 Kézi kis példa

Ugyanezt a generatív modellt egy háromjelenetes, kétnézetes miniatűr adatbázison szemléltetjük, hogy a csoportos split feltétele látható legyen.

scene_id view_id megfigyelt osztály
1 1 a
1 2 a
2 1 b
2 2 b
3 1 a
3 2 b

A fenti táblában a scene 1 és 2 nézetei determinisztikusan azonosak, a scene 3 nézetei zajosak. Ha képszintű felosztást készítenénk, például a páratlan sorokat trainbe, a párosakat testbe tennénk, akkor a scene 1 és 2 egyik nézete a trainben, másik a testben lenne; a tesztben szereplő kép a trainből ismert jelenetet mutatná. A csoportos split ezzel szemben teljes scene-eket oszt be: az egyik lehetséges érvényes kimenet train = {scene 1, scene 2}, test = {scene 3}. Ekkor a teszt minden képe olyan jelenetből származik, amelyet a modell a trainben nem látott.

1.3.5 Labor: scene_id alapú splitellenőrzés

Input és szerződés. A munkakönyvtár a lab/; a bemenet data/metadata.csv, amelynek scene_id, view_id, label és split oszlopa int64, a split string értékű (train, val, test). A labor nem tölti be a képtensort, nem használ GPU-t, és nem végez lebegőpontos műveletet; a dtype-egyezés a CSV-ből olvasott oszlopokon értendő.

Futtatási parancs. A lab/ könyvtárból:

python n003_split_check.py

Kód.

import math
import numpy as np
import pandas as pd
from pathlib import Path

DATA_DIR = Path("data")
meta = pd.read_csv(DATA_DIR / "metadata.csv")

def violating_scenes(df: pd.DataFrame, split_col: str) -> list[int]:
    """Azon scene_id-k, amelyek több különböző splitben szerepelnek."""
    nunique = df.groupby("scene_id")[split_col].nunique()
    return nunique[nunique > 1].index.astype(int).tolist()

def scene_counts(df: pd.DataFrame, split_col: str) -> dict[str, int]:
    return df.groupby(split_col)["scene_id"].nunique().to_dict()

# 1. Jó split ellenőrzése a gyártó szerint
bad_good = violating_scenes(meta, "split")
counts_good = scene_counts(meta, "split")
print("Jó split:")
print("  split-számláló scene-ekre:", counts_good)
print("  szabálytalan scene-ek száma:", len(bad_good))

# 2. Szándékosan hibás, képszintű split
rng = np.random.default_rng(12345)  # a hibás split seedje
idx = rng.permutation(len(meta))
n_train, n_val = 240, 60
bad_split = np.full(len(meta), "unused", dtype="<U5")
bad_split[idx[:n_train]] = "train"
bad_split[idx[n_train:n_train + n_val]] = "val"
bad_split[idx[n_train + n_val:]] = "test"

meta_bad = meta.copy()
meta_bad["split_bad"] = bad_split
bad_bad = violating_scenes(meta_bad, "split_bad")
counts_bad = scene_counts(meta_bad, "split_bad")
print("Hibás split:")
print("  split-számláló scene-ekre:", counts_bad)
print("  szabálytalan scene-ek száma:", len(bad_bad))

# 3. Várható szabálytalan scene-szám a hibás splithez
C = math.comb  # Python 3.8+: pontos egész binomiális együttható
p_same = (C(240, 2) + C(60, 2) + C(60, 2)) / C(360, 2)
print(f"Egy scene együtt maradásának valószínűsége: {p_same:.4f}")
print(f"Várható szabálytalan scene-szám: {180 * (1 - p_same):.2f}")

Elvárt eredmény és értelmezés. A jó splitre a violating_scenes üres lista, és a scene-számláló train: 120, val: 30, test: 30, ahogyan a GlyphLab szerződése rögzíti. A szándékosan hibás felosztásban ezzel szemben a scene_id tudat nélkül permutáltunk képeket, ezért sok scene két view-ja különböző splitbe kerül.

A hibás splithez tartozó várható szabálytalan scene-szám levezethető elemi kombinatorikával. Egy rögzített scene két view-ja akkor marad együtt, ha mindkettő ugyanabba a szegmensbe esik:

pegyütt=(2402)+(602)+(602)(3602)=3222064620≈0,4986.p_{\mathrm{együtt}}=\frac{\binom{240}{2}+\binom{60}{2}+\binom{60}{2}}{\binom{360}{2}}=\frac{32220}{64620}\approx 0{,}4986.

Ezért a várhatóan szabálytalan scene-ek száma

180⋅(1−pegyütt)≈90,25.180\cdot(1-p_{\mathrm{együtt}})\approx 90{,}25.

A kód által kiírt tényleges szám a 12345 seeddel ettől néhánnyal eltérhet; az ellenőrzés kritériuma az, hogy a hibás splitre pozitív, a jóra nulla legyen. Ez a labor éppen a csoportos split feltételét teszteli: a jó felosztásban soha nem lép át scene, a hibásban várhatóan 180-ból körülbelül 90 jelenet igen.

1.3.6 Feladatok

F1 fogalmi (1-es nehézség). Határozza meg a scene és a view különbségét, és indokolja három mondatban, miért nem helyettesítheti a 360 képet 360 független jelenetnek. Elvárt eredmény: pontos fogalmi megkülönböztetés és a közös rejtett okra hivatkozó indoklás.

F2 levezetési (2-es nehézség). Tekintse az S∈{0,1,2}S\in\{0,1,2\} egyenletes eloszlású rejtett jelenetet. Az egyes nézetek az SS értékét 0,70{,}7 valószínűséggel helyesen adják, a másik két értékre egyenként 0,150{,}15–0,150{,}15 valószínűséggel tévednek; a két nézet adott SS mellett független. Számítsa ki P(X1=X2)P(X_1=X_2) értékét, és mutassa meg, miért nem független a két nézet. Elvárt eredmény: levezetett 0,5350{,}535 és a függetlenségi összehasonlítás 1/31/3-dal.

F3 implementációs (2-es nehézség). Írjon split_integrity(df, split_col) függvényt, amely visszaadja azon scene_id-k listáját, amelyek több különböző splitben szerepelnek. Tesztelje a data/metadata.csv-n a split oszlopra, majd egy mesterségesen megcserélt split_bad oszlopra. Elvárt eredmény: futtatható kód, üres lista a jó, nem üres a rossz oszlopra.

F4 hibakeresési (2-es nehézség). Az alábbi kód képszintű splitet készít. Azonosítsa a hibát, írja le a teszt-accuracyra gyakorolt következményét, és adja meg a scene-szintű javítást, valamint annak ellenőrzését.

def split_by_view(meta, rng):
    idx = rng.permutation(len(meta))
    split = np.full(len(meta), "train", dtype="<U5")
    split[idx[240:300]] = "val"
    split[idx[300:]] = "test"
    meta["split"] = split
    return meta

Elvárt eredmény: a hiba lokalizálása, a torzítás irányának indoklása, valamint a javított függvény és a violating_scenes ellenőrzés futási kritériuma.

F5 kísérlettervezési/transzfer (3-as nehézség). A GlyphLab bővített változatában jelenetenként 10 time-lapse nézetet készítünk, és a felhasználási cél ismeretlen jelenetek osztályozása. Tervezzen protokollt: mi a megfigyelési egység, hogyan választja szét a train/validation/test készleteket, mi legyen a baseline, és milyen metrikával, illetve ellenőrzéssel mutatná ki az esetleges szivárgást? Elvárt eredmény: egy lehetséges terv, annak korlátjaival; alternatív terv megadása esetén az eltérő döntés indoklása.

1.3.7 Megoldások és indoklás

F1 megoldás. A scene a közös rejtett geometriai elrendezés, a view ennek egy zajos képi vetülete. Két view ugyanarról a scene-ről nem cserélhető fel két független jelenettel, mert az egyik nézet alapján a másikra jobb előrejelzés adható, mint a peremeloszlásból. Ezért a független mintavételi egység a scene; a képek száma csak a modellbemenetek számát adja, nem a statisztikai bizonyíték erejét.

F2 megoldás. Adott S=sS=s mellett a két csatorna azonos válaszának valószínűsége az esélyek négyzetösszege:

q=0,72+0,152+0,152=0,49+0,0225+0,0225=0,535.q=0{,}7^2+0{,}15^2+0{,}15^2=0{,}49+0{,}0225+0{,}0225=0{,}535.

Mivel qq nem függ ss-től,

P(X1=X2)=0,535.P(X_1=X_2)=0{,}535.

A peremeloszlás egyenletes marad: tetszőleges ss-re

P(X1=s)=13⋅0,7+2⋅13⋅0,15=13.P(X_1=s)=\tfrac13\cdot0{,}7+2\cdot\tfrac13\cdot0{,}15=\tfrac13.

Ha a két nézet független lenne, akkor P(X1=X2)=∑s=02(1/3)2=1/3P(X_1=X_2)=\sum_{s=0}^{2}(1/3)^2=1/3 volna. Mivel 0,535≠1/30{,}535\neq 1/3, a nézetek nem függetlenek; a közös rejtett SS az együttes eloszlást a peremek szorzatától eltéríti.

F3 megoldás. A laborban szereplő violating_scenes függvény közvetlenül használható. Nem elég teljes jelenetek splitjét megváltoztatni: az nem sérti a csoportszintű feltételt. A teszthez két kiválasztott jelenetnek csak egy-egy nézetét kell az eredetitől eltérő splitbe tenni:

meta_test = meta.copy()
meta_test["split_bad"] = meta_test["split"]
selected = meta_test["scene_id"].unique()[:2]
for sid in selected:
    row_id = meta_test.index[meta_test["scene_id"] == sid][0]
    old_split = meta_test.loc[row_id, "split_bad"]
    meta_test.loc[row_id, "split_bad"] = (
        "test" if old_split != "test" else "train"
    )

# A hallgató saját split_integrity függvénye megegyezik a laborbeli
# violating_scenes működésével.
bad = violating_scenes(meta_test, "split_bad")
assert set(bad) == set(selected) and len(bad) == 2

Az ellenőrzés típusa halmaz-egyezés, nem lebegőpontos tolerancia: a jó oszlopra a lista üres, a hibásra pontosan a két módosított scene_id jelenik meg.

F4 megoldás. A hiba az, hogy a np.random.permutation a 360 képet függetlenül sorsolja, ezért a scene_id-t nem veszi figyelembe: ugyanazon scene két view-ja különböző splitbe kerülhet. Következmény: a testben olyan jelenetek jelennek meg, amelyek egy másik nézete a trainben szerepel; a teszt-accuracy felfelé torzul, mert a modell ismert geometriákra válaszol, nem új jelenetekre.

Javítás: először a scene-ekből kell permutációt készíteni, majd a scene-hez tartozó összes view-t együtt kell beosztani. Egy lehetséges vázlat:

def split_by_scene(meta, rng):
    scenes = meta["scene_id"].unique()
    perm = rng.permutation(len(scenes))
    split_map = {}
    for s in scenes[perm[:120]]:
        split_map[s] = "train"
    for s in scenes[perm[120:150]]:
        split_map[s] = "val"
    for s in scenes[perm[150:]]:
        split_map[s] = "test"
    meta["split_fixed"] = meta["scene_id"].map(split_map)
    return meta

Ellenőrzés: a violating_scenes(meta, "split_fixed") eredményének üresnek, a scene-számlálónak pedig train: 120, val: 30, test: 30 értékűnek kell lennie.

F5 megoldásminta. A megfigyelési egység továbbra is a scene, mert a time-lapse sorozat tagjai közös jelenetből erednek. A felosztást scene-szintű osztály-rétegzett permutációval végeznénk, például 120/30/30 jelenettel; egy scene összes nézete egy splitbe kerülne. Baseline-ként egy olyan modellt választanánk, amely a train-osztályok relatív gyakoriságát jósolja, vagy egy korábbi kis CNN-t; mert a kérdés az, hogy a jeleneten belüli plusz nézetek javítják-e az új jelenetekre való általánosítást.

Metrika: scene-szintű átlagos accuracy és per-kép accuracy, valamint páros ellenőrzés, hogy a testben nem szerepel olyan scene, amelynek bármely view-ja a trainben vagy a valben van. A szivárgás detektálására a split_integrity függvényt futtatnánk a végső metadata.csv-n. Fontos korlát: a per-kép accuracy felfelé torzíthat, ha a test sok nézetet tartalmaz ugyanarról a jelenetről; ezért a fő metrika a scene-szintű átlag, amely minden jelenetet egyenlő súllyal vesz.

1.3.8 Tipikus tévedés és ellenpélda

A leggyakoribb tévedés: a több kép több független megfigyelést jelent. A GlyphLab ellenpéldája: a 360 kép 180 jelenetből áll; ha képszintű splitet készítünk, várhatóan körülbelül 90 jelenetnek az egyik nézete a trainbe, a másik a testbe kerül. A teszt-accuracy ekkor olyan jeleneteken is mér, amelyeket a modell a trainből részben ismer; a magas érték nem az ismeretlen jelenetre való általánosítást, hanem a szivárgást mutatja. A képek darabszáma tehát a számítási költséget és a modellbemenetek számát jellemzi, nem a független bizonyítékok számát.

1.3.9 Önellenőrzés és visszahívás

  1. Tudja-e definiálni a csoportot anélkül, hogy a view_id-t használná?
  2. Miért nulla a jó splitre a violating_scenes függvény visszatérési listája?
  3. Mi a különbség a csoportszintű és a preprocessing-átszivárgás között?
  4. Miért nem elegendő a teszt-accuracy magas értéke a helyes split igazolására?

Az 1.2. Szakmailag megalapozott modellezési állítások visszahívása: az empirikus állítás típusa megköveteli az adatot, a splitet és a számítási protokollt. A most bevezetett scene_id azt a hiányzó protokoll-összetevőt adja meg, amely nélkül a pontossági számítás nem minősülhet megalapozott empirikus állításnak.

1.3.10 Rövid összefoglaló

A megfigyelési egység nem mindig azonos a modell bemenetével: a GlyphLabban a bemenet a kép, de a független egység a scene_id. A train/validation/test felosztás érvényességi feltétele, hogy egy csoport minden rekordja ugyanabba a splitbe kerüljön; ennek megsértése csoportszintű adatszivárgást okoz. A zajos nézetek függőségét diszkrét generatív modellel bizonyítottuk: a közös rejtett jelenet miatt az együttes eloszlás nem a peremek szorzata. A labor a jó splitre nulla, a képszintű hibás splitre várhatóan körülbelül 90 szabálytalan jelenetet mutat. A feladatok a fogalmi megkülönböztetést, a levezetést, az ellenőrző kódot, a hibás felosztás javítását és egy time-lapse transzferprotokollt fedik le.

Következő alkalmazás. Az 1.4. Tanulási diagnózis és kísérleti napló egységben a tanulási diagnózist és a kísérleti naplót építjük fel; a mostani split-ellenőrzés lesz a napló első kötelező rovata.

Források