Ez az alfejezet a képosztályozástól a sűrű, térbeli előrejelzés felé lép: minden pixelhez külön osztálycímkét rendelünk. A cél annak pontos rögzítése, hogyan kell a logit-tenzort, a céltenzort és a veszteséget úgy párosítani, hogy az osztálytengely, az érvénytelen pixel és a felbontásváltozás ne okozzon rejtett hibát. Előismeret: a 15.1. Súly- és biasgradiens alfejezetből a konvolúciós réteg gradiense, a 15.4. Független gradiensellenőrzési protokoll alfejezetből a gradiensellenőrzés protokollja, a 16.4. IV. ellenőrző pont: a CNN-operátor teljes megértése alfejezetből a CNN-operátor specifikációja, a 22.4. Augmentációs kísérlet és domainváltozás alfejezetből a clean/corrupt szétválasztás, a 23.4. Hibaszeletek és alkalmazási határ alfejezetből a hibaszeletek, a 26.1. Dobozkoordináta, IoU és lokalizációs loss alfejezetből a doboz és az IoU, a 26.4. Detektálási pipeline és hibaelemzés alfejezetből a képpel szinkronban transzformált annotáció. A korábbi 7.2. Softmax és többosztályos cross-entropy alfejezetben levezettük a softmax–cross-entropy gradiensét; itt csak a pixelszintű általánosítást és a maszkolt átlagot vezetjük le.
Induló kérdés: egy -es, egycsatornás GlyphLab-képen három osztályú szegmentálást készítünk. Hány logitot állít elő egy egyképnyi batch, és hány független osztályozási döntést reprezentál? Becslését ellenőrizzük a következő szakasz alakdefiníciója után.
Definíció (dense prediction). Legyen képbatch. Egy modell dense prediction kimenetet ad, ha minden mintához egy csatornás, térbeli rácson értelmezett logit-tenzort számol:
Minden pozícióhoz egy -dimenziós logitvektor tartozik. A hagyományos képosztályozó ezzel szemben alakú, tehát a térbeli kiterjedést globálisan eliminálja. A dense prediction megtartja a térbeli szerkezetet, ezért a kimenet közvetlenül összehasonlítható egy pixelcímke-ráccsal.
Definíció (fully convolutional network, FCN). FCN-nek nevezzük azt a hálózatot, amelynek számítási gráfja konvolúciós, pooling- és elemenkénti műveletekből, valamint felbontást növelő interpolációból vagy transposed convolutionből áll, és amely nem tartalmaz teljesen összekötött, az összes térbeli pozíciót egyetlen vektorba keverő réteget. A fogalmat itt architekturális szerződésként használjuk: az mintadimenziótól eltekintve minden művelet megőrzi vagy szabályosan változtatja a térbeli rácsot.
A céltenzor alakja:
ahol az -edik kép pixelének osztályindexe. Az osztálytengely a logitban a második dimenzió, NCHW konvencióban az indexű tengely; a céltenzornak nincs osztálytengelye, mert indexeket tárol. A betanított modell az
predikciót adja. A softmax a class axis mentén normalizál pixelről pixelre:
Ezzel az induló kérdésre a válasz: esetén logit készül, ami darab, egymástól függetlenül normalizált háromosztályú döntés.
Az FCN-ek közül a szegmentáló encoder–decoder szerkezet klasszikus példája az U-Net [P14]; az ellenőrzött absztrakt szerint egy kontraháló, kontextust gyűjtő útból és egy szimmetrikus expandáló útból áll, amely a pontos lokalizációt támogatja. A példányszintű szegmentálás felé a Mask R-CNN [P15] absztraktja egy objektumdetektorra épülő, szegmentációs maszkot előrejelző ágat mutat be; az utóbbi a detektálás feltételéhez kötött, ezért nem azonos az itteni, teljes képre kiterjedő pixelszintű FCN-szerződéssel.
Egy pixel vesztesége a többosztályos cross-entropy logitformája:
A pixelszintű célfüggvény nem a batchméret, hanem az érvényes pixelpozíciók szerinti átlag. Ehhez legyen
ismert érvényességmaszk, ahol jelenti, hogy az célcímke felhasználható. Az érvényes halmaz
Lemma (maszkolt átlagos pixel-loss nevezője és gradiense). Legyen minden pixelre differenciálható függvénye a logitvektornak, és legyen a logitoktól független, előre rögzített maszk. Ha , a maszkolt átlagos pixel-loss
nevezője az érvényes pixelek száma, nem a teljes elemszám. A gradiens
Ha , a esetet kizárjuk, és a konvenció .
Bizonyítás. A maszk elemei rögzített skalárok, ezért a tagok lineáris kombinációja: , ahol . A parciális deriválás lineáris művelet, tehát a láncszabály nélkül is:
mert a pixelveszteség csak a saját logitjaitól függ, a szerinti parciális derivált pedig csak az azonos indexű tagban nem nulla. Ezzel a gradiensformula adódik. ∎
A softmax–cross-entropy gradiensét a 7.2. Softmax és többosztályos cross-entropy alfejezetben vezettük le; pixelszinten ugyanaz a formula érvényes minden -re:
A lemmával összetéve:
Az érvénytelen pixeleken a gradiens nulla, az érvényeseken a szokásos softmax-hiba osztva az érvényes pixelszámmal. Ha minden pixel érvényes, és , tehát a maszkmentes átlagot kapjuk vissza. A sum-redukciós változat, , ugyanennek -szerese; az optimalizáló lépésnagyság szempontjából ez nem közömbös, ezért a kísérleti naplóban mindig közölni kell a választott redukciót.
A gyakorlatban az érvénytelenséget gyakran célcímke-szimbólummal jelölik, nem külön maszkkal. Legyen a ignore index; ekkor az értékű pixelek nem vesznek részt az átlagban. Ez ekvivalens a maszkkal:
A lemmánk feltétele teljesül:
a rögzített célból számolt, nem a modell kimenetéből. A keretrendszer
pontos reduction-viselkedését a telepített verzió dokumentációjában kell
ellenőrizni; a fenti levezetés az általunk elvárt numerikus konvenciót
rögzíti. A mean az érvényes elemek átlaga, a
sum az érvényes elemek összege, a none pedig
megőrzi a
alakú, S-nel osztatlan pixelveszteség-rácsot.
Resize mode és class axis. Ha a hálózat kimeneti rácsa kisebb a célrácsnál, például stride-ok miatt, két út közül választhatunk. A helyes irány az, hogy a logit-tenzort interpoláljuk a cél felbontására a térbeli dimenziók mentén, az osztálytengelyt csatornának kezelve. A diszkrét célcímkéket ezzel szemben nem interpoláljuk. A logitinterpoláció módját explicit módon rögzíteni kell; a leggyakrabban bilineáris interpolációt használunk a csatorna mindegyikére. Az interpoláció után a softmax és a veszteség a már definiált képletekkel számolható. A célrácshoz tartozó maszkot a logitokkal együtt sem interpoláljuk; ha a megfigyelés a kimeneti rács pillanatából származik, a maszkot abban a felbontásban kell tárolni.
Legyen , , . A logitok:
A célrács $Y=\begin{bsmallmatrix}0 & 1\\2 & 0\end{bsmallmatrix}$, a maszk $M=\begin{bsmallmatrix}1 & 0\\1 & 1\end{bsmallmatrix}$. Az érvényes pixelszám .
A softmax a pixelen: értékek , összegük , így . A cél , tehát .
A pixelen: értékek , összegük , , cél , .
A pixelen: értékek , összegük , , cél , .
A maszkolt átlag:
A gradiens a lemma szerint: a pixelen ; a pixelen , mert maszkolt; a pixelen ; a pixelen . A három érvényes pixel hozzájárulásainak összege az osztálytengely mentén nullát ad, hiszen minden pixel egy-egy vektort kap.
A következő ellenőrizhető munkamenet ugyanezt számítja ki. A kimenet itt számított példa, nem futtatott laborjegyzőkönyv.
# munkakönyvtár: lab/
import numpy as np
rng = np.random.default_rng(20261020)
Z = np.array([[[[2.0, -0.5], [0.0, 1.0]],
[[-1.0, 1.0], [0.0, 2.0]],
[[0.2, 0.0], [3.0, -2.0]]]], dtype=np.float64) # (1,3,2,2)
Y = np.array([[[0, 1], [2, 0]]], dtype=np.int64) # (1,2,2)
M = np.array([[[1, 0], [1, 1]]], dtype=np.float64) # (1,2,2)
def softmax_pixelwise(Z):
Z_shift = Z - Z.max(axis=1, keepdims=True)
E = np.exp(Z_shift)
return E / E.sum(axis=1, keepdims=True)
P = softmax_pixelwise(Z)
N, K, H, W = Z.shape
Y_flat = Y.reshape(N, 1, H, W)
I_y = np.eye(K)[Y.reshape(N, H, W)] # one-hot: (N,H,W,K)
log_p_y = np.log(P[Y_flat.astype(bool)].reshape(N, H, W)) # hibakerülő, lásd alább
# Megbízhatóbb alakkezelés:
P_ch = np.transpose(P, (0, 2, 3, 1)) # (N,H,W,K)
log_p_y = np.log(np.take_along_axis(P_ch, Y[..., None], axis=3)[..., 0])
ell = -log_p_y
L_masked = np.sum(M * ell) / np.sum(M)
print(f"S={np.sum(M):.0f}, L_masked={L_masked:.6f}") # S=3, L_masked=0.538681A Z.max levonása csak numerikus stabilitást szolgál; a
softmax értéke változatlan, a log–sum–exp túlcsordulását kerüli el. Az
ellenőrző kézi értékkel való egyezés tűrése itt
relatív abszolút hiba float64 mellett.
A labor célja, hogy pixelszintű céladatot töltsünk be, ellenőrizzük a szerződését, és egy triviális, erősítés nélküli baseline-t illesszünk rá. A bináris maszk itt nem a háromosztályú orientációcímke, hanem az előtér/háttér szegmentációs cél leegyszerűsített változata; a küszöbös előrejelzés tehát szegmentációs baseline, nem osztályozó.
# munkakönyvtár: lab/
# futtatás: python n105_baseline.py
import numpy as np
from pathlib import Path
DATA = Path("data")
images = np.load(DATA / "images.npy") # (360,1,24,24), float32, [0,1]
masks = np.load(DATA / "masks.npy") # (360,1,24,24), uint8, {0,1}
labels = np.load(DATA / "labels.npy") # (360,), int64
meta = np.loadtxt(DATA / "metadata.csv", dtype=str, delimiter=",", skiprows=1)
print("images:", images.shape, images.dtype,
"range", float(images.min()), float(images.max()))
print("masks :", masks.shape, masks.dtype,
"values", np.unique(masks).tolist())
assert images.shape == (360, 1, 24, 24)
assert masks.shape == (360, 1, 24, 24)
assert masks.dtype == np.uint8
assert set(np.unique(masks)) <= {0, 1}
# Baseline: fix intenzitásküszöb az egyetlen csatornán; az 1-es csatorna
# dimenzió elhagyásával a predikció alakja (360,24,24).
threshold = 0.5
pred = (images[:, 0] > threshold).astype(np.uint8) # (360,24,24)
target = masks[:, 0] # (360,24,24)
pixel_acc = float(np.mean(pred == target))
inter = np.logical_and(pred == 1, target == 1).sum()
union = np.logical_or(pred == 1, target == 1).sum()
iou = float(inter / union) if union > 0 else float("nan")
print(f"threshold={threshold}, pixel_acc={pixel_acc:.6f}, IoU={iou:.6f}")A kód minden képre és minden pixelre egyetlen átfogó metrikát számol. A várt kimenet itt jelölt szemléltetés, nem a környezet által futtatott eredmény: a pixel-accuracy minden bizonnyal magas, mert a háttér dominálhat, míg az IoU lényegesen alacsonyabb lehet, ha a vékony előtérvonalak egy részét a küszöb levágja. Ez mutatja, hogy a 23.4. Hibaszeletek és alkalmazási határ alfejezetben tárgyalt összesített pontszám miért nem elég: bináris szegmentálásban az osztályarány miatt a pixel-accuracy és az IoU nem cserélhető fel. A küszöböt kizárólag train/val felosztáson szabad hangolni; a test pixelarányos metrikája csak a befagyasztott küszöb mellett értelmezhető.
F1 (fogalmi, nehézség 1). Magyarázza el, miben különbözik a képosztályozó logit-tenzor és a dense prediction logit-tenzor alakja, és adja meg NCHW konvencióban a class axis indexét. Miért nincs osztálytengelye a céltenzornak? Átadandó: saját szavas definíció és egy numerikus alakpélda mindkét tenzorra.
F2 (levezetési, nehézség 2). Legyen , logitok , , cél $Y=\begin{bsmallmatrix}0\\0\end{bsmallmatrix}$, maszk $M=\begin{bsmallmatrix}1\\0\end{bsmallmatrix}$. Számítsa ki -et és a logitokra vonatkozó teljes gradientsűrűséget a lemmából; ellenőrizze, hogy az osztálytengely mentén minden pixel gradiensösszege nulla.
F3 (implementációs, nehézség 2). Írjon
NumPy-függvényt
masked_cross_entropy_mean(logits, targets, ignore_index)
aláírással, ahol logits alakja
float64, targets alakja
int64. A függvény az ignore_index értékű célpontokat kihagyó átlagot
adja vissza. A függvény stabil softmaxot használjon, és S=0
esetén 0.0-t adjon. Ellenőrizze a fenti kézi példa
logitjain, az ignore cél
-gyel.
F4 (hibakeresési, nehézség 2). Az alábbi kódrészlet egy segmentációs loss kiszámítására készült:
loss = ((mask * pixel_loss).sum()
/ mask.numel()) # hiba: teljes elemszámmal osztMi a hiba oka, numerikus következménye, és hogyan kell javítani? Adjon egy konkrét hárompixeles számpéldát, amelyben a hibás és a javított loss értéke eltér, és mutassa meg a javított gradiens arányát.
F5 (kísérleti/transzfer, nehézség 3). Tervezzen egy bináris szegmentálási kísérletet a GlyphLab maszkon a küszöbbaseline-hoz: figyeljen a scene-alapú splitre, a küszöbválasztás train/val protokolljára, az IoU és a pixel-accuracy együttes jelentésére, valamint a per-scene bootstrap érvényességére. Átadandó: protokollszöveg, baseline, metrikák és a teszt használatára vonatkozó korlátozás.
F1 megoldás. A képosztályozó kimenete : minden képhez egyetlen logitvektor tartozik, a térbeli dimenzió eltűnik. A dense prediction kimenete : az osztálydöntés rögzített térbeli rácson ismétlődik. NCHW esetén a class axis a -tól számolt indexű tengely, azaz minden -re a szelet az egy pixel osztálylogitja. A céltenzor alakú, mert minden pozícióhoz egyetlen osztályindexet tárol; az osztályt egy egész érték, nem one-hot vektor jelöli. A megadott alakpélda: alakja , alakja .
F2 megoldás. Az első pixel : . , összeg , . A cél , ezért ; a gradiens a lemma nélküli kézzel a logitokra nézve itt , osztva -gyel, azaz . A második pixel maszkja , ezért nem számít, és gradiense . Tehát , a teljes logitgradiens a pixelen , minden más helyen . Mind a két sorban a két-komponensű gradiensösszeg , mert valószínűségeloszlás és összege .
F3 megoldás.
import numpy as np
def masked_cross_entropy_mean(logits, targets, ignore_index):
N, K, H, W = logits.shape
assert targets.shape == (N, H, W)
logits_shift = logits - np.max(logits, axis=1, keepdims=True)
exp_z = np.exp(logits_shift)
denom = exp_z.sum(axis=1, keepdims=True) # (N,1,H,W)
log_p_y = logits_shift - np.log(denom) # (N,K,H,W)
# kiválasztás osztálytengely mentén:
log_p_y_ch = np.transpose(log_p_y, (0, 2, 3, 1)) # (N,H,W,K)
y_idx = targets[..., None] # (N,H,W,1)
log_p_target = np.take_along_axis(log_p_y_ch, y_idx, axis=-1)[..., 0] # (N,H,W)
loss_per_pixel = -log_p_target
mask = (targets != ignore_index).astype(np.float64)
S = mask.sum()
if S == 0:
return 0.0
return float(np.sum(mask * loss_per_pixel) / S)
# teszt a kézi példával:
Z = np.array([[[[2.0, -0.5], [0.0, 1.0]],
[[-1.0, 1.0], [0.0, 2.0]],
[[0.2, 0.0], [3.0, -2.0]]]], dtype=np.float64)
Y = np.array([[[0, 1], [2, 0]]], dtype=np.int64)
print(masked_cross_entropy_mean(Z, Y, ignore_index=1)) # ~0.5387Az implementáció nem számol külön M maszkot
paraméterként, hanem az ignore indexből állítja elő. Az
ág a lemma konvenciója. A stabil log–sum–exp a
logits_shift - log(denom) alakban marad, mert
numerikusan biztonságosabb, mint
külön számítása.
F4 megoldás. A hiba oka, hogy a nevező az összes
pixel mask.numel()-e, nem az érvényes
mask.sum()-ja. Következménye, hogy ha a maszk ritka, az
átlag lefelé torzul, és a gradiens is a teljes rács, nem az érvényes
halmaz nagyságával skálázódik. Konkrét példa: három pixel,
,
;
a hibás érték
,
a helyes
.
A javított kód mask.sum()-mal oszt, és a lemmából a
gradiens érvényes pontokon
,
míg a hibásnál
lenne. Visszaellenőrzés: mindhárom pixel gradientjét az analitikus
képlettel összevetve a hiba megszűnik.
F5 megoldás. Egy lehetséges terv: a megfigyelési
egység a scene_id, ezért a küszöböt a train/val scene-ekre,
nem a testre hangoljuk. A baseline a képintenzitás fix küszöbös
binarizálása. A search-tér
;
a valasztáshoz a val halmazon számolt IoU-t használjuk, mert a
pixel-accuracy a háttérdominancia miatt gyenge diszkriminátor. A
kiválasztott
-ot
a befagyasztott bináris döntési szabállyal egyszer értékeljük a testen:
per-image IoU átlag, illetve az összes pixelre számolt globális IoU és
pixel-accuracy, mert ezek aggregálása eltérő. A bizonytalansághoz
scene-rétegzett bootstrapet használunk, ahol minden resample egység egy
scene, és a két view-t mindig egy blokkban tartjuk. A teszt eredményéből
nem módosítjuk a küszöböt; a korrupciótesztet a 22.4. Augmentációs kísérlet és domainváltozás
alfejezethez hasonlóan csak a befagyasztott szabályon, páros
scene-szerkezettel értékeljük.
Tipikus tévedés. A diszkrét célcímke felbontásváltoztatását bilineáris interpolációval oldják meg. Ellenpélda: legyen egy kétszomszédos célpixel értéke és . Bilineáris interpolációval köztes értékek, például és keletkezhetnek, amelyek sem a , sem az osztályt nem jelentik. A cél az osztályindexek halmazán kívüli értékeket kap, a loss vagy hibaüzenettel leáll, vagy némán hamis osztályként értelmezi őket. A javítás az, hogy a logitokat interpoláljuk tetszőleges sima móddal, a címketenzorra ellenben nearest-neighbor módot használunk, vagy a megfigyelést már a célrácsban tároljuk. A szabály a 26.4. Detektálási pipeline és hibaelemzés szinkrontranszformációs követelményének pontos megfelelője: a diszkrét céloknál a geometriai transzformáció nem keverhet osztályokat.
Önellenőrzés. (1) Adott alakja . Hány független osztályozási pontot tartalmaz, és hol van a class axis? Válasz: pont, az indexű tengelyen. (2) Ha , mit ad a lemmánk? -t, de ezt kísérleti naplóban külön jelezni kell, mert elrejtheti az üres batch hibáját. (3) Korábbi fogalom visszahívása: a 15.1. Súly- és biasgradiens alfejezetben levezetett biasgradiens a batch és térbeli indexek szerinti upstream-összeg volt. A pixelszintű lossban a pixel-gradiensek hasonlóan adódnak össze a konvolúciós backward során, de az átlagolás skálája előbb a maszkolt loss-szal, utóbb a minibatch átlagával kétszeres redukciót is jelenthet; ezért minden nem-skalár lépésnél ellenőrizni kell, hogy a nevező , a batchméret, vagy a teljes pixelrács-e.
Visszahívás (26.1. Dobozkoordináta, IoU és lokalizációs loss, 23.1. Confusion matrix és aggregálási konvenció). Az IoU a pixelszintű előrejelzésben is a metszet/unió hányados, a dobozos definícióval azonos arányfogalom; a fél-nyílt koordinátakonvenció viszont itt már nem kétdimenziós intervallumra, hanem a diszkrét pixelhalmaz elemszámára vonatkozik. A 23.1. Confusion matrix és aggregálási konvenció tanulsága alapján a pixel-accuracy globális átlag, az IoU osztályszerű halmazátfedés: kiegyensúlyozatlan foreground esetén a két metrika egymásnak ellentmondó következtetésre vezethet.
A dense prediction modell logitrácsot ad, ahol minden pixel külön osztályozási pont, a class axis NCHW-ban az 1. A pixel-cross-entropy az érvényes célpixelek átlaga: , . A lemma bizonyítja, hogy a nevező az érvényes pixelszám, a gradiens pedig . Az ignore index egy adott célból származó maszkkal ekvivalens. A logitokat bilineárisan interpolálhatjuk a célrácsra; a diszkrét címkéket csak nearest módon szabad transzformálni. A labor a GlyphLab maszkon fix küszöbbaseline-t illeszt, és a pixel-accuracy mellé IoU-t tesz, mert a háttérdominancia félrevezető pontszámot adhat.
A masked mean lemma és a kézi példa saját levezetés; a labor futási kimenete a szövegben jelölt szemléltetés, nem mért eredmény.