A 23.2. Kalibráció és döntési küszöb egység a modell valószínűségi kimenetét és a döntési küszöböt választotta szét. Most a pontosság típusú mutatók bizonytalanságát vizsgáljuk, és ehhez a tesztminta szerkezetét tesszük explicitté. A GlyphLab test halmaza 60 képből áll, de a 60 kép csak 30 független jelenetből (scene) származik: minden jelenethez két zajos nézet (view) tartozik. Ha egy jelenet két nézete hasonlóan viselkedik, akkor a 60 kép nem hordoz 60 független bizonyítékot.
Előismeret: a konfidenciaintervallum 5.3. Mintavételi bizonytalanság és intervallum alfejezetbeli ismételt mintavételi értelmezése, a függő képek és a bootstrap egység megválasztása 5.4. Függő képek, seedek és resampling, a split scene-invariánsa 11.3. Dataset és DataLoader a split után, valamint az accuracy aggregálási konvenciói 23.1. Confusion matrix és aggregálási konvenció. Induló kérdés: ha a test accuracy 0,92, akkor a „±” értékét 60 kép vagy 30 jelenet alapján számoljuk? Durva előzetes becslésként egy binomiális jellegű standard hiba mellett 60 független egységnél , 30 független egységnél ; a tényleges eltérés attól függ, mennyire korrelálnak a nézetek a jeleneten belül.
Legyen a tesztben
kép,
jelenet és
osztály. Az
képre
a jelenetazonosító,
a jelenet nézeteinek halmaza,
.
A GlyphLab testen
,
,
,
minden
-re,
és minden osztályból
jelenet van. A predikciók tömbje
alakú int64, a cél
ugyanilyen. A képszintű helyesség
A jelenetátlagos accuracy (per-scene accuracy) a jeleneten belül átlagol, majd a jeleneteket egyenlő súllyal veszi:
A kiegyensúlyozott GlyphLabon a képszintű accuracy és numerikusan megegyezik, mert minden jelenetnek két képe van; a különbség nem a pontértékben, hanem abban van, hogy definíciója explicitté teszi a mintavételi egységet, a jelenetet. A 23.1. Confusion matrix és aggregálási konvenció alfejezetbeli micro/macro megkülönböztetés mintájára ez jelenetszintű átlagolás, nem osztályon belüli macro-átlag.
A GlyphLab orientációs címkéje jelenettulajdonság, ezért minden jelenethez egyértelműen tartozik osztály. Az osztály-rétegzett jelenetátlag
a három osztály jelenetátlagát egyenlő súllyal veszi. Ez a generáló szerződés osztályarányát tükrözi, és nem függ attól, hogy egy véletlen bootstrap húzásban éppen hány jelenet kerül az egyes osztályokból.
Páros hibák (paired errors): ha két körülményt, például a clean és a corrupt tesztet, ugyanazon a jeleneten értékelünk, akkor nem két független accuracy átlagot hasonlítunk össze, hanem a jelenetenkénti különbségeket:
Itt a tiszta, a korrupt teszt. Ha a jelenetek függetlenek és azonos eloszlásúak, akkor
A közös jelenet nehézsége tipikusan pozitív kovarianciát ad, így a
páros összehasonlítás varianciája kisebb, mint a független csoportoké. A
párosítás a GlyphLab korrupt tesztjénél szerződéses: a
corrupted_test.npy sorrendje a metadata test sorrendje,
tehát ugyanaz a jelenet/nézet pár.
A bizonytalanságnak három, gyakran összemosott forrását különböztetjük meg:
Konfidenciaintervallum alatt az [S02] szerinti ismételt mintavételi eljárást értjük: a 95%-os intervallum olyan eljárás, amely sok független tesztminta esetén körülbelül az esetek 95%-ában lefedi a rögzített paramétert. Ez nem annak a valószínűsége, hogy „a paraméter az adott intervallumban van”.
A klaszter-bootstrap (cluster bootstrap) a jelenetet tekinti újramintavételi egységnek: ha egy jelenetet behúzunk, annak minden view-ját együtt vesszük. A rétegzett változat osztályonként külön húz, így minden replikátumban jelenet van a osztályból. A következő lemma a replikátumok feltételes eloszlásának első két momentumát rögzíti. Szándékosan nem állítja a bootstrap intervallum lefedettségét; az utóbbi külön, itt nem bizonyított aszimptotikus kérdés.
Lemma (class-stratified scene bootstrap momentumai). Rögzítsük a tesztjeleneteket és a osztály értékeit. A -edik replikátumban minden rétegben darab független, egyenletes húzást végzünk a halmazból visszatevéssel. Legyen az jelenet húzási gyakorisága, tehát , és legyen a rétegen belüli bootstrap súly
A replikátum
Ekkor , és
A súlyok megőrzött tulajdonságai: , rétegenként , a rétegek összsúlya a teljes replikátumban rögzített , és . A véletlen súlyok várható értéke tehát a rétegen belüli egyenletes jelenetsúly.
Bizonyítás. A felső index a bootstrap húzás szerinti feltételes várható értéket/varianciát jelöli, a tesztadatok rögzítettek. A -edik replikátum húzásai függetlenek, és mindegyik egyenletes eloszlású -n. Mivel ezek átlaga,
A varianciához először egyetlen húzás varianciáját számoljuk:
ahol az utolsó egyenlőség a négyzetes eltérés szokásos kifejtése. A darab húzás függetlensége miatt
Végül a rétegenkénti húzások függetlenek, ezért a -szoros átlag varianciája a rétegvarianciák összegének -szerese. Ezzel a lemmát beláttuk.
Legyen osztály, mindkettőben jelenet, az accuracy-értékek:
| Osztály | értékek | |
|---|---|---|
| 0 | ||
| 1 |
Az osztály-rétegzett jelenetátlag . A lemma varianciája: mindkét rétegben , tehát a rétegvariancia , a teljes variancia , a szórás .
Egy konkrét replikátum lehet: a 0. osztály húzásai értékei , átlaga ; az 1. osztály húzásai értékei , átlaga ; a replikátum . Egy másik replikátum, ahol az 1. osztályban kétszer a értékű jelenet jön, , mutatja a replikátumok ingadozását.
import numpy as np
rng = np.random.default_rng(20261020)
A = {0: np.array([1.0, 1.0, 0.5]),
1: np.array([0.5, 0.5, 0.0])}
B = 2000
repl = np.empty(B, dtype=np.float64)
for b in range(B):
total = 0.0
for vals in A.values():
c = vals.shape[0]
idx = rng.integers(0, c, size=c)
total += vals[idx].mean()
repl[b] = total / len(A)
print(repl.mean(), repl.std(ddof=1),
np.percentile(repl, [2.5, 97.5]))A kézirat írásakor ezt a kódot nem futtattuk; a lemmából számított ellenőrző értékek a fentiek. A futtatáskor mellett a replikátumátlag és -szórás az elméleti értékeket a véletlen hiba nagyságrendjén belül közelíti.
Bemenetek: lab/data/metadata.csv, a modell által
előállított lab/test_preds.npy alakja (60,),
int64, a metadata test sorrendjében; opcionálisan
lab/test_preds_corrupt.npy ugyanebben a sorrendben. A
predikciót a korábbi egységek szerint, a legjobb val-loss checkpointból
állítjuk elő:
import numpy as np
import torch
model.eval()
with torch.inference_mode():
logits = model(images_test) # images_test: (60,1,24,24), float32, CPU
preds = logits.argmax(dim=1).cpu().numpy().astype(np.int64)
np.save("lab/test_preds.npy", preds)A metrikaszámítás numpy-val CPU-n fut. A bootstrap
seedje 20261020, a replikátumszám B=2000, a
percentilis intervallum a [2.5, 97.5] percentilisekből áll.
A futtatás a projekt gyökeréből:
python lab/scene_interval.py --preds lab/test_preds.npy \
--corrupt lab/test_preds_corrupt.npy --out lab/scene_interval.json# lab/scene_interval.py
import argparse
import json
import numpy as np
import pandas as pd
BASE = "lab"
def bootstrap_from_dict(vals_by_class, B, seed):
rng = np.random.default_rng(seed)
classes = list(vals_by_class)
repl = np.empty(B, dtype=np.float64)
for b in range(B):
total = 0.0
for k in classes:
vals = np.asarray(vals_by_class[k], dtype=np.float64)
c = vals.shape[0]
total += vals[rng.integers(0, c, size=c)].mean()
repl[b] = total / len(classes)
return repl
def main():
p = argparse.ArgumentParser()
p.add_argument("--preds", required=True)
p.add_argument("--corrupt", default=None)
p.add_argument("--out", default=f"{BASE}/scene_interval.json")
p.add_argument("--B", type=int, default=2000)
p.add_argument("--seed", type=int, default=20261020)
p.add_argument("--check", action="store_true")
args = p.parse_args()
if args.check:
A = {0: np.array([1.0, 1.0, 0.5]),
1: np.array([0.5, 0.5, 0.0])}
repl = bootstrap_from_dict(A, args.B, args.seed)
assert abs(repl.mean() - 7/12) < 0.01
assert abs(repl.std(ddof=1) - np.sqrt(1/108)) < 0.02
print("check ok", repl.mean(), repl.std(ddof=1))
return
meta = pd.read_csv(f"{BASE}/data/metadata.csv")
test = meta[meta["split"] == "test"].sort_values("index").reset_index(drop=True)
labels = test["label"].to_numpy(dtype=np.int64)
preds = np.load(args.preds).astype(np.int64)
assert preds.shape == (60,), preds.shape
test["correct"] = (preds == labels).astype(np.float64)
image_acc = float(test["correct"].mean())
scene_acc = test.groupby("scene_id")["correct"].mean()
scene_label = test.groupby("scene_id")["label"].first()
assert np.array_equal(scene_acc.index.to_numpy(),
scene_label.index.to_numpy())
scene_mean = float(scene_acc.mean())
assert np.isclose(image_acc, scene_mean, rtol=0, atol=1e-12)
vals = {int(k): scene_acc[scene_label == k].to_numpy(dtype=np.float64)
for k in np.unique(scene_label)}
repl = bootstrap_from_dict(vals, args.B, args.seed)
lo, hi = np.percentile(repl, [2.5, 97.5])
out = {
"image_accuracy_mean": image_acc,
"scene_accuracy_mean": scene_mean,
"scene_ci_low": float(lo),
"scene_ci_high": float(hi),
"bootstrap_unit": "scene_id",
"stratified_by": "label",
"B": args.B,
"seed": args.seed,
}
if args.corrupt:
preds_c = np.load(args.corrupt).astype(np.int64)
assert preds_c.shape == (60,), preds_c.shape
test["correct_c"] = (preds_c == labels).astype(np.float64)
scene_acc_c = test.groupby("scene_id")["correct_c"].mean()
delta = scene_acc - scene_acc_c
vals_d = {int(k): delta[scene_label == k].to_numpy(dtype=np.float64)
for k in np.unique(scene_label)}
drep = bootstrap_from_dict(vals_d, args.B, args.seed)
dlo, dhi = np.percentile(drep, [2.5, 97.5])
out.update({
"paired_mean_delta_clean_minus_corrupt": float(delta.mean()),
"paired_ci_low": float(dlo),
"paired_ci_high": float(dhi),
})
with open(args.out, "w", encoding="utf-8") as f:
json.dump(out, f, indent=2)
print(json.dumps(out, indent=2))
if __name__ == "__main__":
main()Ellenőrzés: a --check kapcsoló a 4. szakasz kézi
példáján a lemmabeli momentumokat ellenőrzi. A fő futás a
(60,) predikcióalakot és a képszintű/jelenetátlagos
accuracy számszerű egyezését is teszteli. A jelen kézirat írásakor a
GlyphLab-predikciós futás nem történt meg, ezért a fenti JSON-mezők
jelölt szemléltetésnek minősülnek; a tényleges
értékeket a hallgatói labor futása állítja elő.
Eredményértelmezés: a scene_ci_low/high a kiválasztott
modell tesztminta-mintavételi bizonytalanságának közelítése a 30 jelenet
mellett, a generáló GlyphLab eloszlásra vonatkoztatva. A páros
paired_mean_delta_clean_minus_corrupt a corrupt bemenet
hatását ugyanazon jelenet/nézet párokon méri; a hozzá tartozó
intervallum a párosítás miatt nem a két accuracy-különbség két független
mintából számolt varianciáját használja.
F1 (fogalmi, nehézség 1). Magyarázza el, miért nem mindegy, hogy a GlyphLab bootstrap egysége a kép vagy a jelenet. Adjon olyan mesterséges tesztmintát, ahol a képszintű bootstrap standard hibája -szerese a jelenet-bootstrapének. Átadandó: definíciók, feltétel és a számpélda.
F2 (levezetési, nehézség 2). A 4. szakasz kézi példáján vezesse le mindkét réteg replikátum-varianciáját, és számítsa ki a teljes varianciát -ként. Átadandó: a lemma minden lépése és a numerikus ellenőrzés.
F3 (implementációs, nehézség 2). Írjon
stratified_scene_bootstrap(A_by_class, B, seed) függvényt,
amely a class -> float64 tömb leképezésből
(B,) replikátumtömböt ad. Ellenőrizze
,
seed=20261020 mellett, hogy a kézi példán a replikátumátlag
és a szórás
tűrésen belül van. Átadandó: teljes kód, shape- és
szélsőeset-kontroll.
F4 (hibakeresési, nehézség 2). Adott a következő
replikátumszámítás:
idx = rng.integers(0, len(scene_acc), size=len(scene_acc)); repl_b = scene_acc[idx].mean().
Nevezze meg a hibát a GlyphLab class-stratified bootstrap kontextusában,
írja le a következményt a replikátumok osztályarányára, és adja meg a
javítást. Átadandó: ok, következmény, javított kód,
visszaellenőrzés.
F5 (kísérleti/transzfer, nehézség 3). Tervezzen protokollt, amely a clean és corrupt GlyphLab teszt accuracy-különbségét páros jelenet-bootstrap intervallummal jelenti, és emellett három training seedet is rögzít. Válassza szét a tesztminta-, seed- és domain-bizonytalanságot. Átadandó: kérdés, hipotézis, egység, baseline, metrika, erőforrás, érvényességi korlát.
F1. A GlyphLab generáló folyamatában a független
húzási egység a scene_id: a jelenet pozíciója, vastagsága
és hossza jelenetszinten dől el, a két view csak a jelenet két zajos
megfigyelése. A képszintű bootstrap ezt a klaszterszerkezetet figyelmen
kívül hagyja. Legyen 30 jelenet, mindegyikben két view, és minden
jelenet vagy mindkét view-val helyes
(),
vagy mindkettővel hibás
();
legyen 27 helyes jelenet. Ekkor a jelenetátlag 0,9, a jelenetek
binomiálisak, a jelenet-átlag standard hibája
.
A képszintű bootstrap 60 képet lát, amelyek páronként duplikált
kimenetűek, és
nagyságrendű szórást becsül; a kettő aránya
.
A feltétel az, hogy a view-k a jeleneten belül tökéletesen korrelálnak;
gyengébb korrelációnál az arány kisebb, de a képszintű eljárás akkor sem
becsüli a jelenet-egység szerinti mintavételt.
F2. A 0. osztályban , az eltérések , , , négyzetösszegük . A lemma szerint a réteg replikátum-varianciája . Az 1. osztályban , az eltérések , , , így ugyanúgy . A teljes replikátum a két rétegátlag átlaga, ezért varianciája . Numerikusan , szórás .
F3. A kód:
import numpy as np
def stratified_scene_bootstrap(A_by_class, B, seed):
rng = np.random.default_rng(seed)
classes = list(A_by_class)
out = np.empty(B, dtype=np.float64)
for b in range(B):
total = 0.0
for k in classes:
vals = np.asarray(A_by_class[k], dtype=np.float64)
c = vals.shape[0]
if c == 0:
raise ValueError("empty class layer")
total += vals[rng.integers(0, c, size=c)].mean()
out[b] = total / len(classes)
return out
A = {0: np.array([1.0, 1.0, 0.5]),
1: np.array([0.5, 0.5, 0.0])}
repl = stratified_scene_bootstrap(A, 2000, 20261020)
assert repl.shape == (2000,)
assert abs(repl.mean() - 7/12) < 0.01
assert abs(repl.std(ddof=1) - np.sqrt(1/108)) < 0.02Szélsőeset: ha egy rétegben , a replikátum az egyetlen értéket adja, a rétegvariancia 0; a kód ezt helyesen kezeli, mert az egyetlen elem átlaga determinisztikus. A tolerancia a replikátum véletlen hibájára méretezett.
F4. A hiba: a kód az összes jelenetet egyetlen
halmazként húzza visszatevéssel, így nem tartja rétegenként a
jelenetszámot. Következmény: a replikátum osztályösszetétele
véletlenszerűen eltér a GlyphLab fix osztályarányától; ha az
osztályaccuracyk különböznek, a replikátumok átlaga a húzott
osztályarány szerint kever, és a becslő várható értéke nem a
célpopuláció fix
--
súlyozását tükrözi. Javítás: minden
rétegből külön
húzás, ahogyan a bootstrap_from_dict teszi.
Visszaellenőrzés a kézi példán: a rétegzett változat átlaga
körül marad, a hibás változat átlaga replikátumonként a véletlen
osztályaránnyal együtt mozog.
F5. Egy lehetséges protokoll. Kérdés: mekkora a
corrupt bemenet jelenetátlagos accuracy-csökkenése ugyanazon modellen?
Hipotézis: a corrupt zaj az egyik orientációt jobban rontja. Adat:
test_preds.npy és test_preds_corrupt.npy,
mindkettő a metadata test sorrendben; a modellválasztás kizárólag val
loss alapján történt. Megfigyelési egység: jelenet. Baseline: a clean
jelenetátlagos accuracy. Metrika:
átlaga és 95%-os páros, osztály-rétegzett jelenet-bootstrap intervallum,
,
seed rögzített. Seed-rész: három training seed mindegyikére kiszámítjuk
a clean jelenetaccuracyt, és a seed-átlagot/min–max tartományt a
teszt-CI-től külön jelentjük. Érvényességi korlát: a CI a betanított
modellre és a GlyphLab generáló eljárásra feltételes; más
adateloszlásra, annotációs hibára és későbbi modellfrissítésre nem ad
fedést. A futáshoz kis CPU elegendő, mert csak 60 predikción végzünk
újramintavételt.
Gyakori tévedés: „a training seedekből vagy a képszintű szórásból
számolt intervallum az alkalmazási kockázat intervalluma.”
Ellenpélda: egy determinisztikus tanítási csővezeték három seeddel
pontosan ugyanazt a súlyt állítja elő; a seed-szórás nulla, a
seed-intervallum [0.9, 0.9], miközben a 30 tesztjelenetből
fakadó mintavételi bizonytalanság nem nulla. Megfordítva: ha a
tesztjelenetek nagyon hasonlók, a bootstrap intervallum szűk, de a
modell egy másik adateloszláson mégis hibázhat; a szűk CI nem
domain-garancia. Az 5.4. Függő képek, seedek és
resampling döntése itt is érvényes: a független mintavételi egység a
scene_id, nem a kép.
Önellenőrzés: szerepel-e a jelentésben a bootstrap egység, a
rétegződés,
és a seed? Ellenőriztem-e a predikciók sorrendjét a metadata
index szerint? A tesztértékelést nem használtam-e modell-
vagy küszöbválasztásra? A seed-ingadozást külön közöltem-e a
tesztminta-intervallumtól? A 19.3. EfficientNet és
architektúraskálázás hardveres mérési szórásához hasonlóan itt is
igaz: két különböző szórás összegzése előtt meg kell nevezni, melyik
véletlen forrást mérjük.
A GlyphLab tesztminta független egysége a jelenet, ezért a jelenetátlagos accuracy a scene-átlagok átlaga, és a bootstrap is scene-egységen húz. Az osztály-rétegzés minden replikátumban rögzíti a jelenetszámot; a replikátumsúlyok rétegenként 1-re normáltak, várható értékük az egyenletes jelenetsúly. A lemma a replikátumok feltételes átlagát és varianciáját indikátorhúzásokkal vezeti le. A clean–corrupt összevetés páros, mert ugyanazon jelenet/nézet párokat használ. A bootstrap intervallum a tesztminta-mintavételi bizonytalanság közelítése, nem alkalmazási kockázat; a training seedek szórása külön dimenzió.
A 23.4. Hibaszeletek és alkalmazási határ egység a hibaszeleteket vizsgálja: ugyanezt a jelenet-egységet és rétegzett bootstrapet használjuk osztályonkénti vagy nehézségi csoportonkénti hibaelemzésre.