Ez az egység a modellértékelés első rétegét adja: egy véges, egycímkés osztályozási predikcióhalmazt hogyan foglalunk össze úgy, hogy a közölt szám értelmezhető és ellenőrizhető legyen. A pontosság egyetlen skalár, de elrejti, hogy a modell mely osztályokat téveszti össze, és hogy a hiba a ritka vagy a gyakori osztályt érinti-e. Az 5.4. Függő képek, seedek és resampling alfejezetben megismert függő képek miatt a confusion matrix cellái képszintű pontszámok; a klaszteres mintavételi bizonytalanságot külön, a 23.3. Páros összehasonlítás és csoportszintű intervallum alfejezetben kapcsoljuk hozzá. A 8.4. II. ellenőrző pont: korrekt tanulási állítás szerint a tesztmetrika a modellválasztás lezárása után, független halmazon értelmezhető; a 21.4. Osztályarány, súlyozás és céleloszlás osztályarányai pedig közvetlenül meghatározzák, hogy az accuracy mennyire érzékeny a többségi osztályra. A 22.4. Augmentációs kísérlet és domainváltozás clean/corrupt párosítását most egyelőre egyetlen kiértékelő halmazra szűkítjük.
Induló kérdés. Egy kiértékelő halmazban 60 kép van:
50 vertical, 6 horizontal, 4
diagonal. Egy konstans modell minden képre a
vertical osztályt adja. Mennyi az accuracy, és mennyi a
három osztály recalljának számtani átlaga? Tippelje meg, mielőtt a 3–4.
szakasz képleteivel kiszámolná; a két szám nagyon eltérő történetet
mond.
Előismeret: osztályindex-célok és a 3-osztályos GlyphLab-séma a 22.1. Adateredet, címke és csoportos split
alfejezetből; a logits és argmax kapcsolata a
7.2. Softmax és többosztályos cross-entropy
alfejezetből; az inference_mode alatti predikciógyűjtés a
11.1. Tensor és autograd-életciklus
alfejezetből.
Legyen az osztályok száma, a kiértékelt minták száma. A predikciókat két int64 vektor tárolja:
Ha a modell logitsot ad, akkor
.
A
pontszámokból a metrikához már nincs szükség softmaxra; a döntési
szabályt az [D07] dokumentált
CrossEntropyLoss-konvenciójától függetlenül, külön
kiértékelő lépésben hajtjuk végre.
Definíció (confusion matrix). A mátrix eleme azon minták száma, amelyekre és :
A könyvben a sor a tényleges, az oszlop a predikált osztály. A szakirodalomban a transzponált konvenció is előfordul; ezért minden riportban a tengelyeket meg kell nevezni. A cellák darabszámok, nem valószínűségek, és
A sorösszeg az osztály supportja, az oszlopösszeg a osztályba sorolt minták száma.
Definíció (accuracy). Az accuracy a helyes döntések aránya:
Ha , az accuracy definiálatlan; a továbbiakban .
Definíció (one-vs-rest sejtszámok). Rögzített osztályra a többi osztályt negatívként összevonva:
A a többi cella összege:
Innen a precision és a recall osztályonként:
A pontosság azt kérdezi: a modell által -nek mondott minták hányada valóban ; a recall azt: a valódi -k hányadát találta meg. Mindkettő akkor értelmes, ha a nevező pozitív:
A nulla nevezőre három konvenciót rögzítünk. A raise mód
hibát dob. A zero mód a nulla nevezőjű értéket
-nak
veszi. Az omit mód NaN-t ad, és a makro
átlagból kihagyja az érintett osztályt; a könyv laborja az
omit módot használja, mert így látható marad, hogy az
osztály nem volt megfigyelhető. A döntést a riportban mindig közölni
kell.
Az F1 osztályonként a precision és a recall harmonikus közepe:
feltéve, hogy a két érték definiált. Ha
és
egyszerre, akkor az osztály sem a célban, sem a predikcióban nem
szerepel; ekkor
-nak
definiáljuk, függetlenül a zero_division beállítástól. Ha
az egyik komponens omit miatt NaN, az
is NaN.
Tekintsük a , esetet:
Az accuracy a főátló összege osztva -nel:
A 0. osztályra: , , , tehát
Az 1. osztályra: , , , így
A 2. osztályra: , , , így
A mikro összegzés a cellákat osztályok fölött összeadja:
Egycímkés osztályozásban minden minta pontosan egy predikciót kap, ezért minden téves cella egyszer jelenik meg -ként és egyszer -ként; így és . Következésképpen
Állítás. Egycímkés, mintás osztályozásban .
Bizonyítás. A definíciókból
mert a belső összeg az oszlop összege. Ugyanígy a recall nevezője a sorösszegek összege, ami szintén . A közös számláló , tehát a három mikro érték egyaránt .
A makro átlagolás a fenti mátrixon:
és ugyanígy . Ez a példa elfedi a makro , és különbségét, mert minden osztályra véletlenül . A következő mátrix megmutatja a nulla nevezőt is:
A 2. osztályt senkinek sem jósolta a modell, ezért és ; a nevezője nulla. A 0. osztályra:
Az 1. osztályra:
A 2. osztályra
,
de
definiálatlan. zero_division='omit' mellett:
míg
A makro az érvényes osztályonkénti -ek átlaga:
A zero_division='zero' választás esetén
,
,
ezért
A mikro érték mindkét konvencióban
Ez a három szám egyszerre mutatja a nulla nevező hatását és a
mikro/makro eltérést: ugyanazon a predikciós halmazon a makro
omit módban
,
zero módban
,
a mikro
pedig
.
A „0,63-as F1” és a „0,58-as F1” állítás tehát nem feltétlenül
ellentmondás.
A mikro összegzés a mátrix összes nem átlós celláját egyenlően kezeli, ezért a gyakori osztályok hibái nagyobb darabszámot kapnak. A makro összegzés minden osztályt egy szavazatként kezel, ezért a ritka osztály hibája ugyanannyit nyom, mint a gyakorié. Egyik sem „helyesebb” általánosságban: a jelentéshez a felhasználási szerződésből kell megmondani, hogy egy tévesztés költsége darabszám-arányos-e.
Definíció (class prevalence). Az osztály prevalenciája a mintában
A prevalence a cél eloszlásának empirikus aránya, nem a modell tulajdonsága. A , definíciójából , ezért
Az accuracy tehát a recall prevalence-súlyozott átlaga. Ez a
levezetés magyarázza az induló kérdést: a konstans vertical
modellnél
,
,
a prevalenciák
,
így
míg a makro recall
Ugyanaz a modell tehát „83% pontosságú” és „33% makro recallú” egyszerre; a két szám nem ugyanazt a kérdést válaszolja meg.
A makro
definíciója a per-class
értékek átlaga. Nem a makro
és makro
harmonikus közepe. A
példán omit módban:
A két képlet közötti különbség akkor nagy, ha az osztályonkénti és párok eltérő irányban szórnak.
Cél. Egy saját NumPy-függvény számítsa ki a confusion matrixot és a metrikákat, és ellenőrizze a kézi példát; majd a tanuló saját CNN-jének kiértékelő riportját értelmezze.
Input. A toy mód beépített tömböket használ. A riport mód bemenete:
data/labels.npy: (360,) int64
GlyphLab-címkék;my_run/eval_preds.npz: indices
(N,) int64 metadata-sorindex, logits
(N,3) float32.A riportot a modell kiértékelésekor így menthetjük:
# a modell saját seed-protokollja után, eval módban:
model.eval()
with torch.inference_mode():
logits_list, index_list = [], []
for x, idx in eval_loader:
logits_list.append(model(x))
index_list.append(idx)
z = torch.cat(logits_list, dim=0).cpu().numpy().astype(np.float32)
idx = torch.cat(index_list, dim=0).cpu().numpy().astype(np.int64)
np.savez_compressed("my_run/eval_preds.npz", indices=idx, logits=z)A metrika számítása determinisztikus, ezért véletlen seedre nincs
szükség. A bemenő logits float32, a belső osztások
float64-ben futnak; az eszköz NumPy-CPU.
Kód. Munkakönyvtár lab/, fájl
metrics_check.py:
#!/usr/bin/env python3
# n089 labor: confusion matrix és aggregációs konvenciók.
# Toy: python metrics_check.py --mode toy
# Riport: python metrics_check.py --mode report --report my_run/eval_preds.npz
import argparse
import numpy as np
def confusion_matrix(y_true, y_pred, num_classes=None):
y_true = np.asarray(y_true, dtype=np.int64)
y_pred = np.asarray(y_pred, dtype=np.int64)
if y_true.shape != y_pred.shape:
raise ValueError("y_true es y_pred alakja elter")
if num_classes is None:
num_classes = int(max(y_true.max(initial=0),
y_pred.max(initial=0))) + 1
K = num_classes
if K <= 0:
raise ValueError("num_classes pozitiv kell legyen")
if y_true.min(initial=0) < 0 or y_pred.min(initial=0) < 0 or \
y_true.max(initial=0) >= K or y_pred.max(initial=0) >= K:
raise ValueError("cimke kivul esik a [0, K) tartomanyon")
C = np.zeros((K, K), dtype=np.int64)
np.add.at(C, (y_true, y_pred), 1)
return C
def precision_recall_from_confusion(C, zero_division="omit"):
if zero_division not in {"raise", "zero", "omit"}:
raise ValueError("ismeretlen zero_division mod: " + str(zero_division))
K = C.shape[0]
tp = np.diag(C).astype(np.float64)
fp = C.sum(axis=0) - tp
fn = C.sum(axis=1) - tp
def safe(num, den):
if zero_division == "raise":
if np.any(den == 0):
raise ZeroDivisionError("nulla nevezo a precision/recall-ban")
return num / den
fill = 0.0 if zero_division == "zero" else np.nan
out = np.full(K, fill, dtype=np.float64)
np.divide(num, den, out=out, where=den > 0)
return out
P = safe(tp, tp + fp)
R = safe(tp, tp + fn)
denom = P + R
both_zero = (P == 0.0) & (R == 0.0)
valid = np.isfinite(P) & np.isfinite(R)
F1 = np.full(K, np.nan, dtype=np.float64)
mask = valid & (denom > 0)
F1[mask] = 2.0 * P[mask] * R[mask] / denom[mask]
F1[both_zero] = 0.0
return P, R, F1
def core_metrics(C, zero_division="omit"):
N = int(C.sum())
if N == 0:
raise ValueError("ures matrixra a metrika definialatlan")
P, R, F1 = precision_recall_from_confusion(C, zero_division)
acc = float(np.diag(C).sum()) / N
return {
"accuracy": acc,
"micro_precision": acc,
"micro_recall": acc,
"micro_f1": acc,
"macro_precision": float(np.nanmean(P)),
"macro_recall": float(np.nanmean(R)),
"macro_f1": float(np.nanmean(F1)),
"prevalence": C.sum(axis=1) / N,
"per_class": {"precision": P, "recall": R, "f1": F1},
}
TOY_TRUE = np.array([0]*6 + [1]*4 + [2]*2, dtype=np.int64)
TOY_PRED = np.array(
[0,0,0,0,1,1, # 0. sor: 4 db 0, 2 db 1
0,1,1,1, # 1. sor: 1 db 0, 3 db 1
0,1], # 2. sor: 1 db 0, 1 db 1
dtype=np.int64)
EXPECTED = np.array([[4,2,0],
[1,3,0],
[1,1,0]], dtype=np.int64)
def run_toy():
C = confusion_matrix(TOY_TRUE, TOY_PRED, num_classes=3)
assert np.array_equal(C, EXPECTED)
res = core_metrics(C, zero_division="omit")
assert np.isclose(res["accuracy"], 7/12)
assert np.isclose(res["micro_f1"], 7/12)
assert np.isclose(res["macro_f1"], 19/30)
assert np.isclose(res["per_class"]["precision"][2], np.nan, equal_nan=True)
print("toy ellenorzes OK")
for k in ["accuracy","micro_f1","macro_precision",
"macro_recall","macro_f1"]:
print(f"{k:18s} {res[k]:.4f}")
def run_report(path):
data = np.load("data/labels.npy")
rpt = np.load(path)
indices = rpt["indices"].astype(np.int64)
logits = rpt["logits"].astype(np.float32)
if logits.ndim != 2 or logits.shape[1] != 3:
raise ValueError("GlyphLab-riport: logits alak (N,3) kell")
if indices.shape != (logits.shape[0],):
raise ValueError("indices es logits hossza elter")
y_true = data[indices]
y_pred = np.argmax(logits, axis=1).astype(np.int64)
C = confusion_matrix(y_true, y_pred, num_classes=3)
assert C.sum() == len(indices)
res = core_metrics(C, zero_division="omit")
print("sorosszeg (tamogatas):", C.sum(axis=1))
print("oszloposszeg (predikcio):", C.sum(axis=0))
print("normalizalt sorok (recall-profil):")
for i, row in enumerate(C):
if row.sum() > 0:
print(i, np.round(row / row.sum(), 3))
for k in ["accuracy","micro_f1","macro_precision",
"macro_recall","macro_f1"]:
print(f"{k:18s} {res[k]:.4f}")
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--mode", choices=["toy","report"], required=True)
p.add_argument("--report", default="")
a = p.parse_args()
if a.mode == "toy":
run_toy()
else:
run_report(a.report)Futtatás és ellenőrzés.
cd lab
python metrics_check.py --mode toyA toy mód várt kimenete magából a
levezetéséből számított, nem mért futás: accuracy
,
micro F1
,
macro precision
,
macro recall
,
macro F1
.
A második osztály precisionje NaN. A riport mód:
python metrics_check.py --mode report --report my_run/eval_preds.npzA riport mód ellenőrzi a shape- és indexszerződést, majd kiírja a
mátrixot, a támogatást és a normalizált sorokat. A sorösszegek az igaz
eloszlást, az oszlopösszegek a predikciós eloszlást mutatják; a
normalizált sor a recall, nem pedig a precision. A gyakori hiba az, ha a
mátrixot a sklearn-stílusú normalize='true' nélkül
közöljük, és az olvasó a nagy cellaszámot automatikusan jó osztályozási
aránynak hiszi.
F1 (fogalmi, nehézség 1). Magyarázza el, miért nem
cserélhető fel a micro és a macro átlagolás. Adjon három osztályból
álló, kis y_true/y_pred példát, amelyben az
accuracy magas, de a macro recall alacsony. Elvárt: definíciók, a példa
tömbjei, a két számérték.
F2 (levezetési, nehézség 2). A
mátrixból számítsa ki az accuracyt, az osztályonkénti
precision/recall/F1 értékeket, a micro és a macro összegzést. Használja
a zero_division='omit' konvenciót, és jelölje, ha
valamelyik nevező nulla lenne. Elvárt: minden közbülső sejtszám és a
három makro érték.
F3 (implementációs, nehézség 2). Implementálja a
precision_recall_from_confusion függvényt a 5. szakasz
specifikációja szerint, a három zero_division móddal.
Tesztelje a
mátrixon omit és zero módban is. Elvárt:
futtatható függvény, a két mód makro F1-értéke, és egy negatív teszt,
amely raise módban nulla nevezőre hibát ad.
F4 (hibakeresési, nehézség 2). Adott a következő hibás kód:
tp = np.diag(C).astype(np.float64)
fp = C.sum(axis=1) - tp # sor szerint
fn = C.sum(axis=0) - tp # oszlop szerint
P = tp/(tp+fp)
R = tp/(tp+fn)
macro_p = P.mean()
macro_r = R.mean()
macro_f1 = 2*macro_p*macro_r/(macro_p+macro_r)Hol a hiba? Számítsa ki a
mátrixon, zero_division='zero' mellett, a hibás és a
javított makro
értéket. Elvárt: a hiba oka, következménye, a javított tengelyek és a
numerikus ellenőrzés.
F5 (kísérleti/transzfer, nehézség 3). A GlyphLab
kiértékelő halmaza osztályonként kiegyenlített, de a telepítési
környezetben a feltételezett prevalencia
a vertical, horizontal, diagonal célra. Tervezze meg,
hogyan hasonlítaná össze a mikro és a makro metrika érzékenységét erre a
prevalence-változásra anélkül, hogy a tesztet újra felhasználná
modellválasztásra. Elvárt: protokoll, a mátrixból számított átsúlyozási
képlet, legalább két értelmezési korlát.
F1. A micro átlagolás a mátrix celláit osztályok fölött összegzi; a gyakori osztály darabszáma nagyobb súlyt kap. A macro átlagolás minden osztály per-class értékét azonos súllyal veszi, függetlenül a supporttól. Példa: és . Ekkor , és , így . Ez az accuracy–macro recall hézag a ritka osztályok teljes eltévesztéséből adódik.
F2. A mátrix sorösszegei: ; oszlopösszegei: ; , , tehát
A 0. osztályra , , ; ezért , . Az 1. osztályra , , ; ezért , , . A 2. osztályra , , ; ezért , , . Nulla nevező nem lép fel. A mikro érték . A makro értékek:
Szélsőeset-ellenőrzés: a mátrix összege , a diagonál , a miatt a 2. osztály precisionje értelmesen ; a miatt az 1. osztály recallja .
F3. A labor metrics_check.py függvénye
a megoldás. zero_division='omit' esetén
;
zero_division='zero' esetén
.
A raise negatív teszt:
C = np.array([[4,2,0],[1,3,0],[1,1,0]], dtype=np.int64)
try:
precision_recall_from_confusion(C, zero_division="raise")
except ZeroDivisionError:
print("raise mod helyesen hibazik")
else:
raise AssertionError("nem dobott ZeroDivisionError-t")A shape- és tartományellenőrzés a confusion_matrix-ban
külön hibát ad, ha a címke kívül esik a
intervallumon.
F4. A hiba a tengelyek felcserélése: a
fp-t a sorösszegből, az fn-t az
oszlopösszegből számolja. A C_z mátrixon a helyes
és
;
a hibás kód
és
értéket ad. A hibás makro átlagok ezért
A hibás harmonikus képlet ezekből
A helyes makro
zero_division='zero' mellett
.
A felcserélt tengelyek önmagukban sem adnak helyes per-class jelentést,
és a makro
harmonikus képlete további eltérést okoz.
F5. Egy lehetséges protokoll:
Korlátok: (a) az átsúlyozás csak label shiftet modellez, azt feltételezi, hogy változatlan; ha a bemeneti eloszlás is eltolódik, a predikciós mátrix már nem reprezentálja a célkörnyezetet. (b) A metrikakülönbség nem bizonyítja, hogy a modell a telepített környezetben jobb vagy rosszabb; az igazi tesztet a célkörnyezetből, a 22.1. Adateredet, címke és csoportos split adatszerződése szerint kell gyűjteni.
Gyakori tévedés. Ugyanaz a metrikanevű szám eltérő
averaging mellett nem összehasonlítható. A
példán a micro
,
a makro
omit módban
,
zero módban
.
Ha egy riport csak „F1 = 0,63” közlést tartalmaz, akkor sem a képlet,
sem a nulla nevező konvenció nem reprodukálható. A közlésnek mindig
tartalmaznia kell:
,
a mikró vagy makró szót, a zero_division módot és a
confusion matrixot vagy a per-class táblázatot. Hasonlóan hibás a makro
-et
a makro
és makro
harmonikus közepeként számolni; a 4. szakasz ellenpéldája
és
.
Önellenőrzés. A mátrix sorösszege az igaz support, oszlopösszege a predikciók száma; a főátló összege -nel osztva az accuracy. A recall a soron belüli találati arány, a precision az oszlopon belüli helyesség. Ha a mátrixot transzponálja, a precision és a recall felcserélődik; ezért az ábra tengelyfelirata és a kód tengelye ugyanazt kell jelentse. A mikro egyenlőség csak egycímkés osztályozásra érvényes; multilabel esetben külön összegzés kell.
Visszahívás. Az 5.4. Függő
képek, seedek és resampling alfejezetben bevezetett függő képek most
is érvényesek: a confusion matrix cellái képszintű darabszámok, de az
intervallumbecsléshez a scene_id klaszter szerint kell resamplingolni;
ez a 23.3. Páros összehasonlítás és csoportszintű
intervallum témája. A 8.4. II. ellenőrző pont:
korrekt tanulási állítás tanulási szerződése szerint a metrikát a
modellválasztás után, független halmazon számítjuk; a 5. szakasz
riportja sem validálhat újra ugyanazon a halmazon, amelyen a
checkpointot választottuk. A 11.1. Tensor és
autograd-életciklus inference_mode-ja biztosítja, hogy
a riportkészítés ne építsen autograd-gráfot, és ne módosítsa a modell
állapotát.
A confusion matrix
darabszámokkal rögzíti, hányszor mondtunk
-t,
amikor az igazság
.
Az accuracy a főátló összege osztva
-nel,
és egyenlő a prevalence-súlyozott recall-lal. A one-vs-rest precision és
recall osztályonként definiálja a találati és a lefedettségi hibát;
nulla nevezőre a raise, zero és
omit konvenciók egyikét kell közölni. A mikro összegzés
egycímkés esetben
;
a makro minden osztályt egyenlően súlyoz, és a makro
a per-class
-ek
átlaga, nem a makro
és
harmonikus közepe. Kiegyensúlyozatlan adaton a mikro és makro szám
lényegesen eltérhet, ezért az összehasonlításhoz a teljes konvenciót
rögzíteni kell.
A 23.2. Kalibráció és döntési küszöb alfejezetben a softmax-kimenet, a döntési küszöb és a kalibráció következik: ott a confusion matrix celláinak folytonos küszöbtől való függése adja a precision–recall trade-off elemzését. A 23.3. Páros összehasonlítás és csoportszintű intervallum ehhez csoportszintű konfidenciaintervallumot kapcsol.