35.2 Magyarázat sanity check

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

A 35.1. Saliency és Grad-CAM egységben definiáltuk a saliency-térképet és a Grad-CAM-et mint „a modell döntésére magyarázatot kínáló” vizualizációt. Ott a képlet megadja, mit rajzolunk. Most azt kérdezzük: honnan tudjuk, hogy a kirajzolt térkép egyáltalán a modelltől függ, nem pedig csak a kép statisztikai tulajdonságaitól? Ha egy módszer akkor is ugyanazt a „fontos területet” jelölné, amikor a modell súlyait véletlenre cseréljük, akkor a magyarázat nem a tanult modell magyarázata.

Előismeret. A 35.1. Saliency és Grad-CAM alfejezetből a bemeneti gradiens és a Grad-CAM súlyozási képlete szükséges. A 34.2. Kvantálás és hibatovábbterjedés arra figyelmeztetett, hogy a numerikus reprezentáció hibája különbözik a modellstruktúra hibájától; a mostani kontroll szintén különválasztja a mérési hibát a magyarázat tényleges modellfüggésétől. A 31.2. GAN mint minimax probléma alfejezetben láttuk, hogy egy optimum létezése nem garancia a véges tanítás konvergenciájára; hasonlóan, egy magyarázó képlet helyessége nem garancia arra, hogy a kimenet a tanult reprezentációt tükrözi. A 23.2. Kalibráció és döntési küszöb kalibrációs tanulsága pedig az, hogy egy számszerű kimenet (ott a softmax, itt a fontossági térkép) önmagában nem tekinthető evidenciának a mögöttes állításra.

Induló kérdés. Adott egy betanított GlyphLab-CNN, és legyen a Grad-CAM-je egy adott képen a kép felső sávjára koncentrált. Tegyük fel, hogy a modell súlyait véletlenre cseréljük, majd ugyanarra a képre újra kiszámítjuk a Grad-CAM-et. Ha a két térkép gyakorlatilag azonos, akkor megbízhatunk-e abban, hogy a magyarázat a tanult modell döntéséről szól?

35.2.2 Definíciók, jelölések és intuíció

Jelölések. Legyen a bemenet x∈[0,1]1×24×24x\in[0,1]^{1\times 24\times 24} (a GlyphLab NCHW konvenciója; a batchdimenziót elhagyjuk), a modell fθ:ℝ1×24×24→ℝKf_\theta:\mathbb R^{1\times 24\times 24}\to\mathbb R^K logitokkal, θ∈ℝP\theta\in\mathbb R^P paramétervektorral. Egy magyarázó módszer

S(x;θ,c)∈ℝ≥024×24S(x;\theta,c)\in\mathbb R_{\ge 0}^{24\times 24}

nemnegatív fontossági térképet ad a cc célosztályra. A 35.1. Saliency és Grad-CAM alfejezetben tárgyalt Grad-CAM esetén cc rendszerint a modell által prediktált osztály; a randomizálási kísérletben rögzített, előre választott cc-t használunk, mert a véletlen súlyú modell predikciója nem hordoz tanult információt. A konkrét SS lehet a bemeneti gradiens nagysága vagy a Grad-CAM upsampling utáni térképe; a kontroll logikája mindkettőre azonos.

Parameter randomization (paraméter-randomizálás). A modell LL tanulható rétegét tetszőleges sorrendben indexeljük. A jj-edik szintű paraméter-randomizálás az 1,…,j1,\ldots,j réteg minden θℓ\theta_\ell paraméterét a réteg inicializálási eloszlásából frissen húzott θ′ℓ\theta'_\ell értékkel helyettesíti, a j+1,…,Lj+1,\ldots,L rétegek a betanított értékükön maradnak. A teljes randomizálásnál j=Lj=L. A kontroll azt méri, hogy

ρ(S(x;θ,c),S(x;θ(j),c))\rho\big(S(x;\theta,c),\,S(x;\theta^{(j)},c)\big)

hogyan változik jj függvényében, ahol ρ\rho egy hasonlósági metrika.

Label randomization (címkerandomizálás). A tanítóhalmaz {(xi,yi)}i=1N\{(x_i,y_i)\}_{i=1}^{N} címkéit a minták indexein végzett π:{1,…,N}→{1,…,N}\pi:\{1,\ldots,N\}\to\{1,\ldots,N\} véletlen permutációval ỹi=yπ(i)\tilde y_i=y_{\pi(i)}-re cseréljük, a képek változatlanok maradnak, majd a modellt a véletlen címkéken újratanítjuk. A permutáció az osztályok gyakoriságát megőrzi, de a képek és a tanító címkék közötti kapcsolatot véletlenszerűvé teszi. Ezzel szemben az osztályazonosítók globális átnevezése (0↦10\mapsto1, 1↦21\mapsto2 stb.) nem megfelelő kontroll, mert az eredeti célfüggvény információtartalmát változatlanul hagyja. A kérdés itt nem a betanított modell perturbálása, hanem hogy a tanító jel és a bemeneti képek közötti érdemi kapcsolat megváltoztatása módosítja-e a magyarázatot. A két randomizálás tehát két különböző függőséget tesztel:

Randomizálás Mit randomizálunk Milyen függőséget vizsgál
Paraméter- betanított súlyok inference-kori paraméterfüggés
Címke- tanító címkék tanulási jel függése

Saliency control. A kontroll egy modellmentes leképezés C:ℝ1×24×24→ℝ24×24C:\mathbb R^{1\times 24\times 24}\to\mathbb R^{24\times 24}, például a Sobel-élkép vagy egy rögzített középpontú Gauss-súlyozás. A C(x)C(x) értéke nem változik, ha a modellt randomizáljuk. Ha a vizsgált magyarázat az eredeti modellhez képest nem mutat nagyobb modellérzékenységet, mint amennyit a modellmentes kontroll adatfüggése önmagában magyaráz, akkor a magyarázat modellhez kötött állítása nem támogatott.

Similarity limits. A hasonlósági metrika megválasztása maga is korlátozza a kontroll értelmezését. A könyv alapértelmezett metrikája a lapított, azonos méretűre interpolált térképek közötti Spearman-rangkoreláció:

ρ(r,s)=∑i(ri−r‾)(si−s‾)∑i(ri−r‾)2∑i(si−s‾)2,ri,si∈[1,n] átlagolt rangok.\rho(r,s)=\frac{\sum_i(r_i-\bar r)(s_i-\bar s)} {\sqrt{\sum_i(r_i-\bar r)^2\sum_i(s_i-\bar s)^2}},\qquad r_i,s_i\in[1,n] \text{ átlagolt rangok.}

A Spearman-ρ\rho invariáns a szigorúan monoton növekvő transzformációkra: ha S′=αS+βS'=\alpha S+\beta, α>0\alpha>0, akkor ρ(S,S′)=1\rho(S,S')=1. Ezért a randomizálás okozta pusztán pozitív skálázást a rangkorreláció nem detektálja. A metrika globális: lokális eltolódások és a térkép invertálása (negatív ρ\rho) csak korlátozottan jelenik meg. A kontrollt ezért mindig rögzített, indokolt metrika mellett kell jelenteni.

35.2.3 Mit cáfolhat és mit nem bizonyít a kontroll?

A sanity check logikai szerkezetét a következő állításban rögzítjük.

Állítás (a paraméter-randomizálás elutasítási logikája). Legyen MM magyarázó módszer, és tegyük fel, hogy MM modellfüggetlen: minden θ,θ′∈Θ\theta,\theta'\in\Theta, minden xx bemenetre és rögzített cc osztályra

SM(x;θ,c)=SM(x;θ′,c).S_M(x;\theta,c)=S_M(x;\theta',c).

Ekkor tetszőleges paraméter-randomizálás után a Spearman-ρ\rho az eredeti és a randomizált magyarázat között determinisztikusan 11, feltéve, hogy a magyarázat nem konstans (nulla szórású) térkép.

Bizonyítás. A modellfüggetlenségi feltételt θ\theta és a randomizált θ(j)\theta^{(j)} párra alkalmazva SM(x;θ,c)=SM(x;θ(j),c)S_M(x;\theta,c)=S_M(x;\theta^{(j)},c). A rangvektorok azonosak, tehát a Spearman-ρ\rho számlálójában a két azonos sorozat kovarianciája a szórással egyenlő, azaz ρ=1\rho=1. Ha a magyarázat konstans, a szórás nulla, és a ρ\rho nem definiált; ezt a kísérletben külön ki kell zárni, például szóráskontrollal. ▫\square

A gyakorlati kontroll ezt a kontrapozíciót használja: ha a mért hasonlóság a randomizálás után szignifikánsan 11 alá esik, akkor a modellfüggetlenség feltételezése megdől. A megfigyelés tehát cáfolhatja azt az állítást, hogy a magyarázat csak a bemenet statisztikájától függ. Azt viszont nem bizonyítja, hogy a megmaradó hasonlóság a „helyes” magyarázat, hogy a módszer a döntési mechanizmust hűen tükrözi, vagy hogy a ρ\rho küszöbértéke általános érvényű. Az alacsonyabb ρ\rho a randomizálás után a modellfüggőség egyik lehetséges jele, de önmagában nem elégséges bizonyíték arra, hogy a hőtérkép hű magyarázat, mert egy buta, de a súlyokat valamilyen nemlineáris módon felhasználó módszer is mutathat modellérzékenységet anélkül, hogy a döntéshez releváns régiókra mutatna.

Ugyanez a logika a címkerandomizálásra: ott a nullhipotézis az, hogy a magyarázat független a tanulási jel osztálytartalmától. Ha a véletlen címkén tanított modell magyarázata nem tér el szignifikánsan az igazi címkén tanítottétól, akkor ez a kontroll nem szolgáltatott bizonyítékot arra, hogy a módszer a tanult feladatfüggést jeleníti meg. Ebből a nemelutasításból viszont nem következik, hogy a magyarázat független a címkétől vagy hibás: a szignifikáns eltérés hiánya származhat alacsony statisztikai erőből (kevés kép, kevés ismétlés), és a választott hasonlósági mértékből is — a fejezet saját ellenpéldája szerint a Spearman-ρ\rho pozitív skálázásra érzéketlen, így egy modellfüggő módszer is adhat ρ=1\rho=1-et. Ilyenkor több kontrollt (paraméter- és címkerandomizálás együtt) és több metrikát (rangkorreláció mellett például a térképek szerkezeti hasonlósága vagy a legfontosabb pixelek átfedése) kell használni. De a címkerandomizálás sem bizonyítja a hűséget: az alacsony szintű képstatisztika (például élirány) véletlen címkék mellett is beépülhet az első konvolúciós rétegbe, ezért a ρ\rho ritkán esik nullára pusztán a címke miatt.

35.2.4 Eredeti kézi kis példa

Legyen egy utolsó konvolúciós jellemzőtérkép két csatornával, A∈ℝ2×2×2A\in\mathbb R^{2\times 2\times 2}, a logit pedig a csatornák lineáris kombinációja:

f=∑c=12∑u=12∑v=12wuvcAuvc+b.f=\sum_{c=1}^{2}\sum_{u=1}^{2}\sum_{v=1}^{2}w_{uv}^{c}A_{uv}^{c}+b.

A betanított modell súlyai w1=w2=(1111)w^{1}=w^{2}= \begin{pmatrix}1&1\\1&1\end{pmatrix}. A Grad-CAM súlyozása 35.1. Saliency és Grad-CAM szerint αc=14∑u,vwuvc\alpha^c=\frac{1}{4}\sum_{u,v}w_{uv}^c, azaz α1=α2=1\alpha^1=\alpha^2=1. Legyen

A1=(0213),A2=(3021).A^1=\begin{pmatrix}0&2\\1&3\end{pmatrix},\qquad A^2=\begin{pmatrix}3&0\\2&1\end{pmatrix}.

Ekkor a Grad-CAM aktiváció ReLU után:

L=ReLU(α1A1+α2A2)=(3234).L=\operatorname{ReLU}\big(\alpha^1A^1+\alpha^2A^2\big) =\begin{pmatrix}3&2\\3&4\end{pmatrix}.

A lapított vektor [3,2,3,4][3,2,3,4]; a rangok: 2→12\to1, a két 33-as 22 és 33 átlaga 2,52{,}5, a 44-es 44. Tehát r=[2,5,1,2,5,4]r=[2{,}5,1,2{,}5,4].

Most randomizáljuk a fej súlyait:

w′1=(−1120,5),w′2=(101−1).w'^{1}=\begin{pmatrix}-1&1\\2&0{,}5\end{pmatrix},\qquad w'^{2}=\begin{pmatrix}1&0\\1&-1\end{pmatrix}.

Az új súlyozás: α′1=(−1+1+2+0,5)/4=0,625\alpha'^{1}=(-1+1+2+0{,}5)/4=0{,}625, α′2=(1+0+1−1)/4=0,25\alpha'^{2}=(1+0+1-1)/4=0{,}25. A randomizált modell magyarázata:

L′=0,625A1+0,25A2=(0,751,251,1252,125).L'=0{,}625A^1+0{,}25A^2 =\begin{pmatrix}0{,}75&1{,}25\\1{,}125&2{,}125\end{pmatrix}.

A lapított vektor [0,75,1,25,1,125,2,125][0{,}75,1{,}25,1{,}125,2{,}125], rangjai [1,3,2,4][1,3,2,4]. A két rangvektor átlaga egyaránt 2,52{,}5. A kovariancia:

∑i(ri−r‾)(si−s‾)=(0)(−1,5)+(−1,5)(0,5)+(0)(−0,5)+(1,5)(1,5)=1,5.\sum_i(r_i-\bar r)(s_i-\bar s)= (0)(-1{,}5)+(-1{,}5)(0{,}5)+(0)(-0{,}5)+(1{,}5)(1{,}5) =1{,}5.

A szórásnégyzetek: ∑(ri−r‾)2=4,5\sum(r_i-\bar r)^2=4{,}5 és ∑(si−s‾)2=5\sum(s_i-\bar s)^2=5, így

ρ=1,54,5⋅5≈0,316.\rho=\frac{1{,}5}{\sqrt{4{,}5\cdot 5}}\approx0{,}316.

A randomizálás tehát a hasonlóságot 11-ről 0,3160{,}316-ra csökkentette ezen a kézzel számolt példán. Ha viszont a randomizált súly az eredeti pozitív skalárszorosa lenne, mondjuk L′=2LL'=2L, akkor a rangok változatlanok maradnának, és ρ=1\rho=1 lenne. Ez a fenti hasonlósági korlát konkrét illusztrációja.

A számítást ellenőrző minimális kód a examples/n138_spearman_example.py munkakönyvtárból:

import numpy as np

def rank_average(v):
    v = np.asarray(v, dtype=np.float64).ravel()
    order = np.argsort(v, kind="mergesort")
    ranks = np.empty_like(order, dtype=np.float64)
    i, n = 0, v.size
    while i < n:
        j = i + 1
        while j < n and v[order[j]] == v[order[i]]:
            j += 1
        ranks[order[i:j]] = (i + j - 1) / 2.0 + 1.0
        i = j
    return ranks

def spearman(a, b):
    ra, rb = rank_average(a), rank_average(b)
    if ra.std(ddof=0) == 0 or rb.std(ddof=0) == 0:
        return float("nan")
    return float(np.corrcoef(ra, rb)[0, 1])

A1 = np.array([[0., 2.], [1., 3.]], dtype=np.float64)
A2 = np.array([[3., 0.], [2., 1.]], dtype=np.float64)
w1 = np.ones((2, 2)); w2 = np.ones((2, 2))
L = w1.mean() * A1 + w2.mean() * A2
w1r = np.array([[-1., 1.], [2., 0.5]])
w2r = np.array([[1., 0.], [1., -1.]])
Lr = w1r.mean() * A1 + w2r.mean() * A2

print("rho(L, Lr) =", round(spearman(L, Lr), 6))
print("rho(L, 2*L) =", round(spearman(L, 2.0 * L), 6))

Futtatás: python examples/n138_spearman_example.py. Az elvárt kimenet ρ(L,Lr)=0.316228\rho(L,Lr)=0{.}316228 és ρ(L,2L)=1.0\rho(L,2L)=1{.}0; az utóbbi a monoton transzformációra való érzéketlenséget mutatja.

35.2.5 Labor: kis sanity check a saját CNN-re

Az alábbi labor a GlyphLab-adat test részének első 20 képén paraméter-randomizálást végez. A kód a 24.4. VI. ellenőrző pont: megbízható CNN-training labor modelljét és checkpointját használja: a TinyCNN osztályt a lab/n096_checkpoint.py modulból importálja (egyetlen forráshely a tanítástól a Grad-CAM-ig), a checkpoint pedig a lab/best.pt, amelyet a cd lab && python n096_checkpoint.py futás ír. A checkpoint kulcsai: model (a state_dict), config (name=TinyCNN, in_ch, num_classes, input_shape, seed), epoch, val_loss, val_acc, data_manifest, parameter_count. Ha a checkpoint hiányzik, előbb a 24.4. VI. ellenőrző pont: megbízható CNN-training labort kell lefuttatni. A Grad-CAM célrétege a features.3, vagyis a TinyCNN második konvolúciója.

Input. data/images.npy float32, alak (360,1,24,24); data/labels.npy int64; data/metadata.csv split oszloppal; a checkpoint a fenti szótár, a súlyok a model kulcs alatt. A kód CPU-n fut, seed 20261020, 30 ismétlés.

# lab/n138_sanity_check.py -- futtatás a lab/ könyvtárból, az n096 labor után:
# python n138_sanity_check.py --data data --checkpoint best.pt \
#   --seed 20261020 --repeats 30 --max_images 20
import argparse, csv, json, math
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from n096_checkpoint import TinyCNN  # az n096 labor modellje és checkpointszerződése


def rank_average(v):
    v = np.asarray(v, dtype=np.float64).ravel()
    order = np.argsort(v, kind="mergesort")
    ranks = np.empty_like(order, dtype=np.float64)
    i, n = 0, v.size
    while i < n:
        j = i + 1
        while j < n and v[order[j]] == v[order[i]]:
            j += 1
        ranks[order[i:j]] = (i + j - 1) / 2.0 + 1.0
        i = j
    return ranks


def spearman(a, b):
    ra, rb = rank_average(a), rank_average(b)
    if ra.std(ddof=0) == 0 or rb.std(ddof=0) == 0:
        return float("nan")
    return float(np.corrcoef(ra, rb)[0, 1])


def sobel_control(x_chw):
    """Sobel-gradiens nagysága egy (1,24,24) CHW képre; kimenet (24,24)."""
    assert x_chw.ndim == 3 and x_chw.shape[0] == 1, x_chw.shape
    x = x_chw[0]
    kx = np.array([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]])
    ky = kx.T
    pad = np.pad(x, 1, mode="reflect")
    gx = np.zeros_like(x); gy = np.zeros_like(x)
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            gx[i, j] = (pad[i:i+3, j:j+3] * kx).sum()
            gy[i, j] = (pad[i:i+3, j:j+3] * ky).sum()
    mag = np.sqrt(gx**2 + gy**2)
    return (mag - mag.min()) / (mag.max() - mag.min() + 1e-12)


def compute_gradcam(model, x, c, layer_name="features.3"):
    model.eval(); model.zero_grad()
    acts, grads = {}, {}
    def fwd(m, i, o): acts[layer_name] = o
    def bwd(m, gi, go): grads[layer_name] = go[0]
    handles = []
    for name, mod in model.named_modules():
        if name == layer_name:
            handles.append(mod.register_forward_hook(fwd))
            handles.append(mod.register_full_backward_hook(bwd))
            break
    xx = x.clone().detach().requires_grad_(True)
    logits = model(xx)
    logits[0, c].backward()
    A = acts[layer_name][0]          # C x h x w
    G = grads[layer_name][0]         # C x h x w
    alpha = G.mean(dim=(1, 2))       # C
    L = torch.einsum("c,cuv->uv", alpha, A).relu()
    L = (L - L.min()) / (L.max() - L.min() + 1e-12)
    cam = F.interpolate(L[None, None], size=x.shape[-2:],
                        mode="bilinear", align_corners=False)[0, 0]
    for h in handles: h.remove()
    return cam.detach().cpu().numpy()


def randomize_parameters(model, seed, reset_bn_buffers=True):
    """Minden tanult paramétert a modul saját alapinicializálására állít vissza.

    Conv/Linear: reset_parameters() (súly és fan-in alapú bias, pontosan a PyTorch
    alapértelmezése szerint). BatchNorm: reset_parameters() a gamma/beta-t is
    visszaállítja; a futó statisztikák (bufferek) visszaállítása külön döntés.
    Visszaadja a randomizált paraméterek és bufferek nevét a naplóhoz."""
    torch.manual_seed(seed)
    touched = []
    with torch.no_grad():
        for name, mod in model.named_modules():
            if isinstance(mod, (nn.Conv2d, nn.Linear)):
                mod.reset_parameters()
                touched += [f"{name}.{p}" for p, _ in mod.named_parameters(recurse=False)]
            elif isinstance(mod, (nn.BatchNorm1d, nn.BatchNorm2d)):
                running = (mod.running_mean.clone(), mod.running_var.clone())
                mod.reset_parameters()                 # gamma=1, beta=0 és bufferek
                touched += [f"{name}.weight", f"{name}.bias"]
                if reset_bn_buffers:
                    touched += [f"{name}.running_mean", f"{name}.running_var"]
                else:                                  # bufferek megtartása, ha így döntünk
                    mod.running_mean.copy_(running[0]); mod.running_var.copy_(running[1])
    return touched


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--data", type=Path, required=True)
    ap.add_argument("--checkpoint", type=Path, required=True)
    ap.add_argument("--seed", type=int, default=20261020)
    ap.add_argument("--repeats", type=int, default=30)
    ap.add_argument("--max_images", type=int, default=20)
    args = ap.parse_args()

    torch.manual_seed(args.seed); np.random.seed(args.seed)
    device = torch.device("cpu")
    images = np.load(args.data / "images.npy")
    labels = np.load(args.data / "labels.npy")
    with open(args.data / "metadata.csv", newline="", encoding="utf-8") as f:
        idx = [int(r["index"]) for r in csv.DictReader(f) if r["split"] == "test"]
    idx = idx[:args.max_images]
    X = torch.from_numpy(images[idx]).float().to(device)  # N,1,24,24
    y = torch.from_numpy(labels[idx]).long()

    base = TinyCNN()
    state = torch.load(args.checkpoint, map_location="cpu", weights_only=True)
    base.load_state_dict(state["model"] if isinstance(state, dict) and "model" in state else state)
    base.to(device).eval()

    base_cams = [compute_gradcam(base, X[i:i+1], int(y[i])) for i in range(len(X))]
    controls = [sobel_control(images[idx[i]]) for i in range(len(X))]   # (1,24,24) -> (24,24)
    assert all(c.shape == (24, 24) for c in controls)
    assert all(c.shape == (24, 24) for c in base_cams)
    n_const = sum(1 for c in base_cams if c.std() == 0)
    cont_rho = [spearman(base_cams[i], controls[i]) for i in range(len(X))]

    rand_rhos, n_nan, n_all_nan = [], 0, 0
    for rep in range(args.repeats):
        m = TinyCNN(); m.load_state_dict(base.state_dict()); m.to(device).eval()
        touched = randomize_parameters(m, args.seed + 1000 * rep)
        if rep == 0:
            print("randomizált:", ", ".join(touched))
            assert set(touched) == {n for n, _ in m.named_parameters()}   # minden tanult paraméter
        vals = np.array([spearman(base_cams[i], compute_gradcam(m, X[i:i+1], int(y[i])))
                         for i in range(len(X))])
        n_nan += int(np.isnan(vals).sum())     # konstans (pl. csupa nulla) randomizált térkép
        if np.isnan(vals).all():
            n_all_nan += 1                     # az ismétlésnek nincs értelmezhető rho-ja
            continue
        rand_rhos.append(float(np.nanmean(vals)))

    out = {
        "seed": args.seed, "repeats": args.repeats, "n_images": len(X),
        "control_sobel_rho_mean": float(np.nanmean(cont_rho)),
        "randomized_rho_mean": float(np.nanmean(rand_rhos)),
        "randomized_rho_median": float(np.nanmedian(rand_rhos)),
        "randomized_rho_p05": float(np.nanpercentile(rand_rhos, 5)),
        "randomized_rho_p95": float(np.nanpercentile(rand_rhos, 95)),
        "excluded_constant_cams": n_const,
        "randomized_constant_maps": n_nan,
        "randomized_repeats_without_rho": n_all_nan,
        "status": "completed",
    }
    print(json.dumps(out, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()

Ellenőrzés és értelmezés. Először a shape-ellenőrzést végezzük el: a Grad-CAM és a Sobel-kontroll egyaránt (24,24) alakú. Másodszor a szóráskontrollt: ha egy base_cams térkép szórása nulla, a megfelelő ρ\rho-t nan-ként kizárjuk, és a kizárások számát a jegyzőkönyvbe írjuk. Az értelmezés két szám összevetése: randomized_rho_mean és control_sobel_rho_mean. Ha a randomizált modellek hasonlósága szignifikánsan alacsonyabb, mint a Sobel-kontroll hasonlósága az eredeti magyarázathoz, és a randomizált eloszlás 95%-os tartománya 11 alatt van, akkor a modellfüggetlenségi nullhipotézist elutasítjuk: a magyarázat hordoz modellfüggő információt. Ha a randomizált ρ\rho nem csökken a Sobel-kontroll szintje alá, a módszer ezen a kontrollon nem mutat többet, mint egy modellmentes él-detektor. Futási jegyzőkönyv. A 24.4. VI. ellenőrző pont: megbízható CNN-training labor best.pt checkpointjával, a fenti paranccsal, az eredeti GlyphLab-adaton (CPU, Python 3.12.3, NumPy 2.3.5, PyTorch 2.14.1+cpu) két egymás utáni futás azonos eredményt adott. A randomizált paraméterek naplója: features.0.weight, features.0.bias, features.3.weight, features.3.bias, fc.weight, fc.bias, vagyis a modell minden tanult paramétere (a TinyCNN-ben nincs BatchNorm). Eredmények: control_sobel_rho_mean =0,078= 0{,}078, randomized_rho_mean =0,121= 0{,}121 (medián 0,1930{,}193, 5–95%-os tartomány [−0,384;0,557][-0{,}384;\ 0{,}557]), excluded_constant_cams =0= 0, a 600 randomizált térképből 60 konstans (a ReLU utáni Grad-CAM csupa nulla), és 1 ismétlésnek nincs értelmezhető ρ\rho-ja. Értelmezés: a randomizált modellek térképei az eredetivel gyengén korrelálnak, és a 95%-os tartomány messze 11 alatt van, tehát a paraméter-randomizálási kontroll a magyarázat modellfüggőségére utal. A Sobel-kontroll ezen az adaton nem élesebb mérce: az eredeti Grad-CAM az él-térképpel is közel korrelálatlan (0,0780{,}078), a randomizált átlag pedig nem esik e szint alá; ebből a kontrollból tehát nem következik, hogy a magyarázat „többet” mutat egy él-detektornál, de az sem, hogy kevesebbet. A konstans randomizált térképek aránya önmagában is jelentendő: a nan-ok kizárása nélkül az átlag nem lenne számolható. Más könyvtárverzió vagy CPU mellett az utolsó jegyek eltérhetnek; a saját futás JSON-ját kell leadni.

35.2.6 Feladatok

F1 (fogalmi, nehézség 1). Definiálja a paraméter-randomizálást és a címkerandomizálást, és adja meg, melyik milyen függőséget tesztel. Miért nem helyettesíthető a két kontroll egymással? Átadandó: két-két mondat definíció és egy mondat indoklás.

F2 (levezetési, nehézség 2). A fenti kézi példa adataival vezesse le a Spearman-ρ\rho értékét az LL és L′L' térképekre, majd mutassa meg, hogy a 2L′2L' térképre ugyanaz az érték adódik. Átadandó: a rangvektorok, a kovariancia és a két ρ\rho értéke.

F3 (implementációs, nehézség 2). Futtassa az n138_sanity_check.py programot a saját checkpointtal, seeddel 20261020, 30 ismétléssel. Ellenőrizze, hogy minden Grad-CAM és Sobel-térkép alakja (24,24), és hogy a kizárt konstans térképek száma nulla. Átadandó: a JSON-kimenet és az ellenőrző shape-teszt utolsó sora.

F4 (hibakeresési, nehézség 2). Egy hallgató a paraméter-randomizálásnál a Conv2d és Linear rétegek súlyait újra inicializálja, de a BatchNorm2d rétegek running_mean és running_var állapotát változatlanul hagyja, majd model.eval() módban számít Grad-CAM-et. Mi a hiba oka, mi a következménye a randomizált eloszlásra, és mi a javítás? Átadandó: ok, következmény, javítás és egy mini ellenőrzés.

F5 (kísérleti/transzfer, nehézség 3). Tervezze meg a címkerandomizálási kísérletet a GlyphLab-adaton: a tanító címkék permutációja, a train/val/test split érintetlenül hagyása, tanítási protokoll, baseline és kiértékelési metrika. Mi a fő érvényességi korlát? Átadandó: protokolltáblázat és a korlát indoklása.

35.2.7 Megoldások és indoklás

F1. A paraméter-randomizálás a betanított modell rétegenkénti súlyait friss inicializálásra cseréli, és azt teszteli, hogy a magyarázat függ-e a tanult paraméterértékektől az inference során. A címkerandomizálás a tanító címkéket permutálja és újratanít, és azt teszteli, hogy a magyarázat a tanulási jel osztálytartalmától függ-e. A kettő nem cserélhető fel: egy módszer lehet érzékeny a betanított súlyokra, de lehet, hogy a súlyok a képstatisztikától, nem a címkéktől függenek; illetve a címkerandomizálás a tanítási folyamatot, nem az inference-kori paraméterfüggést vizsgálja.

F2. A rangvektorok a kézi példában: r=[2,5,1,2,5,4]r=[2{,}5,1,2{,}5,4], s=[1,3,2,4]s=[1,3,2,4]. Átlaguk 2,52{,}5. A kovariancia 1,51{,}5, a szórásnégyzetek 4,54{,}5 és 55, tehát ρ=1,5/22,5≈0,316\rho=1{,}5/\sqrt{22{,}5}\approx0{,}316. A 2L′2L' térkép lapított vektora [1,5,2,5,2,25,4,25][1{,}5,2{,}5,2{,}25,4{,}25], rangjai szintén [1,3,2,4][1,3,2,4], mert a pozitív skalározással a rendezés nem változik, így ρ(L,2L′)=0,316\rho(L,2L')=0{,}316. A szélsőeset-ellenőrzés: ha a két térkép konstans lenne, a nevezőben a szórás nulla lenne, és a ρ\rho nem definiált; ezért zárjuk ki a laborban a nulla szórást.

F3. A futtatás után a JSON-ben a randomized_rho_mean és a control_sobel_rho_mean összevetendő. Az elvárt shape-ellenőrzés:

for i in range(len(idx)):
    assert base_cams[i].shape == (24, 24)
    assert sobel_control(images[idx[i]]).shape == (24, 24)

Ha a randomizált ρ\rho 95%-os felső percentilise kisebb, mint a kontroll ρ\rho értéke, akkor a modellfüggőséget alátámasztja a kontroll; ha nem, a sanity check nem támogatja a magyarázat modellhez kötöttségét. A tolerancia itt nem lebegőpontos, hanem eloszlásalapú: a percentiliseket az ismétlésekből számítjuk, ezért a döntés a 30 ismétlés mintavételi bizonytalanságát is hordozza.

F4. Ok: a BatchNorm2d értékelő módban a running_mean és running_var futó statisztikákat használja, nem a minibatch statisztikát; a hallgató csak a tanulható paramétereket randomizálta, a normalizálási állapot a régi eloszlásra maradt kalibrálva. Következmény: a randomizált modell továbbra is a betanított eloszlás skálázását alkalmazza, így a magyarázat részben a régi modellhez hasonló marad, ami hamisan magas ρ\rho értéket adhat. Javítás: a randomizáló függvénynek a BatchNorm rétegeken reset_running_stats()-et kell hívnia, vagy a running_mean-t nullára és a running_var-t egyre kell állítania. Ellenőrzés: közvetlenül a reset_running_stats() után, még újabb train forward előtt a running_mean pontosan nulla és a running_var pontosan egy. Mindkét puffer alakja (C,); eval() módban a megfigyelések nem módosítják őket.

F5. Protokoll: a train split 120 jelenetének osztálycímkéit rögzített, seedelt jelenetszintű permutációval keverjük, és a jelenet mindkét nézete ugyanazt a permutált címkét kapja; a val és test split érintetlen. A modellt ugyanazzal az architektúrával, optimizerrel, seeddel és epoch-számmal tanítjuk, mint a valódi címkés baseline-t. A metrika a Grad-CAM Spearman-ρ\rho a valódi és a véletlen címkés modell között a test-képeken, összevetve a Sobel-kontroll hasonlóságával. Fő korlát: az alacsony szintű képstatisztika véletlen címkék mellett is beépülhet a korai rétegekbe, ezért a ρ\rho nem várhatóan 00; a kísérlet csak a tanult címkeinformációhoz kötött függést méri, nem a magyarázat abszolút hűségét. A scene-szintű függőség miatt a permutációt jelenetenként, a kiértékelést pedig a változatlan tesztjeleneteken végezzük; a páros nézeteket sem a permutációnál, sem a bizonytalanságbecslésnél nem tekintjük függetlennek.

35.2.8 Tipikus tévedés ellenpéldával

Gyakori tévedés, hogy ha egy magyarázó módszer egy adathalmazon és architektúrán átmegy a sanity checken, akkor az minden magyarázatra és feladatra érvényes. Ez a következtetés érvénytelen. Ellenpélda: ugyanaz a Grad-CAM-implementáció a GlyphLab-CNN-en mutathat modellérzékenységet, míg egy pusztán lineáris pooling-fejjel képzett változaton a Spearman-ρ\rho randomizálás után is magas maradhat, mert a magyarázat ott gyakorlatilag a fix jellemzőtérkép skalározott másolata. A két eredmény közül egyik sem terjeszthető ki a másik architektúrára, és egyik sem mond semmit egy transzformer attention-térképéről. Az általánosítás csak új kontrollált kísérlettel igazolható.

35.2.9 Önellenőrzés és fogalom-visszahívás

Ellenőrizze, hogy a laborban a Grad-CAM célosztálya rögzített-e, a randomizált modellek eval() módban futnak-e, és a konstans térképeket a szóráskontroll kizárja-e. Ha a randomizált eloszlás 95%-os tartománya tartalmazza az 11-et, akkor a modellfüggetlenség nullhipotézisét nem vethetjük el; ez nem a módszer „jóságának” bizonyítéka, hanem a kontroll negatív eredménye.

Rövid visszahívás: a 31.2. GAN mint minimax probléma GAN-optimuma sem garantálta a véges tanítás konvergenciáját; itt a sanity check sem garantálja a magyarázat hűségét, csak egy cáfolható nullhipotézist tesztel. A 23.2. Kalibráció és döntési küszöb kalibrációs tanulsága szerint egy számszerű kimenet önmagában nem bizonytalansági garancia; itt egy hasonlósági érték önmagában nem hűséggarancia. A 34.2. Kvantálás és hibatovábbterjedés numerikus hibája pedig arra figyelmeztet, hogy a randomizálási kísérletben használt metrika megvalósításának saját numerikus hibája is ellenőrzést igényel.

35.2.10 Rövid összefoglaló

A magyarázat sanity check azt a szükséges feltételt ellenőrzi, hogy a saliency- vagy Grad-CAM-térkép valóban a modelltől függjön. A paraméter-randomizálás a betanított súlyok, a címkerandomizálás a tanulási jel iránti érzékenységet teszteli. A kontroll logikai szerkezete kondicionális-negatív: a magas hasonlóság megőrzése modellfüggetlen módszernél elvileg determinisztikus lenne, ezért a randomizálás utáni erős csökkenés cáfolja a bemenetfüggetlen magyarázatot, de nem bizonyítja a hűséget. A Spearman-ρ\rho monoton transzformációkra érzéketlen, ezért a kontrollt fix metrika és modellmentes saliency control mellett, architektúránként külön kell jelenteni.

35.2.11 Következő alkalmazás

A 35.3. Perturbáció és adversarial példa egységben a perturbációs irányból vizsgáljuk ugyanezt a kérdést: az FGSM-alapú adversarial példa generálásának [D21] oktatóanyaga szerinti alapváltozatát a saját GlyphLab-modellre alkalmazzuk, és azt kérdezzük, hogyan változik a magyarázat, ha a bemenetet nem a súlyok, hanem egy kis, célzott perturbáció mentén módosítjuk. A most bevezetett hasonlósági metrikák és a randomizálási kontroll ott is a mérési protokoll részét képezik.

35.2.12 Felhasznált források