25.3 Kis adat és feature-alapú baseline

25.3.1 Cél, előismeret és induló becslés

Ez az egység azt a mérnöki kérdést rendezi szerződésbe, hogy egy CNN milyen referenciapontokhoz képest nevezhető jobbnak kis, saját adaton. Előismeret a 25.2. Multilabel és hierarchikus cél alfejezetből: a kölcsönösen kizáró osztályokhoz softmax és int64 osztályindex-cél tartozik, a multilabel BCE ettől különböző feladat. Visszahívjuk a 24.3. Mixed precision és nagyobb batch alfejezetből, hogy f32 CPU-n teljes batch esetén nincs szükség gradiensakkumulációra, a 21.3. Dropout, súlyregularizálás és augmentáció alfejezetből, hogy a regularizálás nem univerzális győzelmi trükk, a 13.3. Lokális szűrők és tanult reprezentáció alfejezetből pedig, hogy a szűrőtanulás lokális feature mapokat hoz létre.

Induló becslés. A kezdő GlyphLab adat kiegyensúlyozott: trainen osztályonként 80, val/testen 20–20 kép van. A többségi osztályt tippelő modell tehát körülbelül 1/31/3 testpontosságot ér el; a lineáris pixelmodell ennél többet tudhat, mert a három orientáció képstatisztikája eltérő. Egy kis CNN előnyét csak akkor érdemes állítani, ha a két egyszerűbb referencia fölé megy, és a mérés a val–test protokollt betartja.

25.3.2 Baseline-ok és a reprezentáció–classifier elválasztás

Majority classifier. Legyen a tanító célvektor ytr∈{0,…,K−1}Ntry_{tr}\in\{0,\ldots,K-1\}^{N_{tr}}. A tanító osztálygyakoriság

p̂k=1Ntr∑n=1Ntr𝟏[ytr,n=k],k∈{0,…,K−1}. \hat p_k=\frac1{N_{tr}}\sum_{n=1}^{N_{tr}} \mathbf1[y_{tr,n}=k],\qquad k\in\{0,\ldots,K-1\}.

A majority classifier minden bemenetre ugyanazt az osztályt adja:

gmaj(x)=argmaxkp̂k. g_{\mathrm{maj}}(x)=\arg\max_k \hat p_k .

Ez a definíció döntetlen esetén hiányos, ezért rögzítjük a könyv konvencióját: ha több maximális gyakoriságú osztály van, a legkisebb indexet választjuk. A GlyphLab train kiegyensúlyozott, tehát p̂k=1/3\hat p_k=1/3 minden kk-ra, és a döntetlen-szabály a 0 osztályt adja. A test is kiegyensúlyozott, ezért a várhatóan használt 20/60=1/320/60=1/3 pontosság itt nem becsült zaj, hanem a felépítésből következő determinisztikus érték. A modellnek nulla tanulható paramétere van; ez benchmark-padló, nem előrejelző rendszer.

Linear pixel baseline. A bemeneti kép X∈ℝN×1×24×24X\in\mathbb R^{N\times 1\times 24\times 24}, intenzitás [0,1][0,1], float32 NCHW. A pixelmodell először ellapítja a képet d=1⋅24⋅24=576d=1\cdot24\cdot24=576 elemű vektorrá, majd egy affin réteget alkalmaz:

Z=XflatW⊤+b,Xflat∈ℝN×576,W∈ℝK×576,b∈ℝK. Z = X_{\mathrm{flat}} W^\top + b,\qquad X_{\mathrm{flat}}\in\mathbb R^{N\times576},\quad W\in\mathbb R^{K\times576},\quad b\in\mathbb R^{K}.

A tanításhoz [D07] szerint logits bemenetű cross-entropy-t használunk, int64 céllal, softmax nélkül a modell végén. Ez ekvivalens egy 1×11\times1 magméretű, 576 bemeneti és KK kimeneti csatornás konvolúciós réteggel: nincs lokális recepciós mező, csak csatornánkénti lineáris pontozás. A paraméterszám 576K+K=576⋅3+3=1731576K+K=576\cdot3+3=1731. A modell képes megtanulni minden lineárisan szeparálható pixelkombinációt, de nem tudja a képen belüli eltolást vagy lokális mintázatot reprezentálni.

Frozen features. Rögzítsünk egy fW:ℝ1×H×W→ℝdzf_W:\mathbb R^{1\times H\times W}\to\mathbb R^{d_z} feature-extractort, például egy CNN konvolúciós törzsét. A kimenet z=fW(x)∈ℝdzz=f_W(x)\in\mathbb R^{d_z}. A tanulható fej ekkor s=Vz+bs=Vz+b, ahol V∈ℝK×dzV\in\mathbb R^{K\times d_z}, b∈ℝKb\in\mathbb R^K. A „frozen” azt jelenti, hogy WW-t a tanítás során konstansnak tekintjük: a fej paramétereit frissítjük, a törzs paramétereit nem. Ez a transzfertanulás egyik szélső ága: külső vagy véletlen súlyú törzsből kinyert jellemzőkre lineáris osztályozót tanítunk. A másik ág a finomhangolás, ahol WW-t és VV-t együtt frissítjük, általában kisebb tanulási rátával.

A reprezentáció–classifier elválasztó összehasonlítás. A következő lemma pontosan megadja, mely gradiens-tagok különböztetik meg a fej tanulását a reprezentáció tanulásától.

Lemma (fej–törzs gradiens egy batch-lépésben). Legyen zn=fW(xn)∈ℝdzz_n=f_W(x_n)\in\mathbb R^{d_z}, sn=Vzn+b∈ℝKs_n=Vz_n+b\in\mathbb R^K és ℓn=ℓ(sn,yn)\ell_n=\ell(s_n,y_n) differenciálható veszteség. Legyen ℓ=1N∑nℓn\ell=\frac1N\sum_n\ell_n, és jelölje gn=∇snℓn∈ℝKg_n=\nabla_{s_n}\ell_n\in\mathbb R^K. Ha W↦fW(x)W\mapsto f_W(x) a könyvtár gradienskonvenciójával differenciálható (a ReLU töréspontokon a 9.4. Aktivációk és gradiensáramlás alfejezetben rögzített konvencióval), akkor

∇Vℓ=1N∑n=1Ngnzn⊤∈ℝK×dz,∇bℓ=1N∑n=1Ngn∈ℝK, \nabla_V\ell=\frac1N\sum_{n=1}^N g_n z_n^\top\in\mathbb R^{K\times d_z}, \qquad \nabla_b\ell=\frac1N\sum_{n=1}^N g_n\in\mathbb R^K,

és a 4.3. Jacobi-mátrix, láncszabály és VJP Jacobi-oszlopkonvencióval, JfW(xn)∈ℝdz×dim(W)J_{f_W}(x_n)\in\mathbb R^{d_z\times\dim(W)} mellett

∇Wℓ=1N∑n=1NJfW(xn)⊤V⊤gn. \nabla_W\ell=\frac1N\sum_{n=1}^N J_{f_W}(x_n)^\top V^\top g_n .

Levezetés. A logitok sn,i=∑r=1dzVirzn,r+bis_{n,i}=\sum_{r=1}^{d_z} V_{ir}z_{n,r}+b_i. Rögzített znz_n mellett ∂sn,i/∂Vjr=δijzn,r\partial s_{n,i}/\partial V_{jr}=\delta_{ij}z_{n,r}, ezért a láncszabályból

∂ℓn∂Vjr=∑i=1K∂ℓn∂sn,i∂sn,i∂Vjr=∑ign,iδijzn,r=gn,jzn,r, \frac{\partial\ell_n}{\partial V_{jr}} =\sum_{i=1}^K \frac{\partial\ell_n}{\partial s_{n,i}} \frac{\partial s_{n,i}}{\partial V_{jr}} =\sum_i g_{n,i}\delta_{ij}z_{n,r} =g_{n,j}z_{n,r},

tehát ∇Vℓn=gnzn⊤\nabla_V\ell_n=g_nz_n^\top. A biasra ∂sn,i/∂bj=δij\partial s_{n,i}/\partial b_j=\delta_{ij}, így ∇bℓn=gn\nabla_b\ell_n=g_n. A WW-függés csak zn=fW(xn)z_n=f_W(x_n)-en át lép be; a zz-gradiens a lineáris fej VJP-je, ∇zℓn=V⊤gn\nabla_z\ell_n=V^\top g_n, majd a 4.3. Jacobi-mátrix, láncszabály és VJP láncszabály adja a JfW(xn)⊤V⊤gnJ_{f_W}(x_n)^\top V^\top g_n alakot. Az átlag linearitása hozza a batch-átlagokat. ▫\square

A fagyasztott ágban ∇Wℓ\nabla_W\ell nem kerül az optimizer frissítési szabályába, ezért a WW pályája konstans W0W_0; a trainable ágban ugyanez a tag elmozdítja WW-t. Ha a két ág azonos W0W_0 törzzsel, azonos kezdeti fejjel, azonos adatsorrenddel és azonos head-optimizerbeállítással indul, akkor a predikciók különbsége a WW elmozdulásához köthető. Ez kísérleti kontroll, nem kauzális bizonyíték: azt méri, hogy az adott recept alatt a reprezentáció megváltoztatása javít-e a validációs metrikán.

Kis adat és transzfer. A GlyphLab train 240 kép, de csak 120 független scene_id; a megfigyelési egység a jelenet, nem a kép. A test 6060 képen egy képnyi tévesztés 1/60≈0,01671/60\approx0{,}0167 pontosságkülönbség, ezért a három modell közti kis eltérést scene-szintű páros összehasonlítással kell értelmezni 23.3. Páros összehasonlítás és csoportszintű intervallum. Külső előtanított súlyt itt nem használunk a laborban; a transzfer fogalmilag az fW0f_{W_0} törzs külső súlyból való indulását jelenti, és a fagyasztott–finomhangolt két ág ugyanazt a protokollt követi. A [D13] modellkatalógus konkrét súlyverzióját, preprocessingjét és licencét külön kell ellenőrizni, mielőtt transzfer-eredményt közlünk.

25.3.3 Kézi gradienspélda és minimális ellenőrzés

Legyen egyetlen példa, z=(3,−1)⊤z=(3,-1)^\top, V=0∈ℝ2×2V=0\in\mathbb R^{2\times2}, b=0∈ℝ2b=0\in\mathbb R^2, cél y=0y=0. Ekkor a logit s=(0,0)s=(0,0), a softmax p=(1/2,1/2)p=(1/2,1/2), és a cross-entropy ss-gradiense g=p−e0=(−1/2,1/2)g=p-e_0=(-1/2,1/2). A lemma szerint

∇VL=gz⊤=(−1212)(3−1)=(−321232−12),∇bL=(−1212). \nabla_V L = gz^\top = \begin{pmatrix} -\frac12\\[2pt]\frac12 \end{pmatrix} \begin{pmatrix}3&-1\end{pmatrix} = \begin{pmatrix} -\frac32 & \frac12\\[3pt] \frac32 & -\frac12 \end{pmatrix}, \qquad \nabla_b L = \begin{pmatrix} -\frac12\\[2pt]\frac12 \end{pmatrix}.

A fagyasztott törzsnél ∇WL\nabla_WL nem létezik frissítési tagként, mert zz bemeneti konstansként viselkedik a fej számára. Ha zz-t egy fW(x)=Wxf_W(x)=Wx törzs állítaná elő, a trainable ágban JfW(x)=x⊤J_{f_W}(x)=x^\top és ∇WL=x(V⊤g)⊤\nabla_WL = x(V^\top g)^\top is fellépne.

Minimális ellenőrző kód (# munkakönyvtár: lab/, az alábbi kimenet a képletekből számított elvárt érték, nem most végrehajtott futás):

import torch
import torch.nn.functional as F

z = torch.tensor([[3.0, -1.0]])
V = torch.zeros(2, 2, requires_grad=True)
b = torch.zeros(2, requires_grad=True)

s = z @ V.T + b          # logits: (1,2)
loss = F.cross_entropy(s, torch.tensor([0]))
loss.backward()

print(V.grad)
# elvárt: tensor([[-1.5000,  0.5000],
#                 [ 1.5000, -0.5000]])
print(b.grad)
# elvárt: tensor([-0.5000,  0.5000])

25.3.4 Labor: majority, lineáris pixel, fagyasztott és tanuló CNN

Protokoll a futás előtt. Kérdés: a tanuló CNN jobb validációs/testpontosságot ér-e el a GlyphLab orientációfeladaton, mint a majority, a lineáris pixel és a fagyasztott véletlen törzsű lineáris fej? Hipotézis: a lineáris pixel a geometriai intenzitáskülönbségek miatt a majority fölé kerül; a fagyasztott véletlen törzs rossz reprezentációt ad, ezért a fej önmagában keveset tanul; a trainable CNN a W elmozdításával ezt felülmúlja. Adat: a GlyphLab data/images.npy, data/labels.npy, data/metadata.csv; a maszk és doboz nem modellbemenet. Megfigyelési egység: scene_id; a pontosság-pontbecslés mellett a szignifikanciavizsgálat a 23.3. Páros összehasonlítás és csoportszintű intervallum páros, scene-bootstrap kerete. Változó tényező: reprezentációs kapacitás. Metrika: accuracy; a legjobb checkpoint kiválasztása kizárólag a val spliten történik, a testet utána egyszer értékeljük. Keret: CPU, float32, seed 20261020, teljes batch, Adam lr=1e-2, 30 epoch.

A labor CNN-je az ebben az egységben definiált SmallCNN: Conv2d(1,8,3,padding=1), ReLU, MaxPool2d(2), Conv2d(8,16,3,padding=1), ReLU, MaxPool2d(2), Flatten, Linear(576,3). Paraméterszám 2979; a lineáris pixelmodellé 1731. A fagyasztott ág a CNN törzsét rögzíti, és csak a 576→3576\to3 fejet tanítja.

# Munkakönyvtár: lab/
# Futás: cd lab && python baseline_compare.py
import copy
import csv
import json

import numpy as np
import torch
import torch.nn as nn

SEED = 20261020
DEVICE = torch.device("cpu")


def load_glyphlab(base="data"):
    X = np.load(f"{base}/images.npy")          # (360,1,24,24), float32
    y = np.load(f"{base}/labels.npy")          # (360,), int64
    with open(f"{base}/metadata.csv", newline="") as f:
        rows = list(csv.DictReader(f))
    split = np.array([r["split"] for r in rows])
    idx = {k: split == k for k in ("train", "val", "test")}
    assert X.shape == (360, 1, 24, 24) and X.dtype == np.float32
    assert np.all((X >= 0.0) & (X <= 1.0))
    assert y.dtype == np.int64
    return X, y, idx


def majority_accuracy(y_tr, y_te):
    counts = np.bincount(y_tr)
    pred = int(np.flatnonzero(counts == counts.max())[0])  # legkisebb index donor
    return float((y_te == pred).mean())


class LinearPixel(nn.Module):
    def __init__(self, d=24 * 24, K=3):
        super().__init__()
        self.fc = nn.Linear(d, K)

    def forward(self, x):
        return self.fc(x.flatten(1))


class SmallCNN(nn.Module):
    def __init__(self, K=3):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 8, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(8, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2))
        self.head = nn.Sequential(nn.Flatten(), nn.Linear(16 * 6 * 6, K))

    def forward(self, x):
        return self.head(self.features(x))


def train_model(model, x_tr, y_tr, x_va, y_va, x_te, y_te, epochs=30, lr=1e-2):
    opt = torch.optim.Adam(model.parameters(), lr=lr)
    lossf = nn.CrossEntropyLoss()
    best_va, best_state = -1.0, None
    for _ in range(epochs):
        model.train()
        opt.zero_grad()
        loss = lossf(model(x_tr), y_tr)
        loss.backward()
        opt.step()
        model.eval()
        with torch.no_grad():
            va = (model(x_va).argmax(1) == y_va).float().mean().item()
        if va > best_va:
            best_va, best_state = va, copy.deepcopy(model.state_dict())
    model.load_state_dict(best_state)
    model.eval()
    with torch.no_grad():
        te = (model(x_te).argmax(1) == y_te).float().mean().item()
    return best_va, te


torch.manual_seed(SEED)
X, y, idx = load_glyphlab()
TX = torch.as_tensor(X, dtype=torch.float32, device=DEVICE)
TY = torch.as_tensor(y, dtype=torch.int64, device=DEVICE)
trn, va, te = idx["train"], idx["val"], idx["test"]

maj_va = majority_accuracy(y[trn], y[va])
maj_te = majority_accuracy(y[trn], y[te])
assert abs(maj_te - 1.0 / 3.0) < 1e-6

# Lineáris pixelmodell.
torch.manual_seed(SEED)
lin = LinearPixel().to(DEVICE)
lin_va, lin_te = train_model(lin, TX[trn], TY[trn], TX[va], TY[va], TX[te], TY[te])

# Tanuló CNN: azonos W0 törzs és fej.
torch.manual_seed(SEED)
cnn = SmallCNN().to(DEVICE)
w0 = copy.deepcopy(cnn.features.state_dict())
head0 = copy.deepcopy(cnn.head[1].state_dict())
cnn_va, cnn_te = train_model(cnn, TX[trn], TY[trn], TX[va], TY[va], TX[te], TY[te])

# Fagyasztott törzs; a fej kezdőpontja azonos a CNN fejével.
head = nn.Linear(576, 3).to(DEVICE)
head.load_state_dict(copy.deepcopy(head0))
with torch.no_grad():
    Ztr = cnn.features(TX[trn]).flatten(1)
    Zva = cnn.features(TX[va]).flatten(1)
    Zte = cnn.features(TX[te]).flatten(1)
# Kezdeti predikció-egyezés a tanuló CNN-nel.
with torch.no_grad():
    cnn.eval()
    full0 = cnn(TX[te])
    frozen0 = head(Zte)
assert torch.allclose(full0, frozen0, atol=1e-6)
# Törzs súlya nem változhat a fagyasztott ág tanítása alatt.
w_before = copy.deepcopy(cnn.features.state_dict())
frozen_va, frozen_te = train_model(head, Ztr, TY[trn], Zva, TY[va], Zte, TY[te])
assert all(torch.equal(w_before[k], cnn.features.state_dict()[k]) for k in w_before)

result = {
    "majority_val_acc": maj_va,
    "majority_test_acc": maj_te,
    "linear_pixel_val_acc": lin_va,
    "linear_pixel_test_acc": lin_te,
    "frozen_cnn_val_acc": frozen_va,
    "frozen_cnn_test_acc": frozen_te,
    "trainable_cnn_val_acc": cnn_va,
    "trainable_cnn_test_acc": cnn_te,
    "cnn_params": sum(p.numel() for p in cnn.parameters()),
    "linear_params": sum(p.numel() for p in lin.parameters()),
}
print(json.dumps(result, indent=2))

A kimeneti séma szemléltetése, nem ennek a kéziratnak az írásakor futott mérés:

Baseline-összehasonlítás (jelölt szemléltetés) - A laborkód által kiszámítandó, itt jelölt szemléltető értékek; nem mért futás eredménye.

Eredményértelmezés. A majority padlója 1/31/3. A lineáris pixelmodell és a CNN közti különbséget a fagyasztott ág teszi értelmezhetővé: ha a fej a rögzített véletlen W0W_0 jellemzőkből alig tanul, a trainable CNN többlete a reprezentáció elmozdulásából származik. Ha a lineáris pixelmodell a CNN közelében vagy fölötte van, az nem hiba: a GlyphLab feladat lineáris intenzitásjele erős, és ebből a szintetikus eredményből nem következtetünk természeteskép-teljesítményre.

25.3.5 Feladatok

F1 (fogalmi, nehézség 1). Mi a majority classifier döntetlen-konvenciója ebben a könyvben, és miért pontosan 1/31/3 a GlyphLab testpontossága? Adjunk olyan train–test címkepárt, ahol a train-majority a testen 0 pontosságú. Átadandó: a döntetlen-szabály, a kiegyensúlyozottsági indoklás és egy konkrét ellenpélda.

F2 (levezetési, nehézség 2). Vezesse le a lemma alkalmazásával ∇VL\nabla_V L-t és ∇bL\nabla_b L-t egyetlen példára, ha z=(2,4)⊤z=(2,4)^\top, V=0V=0, b=0b=0, K=2K=2, y=1y=1. Melyik tag marad el a fagyasztott törzs frissítéséből, és miért? Átadandó: a softmax-gradiens, a két mátrix/vektor és egy alakellenőrzés.

F3 (implementációs, nehézség 1). Írjon futtatható NumPy-függvényt majority_accuracy(y_tr, y_te) néven, amely a legkisebb indexű többségi osztályt választja döntetlennél, és a testpontosságot adja. Tesztelje a ([0,0,1,1],[1,1,0,0]) és ([0,0,0,1],[0,0,1,0]) eseteken. Átadandó: kód, elvárt értékek, futási ellenőrzés.

F4 (hibakeresési, nehézség 2). Adott a szándék szerint fagyasztott törzsű fej-tanítás:

backbone = SmallCNN().features
head = nn.Linear(576, 3)
opt = torch.optim.Adam(list(backbone.parameters()) + list(head.parameters()), lr=1e-2)
for xb, yb in loader:
    opt.zero_grad()
    loss = F.cross_entropy(head(backbone(xb).flatten(1)), yb)
    loss.backward()
    opt.step()

Mi a hiba, mi a kísérleti következménye, és hogyan javítandó? Átadandó: ok, következmény, javított kód és egy assert, amely a törzs változatlanságát ellenőrzi.

F5 (kísérleti transzfer, nehézség 3). Tervezzen transzfer-kísérletet egy kisméretű természetes képi osztályozásra, külső előtanított törzzsel. A terv kötelezően tartalmazza: a downstream adat splitjét, a fagyasztott és finomhangolt baseline-t, az azonos preprocessinget, a metrikát, a seed/batch receptet és a következtetési korlátot. A [D13] katalógusból választott konkrét súlyverzióval együtt rögzítse az előfeldolgozást és a licencfeltételeket is. Átadandó: protokollterv pontokban, nem futtatott numerikus eredmény.

25.3.6 Megoldások és indoklás

F1 megoldása. Döntetlennél a legkisebb indexű osztályt választjuk. A GlyphLab trainen mindhárom osztály gyakorisága 80/240, a testen 20/60, ezért a donor osztály 0, és a testbeli 0-címkék aránya 20/60=1/320/60=1/3; itt nincs mintavételi zaj, mert a split-generátor rögzített osztályonkénti jelenetszámot képez. Ellenpélda: y_tr=[0,0,0,1], y_te=[1,1,1,0]; a train-majority 0, de a testen minden címke 1, így a pontosság 0.

F2 megoldása. s=(0,0)s=(0,0), p=(1/2,1/2)p=(1/2,1/2), cél y=1y=1, ezért g=p−e1=(1/2,−1/2)g=p-e_1=(1/2,-1/2). Ebből

∇VL=gz⊤=(12−12)(24)=(12−1−2),∇bL=g=(12−12). \nabla_VL=gz^\top = \begin{pmatrix}\frac12\\[2pt]-\frac12\end{pmatrix} \begin{pmatrix}2&4\end{pmatrix} = \begin{pmatrix}1&2\\-1&-2\end{pmatrix}, \qquad \nabla_bL=g= \begin{pmatrix}\frac12\\[2pt]-\frac12\end{pmatrix}.

Alakellenőrzés: g∈ℝKg\in\mathbb R^K, z⊤∈ℝ1×dzz^\top\in\mathbb R^{1\times d_z}, tehát ∇VL∈ℝK×dz=ℝ2×2\nabla_VL\in\mathbb R^{K\times d_z}=\mathbb R^{2\times2}; ∇bL∈ℝ2\nabla_bL\in\mathbb R^2. A fagyasztott ágban ∇WL\nabla_WL nincs a frissítésben, mert zz állandó bemenet, és a WW-hez vezető gráfélt levágtuk; trainable ágban JfW(x)⊤V⊤gJ_{f_W}(x)^\top V^\top g lép fel.

F3 megoldása.

import numpy as np

def majority_accuracy(y_tr, y_te):
    counts = np.bincount(y_tr)
    pred = int(np.flatnonzero(counts == counts.max())[0])
    return float((y_te == pred).mean())

print(majority_accuracy(np.array([0, 0, 1, 1]), np.array([1, 1, 0, 0])))
# elvárt: 0.0, mert döntetlen -> 0, a test csak 1-es.
print(majority_accuracy(np.array([0, 0, 0, 1]), np.array([0, 0, 1, 0])))
# elvárt: 0.75, mert a donor 0, a testen három 0 van.

A függvény a döntetlennél a legkisebb indexet adja; a bincount a bemenet nemnegatív int64 címkéire értelmes. Szélsőeset: egyetlen osztályt tartalmazó train esetén a pred egyértelmű.

F4 megoldása. Hiba: az optimizer a backbone.parameters() listát is tartalmazza, tehát a törzs súlyai frissülnek; ez finomhangolás, nem fagyasztott features. Következmény: a reprezentáció–classifier összehasonlítás érvénytelenné válik, mert a head-teljesítményhez reprezentációváltozás is társul. Javítás:

for p in backbone.parameters():
    p.requires_grad_(False)

with torch.no_grad():
    Ztr = backbone(Xtr).flatten(1)
    Zva = backbone(Xva).flatten(1)

head = nn.Linear(576, 3)
opt = torch.optim.Adam(head.parameters(), lr=1e-2)
w0 = copy.deepcopy(backbone.state_dict())

for ep in range(5):
    opt.zero_grad()
    loss = F.cross_entropy(head(Ztr), ytr)
    loss.backward()
    opt.step()

assert all(torch.equal(w0[k], backbone.state_dict()[k]) for k in w0)

Az assert egy tanítási ciklus után is garantálja, hogy a törzs bitre azonos maradt. A no_grad cache egyúttal levágja az autograd gráfot a törzs irányába.

F5 megoldásminta. Terv: (1) downstream adat rögzített train/val/test csoportos splitje, ahol a független egység a jelenet vagy személy, és a val kizárólagos modellválasztásra szolgál. (2) Baseline A: fagyasztott külső törzs, újonnan inicializált lineáris fej, a törzs súlyai requires_grad_(False). (3) Baseline B: azonos törzs finomhangolva, kisebb lr, például a fej tizede, azonos epoch- és batch-recepttel; a kezdeti predikció-egyezést a két ág indulásakor run-ellenőrzéssel rögzítjük. (4) Preprocessing a törzs dokumentált bemeneti sémájával egyezik; eltérés esetén a transzfer domain-shiftet szenved. (5) Metrika: top-1 accuracy és a scene-szintű páros bootstrap a 23.3. Páros összehasonlítás és csoportszintű intervallum szerint; a testet a val-alapú checkpoint után egyszer értékeljük. (6) Korlát: a kis downstream minta miatt a két ág különbsége nem általános transzfer-törvény; a [D13] katalógus konkrét súlyverzióját, licencét és preprocessingjét a futtatás előtt ellenőrizni kell.

25.3.7 Tipikus tévedés, önellenőrzés, visszahívás

Tipikus tévedés. „A szintetikus GlyphLab-on a CNN magas pontossága azt mutatja, hogy a modell természetes képeken is jól teljesít.” Ez nem következik. Ellenpélda: egy 2×22\times2-es XOR-képfeladaton a lineáris pixelmodell legfeljebb 3/43/4 trainpontosságot érhet el, mert a négy sarokpont affin hipersíkkal nem választható szét; egy megfelelő nemlineáris CNN a négy mintát megtanulhatja. A GlyphLab orientáció lineárisan erős geometriai jelet hordoz, ezért ott a lineáris baseline is magas lehet; egy másik szintetikus vagy természetes feladat nehézsége ettől független.

Önellenőrzés. Rögzítettem-e a majority döntetlen-szabályát? A baseline-ok csak a képtensort használják, a maszkot és dobozt nem? A fagyasztott ág kezdeti logitja numerikusan egyezik a trainable CNN kezdeti logitjával? A legjobb modellt val, a testet azután választom? A testkülönbséget kép vagy scene-szintű egységgel értelmezem?

Visszahívás. A 13.3. Lokális szűrők és tanult reprezentáció alfejezetben rögzített szűrő a tanulható kernel egy pontja; itt a lineáris pixelmodell 1×11\times1 kernel, tehát nem használja a súlymegosztás térbeli lokalitását. A 21.3. Dropout, súlyregularizálás és augmentáció szerint a weight decay és dropout heurisztika, ezért a labor összehasonlítása ugyanazt a regularizálásmentes receptet alkalmazza minden ágra. A 24.3. Mixed precision és nagyobb batch szerint f32, teljes batch és CPU mellett nincs szükség GradScalerre vagy akkumulációra; ha később kevert pontosságúra váltunk, a három ágat azonos numerikus úton kell futtatni.

25.3.8 Rövid összefoglaló

A kis adaton a CNN-t csak azonos spliten futtatott referenciákhoz képest értékeljük. A majority a leggyakoribb tanítóosztályt adja, döntetlennél a legkisebb indexet; a kiegyensúlyozott GlyphLab testen pontossága 1/31/3. A lineáris pixelmodell 576576 bemenetet képez le három logitra, paraméter nélküli térbeli lokalitás nélkül. A fagyasztott törzsű fej csak a V,bV,b fejgradienst frissíti; a trainable CNN a JfWJ_{f_W}-n át ∇W\nabla_W-t is. Ugyanazon W0W_0, fejkezdőpont és recept mellett a két ág közti különbség a reprezentáció elmozdulását méri. A labor protokollt, ellenőrző assertet és jelölt szemléltető kimenetet ad; a tényleges futás a 25.4. Nyílt halmaz és ismeretlen input nyílt halmaz előtt a transzfer és baseline korlátokat is rögzíti.

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

A 25.4. Nyílt halmaz és ismeretlen input egység a nyílt halmazú felismerést tárgyalja: a három GlyphLab-osztályra tanított softmax pontszám nem dönthet ismeretlen inputról anélkül, hogy a logit-kalibráció és a döntési küszöb szerződését újra megnyitnánk.

Felhasznált források: