A 29.3. Regularizált latent reprezentáció alfejezetben láttuk, hogy a rekonstrukciós cél önmagában még nem hoz létre szemantikus reprezentációt: kapacitás és regularizáció nélkül az identitáshoz közeli leképezés is elérhető. Ebben az alfejezetben a rekonstrukciót döntési feladatra használjuk: egy modellt kizárólag „normálisnak” tekintett mintákon tanítunk, majd a rekonstrukciós hibát anomáliapontszámként (anomaly score) értelmezzük. A cél nem az, hogy a hibát valószínűségnek nevezzük, hanem az, hogy a küszöbválasztást és a hibaköltséget validációs protokollhoz kössük, és kimondjuk az eljárás érvényességi határait.
Előismeret: a 29.1. Lineáris és nemlineáris autoencoder autoencoder-definíciója, a 29.2. Denoising és korrupciós modell rekonstrukciós és denoising-célja, valamint a 25.4. Nyílt halmaz és ismeretlen input nyílt halmazú döntés gondolata. Nem szükséges haladó statisztikai előismeret.
Induló kérdés. Az autoencoder átlagos tanítási hibája 0,01. Egy új képen ugyanez a modell 0,05 hibát ad. Mekkora a valószínűsége annak, hogy az új kép anomália? Becslés: ebből az egyetlen számból nem határozható meg. A hiba nem valószínűség, és a modell a tanult sémán kívüli inputot is rekonstruálhatja jól, ahogyan a 4. szakasz ellenpéldája mutatja.
Legyen egy kép , például a GlyphLab szerződésben , . Az autoencoder egy paraméteres függvény, a rekonstrukció . A pontszám a kép átlagos négyzetes hibája:
Batch esetén az bemenethez és rekonstrukcióhoz egy pontszámvektor tartozik, ahol a fenti átlag. A továbbiakban az MSE-pontszámot használjuk; bináris bemenetre a BCE-loss 29.2. Denoising és korrupciós modell alfejezetből szintén lehetséges, de akkor a ponthatárok és a küszöbértékek nem cserélhetők fel automatikusan.
Normal training. A tanítóhalmaz , amelyről feltesszük, hogy a normál működésből származik. Anomáliacímkét a tanítás nem használ. A cél
ahol a 29.3. Regularizált latent reprezentáció alfejezetben bevezetett sparse, contractive vagy más regularizáció, . A regularizáció itt nem dísz: a szűk keresztmetszet vagy a büntetőtag akadályozhatja, hogy a modell tetszőleges inputot identikusan visszaadjon. Ez azonban heurisztika, nem bizonyított szeparációs garancia.
Contamination. Két különböző fogalmat ne keverjünk. A tanítási contamination annak aránya, hogy a feltételezett normál tanítóhalmaz valójában anomáliákat is tartalmaz. Ha , a normal training feltételezése sérül, és a minimalizált cél a nem kívánt mintákra is illesztheti a modellt. Az értékelési contamination a validációs vagy tesztkeverék anomáliaaránya; ez a küszöbválasztás és a költség becslésének bemenete, nem a modell tanításáé.
A döntési szabály rögzített küszöbbel:
ahol az 1 címke jelenti az anomáliát. A megválasztásához olyan validációs halmaz kell, amelyben ismert címkék vannak. A validációs pontszámokból becsüljük a hamis pozitív és hamis negatív arányt:
ahol és a normál és anomália minták száma a validációban. Legyen egy kihagyott anomália költsége, egy hamis riasztásé, pedig az anomáliák validációs aránya. A várható költség becslése
A küszöböt a validációs pontszámokból álló rács felett választjuk:
Ha a validáció csak normál mintát tartalmaz, a tag nem becsülhető. Ekkor legfeljebb a normál pontszámok -kvantilisét választhatjuk, ami a hamis riasztási arányt vezérli, de a detektálási képességről nem ad állítást. A küszöböt nem a tanítóhalmaz pontszámaiból választjuk: azok tipikusan alacsonyak, mert a modell látta a mintákat, ezért a tanítási ponthatár optimista becslés lenne.
A fejezet levezetési kötelezettsége annak formális megmutatása, hogy a modell ismeretlen mintát is jól rekonstruálhat, tehát az alacsony pontszám nem azonosítja a normál eloszlást. A legegyszerűbb ilyen eset az identikus rekonstrukció.
Lemma 1 (identikus rekonstrukció mint OOD-kudarc). Legyen a lehetséges inputok nem üres halmaza, egy paraméteres függvénycsalád, és legyen olyan veszteség, amelyre minden esetén . Tegyük fel, hogy az identitás eleme -nek. Ekkor bármely tanítóhalmazra létezik olyan paraméter, amelyre a tanítási veszteség , és minden — így minden ismeretlen anomáliamintára is — .
Bizonyítás. Válasszuk a paramétert úgy, hogy . Ekkor minden tanítópontra , tehát a veszteségtagok mindegyike , az empirikus cél . Ugyanebből az okból tetszőleges -re , így az MSE-pontszám minden tagja eltűnik, . A kihagyott anomáliákat a küszöb mind normálnak osztályozza, tehát a modell semmilyen elválasztást nem végez.
A lemma feltétele triviálisnak tűnhet, de az identitás nem puszta elméleti eset: túl nagy látens térrel és linearitással az optimális rekonstrukció könnyen közelítheti. Tekintsünk egy lineáris autoencodert, ahol az encoder, a decoder, és . Ha , akkor az egész leképezés . Ilyen pár létezik, például és . A normal training tehát elérheti a nulla hibát, miközben az anomáliafelismerő képesség nulla.
Ez az ellenpélda nem jelenti azt, hogy a rekonstrukciós pontszám használhatatlan. Azt jelenti, hogy a használhatóság az architektúra kapacitásán, a regularizáción és a validáción mért elválasztáson múlik; a tanítási hiba nagysága önmagában nem bizonyíték.
Legyen , és . Ekkor
A modell a második koordinátát eldobja. A tanítóhalmaz legyen . Mindhárom tanítópontra , tehát . Az pont messze van a tanító felhőtől, de a rekonstrukciós hibája szintén : a pontszám nem a tanítóhalmaztól mért távolság, hanem a modellezett altértől mért távolság. A pont esetén , tehát
Ha viszont és , akkor mind az öt pontra , beleértve a pontot is. Ez a Lemma 1 konkrét kétváltozós esete.
# lab/score_hand_check.py
import numpy as np
E = np.array([[1.0, 0.0]]) # (1,2)
D = np.array([[1.0], [0.0]]) # (2,1)
X = np.array([[2, 0], [4, 0], [1, 0], [100, 0], [0, 3]],
dtype=np.float32) # (5,2)
Xhat = X @ E.T @ D.T # (5,1)@(1,2) = (5,2)
s = np.mean((X - Xhat) ** 2, axis=1)
print(s) # [0. 0. 0. 0. 4.5]
E2 = np.eye(2, dtype=np.float32)
D2 = np.eye(2, dtype=np.float32)
Xhat2 = X @ E2.T @ D2.T
print(np.mean((X - Xhat2) ** 2, axis=1)) # [0. 0. 0. 0. 0.]A példa egyszerre tölti be a kézi kontroll és az ellenpélda szerepét. A szélső eset azt is megmutatja, hogy a nullpontszám nem a tanítóeloszlás tagságát igazolja.
A labor célja egy kis jelölt anomáliateszt, nem valódi csalás- vagy
diagnózisállítás. A normál eloszlást a GlyphLab tiszta tanítóképei
alkotják; anomáliának a corrupted_test.npy zajos és
fényességgradienssel torzított tesztképeit tekintjük. A cél annak
eldöntése, hogy a rekonstrukciós pontszám a párosított tesztpárokban a
korrupt oldalon nagyobb-e.
Protokoll.
scene_id/view_id pár, tehát a csoportos
szerződés 5.4. Függő képek, seedek és resampling
szerint járunk el.corrupted_test.npy-ból. A korrupt tesztet csak a
befagyasztott modell és a kiválasztott küszöb után értékeljük,
ráhangolás nélkül.Conv2d(1, 8, 3, stride=2, padding=1),
ReLU;Conv2d(8, 16, 3, stride=2, padding=1), ReLU;
flatten;Linear(576, 32);Linear(32, 576), ReLU;
Unflatten(1, (16, 6, 6));ConvTranspose2d(16, 8, 3, stride=2,
padding=1, output_padding=1), ReLU;ConvTranspose2d(8, 1, 3, stride=2,
padding=1, output_padding=1), Sigmoid.# lab/anomaly_lab.py — futtatás: cd lab && python anomaly_lab.py
import csv
import json
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
ROOT = Path(".")
SEED = 20261020
torch.manual_seed(SEED)
np.random.seed(SEED)
device = torch.device("cpu")
dtype = torch.float32
images = np.load(ROOT / "data" / "images.npy") # (360,1,24,24)
corrupted = np.load(ROOT / "data" / "corrupted_test.npy") # (60,1,24,24)
with (ROOT / "data" / "metadata.csv").open() as f:
meta = list(csv.DictReader(f))
def idx(split):
return np.array([int(r["index"]) for r in meta if r["split"] == split])
Xtr = images[idx("train")] # (240,1,24,24)
Xva = images[idx("val")] # (60,1,24,24)
Xte = images[idx("test")] # (60,1,24,24)
assert Xtr.shape == (240, 1, 24, 24)
assert corrupted.shape == (60, 1, 24, 24)
# Validációs szintetikus anomália; ez NEM a corrupted_test, és csak
# a küszöbválasztáshoz használható.
rng = np.random.default_rng(SEED + 1)
Xva_anom = np.clip(Xva + 0.35 * rng.standard_normal(Xva.shape).astype(np.float32),
0.0, 1.0)
class ConvAE(nn.Module):
def __init__(self):
super().__init__()
self.enc = nn.Sequential(
nn.Conv2d(1, 8, 3, stride=2, padding=1), nn.ReLU(),
nn.Conv2d(8, 16, 3, stride=2, padding=1), nn.ReLU(),
nn.Flatten(), nn.Linear(576, 32))
self.dec = nn.Sequential(
nn.Linear(32, 576), nn.ReLU(),
nn.Unflatten(1, (16, 6, 6)),
nn.ConvTranspose2d(16, 8, 3, stride=2, padding=1, output_padding=1),
nn.ReLU(),
nn.ConvTranspose2d(8, 1, 3, stride=2, padding=1, output_padding=1),
nn.Sigmoid())
def forward(self, x):
return self.dec(self.enc(x))
model = ConvAE().to(device)
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
lossf = nn.MSELoss()
Xtr_t = torch.from_numpy(Xtr).to(device, dtype)
for epoch in range(30):
model.train()
perm = torch.randperm(Xtr_t.shape[0])
for i in range(0, Xtr_t.shape[0], 32):
xb = Xtr_t[perm[i:i + 32]]
opt.zero_grad()
loss = lossf(model(xb), xb)
loss.backward()
opt.step()
def score(model, x_np):
model.eval()
with torch.no_grad():
x = torch.from_numpy(x_np).to(device, dtype)
y = model(x)
return torch.mean((x - y) ** 2, dim=(1, 2, 3)).cpu().numpy()
s_va_n = score(model, Xva)
s_va_a = score(model, Xva_anom)
c_fn, c_fp, pi = 3.0, 1.0, 0.5
best_tau, best_cost = None, float("inf")
for tau in np.quantile(np.r_[s_va_n, s_va_a], np.linspace(0.05, 0.95, 191)):
fp = np.mean(s_va_n > tau)
fn = np.mean(s_va_a <= tau)
cost = pi * c_fn * fn + (1.0 - pi) * c_fp * fp
if cost < best_cost:
best_cost, best_tau = cost, float(tau)
s_te = score(model, Xte)
s_co = score(model, corrupted) # a betöltéskori név: corrupted
tn = int(np.sum(s_te <= best_tau))
fp = int(np.sum(s_te > best_tau))
fn = int(np.sum(s_co <= best_tau))
tp = int(np.sum(s_co > best_tau))
paired = float(np.mean(s_co > s_te))
def auc_pairs(neg, pos):
"""ROC AUC Mann–Whitney-alakban: P(pos > neg) + 0,5 · P(pos = neg).
Minden (negatív, pozitív) párt összevet, ezért a holtversenyt helyesen
kezeli, és az osztálydarabszámokat a bemenetből veszi."""
pos = np.asarray(pos)[:, None]
neg = np.asarray(neg)[None, :]
return float(np.mean((pos > neg) + 0.5 * (pos == neg)))
assert auc_pairs(np.zeros(4), np.zeros(3)) == 0.5 # minden pontszám azonos
assert auc_pairs([0.0, 0.1], [0.2, 0.3]) == 1.0 # tökéletes rangsor
assert auc_pairs([0.2, 0.3], [0.0, 0.1]) == 0.0 # fordított rangsor
auc = auc_pairs(s_te, s_co)
import datetime, platform, uuid
report = {
"run_id": uuid.uuid4().hex,
"created_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"),
"status": "completed", # a program ténylegesen lefutott
"config": {"seed": SEED, "epochs": 30, "batch": 32, "lr": 1e-3, "device": str(device),
"dtype": str(dtype), "c_fn": c_fn, "c_fp": c_fp, "pi": pi,
"torch": torch.__version__, "numpy": np.__version__,
"python": platform.python_version()},
"threshold": best_tau,
"val_cost": best_cost,
"test_confusion": {"TN": tn, "FP": fp, "FN": fn, "TP": tp},
"paired_win_rate": paired,
"auc": auc,
}
Path("results").mkdir(exist_ok=True)
(Path("results") / "anomaly_report.json").write_text(json.dumps(report, indent=2))
print(json.dumps(report, indent=2))Futási jegyzőkönyv. A fenti szkript a mellékelt
laborcsomag eredeti GlyphLab-adatán, CPU-n (Python 3.12.3, NumPy 2.3.5,
PyTorch 2.14.1+cpu) lefutott; két egymás utáni futás azonos számokat
adott. A riport mérési része (a run_id, az időpont és a
config futásonként, illetve környezetenként változik):
{
"status": "completed",
"threshold": 0.04370777867734401,
"val_cost": 0.0,
"test_confusion": {"TN": 60, "FP": 0, "FN": 30, "TP": 30},
"paired_win_rate": 1.0,
"auc": 1.0
}Más verziójú NumPy/PyTorch vagy más CPU mellett a küszöb utolsó jegyei eltérhetnek; a saját futás riportját kell leadni, nem ezt a számsort.
Értelmezés. A két rangsoroló mutató tökéletes: mind a 60 párban a
korrupt kép pontszáma a nagyobb (paired_win_rate 1,0), és
az AUC is 1,0, tehát létezik olyan küszöb, amely a két
tesztcsoportot hibátlanul szétválasztja. A validáción választott küszöb
mégis 30 korrupt képet normálisnak minősít (FN = 30). Ennek oka, hogy a
validációs szintetikus anomália
(
szórású zaj) sokkal könnyebben felismerhető, mint a teszt korrupciója: a
validációs költség
,
így a kvantilisrács már egy alacsony küszöbnél optimumot ér el, amely a
valódi tesztkorrupció felére nem elég érzékeny. A tanulság kettős.
Egyrészt a küszöb minősége a validációs anomália reprezentativitásán
múlik, nem a rangsor minőségén. Másrészt az AUC és a páros nyerési arány
küszöbfüggetlen, a confusion matrix viszont egy konkrét döntési szabályt
értékel; a kettőt külön kell jelenteni. A val_cost a
validációs szintetikus anomáliákra érvényes; a tesztelt korrupt minták
is szintetikusak, ezért ez nem valódi csalás- vagy hibadetektálási
állítás. A páros összehasonlítás azért fontos, mert a tiszta teszt és a
korrupt teszt ugyanazon scene_id/view_id
párokat tartalmazza.
F1 (fogalmi, nehézség 1). Definiáld a rekonstrukciós pontszámot, a normal traininget, a contaminatiót és a küszöböt. Magyarázd el egy-egy mondatban, miért nem valószínűség a pontszám. Elvárt átadandó: legfeljebb tíz mondatos, saját szavas definíció, egy explicit ellenpélda-utalással.
F2 (levezetési, nehézség 2). Bizonyítsd be a Lemma 1-et a saját szavaiddal. Mutasd meg a , overcomplete lineáris esetben, hogy a választás minden inputra nulla pontszámot ad. Elvárt átadandó: a bizonyítás lépései, a és konkrét tömbjei, és a szorzat ellenőrzése.
F3 (implementációs, nehézség 2). Írj olyan
NumPy-függvényt, amely egy
batch minden sorára kiszámítja az
pontszámot, és reduction='mean' jellegű csapdát ne kövess
el. Teszteld:
,
esetén az eredmény
,
mert az első sor hibája
(a koordinátánkénti átlag; az
a négyzetösszeg lenne). Elvárt: futtatható kód, import, két pontra
pontos output, float64 típus.
F4 (hibakeresési, nehézség 2). Az alábbi kódrészlet
a pontszámot a batch összes elemének átlagaként számítja ki, majd
ugyanezt a skalárt használja minden mintához. Mi a hiba, mi a
következménye a küszöbválasztásra, és hogyan javítanád? Elvárt: ok,
következmény, javított két-két sor, egy assert-tel
ellenőrzött visszateszt.
s = np.mean((X - Xhat) ** 2) # hiba: egyetlen skalár
scores = np.full(X.shape[0], s) # minden mintára ugyanazF5 (kísérleti/transzfer, nehézség 3). Tervezz kísérletet annak eldöntésére, hogy a GlyphLab tiszta vertical képein tanult autoencoder pontszáma elválasztja-e a horizontal és diagonal mintákat a vertical mintáktól. Add meg a protokollt, a baseline-t, a splitet, a metrikát és a következtetési korlátot. Elvárt átadandó: legalább fél oldalas terv, nyitott eredménnyel; előre megadott győztes nem fogadható el.
F1. A pontszám . A normal training kizárólag normál feltételezésű mintákon minimalizálja a rekonstrukciós veszteséget. A contamination egy halmaz anomáliaaránya; tanítási és értékelési változatát külön kell kezelni. A küszöb olyan szám, amelynél nagyobb pontszám anomáliadöntést ad. A pontszám azért nem valószínűség, mert nem -be eső normalizált mérték, és az identikus rekonstrukció esetén ismeretlen mintára is nulla lehet.
F2. A Lemma 1 bizonyítása: ha tartalmazza az identitást, az identitás paramétere minden tanítópontra , tehát a veszteség . Ugyanez tetszőleges új -re, tehát az MSE-tagok mindegyike , és . A , esetben például
Ekkor , tehát minden -re, és a pontszám azonosan nulla. Alakellenőrzés: , , a szorzat -es egységmátrix.
F3.
import numpy as np
def per_sample_mse(X, Xhat):
X = np.asarray(X, dtype=np.float64)
Xhat = np.asarray(Xhat, dtype=np.float64)
assert X.shape == Xhat.shape
assert X.ndim == 2
return np.mean((X - Xhat) ** 2, axis=1)
X = np.array([[1.0, 0.0], [0.0, 0.0]])
Xhat = np.array([[2.0, 0.0], [0.0, 0.0]])
out = per_sample_mse(X, Xhat)
assert np.allclose(out, [0.5, 0.0], atol=1e-15)
print(out) # [0.5 0. ]A mean csak az 1. tengelyre redukál, ezért minden
megfigyelésnek külön pontszáma marad.
F4. A hiba a redukciós tengely elhagyása. A
np.mean mind a
elem átlagát adja, így egyetlen skalár jön létre; a minták közötti
különbség eltűnik, és a küszöb sem képes az egyedeket szétválogatni.
Javítás: tartsuk meg a mintatengelyt, axis=1, illetve
tensornál dim=(1,2,3) a NCHW-átlaghoz. Visszateszt:
s = np.mean((X - Xhat) ** 2, axis=1)
assert s.shape == (X.shape[0],)F5. Minta. Kérdés: a rekonstrukciós pontszám elválasztja-e a nem látott orientációkat a normál vertical mintáktól? Adat: GlyphLab train 80 vertical kép a modellhez, validáció a vertical val 20 kép plusz horizontal/diagonal val 40 kép, teszt a maradék független jelenetek 20 vertical és 40 más orientációjú képe. Baseline legyen a véletlen küszöb vagy a teljes normal osztályozó, metrika a pontszám AUC-értéke csupán a tesztjeleneteken. Protokoll: a modell csak train vertical képeken tanul, a tesztet a küszöbválasztás után értékeljük. Korlát: a GlyphLab szintetikus, tehát az eredmény nem természetes kép-anomáliáról szól; az orientációk különbözősége nem feltétlenül valódi hibamód.
A leggyakoribb tévedés, hogy a rekonstrukciós hiba általános anomália-valószínűség. Ez két irányban is hibás. Az 5. szakaszban a pont a tanítófelhőtől távol van, mégis nulla pontszámot kapott, tehát a kis hiba nem jelent normál tagságot. A nagy hiba sem jelent feltétlenül anomáliát: egy ritka, de a normál eloszlásból származó minta is lehet rosszul rekonstruálható, ha a modell szűk kapacitású. A döntési szabály nem ad értéket; ilyen állításhoz likelihood-arány, prior és kalibrált modell kellene.
Önellenőrzés. Ha a validációs normál pontszámok 95%-os kvantilise 0,02, akkor egy 0,021 pontszámú minta biztosan anomália? Nem, ez a küszöb a hamis pozitív arányt vezérli, nem a pontszám jelentését.
Visszahívás. A 28.4. VII. ellenőrző pont: CNN alkalmazási szerződés alkalmazási szerződése itt is érvényes: a cél anomáliadöntés, a target a validációban és teszten bináris címke, a tanító cél rekonstrukció, az output egy pontszám, a metrika FPR/FNR/AUC vagy páros arány, a split scene-szintű, és az inference-szerződés rögzített mellett zajlik. A 17.4. Architektúra összehasonlítása kontrollált keretben fair-comparison elve szerint az identikus és a rendesen regularizált autoencoder összevetésekor a kapacitást, a tanítási iterációt és a küszöbválasztási protokollt előre rögzíteni kell, különben az identitás kudarca nem a regularizáció hatását méri.
A rekonstrukciós pontszám egy minta és rekonstrukciója közötti átlagos négyzetes hiba, amelyet normal traininggel tanított autoencoder ad. A küszöböt validációs halmazon, ismert anomáliacímkékkel, a hamis pozitív és hamis negatív arányok költséggel súlyozott összege alapján választjuk. A Lemma 1 megmutatja, hogy az identikus leképezés minden ismeretlen mintát nulla hibával rekonstruálhat, tehát a pontszám nem anomália-valószínűség és nem ad önmagában szeparációs garanciát. A labor párosított tiszta-korrupt GlyphLab teszten futtatott, run_id-vel és konfigurációval azonosított jelentést készít; a kimenet nem valódi diagnózis. A gyakorlatban a kapacitás, a regularizáció és a validációs korlát együtt határozza meg az eljárás érvényességét.
A 30.1. Pretext és downstream feladat a rekonstrukciót pretext feladatként használja tovább: az encoder itt tanult reprezentációja downstream osztályozás bemenete lehet, és a fejezetben bevezetjük az önfelügyelt kiértékelés alapfogalmait.