A 25.1. Teljes képosztályozó adatfolyam egységben egy teljes képosztályozó adatfolyamot építettünk fel: a batch a betöltéstől a háromlogitos kimenetig egyetlen rögzített alakszerződést követett, és a címke egyetlen kölcsönösen kizáró osztályindex volt. Ebben az alfejezetben azt a kérdést vizsgáljuk, mi történik, ha egy képen egyszerre több címke is igaz lehet. Előismeretnek elegendő a 25.1. Teljes képosztályozó adatfolyam alfejezetből a logit, a fej és a címke fogalma, valamint a 21.2. Learning rate és ütemezés alfejezetből a döntési küszöb szerepe. Induló becslés: két független bináris címke esetén hány kimeneti számra van szüksége a modellnek, és ezeknek összege kötelezően kell-e legyen?
Legyen a batch mérete , a címkék száma . A multilabel feladatban minden megfigyeléshez egy
célmátrix tartozik, ahol azt jelenti, hogy a -adik címke jelen van az -edik képen, pedig azt, hogy a megfigyelt annotáció szerint nincs jelen. A modell itt is logitokat ad:
tehát ugyanaz a háló és fej használható, mint a többosztályos esetben; a különbség a normalizálásban és a cél értelmezésében van. A többosztályos cél int64 index, és a softmax egyetlen valószínűségvektorrá normalizálja a logitokat. A multilabel célban ezzel szemben minden címke külön Bernoulli-döntés, ezért az elemenkénti szigmoidot használjuk:
Az -edik sor elemei nem alkotnak szükségképpen összegű eloszlást: egy képen lehet egyszerre „jármű” és „éjszakai felvétel”, vagy éppen egyik sem. A kölcsönösen kizáró címkék ezzel szemben egy oldalú osztályozás kategóriái; ott a softmax az összes logit együttes függvényeként normál. Kétosztályos határesetben a kapcsolat ragadható meg: ha és , akkor
ezért a bináris osztályozás egyetlen szigmoidja a softmax speciális paraméterezése. Ez nem jelenti azt, hogy két független multilabel szigmoid ugyanaz, mint egy kétosztályos softmax: az utóbbi kényszert ad, az előbbi két tetszőleges -beli számot.
A szigmoid numerikusan stabil alakja:
Így mindkét irányban elkerüljük a túl nagy exponenciális értéket.
A többosztályos főút a [D07] szerint logit bemenetet és int64
osztályindexet vár a CrossEntropyLoss-ban. A multilabel
főút célja a bináris cross-entropy logitokkal, PyTorchban
BCEWithLogitsLoss. Vezessük le a célfüggvényt egyetlen
elemre. A Bernoulli-modell likelihoodja:
A negatív log-likelihood:
Mivel és , az első tag , a második pedig . A két eset összevonható:
A
függvényt softplus-nak nevezzük; a memóriaigényt és a
numerikus túlcsordulást tekintve javasolt stabil megvalósítása
.
A gradiens a láncszabályból:
Ez megegyezik azzal az intuitív szabállyal, hogy pozitív cél mellett a predikciót felé, negatív cél mellett felé húzzuk; a gradiens akkor nulla, ha .
Most mondjuk ki a megfigyelt címkék maszkolásának szabályát.
Legyen a logitmátrix, a megfigyelt célmátrix, és a megfigyelési maszk, ahol pontosan akkor, ha az címke annotáltsága ismert, pedig ha hiányzik. Tegyük fel, hogy a bemenet feltétele mellett a címkék feltételesen független Bernoulli-változók paraméterrel, és a hiányzási mechanizmus ignorálható: (i) a megfigyeltség valószínűsége a bemenet ismeretében nem függ a nem megfigyelt valódi címkétől, (MAR; speciális esete a teljesen véletlen hiányzás, MCAR, ahol a megfigyeltség az -től sem függ), és (ii) a hiányzási mechanizmus paraméterei elkülönülnek a modell paramétereitől. Legyen . Ekkor a megfigyelt adatok negatív log-likelihoodjának átlagos értéke
$$L(Z,Y,M)=\frac1S\sum_{n=1}^{N}\sum_{k=1}^{K}M_{n,k}\Bigl[\log\!\bigl(1+e^{z_{n,k}}\bigr)-Y_{n,k}z_{n,k}\Bigr].$$
Üres maszk, azaz esetén a loss nem definiált; megvalósításban vagy hibát adunk, vagy szerződésszerűen -t adunk vissza, de ezt a hívónak dokumentálnia kell.
Bizonyításvázlat, státusz: saját levezetés. A feltételes függetlenség miatt a megfigyelt rész likelihoodja a megfigyelt elemek likelihoodjainak szorzata. Maszkolatlan elemről az adat nem hordoz likelihood-információt, ezért a log-likelihood összegébe nem kerül tag. A negatív log-likelihood átlaga így a fenti összeg -szerese, ami a BCE elemenkénti definíciójából következik.
Az ignorálhatósági feltétel mellett a megfigyelési mechanizmus tényezője a likelihoodban a modellparaméterektől független konstans, ezért a megfigyelt-adat likelihood maximalizálása a fenti maszkolt loss minimalizálása. A feltétel nem technikai részlet: ha a megfigyeltség magától a valódi címkétől függ (például a pozitív címkéket gyakrabban jegyzik fel), akkor a megfigyelt célok eloszlása torzított (MNAR), és a maszkolt loss sem ad torzítatlan populációs illesztést; ilyenkor a megfigyelési mechanizmust is modellezni kell. A maszkolás tehát elkerüli a hamis negatív kényszert, de önmagában nem garantál torzítatlan becslést.
A tétel gyakorlati tartalma: a hiányzó címke nem kap
célértéket, hanem kimarad az összegből. A PyTorch-ban a
BCEWithLogitsLoss(reduction='none') adja a tagokat, a
maszkkal szorozva és a maszk összegével osztva kapjuk a normalizált
loss-t. Fontos, hogy a default reduction='mean' a
batch elemek átlagát venné, ami maszkolt elemeknél nem
helyes normalizálás; mindig explicitté kell tenni, mit nevezünk egy
megfigyelésnek.
Legyen , , és
A második sor középső címkéje tehát hiányzik: , ezért annak értékét nem használjuk célként. A szigmoid értékek: , , , , , .
Elemenként a képlettel, amely pozitív célra () , negatív célra () :
| BCE-tag | |||
|---|---|---|---|
| 1 | |||
| 0 | |||
| 1 | |||
| 1 | |||
| 0 |
Az összeg , a maszk összege , így . A legnagyobb tag az elem: pozitív cél mellett erősen negatív logit, tehát magabiztos téves predikció. Ha tévesen a hiányzó elemet negatívként vennénk be, a tag is beleszámít, és a loss lenne; a különbség kicsinek tűnik, de a gradiens-ellenőrzés megmutatja a torzítást:
holott a maszkolt változatban ehhez az elemhez nulla gradiens tartozik. A modell tehát hamis negatív cél irányába tanulna egy olyan példán, ahol valójában nincs is megfigyelt negatív annotáció.
Az alábbi függvény a PyTorch nélküli, NumPy referenciát adja meg; a
laborban ugyanezt a
torch.nn.functional.binary_cross_entropy_with_logits
függvénnyel ellenőrizzük.
import numpy as np
def stable_sigmoid(z: np.ndarray) -> np.ndarray:
# float64 bemenet; z >= 0 elágazással a túlcsordulást elkerüljük
out = np.empty_like(z, dtype=np.float64)
pos = z >= 0
e = np.exp(-np.abs(z))
out[pos] = 1.0 / (1.0 + e[pos])
out[~pos] = e[~pos] / (1.0 + e[~pos])
return out
def masked_bce_with_logits(z: np.ndarray, y: np.ndarray, mask: np.ndarray) -> float:
# z, y, mask alakja (N, K); y értéke 0 vagy 1; mask értéke 0 vagy 1
z = np.asarray(z, dtype=np.float64)
y = np.asarray(y, dtype=np.float64)
mask = np.asarray(mask, dtype=np.float64)
if z.shape != y.shape or z.shape != mask.shape:
raise ValueError("Alakegyezés szükséges: (N, K) mindhárom bemenetre.")
s = mask.sum()
if s <= 0:
raise ValueError("Üres maszk: nincs megfigyelt címke.")
# stabil softplus: log(1 + e^z) = softplus(z)
softplus = np.logaddexp(0.0, z)
terms = softplus - y * z
return float(np.sum(mask * terms) / s)A függvény a fenti példára -ot ad (float64-ben ). A PyTorch-oldali ellenőrzés:
import torch
z = torch.tensor([[-1.5, 0.8, 2.2], [0.2, -0.4, 1.1]], dtype=torch.float64)
y = torch.tensor([[1.0, 0.0, 1.0], [1.0, 1.0, 0.0]], dtype=torch.float64)
mask = torch.tensor([[1.0, 1.0, 1.0], [1.0, 0.0, 1.0]], dtype=torch.float64)
terms = torch.nn.functional.binary_cross_entropy_with_logits(
z, y, reduction='none')
loss = (mask * terms).sum() / mask.sum()
print(loss.item()) # várt: 0.992614..., 1e-4 toleranciával
print(terms) # tételenként: 1.7014 1.1711 0.1051 / 0.5981 0.9130 (maszkolt, kimarad) 1.3873A labor célja egy kis, CPU-n ellenőrizhető multilabel osztályozó összeállítása. A GlyphLab három kölcsönösen kizáró osztálya erre nem alkalmas, ezért eredeti szintetikus példát használunk: képhez 12 dimenziós fix mesterséges jellemzőt rendelünk, és címkét figyelünk meg. A jellemzők nem valós képekből származnak; a labor a fej, a loss és a metrika szerződését vizsgálja, nem a látási teljesítményt.
# munkakönyvtár: lab/
# python n098_multilabel_lab.py
import torch
import torch.nn as nn
torch.manual_seed(20261020)
N, K, D = 96, 4, 12
X = torch.randn(N, D, dtype=torch.float32) # (96, 12)
W_true = torch.randn(D, K, dtype=torch.float32) * 0.5
logit_true = X @ W_true
prob_true = torch.sigmoid(logit_true)
Y = (prob_true > 0.5).float() # (96, 4)
# 30% véletlenszerű hiányzó annotáció
mask = (torch.rand(N, K) > 0.3).float() # (96, 4)
class MultilabelHead(nn.Module):
def __init__(self, d: int, k: int):
super().__init__()
self.linear = nn.Linear(d, k)
def forward(self, x):
return self.linear(x) # logitok, nincs sigmoid
model = MultilabelHead(D, K)
opt = torch.optim.SGD(model.parameters(), lr=0.5)
for step in range(20):
opt.zero_grad()
z = model(X) # (96, 4)
terms = torch.nn.functional.binary_cross_entropy_with_logits(
z, Y, reduction='none')
loss = (mask * terms).sum() / mask.sum()
loss.backward()
opt.step()
with torch.no_grad():
pred_prob = torch.sigmoid(model(X))
pred = (pred_prob > 0.5).float()
obs = mask.bool()
acc = ((pred[obs] == Y[obs]).float().mean().item())
print(f"loss={loss.item():.6f}")
print(f"maszkolt elemek a predikcióban egyeznek: {acc:.6f}")Átadandó eredmény. A futtatás után megadjuk a
tanítási loss-t, a maszkolt elemekre számított elemszintű accuracy-t,
valamint a
alakú predikciós mátrixot. A véletlen seed és a float32
típus rögzíti a futtatás feltételeit. Az itt közölt loss
érték nem mért eredmény, hanem a kód definíciója
szerint kiszámítandó kimenet; a labor akkor teljes, ha a tanuló maga
futtatja, és a loss tagonkénti kézi számítással egyezik
relatív tűrésen belül.
A metrikák közül a multilabel feladatban az elemszintű accuracy félrevezető lehet ritka címkéknél, ezért per-címke F1-et is számolunk: minden oszlopra a predikció és cél kétdimenziós kontingencia-táblájából és , majd az F1 mikro- és makroátlagát. A micro a pozitív példák halmazát összevonva számol globális pontosságot, a macro a per-címke F1 egyszerű átlaga; ritka címkékre a kettő eltér.
A küszöb nem a tanult modell része. A BCE a szigmoid logitot tanítja; a -nél nagyobb szabály az esetszámok és költségek ismerete nélkül választott alapértelmezés. A 21.2. Learning rate és ütemezés alfejezetben bevezetett elv itt is érvényes: a küszöböt a validációs halmazon, a maszkolt elemeket figyelembe vevő metrika szerint választjuk, és a végleges modellre csak a rögzített küszöbbel számítunk tesztmetrikát.
A hierarchikus annotációban a címkék egy irányított gráf vagy fa csúcsai, és konzisztenciakényszer áll fenn: ha egy gyermek címke pozitív, akkor a szülője is pozitív. Formálisan legyen irányított gráf a csúcsú címkehalmazon, és konzisztens, ha minden élre . Például „jármű” „kétkerekű”, és „kétkerekű” „kerékpár” esetén a „kerékpár” pozitivitása maga után vonja mindkét ős pozitivitását.
A hierachikus kényszer nem változtat a BCE alakegyezésén; a modell továbbra is logitot ad. A kényszert két módon kezelhetjük. Az egyik a predikció utáni vetítés: a gyermek predikcióját nem engedjük nagyobbnak az ősénél, amit a gráfon alulról felfelé haladva lépéssel végzünk. A másik a cél oldali hierarchikus loss, amely a szülő–gyermek élpárok logitjait is bünteti az inkonzisztenciáért.
A vetítéses szabály heurisztika, nem általános tétel: feltételezi, hogy a modell egyébként jól kalibrált, és a címkefa konzisztens annotációt ír le. Nem állítja, hogy a vetített kimenet optimális az alkalmazási metrikára, és nem pótolja a hiányzó szülő címkék valódi tanítását.
A leggyakoribb hiba a multilabel tanításban, hogy a hiányzó annotációt -ként töltjük ki. Ellenpélda. Tegyük fel, hogy egy gyűjteményben a „vaku használata” címke csak akkor kap -est, ha a fotós feljegyezte; az üres mező azt jelenti, hogy nem tudjuk. Egy éjszakai képen, amelyen valójában vaku volt, a téves negatív cél hatására a BCE gradiens , tehát a logitot lefelé húzza, és a recall romlik annak ellenére, hogy az adatban nem volt megfigyelt negatív példa. A maszk ezt az elemet kihagyja a likelihood-összegből: a modell az adott címkére ott nem tanul, mintha az a megfigyelés nem létezne. A nem annotált elem negatívként kezelése tehát nem pusztán pontatlanság, hanem a likelihood-modell hibája, és a tanult döntési küszöböt szisztematikusan torzíthatja. A példa ugyanakkor a maszkolás határát is mutatja: ha a fotósok éppen a vakus képeknél jegyzik fel gyakrabban a címkét, a hiányzás a valódi címkétől függ (MNAR). A maszk ekkor is kiküszöböli a hamis negatív célokat, de a megfigyelt címkék között a pozitívak túlreprezentáltak, így a modell a vaku valószínűségét a populációra felülbecsülheti. Egy egyszerű szimulációban (valódi arány ; a pozitívak 90%-át, a negatívak 30%-át jegyzik fel) a megfigyelt címkék pozitív aránya , míg MCAR-hiányzásnál . Ezt csak a feljegyzési mechanizmus modellezése vagy egy teljesen annotált validációs részminta korrigálhatja.
Ellenőrző kérdések a szakasz után: (1) A logitmátrix minden eleme a szigmoid után -ben van-e? (2) A maszkolt loss-ban a nevező vagy ? (3) A gradienst a hiányzó elemre tényleg nullának definiáljuk-e? (4) A küszöbátlépés a loss vagy az értékelés része?
Visszahívás a 13.2. Konvolúció és cross-correlation alfejezetből: a cross-correlation és a konvolúció megkülönböztetésénél is az volt a tanulság, hogy a numerikus forma önmagában nem rögzíti a szemantikai szerződést; itt ugyanúgy, a célforma önmagában nem mondja meg, hogy a nulla negatív vagy hiányzó annotáció-e. A 25.1. Teljes képosztályozó adatfolyam adatfolyamában a logitok alakja volt; a multilabel ugyanazt a fejet használja, csak a normalizálás és a loss célalakja változik. A 24.2. CPU/GPU profiling és helyes időmérés alfejezetből pedig azt idézzük fel, hogy a metrika futásidejét a threshold sweep során külön kell mérni: a per-címke F1 szorosa az elemszintű pontosságnak, és a sweep költsége nem elhanyagolható, ha a modellt nagyméretű adaton értékeljük.
Magyarázza el, miért nem ekvivalens a kétcímkés multilabel feladat a kétosztályos multiclass feladattal. Adjon egy logitból álló konkrét vektort, amelyre a softmax és a két elemenkénti szigmoid kimenete különböző döntést ad -ös küszöb mellett.
Vezesse le részletesen, hogy a cél deriváltja , és számítsa ki a gradienst a elemre a kézi példában. Ellenőrizze a maszkolt elem gradiensének definícióját.
A fent közölt NumPy függvényt egészítse ki egy
masked_bce_grad függvénnyel, amely a loss-nak a logitmátrix
szerinti gradiensét adja:
.
Futtassa le a kézi példára, és ellenőrizze a PyTorch
autograd gradiensével
abszolút tűrés mellett.
Az alábbi kódrészlet hibás:
logits = model(images) # (N, K)
probs = torch.softmax(logits, dim=1)
loss = torch.nn.functional.binary_cross_entropy(
probs, targets.float(), reduction='mean')Melyik két fogalmat keveri a kód? Mi a következménye egy olyan képen, ahol a két címke közül mindkettő pozitív? Javítsa a kódot, és nevezze meg a numerikusan stabil változatot is.
Tervezzen kísérletet egy olyan adatkészletre, ahol a címkék -a hiányzik, és a hiányzás valószínűsége nagyobb a ritka címkéknél. A feladat ötből három címke együttes előfordulásának detektálása. Adja meg a megfigyelési egységet, a splitet, a baseline-t, a választott metrikát, a küszöbválasztás protokollját és a következtetés korlátját. Magyarázza el, miért nem használható a teszt halmaz a küszöb keresésére.
A kétosztályos multiclass a softmax normalizálással párt ad, tehát a címkék egymást kizárják. A multilabel két szigmoidja két független -beli érték, amelyek összege nincs -hez kötve. Legyen . A softmax , tehát -ös küszöb mellett egyik címkét sem választjuk vagy éppen egy arbitrázs döntésbe kerülünk; a két szigmoid mindkét címkére, tehát mindkettőt jelen levőnek predikáljuk. A softmax elveszi egy második pozitív címke lehetőségét.
A deriváltja a láncszabállyal . Ezért
A elemre , , így a gradiens , ami a fenti tévedés-elemzésben használt pozitív húzás irányát mutatja a negatív cél felé. A maszkolt gradiens definíciója az szorzó: a hiányzó elemnél , ezért a gradiens komponense nulla, bár a formula lenne, ha azt negatívként kezdenénk tanítani.
def masked_bce_grad(z, y, mask):
# z, y, mask alakja (N, K)
z = np.asarray(z, dtype=np.float64)
y = np.asarray(y, dtype=np.float64)
mask = np.asarray(mask, dtype=np.float64)
s = mask.sum()
if s <= 0:
raise ValueError("Üres maszk.")
return mask * (stable_sigmoid(z) - y) / sA PyTorch-ellenőrzés:
zt = torch.tensor([[-1.5, 0.8, 2.2], [0.2, -0.4, 1.1]],
dtype=torch.float64, requires_grad=True)
yt = torch.tensor([[1.0, 0.0, 1.0], [1.0, 1.0, 0.0]], dtype=torch.float64)
mt = torch.tensor([[1.0, 1.0, 1.0], [1.0, 0.0, 1.0]], dtype=torch.float64)
terms = torch.nn.functional.binary_cross_entropy_with_logits(
zt, yt, reduction='none')
loss = (mt * terms).sum() / mt.sum()
loss.backward()
print(np.max(np.abs(zt.grad.numpy() -
masked_bce_grad(zt.detach().numpy(), yt.numpy(), mt.numpy()))))
# 0.0 várt; a numerikus tűrés 1e-8 alatt kell maradjonAlakellenőrzés: a gradiens mátrix ugyanakkora, mint , tehát ; a maszk nélküli formula , a maszkkal szorzás nem változtatja az alakot. Szélsőeset: ha és , a gradiens -hoz tart; ha és , a gradiens -hoz tart; a hiányzó maszk elem tartósan nulla.
A kód a softmaxot és a bináris cross-entropy-t keveri. A
softmax a
logitot egyetlen eloszlássá normalizálja, ezért a két pozitív címkés
képen a cél
nem lehet egyszerre teljesíthető: a softmax probabilitások összege
,
a BCE a szigmoid kimenetet várja, és a log-likelihood interpretáció
sérül. Ha a két logit egyenlő, a softmax két
-öt
ad, a loss pedig
értékű marad a tökéletesen prediktált két pozitív címke ellenére. A
javítás:
BCEWithLogitsLoss(logits, targets.float(), reduction='mean'),
vagy annak reduction='none' változata maszkkal. Ez
numerikusan stabil, mert a log-sum-exp trükköt használja a loss
belsejében, és nem igényel külön szigmoidot.
Egy lehetséges terv: a megfigyelési egység a kép, de ha egy
jelenetből több nézet van, a független csoport a scene_id;
ezt a GlyphLab-szerződés tanította a 25.1. Teljes
képosztályozó adatfolyam alfejezetben. A feladat nem a GlyphLab,
ezért az átvitelnél a scene-csoportosítást meg kell őrizni. Az adatot
train/val/test arányban
-ra
osztjuk a ritka címkék előfordulását rétegezve. Baseline: az empírikus
prior, azaz minden címke prevalenciája a train halmazon; az összetett
detektálásra az egyesített címkés pozitív arányt mérjük. A modell egy
CNN-backbone
logit fejjel, veszteség maszkolt BCE. A választás metrikája a validációs
per-címke F1 makroátlaga, mert ritka címkékre az elemszintű accuracy
félrevezető. A küszöböt a validációs halmazon szabályozzuk globálisan és
per-címkeként is; a teszt halmazra csak a kiválasztott küszöbbel
számolunk, különben a teszt maga is adaptív modellválasztási ciklusba
kerül, és az elvárt generalizációs hibánál optimistább becslést ad. A
következtetés korlátja: a hiányzási mechanizmus nem igazoltan
véletlenszerű, ezért a hiányzó maszk feltételes függetlenségét a
kísérlet nem tudja ellenőrizni; az eredmény csak a megfigyelt címkékre
érvényes.
A multilabel feladat célja mátrix, a modell logitokat ad, és minden címke külön Bernoulli-döntés. A szigmoid elemenkénti, a softmax ezzel szemben egyetlen összegű normalizálást végez; a kétosztályos softmax egyetlen szigmoiddá fajul, de két multilabel szigmoid nem ekvivalens vele. A BCE logitokkal az tagot minimalizálja, gradiense . A megfigyelési maszk a hiányzó címkéket kihagyja a likelihood-összegből; az üres maszk definiálatlan. A küszöböt validáción választjuk, nem a BCE része. A hierarchia apa–gyermek konzisztenciát követel, a vetítés heurisztika. A legfontosabb hiba a nem annotált címke negatívként kezelése, amely hamis gradienssel torzítja a modellt.
A 25.3. Kis adat és feature-alapú baseline egységben a kis adat és a feature-alapú baseline kérdése következik. A multilabel maszk ott is szerepet kap: ha kevés annotált példánk van, a hiányzó címkék kezelése nélkül a baseline pontossága éppen a leggyakoribb hibás célokból tanul, ezért a feature-alapú modell és a CNN összehasonlításakor a maszkot a baseline-nál is explicit módon kell kezelni.