A 23.3. Páros összehasonlítás és csoportszintű intervallum egységben a páros összehasonlítást és a scene-szintű, osztály-rétegzett bootstrap intervallumot vezettük be. Ott a metrika a teljes tesztkészletre vonatkozott. Itt a kérdés az: mely részhalmazokon romlik el a modell, és mekkora csoportméret alatt már nem tehetünk értelmes állítást. A 22.4. Augmentációs kísérlet és domainváltozás alfejezetben a clean és corrupt teszt eloszlását különítettük el; a 19.4. ConvNeXt és a recept/architektúra elválasztása arra tanított, hogy az architektúra és a tanítási recept hatását ne keverjük, amit most annyiban használunk, hogy a szeletelemzés a modellválasztástól független értékelési réteg. A 11.4. Egy teljes PyTorch tanítási iteráció logitokból származó döntési szabálya adja a döntési határ definícióját.
Induló becslés: lehet-e egy 80%-os tesztpontosságú modellnek olyan szelete, amelyen a pontosság 0%? A következő szakaszokban ezt formálisan megválaszoljuk.
Definíció (szelet). Legyen a teszt indexeinek halmaza, . Egy szelet , a gyakorlatban egy megfigyelhető attribútum (osztály, nézet, korrupció, scene-csoport) által indukált részhalmaz. A predikciók , a célok . A szelet pontossága
Ha
,
az érték definiálatlan; az üres szeletet a modellkártyán „nincs adat”
jelöléssel hagyjuk ki, nullával való feltöltés hamis eredmény lenne. A
kódban a szeletet
hosszú logikai maszkkal ábrázoljuk: mask[i] == True
pontosan akkor, ha
.
Definíció (csoportméret). A csoportméret a szelet elemszáma . A GlyphLab tesztben osztályonként 10 scene és 20 kép van; egy osztály-korrupt keresztmetszet például . A bootstrap becslés szórása növekszik, ha kicsi, ezért a jelentésben minden szelet mellé kiírjuk -t, nemcsak a pontosságot.
Definíció (shift). Shiftnek nevezzük, ha a kiértékelési bemeneti eloszlás eltér a tanításitól. A GlyphLab corrupt tesztje ugyanazon scene/view párokat zajjal és fényességi gradienssel módosítja, tehát egy pontosan specifikált korrupciós eloszlást reprezentál; nem általános „valós világ” shiftet. A clean és corrupt teszt páros szerkezete miatt a romlást páronként számoljuk, ahogyan 22.4. Augmentációs kísérlet és domainváltozás rögzítette.
Definíció (hibataxonómia). A hibataxonómia a téves predikciók rendezése három tengely mentén:
A taxonómia tehát minden hibás indexhez egy rekordot rendel; ez operatív osztályozás, nem ok-okozati magyarázat. A belőle képzett összegzés például „a corrupt környezetben a vertical→diagonal tévesztések száma 7, ebből 5 a 12-es scene mindkét nézetéből származik”. Az ilyen mondat leíró állítás; azt, hogy miért éppen az a scene hibázik, külön vizsgálat nélkül nem állítjuk.
Definíció (döntési határ). A modell logitokat ad: , és
A döntési határ azon pontok halmaza, ahol az argmax nem egyértelmű, azaz létezik , hogy . Ha a shift az input eloszlást a határ felé tolja, a hibavalószínűség nőhet. Ez utóbbi heurisztika: abból, hogy egy szelet hibái a határhoz közeliek, nem következik bizonyítottan, hogy a shift okozta őket.
Állítás. Legyen teszt-indexhalmaz, , és nemüres. Ha minden téves, akkor
Bizonyítás. A hibák száma . Feltétel szerint . Ezért
Egyenlőség pontosan akkor áll fenn, ha , vagyis a halmaz minden eleme helyes. Az állításból következik a levezetési kötelezettség ellenpéldája: , mellett, ha minden eleme hibás és a többi 95 helyes, akkor , miközben . A globális pontosság tehát nem garantál semmilyen alsó korlátot egy tetszőleges szeletre; a globális érték a szeletpontosságok méret szerint súlyozott átlaga, az átlag pedig elfedheti a kis súlyú, teljesen hibás csoportot.
Legyen
,
,
és két szelet: a clean az első 8, a corrupt az
utolsó 2 index.
| Szelet | Méret | Helyes | Pontosság |
|---|---|---|---|
| Összes | 10 | 8 | 0,80 |
| clean | 8 | 8 | 1,00 |
| corrupt | 2 | 0 | 0,00 |
A globális 0,80 mellett a corrupt szelet 0,00; ez a fenti állítás egyenlőségi esete, hiszen .
# workdir: lab/
import numpy as np
def slice_accuracy(y_true, y_pred, mask):
"""Visszaadja: (correct, size, acc); acc None, ha a szelet üres."""
mask = np.asarray(mask, dtype=bool)
n = int(np.sum(mask))
if n == 0:
return 0, 0, None
correct = int(np.sum(y_true[mask] == y_pred[mask]))
return correct, n, correct / n
y_true = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 2], dtype=np.int64)
y_pred = np.array([0, 0, 0, 1, 1, 1, 2, 2, 0, 0], dtype=np.int64)
clean = np.array([True] * 8 + [False] * 2)
corrupt = ~clean
print("clean :", slice_accuracy(y_true, y_pred, clean))
print("corrupt :", slice_accuracy(y_true, y_pred, corrupt))
print("overall :", np.mean(y_true == y_pred))Várt kimenet: clean : (8, 8, 1.0),
corrupt : (2, 0, 0.0), overall : 0.8.
Cél. A hallgató a saját, befagyasztott GlyphLab-modelljének clean és corrupt predikcióiból szeletelemzést és hibataxonómiát készít, majd JSON modellkártyát állít elő. A teszt nem befolyásolhatja a modellválasztást: a checkpointot kizárólag a validációs loss alapján választottuk, a clean és corrupt kiértékelés ezután, egyszer történik.
Input. data/ könyvtár a GlyphLab
szerződés szerint; pred_labels.npy alak (60,)
int64, corr_pred_labels.npy alak (60,) int64,
mindkettő a metadata.csv test sorainak sorrendjében.
# workdir: lab/
# make_model_card.py
import argparse, json
import numpy as np
import pandas as pd
def scene_resample_indices(meta, rng):
idxs = []
for cls in range(3):
scenes = meta[meta.label == cls].scene_id.unique()
chosen = rng.choice(scenes, size=len(scenes), replace=True)
for sc in chosen:
sub = meta[(meta.label == cls) & (meta.scene_id == sc)]
idxs.append(sub.index.to_numpy())
return np.concatenate(idxs)
def bootstrap_ci(meta, y_true, y_pred, rng, B=2000):
stats = []
for _ in range(B):
idx = scene_resample_indices(meta, rng)
stats.append(np.mean(y_true[idx] == y_pred[idx]))
return float(np.percentile(stats, 2.5)), float(np.percentile(stats, 97.5))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--pred", required=True)
ap.add_argument("--corr-pred", required=True)
ap.add_argument("--out", default="model_card.json")
ap.add_argument("--data-dir", default="data")
ap.add_argument("--seed", type=int, default=20261020)
a = ap.parse_args()
meta = pd.read_csv(f"{a.data_dir}/metadata.csv")
y_all = np.load(f"{a.data_dir}/labels.npy")
test_meta = meta[meta.split == "test"].copy()
idx = test_meta.index.to_numpy()
y_true = y_all[idx]
y_clean = np.load(a.pred).astype(np.int64)
y_corr = np.load(a.corr_pred).astype(np.int64)
rng = np.random.default_rng(a.seed)
card = {
"selection": "best val loss only",
"overall": {
"clean_acc": float(np.mean(y_true == y_clean)),
"corrupt_acc": float(np.mean(y_true == y_corr)),
"paired_drop": float(np.mean(y_true == y_clean)
- np.mean(y_true == y_corr)),
},
"slices": {},
"failure_taxonomy": {},
"status": "measured by the student run",
}
for cls in range(3):
mask = (test_meta.label == cls).to_numpy()
acc = np.mean(y_true[mask] == y_clean[mask])
lo, hi = bootstrap_ci(test_meta[mask], y_true[mask],
y_clean[mask], rng)
card["slices"][f"clean_class_{cls}"] = {
"n": int(mask.sum()), "acc": float(acc), "ci95": [lo, hi]}
lo, hi = bootstrap_ci(test_meta, y_true, y_corr, rng)
card["slices"]["corrupt_all"] = {
"n": len(y_true),
"acc": float(np.mean(y_true == y_corr)),
"ci95": [lo, hi]}
for env, yp in [("clean", y_clean), ("corrupt", y_corr)]:
C = np.zeros((3, 3), dtype=int)
for a in range(3):
for b in range(3):
C[a, b] = int(np.sum((y_true == a) & (yp == b)))
card["failure_taxonomy"][env] = C.tolist()
with open(a.out, "w", encoding="utf-8") as f:
json.dump(card, f, indent=2)
print(card["overall"])
if __name__ == "__main__":
main()Futtatási parancs:
python make_model_card.py --pred pred_labels.npy \
--corr-pred corr_pred_labels.npy --out model_card.json --data-dir dataEllenőrzés. A JSON minden acc értéke
-ben
legyen; minden n pozitív; a CI határok monotonok
().
A confusion mátrixok sorösszege a clean esetben osztályonként 20, a
corrupt esetben ugyancsak 20. Jelölt szemléltetés, nem mért
futás: a várható JSON szerkezete például
{
"overall": {"clean_acc": 0.82, "corrupt_acc": 0.61, "paired_drop": 0.21},
"slices": {
"clean_class_0": {"n": 20, "acc": 0.90, "ci95": [0.80, 1.00]},
"clean_class_1": {"n": 20, "acc": 0.75, "ci95": [0.60, 0.90]},
"clean_class_2": {"n": 20, "acc": 0.80, "ci95": [0.65, 0.95]},
"corrupt_all": {"n": 60, "acc": 0.61, "ci95": [0.50, 0.72]}
},
"failure_taxonomy": {"clean": [[18,1,1],[2,15,3],[1,3,16]]},
"status": "illustrative values, not a measured run"
}Értelmezés. Ha a corrupt CI nem tartalmazza a clean pontosságot, a páros romlás a definiált korrupciós eloszláson jelentős; ez nem általános domain-shift garancia. A kis csoportméretű szeletek CI-je széles; azokból nagy állítást tenni tilos.
1. F1 fogalmi, nehézség 1. Definiálja a szelet pontosságát, és indokolja, miért nem jelentünk értéket üres szeletre. Adjon egy üres szelet példát a GlyphLab teszt metadata alapján. Átadandó: képlet, az üres eset szabálya, egy konkrét üres mask.
2. F2 levezetési/bizonyítási, nehézség 2. Bizonyítsa be a fenti Állítást, és adja meg az egyenlőség pontos feltételét. Ellenőrizze a , példán, hogy a globális pontosság maximuma . Átadandó: teljes bizonyítás és a maximum indoklása.
3. F3 implementációs, nehézség 2. Írjon NumPy
függvényt slice_accuracy(y_true, y_pred, mask) néven, amely
üres szeletre (0, 0, None) értéket ad. Számítsa ki a kézi
példa mindhárom sorát 64 bites egészekkel. Átadandó: kód,
futtatási parancs, a három sor kimenete.
4. F4 hibakeresési, nehézség 2. Az alábbi függvény hibás:
def slice_accuracy_bad(y_true, y_pred, mask):
return np.mean(y_true == y_pred) # a mask elveszikLokalizálja a hibát, írja le a következményét a modellkártyán, javítsa, és a kézi példán mutassa meg, hogy a helyes függvény a corrupt szeletre 0,0-t ad, a hibás pedig 0,8-at. Átadandó: ok, következmény, javított kód, ellenőrző futtatás kimenete.
5. F5 kísérleti/transzfer, nehézség 3. Tervezzen
szeletelemzést a GlyphLab test view_id értékei szerint (0
és 1 nézet). Adja meg a megfigyelési egységet, a bootstrap mintavételi
egységet, a baseline-t, a metrikát és a következtetési korlátot.
Átadandó: féloldalas protokoll, amely kitér arra, hogy a két
view ugyanazon scene-hez tartozik.
1. A szelet pontossága
,
.
Üres szeletre a nevező nulla, a tört definiálatlan; a 0-val jelölés
hamis „minden rossz” állítást sugallna. Üres szelet a GlyphLab-ban
például a split == "train" mask a test predikciókra; vagy
egy nem létező scene_id szűrés, például
metadata.scene_id == 999 a testben.
2. Az Állítás bizonyítása a fenti levezetés. Egyenlőség pontosan akkor, ha , azaz minden eleme helyes. A példán: a hibaszám legalább 5, ezért a pontosság legfeljebb ; ez elérhető, ha a maradék 95 mind helyes, tehát a maximum pontosan .
3. A kézi példa függvénye a fenti kód. Az
np.int64 miatt a shape
;
a kimenet:
clean : (8, 8, 1.0)
corrupt : (2, 0, 0.0)
overall : 0.8
4. A hiba oka: a függvény nem alkalmazza a maskot,
így a slice-ra vonatkozó kérdésre a teljes minta pontosságát adja.
Következmény: a modellkártya minden szeletsorában ugyanaz a globális
érték jelenik meg, a kis hibás szelet eltűnik. Javítás a 3. feladat
slice_accuracy függvénye. A kézi példán a hibás függvény a
corrupt maskra is
-at
ad, a helyes pedig
-t.
5. Protokollváz: split: a rögzített test 60 képe;
megfigyelési egység: kép, de a bootstrap mintavételi egység scene_id,
osztályonként rétegezve (osztályonként 10 scene visszatevéssel).
Csoportosítás: view_id == 0 és view_id == 1,
mindkét csoport mérete 30. Baseline: a clean teszt teljes pontossága.
Metrika: szeletpontosság és scene-bootstrap 95% CI; a két view
összehasonlítása páros, mert minden scene mindkét view-t tartalmazza,
ezért a view-különbségre scene-szintű páros bootstrap vagy előjel-teszt
kell. Következtetési korlát: ha a CI-k átfednek, nem állítunk view-függő
különbséget; a view nem új független eloszlás, hanem ugyanazon jelenet
két zajrealizációja.
Tipikus tévedés. Kis szeletből nagy általános állítást tenni. Ellenpélda: egy 5 elemű corrupt szeleten mért 0% pontosságból azt állítani, hogy „a modell használhatatlan a corrupt környezetben”. A scene-bootstrap 95% CI ilyen kis csoporton rendkívül széles lehet; egy másik, független corrupt mintán a pontosság lehet 80% is. A kis minta nagy varianciájú becslést ad; a korrekt állítás a CI-vel együtt fogalmazható meg: „a pontosság becslése 0, de a 95% CI [0, 60%], ezért a használhatatlanság nem igazolt”.
Önellenőrzés. (1) A szelet definíciója a teszt
indexeire épül-e, nem a tanítóra? (2) Üres szelet esetén
None szerepel-e? (3) A CI scene-szintű bootstrapből
származik-e? (4) A corrupt kiértékelés a befagyasztott modellen,
modellválasztás után történt-e? Ha bármelyik nem, a modellkártya
állítása nem érvényes.
Visszahívás. A 23.3. Páros összehasonlítás és csoportszintű intervallum bootstrap egysége a scene_id volt, osztályonkénti rétegzéssel; ezt a szeletek CI-jében is megtartottuk. A 22.4. Augmentációs kísérlet és domainváltozás clean/corrupt eloszlás-megkülönböztetése adja a környezeti szeletet. A 11.4. Egy teljes PyTorch tanítási iteráció logit–döntés szerződése a döntési határ definíciójának alapja.
Az összesített pontosság a szeletek feletti súlyozott átlag; jó globális érték mellett is létezhet nulla pontosságú kis szelet, mert a hiba koncentrálódhat. A modellkártya ezért clean/korrupt, osztály- és nézetszintű bontást, csoportméreteket és scene-rétegzett bootstrap intervallumokat közöl. A kis csoportból levont állítás a széles CI miatt bizonytalan. A corrupt teszt csak a befagyasztott modell kiválasztása után értékelhető, és a shift csupán az adott korrupciós eloszlást reprezentálja. A hibataxonómia a hibákat cella, környezet és csoport szerint rendezi; a döntési határ közelsége heurisztika, nem bizonyított ok-okozat.
A következő 24.1. Tanulási görbe és hibadiagnózis egység a tanulási görbét és a hibadiagnózist vizsgálja; az itteni szeleteredmények ott azt segítik eldönteni, hogy a hiba adat-, modell- vagy optimalizálási eredetű-e.