20.3 Preprocessing, domain shift és súlylicenc

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

Egy előtanított modell helyes használatához három, egymástól független kérdést kell külön megválaszolni. (1) Kompatibilis előfeldolgozás: a bemenet reprezentációja (méret, crop, normalizálás, csatornarend, értéktartomány) megegyezik-e azzal, amit a súlycsomag elvár — ez dokumentációból ellenőrizhető szerződés. (2) Adatdomén-hasonlóság: mennyire hasonlít a saját adat a forrás tanítóadatához — ez nem garantálható, csak vizsgálható. (3) A target teljesítmény igazolása: a modell a saját adaton, külön target-domain értékeléssel mérve elég jó-e. Az első feltétel teljesülése nem jelenti a forrás- és a céladat azonos eloszlását, és az eloszláseltérés önmagában nem teszi szabálytalanná a transzfert; csak azt jelenti, hogy a (3) mérés nem hagyható ki. Ez az alfejezet ezt a preprocessing-szerződést járja körbe: resize/crop, normalizálás, csatornarend, a súlycsomag enumja és a felhasználási licenc. Előismeret a 20.2. Finomhangolás és tanulási ráta finomhangolási anyaga: a state_dict, a fagyasztott paraméterek és a BatchNorm-pufferek kezelése. A 19.3. EfficientNet és architektúraskálázás alfejezetben tárgyalt architektúra-skálázás és a 16.3. Elméleti és effektív recepciós mező alfejezetben levezetett recepciós mező itt abban tér vissza, hogy a bemeneti felbontás változtatása a háló belső aktivációinak térbeli méretét is megváltoztatja. Induló kérdés: Milyen konkrét ellenőrzésekkel dokumentálható, hogy a letöltött súlycsomag a mi adatunkon a várt bemeneti reprezentációt kapja, milyen korlátozott target-domain validáció kell a teljesítmény igazolásához, és megengedett-e jogilag a felhasználás?

20.3.2 Definíciók: preprocessing-szerződés, domain shift, licenc

Legyen X∈ℝN×Cin×H×WX \in \mathbb{R}^{N \times C_{in} \times H \times W} a nyers képadat batch, SS a tanító adateloszlás (source), TT a felhasználáskori eloszlás (target). Preprocessing-szerződésnek nevezzük azt a leképezést, amely a nyers pixelekből a háló által várt normalizált bemenetet állítja elő. Ez legalább négy komponenst tartalmaz:

  1. Geometriai előkészítés (resize, crop): a bemenet térbeli méretét (H,W)(H, W)-re állítja. A resize interpolációt, a crop térbeli kivágást jelent. A háló első rétegeinek recepciós mezője és a későbbi rétegek outputmérete a 14.1. Stride, padding, dilation és outputméret és 16.3. Elméleti és effektív recepciós mező képletei szerint a bemeneti mérettől függ, ezért a szerződésben rögzíteni kell a pontos értéket.
  2. Normalizálás (mean/std): minden csatornára egy (μc,σc)(\mu_c, \sigma_c) pár. A normalizált pixel: x′c=(xc−μc)/σcx'_c = (x_c - \mu_c)/\sigma_c.
  3. Csatornarend: a CinC_{in} csatornák sorrendje. PyTorchban NCHW, más keretrendszerekben gyakran NHWC; színképeknél RGB–BGR eltérés is előfordul.
  4. Értéktartomány: a bemeneti intenzitás skálája, például [0,1][0,1] vagy [0,255][0,255]. A normalizálás előtt a kettő közötti konverzió is a szerződés része.

Domain shift az az állapot, amikor S≠TS \neq T. Ez nem önmagában hiba, hanem mérhető eltérés: az adateloszlások különbözősége miatt a modell a target eloszláson rosszabb teljesítményt mutathat. A súlycsomag enumja a közzétett súlyváltozatok azonosítója (pl. IMAGENET1K_V1, DEFAULT). A licenc a súlyok felhasználási feltételeit rögzítő jogi dokumentum; a letölthető súly megléte nem jelenti azt, hogy tetszőleges célra felhasználható.

A torchvision dokumentációja [D13] a modellek betöltéséhez weights enum értékeket használ; a DEFAULT az adott könyvtárverzió ajánlott súlyát jelöli. A [D09] oktatóanyag a saját modell mentését és betöltését tárgyalja; ezt a saját kísérleti checkpointra alkalmazzuk. A [P06] a ResNet-reziduális blokkokat vezeti be; az olvasott 3.1–3.2 szakasz a y=F(x)+xy = F(x) + x rövidítést bizonyítja, de a cikk licencfeltételeire nem terjed ki.

20.3.3 Levezetési kötelezettség: két konkrét ellenpélda

20.3.3.1 Ellenpélda 1: rossz normalizálás megváltoztatja a modell kimenetét

Tegyük fel, hogy egy előtanított modell első rétege egy lineáris projekció Z=XW⊤+bZ = XW^\top + b alakban, ahol X∈ℝ1×dX \in \mathbb{R}^{1 \times d} egy bemeneti sorvektor, W∈ℝm×dW \in \mathbb{R}^{m \times d}, b∈ℝmb \in \mathbb{R}^{m}. A modell készítője a normalizálást (μ,σ)=(0.5,0.25)(\mu, \sigma) = (0.5, 0.25) értékekkel végezte, így a háló bemenete X′=(X−0.5)/0.25=4X−2X' = (X - 0.5)/0.25 = 4X - 2. A felhasználó ezt elhagyja, és nyers XX-et ad be. Ekkor a háló által számított érték Zraw=XW⊤+bZ_{raw} = XW^\top + b, míg a szerződés szerint Zexpected=(4X−2)W⊤+b=4XW⊤−2W⊤𝟏d+bZ_{expected} = (4X - 2)W^\top + b = 4XW^\top - 2W^\top \mathbf{1}_d + b, ahol 𝟏d\mathbf{1}_d a dd-dimenziós csupa egyes oszlopvektor. A különbség:

Zraw−Zexpected=XW⊤+b−(4XW⊤−2W⊤𝟏d+b)=−3XW⊤+2W⊤𝟏d.Z_{raw} - Z_{expected} = XW^\top + b - (4XW^\top - 2W^\top \mathbf{1}_d + b) = -3XW^\top + 2W^\top \mathbf{1}_d.

Ez általában nulla, csak ha XW⊤XW^\top éppen a konstans 2W⊤𝟏d/32W^\top\mathbf{1}_d/3 vektorral egyenlő. Legyen például d=2d = 2, W=(1,1)W = (1, 1), b=0b = 0, és X=(1,1)X = (1, 1). Ekkor Zraw=2Z_{raw} = 2, míg Zexpected=4⋅2−2⋅2=8−4=4Z_{expected} = 4 \cdot 2 - 2 \cdot 2 = 8 - 4 = 4. A modell más logitot ad, holott a bemenet ugyanaz a kép. Ez az ellenpélda mutatja: a normalizálás elhagyása nem egyszerűen skálázza a kimenetet, hanem a bias és a súly interakciója miatt el is tolja.

20.3.3.2 Ellenpélda 2: azonos elemszámú, de hibás csatornarend

Vegyünk egy 3×2×23 \times 2 \times 2 méretű színképet, azaz Cin=3C_{in}=3, H=W=2H=W=2. A csatornák sorrendje PyTorchban NCHW: a tensor [1, 3, 2, 2] alakú. Egy másik könyvtárból érkező adat lehet NHWC, [1, 2, 2, 3]. Ha a felhasználó az NHWC tensort NCHW-ként adja be, akkor a [1, 2, 2, 3] alakot a háló úgy értelmezi, mint N=1N=1, C=2C=2, H=2H=2, W=3W=3 bemenetet. Ez egyrészt shape-hiba, másrészt ha a csatornák száma véletlenül egybeesik (például egy RGB kép és egy BGR kép esetén mindkettő C=3C=3), akkor a shape egyezik, de a csatornák sorrendje fel van cserélve. Legyen az eredeti RGB pixel: p=(r,g,b)=(0.9,0.1,0.2)p = (r, g, b) = (0.9, 0.1, 0.2). A BGR sorrendben ugyanez a pixel (0.2,0.1,0.9)(0.2, 0.1, 0.9). A modell első konvolúciós rétegének súlyai az RGB csatornákra vannak tanítva; a BGR sorrendben beadott bemenet esetén az első csatorna súlyai a kék intenzitást látják, a harmadiké a vöröset. A kimeneti aktivációk megváltoznak, mégpedig nem pusztán a csatornák felcserélésével, mert a rákövetkező rétegek súlyai is fixek. Az elemszám (3 csatorna) és a tensor mérete azonos, de a szemantikai tartalom hibás. Ez a példa azt igazolja, hogy a csatornarend nem redukálható a csatornák darabszámára.

20.3.4 Kézi számpélda és ellenőrző kód

Tekintsünk egy 1×11 \times 1 méretű, 3 csatornás képet: x=[0.8,0.4,0.6]x = [0.8, 0.4, 0.6]. A szerződés szerint a normalizálás μ=[0.485,0.456,0.406]\mu = [0.485, 0.456, 0.406], σ=[0.229,0.224,0.225]\sigma = [0.229, 0.224, 0.225]. Számoljuk ki a normalizált értékeket:

x′0=(0.8−0.485)/0.229≈1.3755,x′1=(0.4−0.456)/0.224≈−0.25,x′2=(0.6−0.406)/0.225≈0.8622.x'_0 = (0.8 - 0.485)/0.229 \approx 1.3755, \quad x'_1 = (0.4 - 0.456)/0.224 \approx -0.25, \quad x'_2 = (0.6 - 0.406)/0.225 \approx 0.8622.

Python-ellenőrzés (a művelet egyszerű, NumPy-val vagy PyTorch-kal is elvégezhető; az alábbi kód futtatható a lab/ munkakönyvtárból, de e kéziratban a kimenet kézzel számított):

# lab/preprocess_check.py
import numpy as np

x = np.array([0.8, 0.4, 0.6], dtype=np.float32)
mu = np.array([0.485, 0.456, 0.406], dtype=np.float32)
sigma = np.array([0.229, 0.224, 0.225], dtype=np.float32)

x_norm = (x - mu) / sigma
print(x_norm)  # [1.3755..., -0.25, 0.8622...]

Ha a felhasználó a normalizálást kihagyja, és a nyers [0.8,0.4,0.6][0.8, 0.4, 0.6]-t adja be, az első réteg lineáris transzformációja eltér a várttól, ahogy az 1. ellenpéldában láttuk. A csatornarend ellenőrzésére hasonlóan: ha a képet NHWC-ból NCHW-ba konvertáljuk, ügyelni kell arra, hogy a transpose ne cserélje fel a színcsatornákat. Egy RGB → BGR hiba reprodukálásához elegendő a csatornák indexelését megfordítani: x_bgr = x[::-1]. A két vektor elemszáma azonos, de a kép szemantikailag más.

20.3.5 Labor: súly-adat-preprocessing eredetjegyzék és licencellenőrzés

A labor célja egy súly-adat-preprocessing eredetjegyzék elkészítése a GlyphLab saját adataira és egy lehetséges külső súlycsomagra. A GlyphLab adatok a 20.3. Preprocessing, domain shift és súlylicenc előtti fejezetekben már ellenőrzöttek: data/images.npy alakja (360, 1, 24, 24), float32, intenzitás [0,1][0,1], grayscale. Mivel a saját modellünk ezen az adaton tanult, a preprocessing-szerződés a következő:

Ezt a szerződést a lab/preprocessing_ledger.md fájlban rögzítjük. A fájl tartalma:

# Preprocessing eredetjegyzék – GlyphLab

| Mező | Érték | Ellenőrzés |
|---|---|---|
| Adatfájl | data/images.npy | quickcheck.py |
| Alak | (360, 1, 24, 24) | kézi |
| dtype | float32 | kézi |
| Intenzitás tartomány | [0, 1] | min/max |
| Normalizálás | nincs | – |
| Csatornarend | NCHW | – |
| Súly forrása | saját training, seed 20261020 | state_dict |
| Licenc | saját, oktatási célra | – |

A licencellenőrzéshez a külső súlyok esetén a [D13] és [P06] forrásokat kell használni. Az előtanított súlyok felhasználási feltételei modell- és súlyváltozatonként eltérhetnek; a pontos licencet a modellkatalógus és a súlyok eredeti forrása alapján, még letöltés vagy továbbadás előtt kell rögzíteni. A puszta katalógusbeli elérhetőség nem jelent korlátlan újrafelhasználási jogosultságot. A [P06] cikk esetében a licenc a preprintben nem szerepel; az arXiv-rekord nem jogi licencnyilatkozat. A labor átadandó eredménye tehát:

  1. A fenti preprocessing_ledger.md fájl elkészítése.
  2. Egy külön license_checks.md fájl, amely rögzíti: (a) a GlyphLab saját súlyainak oktatási felhasználása; (b) a torchvision modellkatalógus [D13] felhasznált súlyváltozatának licencfeltételeit; (c) a [P06] licenc hiányának megjelölését.

Fontos: a felhasználó a saját modelljének súlyát szabadon használhatja oktatási célra, de a külső súlyokra vonatkozó állításokat csak a forrás tényleges elolvasása után tehet. A labor kódja nem igényel külső letöltést; a GlyphLab adatok helyben elérhetők. Az alábbi ellenőrző kód a preprocessing-szerződés betartását teszteli:

# lab/check_preprocessing.py
import numpy as np
from pathlib import Path

images = np.load(Path("data/images.npy"))
assert images.dtype == np.float32
assert images.shape == (360, 1, 24, 24)
assert images.min() >= 0.0 and images.max() <= 1.0
print("Preprocessing-szerződés teljesül: [0,1], float32, NCHW, alak (360,1,24,24)")

Ez a kód a lab/ könyvtárból futtatható: python check_preprocessing.py. Az output a fenti kiírás; mivel a kéziratban nem futtattuk, az állítást a quickcheck.py korábbi ellenőrzött jelentésére alapozzuk.

20.3.6 F1–F5 feladatok

F1 – fogalmi (1. nehézség). Mit jelent a preprocessing-szerződés négy komponense? Adjon mindegyikre egy-egy mondatos magyarázatot, és egy példát, ahol a komponens elhanyagolása hibához vezet.

F2 – levezetési (2. nehézség). Legyen az előtanított modell első rétege Z=XW⊤+bZ = XW^\top + b a fenti 1. ellenpélda szerint, d=3d=3, W=(1,1,1)W = (1,1,1), b=0b = 0, normalizálás (μ,σ)=(0.5,0.25)(\mu, \sigma) = (0.5, 0.25). A felhasználó bemenete X=(0.5,0.5,0.5)X = (0.5, 0.5, 0.5). Számítsa ki ZrawZ_{raw}-t és ZexpectedZ_{expected}-et, és igazolja a különbséget.

F3 – implementációs (2. nehézség). Írjon Python-kódot (lab/preprocess_normalize.py), amely beolvassa a GlyphLab data/images.npy-t, ellenőrzi a dtype-ot és a tartományt, majd alkalmazza a (μ=0.5,σ=0.25)(\mu=0.5, \sigma=0.25) normalizálást, és elmenti data/images_norm.npy néven. A kód tartalmazzon input-ellenőrzést és a kimenet min/max értékének kiírását.

F4 – hibakeresési (3. nehézség). A következő kód egy külső súlyú modell betöltését végzi, de hibás. Azonosítsa a hibát, és magyarázza el, miért okoz domain shiftet.

import torch
from torchvision import models

model = models.resnet18(weights=None)
model.load_state_dict(torch.load("weights.pth", weights_only=True))
# A felhasználó saját adatai NHWC formátumban, [0,255] uint8.
image = torch.randint(0, 256, (1, 224, 224, 3), dtype=torch.uint8)
image = image.permute(0, 3, 1, 2).float()
logits = model(image)

F5 – kísérlettervezési (2. nehézség). Tervezzen egy kísérletet, amely a GlyphLab adatokon méri a domain shift hatását, ha a bemeneti intenzitásokat lineárisan átskálázzuk [0,1][0,1]-ből [0,0.5][0,0.5]-be. Adja meg a protokollt, a baseline-t, a metrikát és az érvényességi korlátot. (A futást nem kell elvégezni, csak a tervet leírni.)

20.3.7 Megoldások és indoklás

F1. A négy komponens: (1) geometriai előkészítés – a térbeli méret rögzítése; hiba: ha a háló 224×224-re tanult, és 32×32-t kap, a láncolt recepciós mezők miatt az outputméret eltér. (2) Normalizálás – a csatornánkénti közép és szórás levonása; hiba: a nyers [0,255] bemenet a bias interakciója miatt eltolja a logiteket (1. ellenpélda). (3) Csatornarend – NCHW vs NHWC; hiba: RGB–BGR felcserélés azonos elemszám mellett más színinformációt ad. (4) Értéktartomány – [0,1] vagy [0,255]; hiba: a skálázás elmaradása nagy gradienst és rossz logiteket okoz.

F2. A normalizálás: X′=(X−0.5)/0.25=4X−2X' = (X - 0.5)/0.25 = 4X - 2. X=(0.5,0.5,0.5)X = (0.5,0.5,0.5) esetén Zraw=XW⊤+b=0.5+0.5+0.5=1.5Z_{raw} = XW^\top + b = 0.5+0.5+0.5 = 1.5. X′=(4⋅0.5−2,4⋅0.5−2,4⋅0.5−2)=(0,0,0)X' = (4\cdot0.5-2, 4\cdot0.5-2, 4\cdot0.5-2) = (0,0,0). Ekkor Zexpected=0⋅W⊤+b=0Z_{expected} = 0 \cdot W^\top + b = 0. A különbség Zraw−Zexpected=1.5−0=1.5Z_{raw} - Z_{expected} = 1.5 - 0 = 1.5. Ez megegyezik a korábbi levezetéssel: −3XW⊤+2W⊤𝟏d=−3⋅1.5+2⋅3=−4.5+6=1.5-3XW^\top + 2W^\top\mathbf{1}_d = -3 \cdot 1.5 + 2 \cdot 3 = -4.5 + 6 = 1.5. Alakellenőrzés: ZZ skalár, mert m=1m=1.

F3. A kód:

# lab/preprocess_normalize.py
import numpy as np
from pathlib import Path

images = np.load(Path("data/images.npy"))
assert images.dtype == np.float32
assert images.min() >= 0.0 and images.max() <= 1.0
mu, sigma = 0.5, 0.25
norm = (images - mu) / sigma
assert norm.dtype == np.float32
print(f"Eredeti tartomány: [{images.min():.3f}, {images.max():.3f}]")
print(f"Normalizált tartomány: [{norm.min():.3f}, {norm.max():.3f}]")
np.save(Path("data/images_norm.npy"), norm)

Ellenőrzés: a bemenet [0,1][0,1] tartománya miatt a normalizált értékek [−2,2][-2,2] közé esnek; a kód ezt kiírja.

F4. A kód hibája: a betöltött weights.pth egy olyan modellhez tartozik, amely 224×224-es, [0,1]-re normalizált, RGB csatornájú bemenetre lett tanítva. A felhasználó (a) NHWC → NCHW konverziót végez, de nem biztos, hogy a csatornasorrend RGB marad; (b) a [0,255] uint8 értékeket közvetlenül floatra konvertálja, normalizálás nélkül, így a modell [0,255]-es bemenetet kap, ami domain shiftet okoz: a tanítóeloszlás SS [0,1], a cél TT [0,255]. A hiba javítása: a bemenetet image.float() / 255.0 értékre skálázni, majd a szerződött mean/std párt alkalmazni. A csatornasorrend ellenőrzéséhez meg kell nézni a modell dokumentációját.

F5. Kísérleti terv: (a) baseline a GlyphLab teszt splitjén a már betanított CNN pontossága. (b) Kezelés: a tesztképek intenzitásait átskálázzuk x↦0.5xx \mapsto 0.5x, így a tartomány [0,0.5][0,0.5]-re szűkül. (c) Metrika: per-image pontosság és macro-F1 a három osztályra. (d) Protokoll: ugyanaz a modell, ugyanaz a split, rögzített seed a szorzásnál. (e) Érvényességi korlát: a GlyphLab adat szintetikus, így az eredmény nem általánosítható természetes képekre; a mérés nem bizonyítja, hogy a modell más domainen is hasonlóan romlik.

20.3.8 Tipikus tévedés és ellenpélda

A leggyakoribb tévedés: „A letölthető súly automatikusan minden célra engedélyezett modell.” Ez hamis. A súlyok letöltése nem jogosít fel semmilyen felhasználásra; a licencet a terjesztő határozza meg. Például a torchvision súlyaihoz tartozó licencfeltételek a [D13] modellkatalógusban vannak felsorolva, de ezek nem mindig azonosak a modell kutatási cikkének licencével. A [P06] ResNet esetében az arXiv preprint nem tartalmaz licencszekciót; a modellekhez tartozó súlylicenceket külön kell ellenőrizni. Ha valaki kereskedelmi termékben használja a súlyokat anélkül, hogy elolvasta volna a licencet, jogi és etikai hibát követ el. A helyes eljárás: a súlycsomag mellékelt LICENSE fájlját vagy a terjesztő weboldalán feltüntetett feltételeket kell alapul venni, és rögzíteni az eredetjegyzékben.

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

Ellenőrizze: (1) a bemeneti tensor alakja NCHW, dtype float32, tartomány [0,1]; (2) ha a szerződés normalizálást ír elő, akkor a mean és std vektorok csatornánkénti sorrendje megegyezik a súlyok tanításakor használttal; (3) a licencre vonatkozó állításokat csak forrás alapján tegye, és a hiányzó ellenőrzéseket jelölje meg. Visszahívás a 20.2. Finomhangolás és tanulási ráta alfejezetből: a state_dict betöltésekor a weights_only=True opció csökkenti a deszerializációs kockázatot, de nem oldja meg a preprocessing problémát; a BatchNorm-pufferek eval módban a tanítás alatt frissített running statisztikákat használják.

20.3.10 Rövid összefoglaló

A preprocessing-szerződés rögzíti a bemeneti képek geometriai méretét, normalizálását, csatornarendjét és értéktartományát. A rossz normalizálás és az azonos elemszámú, de hibás csatornarend konkrét ellenpéldákkal igazolhatóan megváltoztatja a modell kimenetét. A domain shift a forrás- és céleloszlás eltérését jelenti, és a teljesítmény romlásához vezethet; a helyes előfeldolgozás ezt nem zárja ki, ezért a target teljesítményt külön, a céladaton kell mérni. A súlycsomag enumja azonosítja a súlyváltozatot, a licenc pedig a felhasználás feltételeit; a letölthető súly nem automatikusan engedélyez minden célra. A laborban elkészítettük a GlyphLab adatokra az eredetjegyzéket, és a külső súlyok licencellenőrzését forrásalapú feladatként jelöltük meg.

20.3.11 Következő alkalmazás és források

A következő egység 20.4. V. ellenőrző pont: indokolt CNN-választás az V. ellenőrző pont: ott a teljes CNN-választást indokolni kell, a preprocessing-szerződést, a finomhangolási stratégiát és a licencet együttesen kell értékelni. Források: [D09] PyTorch contributors (2.14). Saving and Loading Models. https://docs.pytorch.org/tutorials/beginner/saving_loading_models.html – state_dict, eval és weights_only; [D13] PyTorch contributors (online). TorchVision models. https://docs.pytorch.org/vision/stable/models.html – modellkatalógus; [P06] He, Zhang, Ren és Sun (2015). Deep Residual Learning for Image Recognition. https://arxiv.org/abs/1512.03385 – reziduális blokk, 3.1–3.2 szakasz olvasva; a licenc nem ellenőrzött.