39.3 Geometriai kontroll és metrika

39.3.1 Cél, előismeret és induló ellenőrzés

A Projekt III. a 39.2. Encoder–decoder vagy lokalizációs fej alfejezetben egy bináris, egyobjektumos szegmentáló fejet definiált: a háló minden 24×24-es GlyphLab-képhez egy logit-maszkot ad, amelyből küszöböléssel Ĝ∈{0,1}H×W\hat G\in\{0,1\}^{H\times W} bináris előrejelzés lesz. Ebben az alfejezetben azt a kérdést járjuk körül, hogyan ellenőrizzük ezt a kimenetet geometriailag és statisztikailag úgy, hogy az állítás ne csak a nagy háttérterület miatt tűnjön jónak.

A szükséges előismeret: a maszk mint H×WH\times W bináris mátrix, a half-open doboz 26.1. Dobozkoordináta, IoU és lokalizációs loss alfejezetből, valamint az IoU/Dice alapfogalmak 27.3. Dice, IoU és aggregálás alfejezetből. A GlyphLab anotációja minden képhez tartalmazza az előtér maszkját; a masks.npy alakja (360,1,24,24)(360,1,24,24), uint8, értéke 0 vagy 1.

Induló ellenőrző kérdés: ha egy 24×24-es képen a GT maszk 24 előtérpixelt tartalmaz (4,17%), és a modell üres maszkot ad, akkor a pixel accuracy 576−24576=0,9583\frac{576-24}{576}=0{,}9583. Jó eredmény-e ez? A kérdésre a válasz nem, és a különbség az alfejezet központi témája.

39.3.2 Pixelmetrikák és hibakategóriák

Rögzítsünk egyetlen képre egy GT maszkot G∈{0,1}H×WG\in\{0,1\}^{H\times W} és egy bináris predikciót Ĝ∈{0,1}H×W\hat G\in\{0,1\}^{H\times W}. A pixelpárok négy hibakategóriába sorolhatók:

TP=∑h,wGh,wĜh,w,FP=∑h,w(1−Gh,w)Ĝh,w,FN=∑h,wGh,w(1−Ĝh,w),TN=∑h,w(1−Gh,w)(1−Ĝh,w). \begin{aligned} \text{TP} &= \sum_{h,w} G_{h,w}\hat G_{h,w}, & \text{FP} &= \sum_{h,w} (1-G_{h,w})\hat G_{h,w},\\ \text{FN} &= \sum_{h,w} G_{h,w}(1-\hat G_{h,w}), & \text{TN} &= \sum_{h,w} (1-G_{h,w})(1-\hat G_{h,w}). \end{aligned}

Mind a négy mennyiség nemnegatív egész, összegük N=H⋅WN=H\cdot W. A maszkok uint8 típusból bool-lá alakíthatók, így a fenti összegek a NumPy logical_and, logical_not műveleteivel számíthatók.

A bináris szegmentálás két szokásos metrikája:

J=TPTP+FP+FN,D=2TP2TP+FP+FN. J = \frac{\text{TP}}{\text{TP}+\text{FP}+\text{FN}}, \qquad D = \frac{2\,\text{TP}}{2\,\text{TP}+\text{FP}+\text{FN}}.

A 27.3. Dice, IoU és aggregálás alfejezetben bizonyítottuk, hogy nemüres, pozitív metszetű halmazokra D=2J1+JD=\frac{2J}{1+J}, és rögzítettük az üres–üres konvenciót: ha TP+FP+FN=0\text{TP}+\text{FP}+\text{FN}=0, azaz a két maszk egyaránt üres, akkor J=D=1J=D=1 képpáronként. Ha a nevező nulla, de a kategóriák nem mind nullák, a hányados 0. A projektben a GT maszk a generátor szerződése miatt nem üres, de a predikció lehet üres, ezért az üres eset kezelése kötelező.

A pixel accuracy

A=TP+TNN A = \frac{\text{TP}+\text{TN}}{N}

egyszerű, de háttérdomináns képen torzít: az induló példa 0,95830{,}9583 accuracy mellett J=0J=0 és D=0D=0, mert TP=0\text{TP}=0. A szegmentáló modell kiértékelésénél ezért az IoU/Dice az elsődleges metrika, az accuracy-t csak a hibakategóriák mellett közöljük.

39.3.3 Geometriai doboz és centroid

A doboz az előtér pixeleinek legkisebb befoglaló téglalapja. Az indexelés nullától kezdődik, a doboz félig nyílt xyxy intervallum:

xmin=min{w:∃h,Mh,w=1},xmax=1+max{w:∃h,Mh,w=1}, x_{\min} = \min\{w : \exists h,\ M_{h,w}=1\},\quad x_{\max} = 1+\max\{w : \exists h,\ M_{h,w}=1\},

és ugyanígy yminy_{\min}, ymaxy_{\max}. Az intervallum [xmin,xmax)[x_{\min},x_{\max}) vízszintesen és [ymin,ymax)[y_{\min},y_{\max}) függőlegesen. A doboz területe

Abox=(xmax−xmin)(ymax−ymin). A_{\text{box}} = (x_{\max}-x_{\min})(y_{\max}-y_{\min}).

A félig nyílt konvenció nem esztétikai kérdés: két doboz metszetének szélessége min(xmax,x′max)−max(xmin,x′min)\min(x_{\max},x'_{\max})-\max(x_{\min},x'_{\min}), amely pontosan akkor pozitív, ha a két intervallum valódi átfedésben van. Ha valaki +1+1-et adna a pixelindexhez, a terület és a metszet konzisztenciája megtörne. A GlyphLab metadata.csv-ben szereplő xmin,ymin,xmax,ymaxx_{\min},y_{\min},x_{\max},y_{\max} pontosan ezt a konvenciót követi.

A centroid, azaz a maszk tömegközéppontja:

c(M)=1|M|∑(h,w)∈M(h,w), c(M) = \frac{1}{|M|}\sum_{(h,w)\in M}(h,w),

ahol |M|=∑h,wMh,w|M|=\sum_{h,w}M_{h,w} az előtérpixelek száma. A koordináták valós számok, a tengelysorrend (y,x)(y,x), mert a tömb első indexe a sor. A centroid a doboz négy sarkánál finomabb pozícióinformációt ad: a doboz a szélső pontokra, a centroid a tömeg eloszlására érzékeny.

39.3.4 Levezetés: a centroid-hiba eltolási ekvivarianciája

A projektben a centroid-hibát is használjuk, ezért nem elegendő azt mondani, hogy „a centroid eltolódik az eltolással”. Állításként és bizonyításként rögzítjük.

4.1. lemma (centroid eltolási ekvivarianciája). Legyen M⊂ℤ2M\subset\mathbb Z^2, 0<|M|<∞0<|M|<\infty, és c(M)=1|M|∑(h,w)∈M(h,w)c(M)=\frac{1}{|M|}\sum_{(h,w)\in M}(h,w). Ekkor minden t=(th,tw)∈ℤ2t=(t_h,t_w)\in\mathbb Z^2 esetén

c(M+t)=c(M)+t, c(M+t)=c(M)+t,

ahol M+t={(h+th,w+tw):(h,w)∈M}M+t=\{(h+t_h,w+t_w):(h,w)\in M\}.

Bizonyítás. Az eltolás bijekció MM és M+tM+t között, ezért |M+t|=|M||M+t|=|M|. A definíciót alkalmazva:

c(M+t)=1|M+t|∑(h′,w′)∈M+t(h′,w′)=1|M|∑(h,w)∈M(h+th,w+tw). c(M+t) = \frac{1}{|M+t|}\sum_{(h',w')\in M+t}(h',w') = \frac{1}{|M|}\sum_{(h,w)\in M}(h+t_h,w+t_w).

Az összeget komponensenként szétbontva:

1|M|[∑(h,w)∈M(h,w)+∑(h,w)∈M(th,tw)]=c(M)+1|M|⋅|M|t=c(M)+t. \frac{1}{|M|}\left[\sum_{(h,w)\in M}(h,w)+\sum_{(h,w)\in M}(t_h,t_w)\right] = c(M)+\frac{1}{|M|}\cdot |M|\,t = c(M)+t.

A második egyenlőségnél használtuk, hogy az összeg második tagja konstans tagú: minden elemhez ugyanazt a tt vektort adjuk, ezért az összeg |M|t|M|t. ◼\blacksquare

Szélsőeset. Üres maszkra c(M)c(M) nem definiált; a kódban None értékkel jelöljük. Ha az eltolás a képkeretből kilóg, akkor a levágott, megfigyelt halmaz nem M+tM+t, hanem annak a képkerettel vett metszete, így a lemma nem alkalmazható. A centroid-hiba tehát eltolás-invariáns a kereten belül, de a határnál a csonkolás új geometriai hibát hoz létre.

4.2. következmény (a centroid-hiba eltolás-invarianciája). Legyen G,ĜG,\hat G két nemüres maszk, és t∈ℤ2t\in\mathbb Z^2 olyan, hogy G+tG+t és Ĝ+t\hat G+t is a képkeretben marad. Ekkor

∥c(Ĝ+t)−c(G+t)∥2=∥c(Ĝ)−c(G)∥2. \|c(\hat G+t)-c(G+t)\|_2 = \|c(\hat G)-c(G)\|_2.

Bizonyítás. A 4.1. lemmát kétszer alkalmazva c(Ĝ+t)−c(G+t)=c(Ĝ)−c(G)c(\hat G+t)-c(G+t)=c(\hat G)-c(G), mert a +t+t tag kiejti egymást. A norma megőrzi az egyenlőséget. ◼\blacksquare

Ez a következmény a projekt metrikájának matematikai azonossága: a centroid-hiba a két maszk együttes eltolására érzéketlen, így tisztán a relatív pozícióhibát méri. A bizonyítás a koordináták egy-egy értelmű eltolásán (bijekción) alapul: az eltolás a maszk előtérpixeleit egy-egy értelműen képezi le, így a pixelek száma nem változik, és minden koordinátájuk ugyanazzal a vektorral tolódik. A centroid a maszkra nézve általában nem lineáris: c(M)=∑rrMr/∑rMrc(M)=\sum_r r\,M_r/\sum_r M_r, a nevező is a bemenettől függ; például a maszk pozitív skalárral szorzása nem skálázza a centroidot. Linearitás legfeljebb rögzített számú pont koordinátáinak átlagolására mondható ki, és az eltolási ekvivariancia nem ebből, hanem a fenti bijekcióból következik. A doboz ugyanígy ekvivariáns az eltolásra, de mivel minimumra és maximumra épül, a pixelek helyzetének kis változására érzékenyebben reagál.

39.3.5 Eredeti kézi kontrollpélda

Vegyünk egy 6×8-as rácsot. A GT maszk egy 3×2-es téglalap:

0 0 0 0 0 0 0 0
0 0 1 1 0 0 0 0
0 0 1 1 0 0 0 0
0 0 1 1 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

A predikció az alsó sort elveszti, de a felső kettőt megtartja:

0 0 0 0 0 0 0 0
0 0 1 1 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

Kézzel: TP=3\text{TP}=3 (a közös (1,2),(1,3),(2,3)(1,2),(1,3),(2,3)), FP=0\text{FP}=0, FN=3\text{FN}=3 (a GT (2,2),(3,2),(3,3)(2,2),(3,2),(3,3) pixelei hiányoznak), TN=42\text{TN}=42. Így

J=33+0+3=12,D=66+0+3=23,A=3+4248=4548=0,9375. J=\frac{3}{3+0+3}=\frac12,\qquad D=\frac{6}{6+0+3}=\frac23,\qquad A=\frac{3+42}{48}=\frac{45}{48}=0{,}9375.

A GT doboz: xmin=2x_{\min}=2, xmax=4x_{\max}=4, ymin=1y_{\min}=1, ymax=4y_{\max}=4, terület 2⋅3=62\cdot 3=6. A pred doboz: xmin=2x_{\min}=2, xmax=4x_{\max}=4, ymin=1y_{\min}=1, ymax=3y_{\max}=3, terület 2⋅2=42\cdot 2=4. A metszet területe 2⋅2=42\cdot 2=4, az unió 6+4−4=66+4-4=6, tehát a box-IoU 23≈0,6667\frac23\approx0{,}6667.

A GT centroid:

c(G)=16((1,2)+(1,3)+(2,2)+(2,3)+(3,2)+(3,3))=(126,156)=(2,2,5). c(G)=\frac{1}{6}\Big((1,2)+(1,3)+(2,2)+(2,3)+(3,2)+(3,3)\Big) =\left(\frac{12}{6},\frac{15}{6}\right)=(2, 2{,}5).

A pred centroid:

c(Ĝ)=13((1,2)+(1,3)+(2,3))=(43,83)≈(1,3333,2,6667). c(\hat G)=\frac13\Big((1,2)+(1,3)+(2,3)\Big) =\left(\frac43,\frac83\right) \approx(1{,}3333,\ 2{,}6667).

A centroid-hiba

∥c(G)−c(Ĝ)∥2=(23)2+(−16)2=176≈0,6872. \|c(G)-c(\hat G)\|_2 = \sqrt{\left(\frac23\right)^2+\left(-\frac16\right)^2} = \frac{\sqrt{17}}{6} \approx 0{,}6872.

Ez a kézi számpélda lesz a labor numerikus ellenőrzése.

39.3.6 A labor: geometriai kontrollmodul

A labor egy NumPy-alapú kiértékelő modult valósít meg, amelyet a 39.4. Alkalmazási riport és átadás exportja és auditja is importál; ezért a dobozkonvenció egyetlen helyen, egy névvel ellátott Box(xmin, ymin, xmax, ymax) struktúrában és a validate_box központi validátorban rögzített. A sorrend mindenhol a metadata.csv xyxy sorrendje; a maszkból számolt dobozt a kód mind a 360 képre összeveti a metadata dobozával, és egy aszimmetrikus doboz teljes láncát (maszk → doboz → terület → JSON-export → visszaolvasás) is ellenőrzi. A maszkon mért IoU neve mask_iou, a dobozon mérté bbox_iou. A munkakönyvtár lab/project3; a GlyphLab adat a lab/data alatt van.

# lab/project3/n155_geom_control.py — n155 geometriai metrika kontroll, xyxy dobozokkal.
"""Egyetlen dobozkonvenció: Box(xmin, ymin, xmax, ymax), nullától indexelt,
félig nyílt [xmin,xmax) x [ymin,ymax); üres maszk szentinelje (-1,-1,-1,-1)."""
from __future__ import annotations
import json
from pathlib import Path
from typing import NamedTuple
import numpy as np
import pandas as pd

DATA = Path(__file__).resolve().parents[1] / "data"
H = W = 24


class Box(NamedTuple):
    xmin: int
    ymin: int
    xmax: int
    ymax: int


EMPTY = Box(-1, -1, -1, -1)


def validate_box(b: Box, h: int = H, w: int = W) -> Box:
    """Központi validátor: a szentinel vagy érvényes, nemüres, a képen belüli doboz."""
    b = Box(*map(int, b))
    if b == EMPTY:
        return b
    if not (0 <= b.xmin < b.xmax <= w and 0 <= b.ymin < b.ymax <= h):
        raise ValueError(f"érvénytelen xyxy doboz: {tuple(b)}")
    return b


def conf_counts(pred: np.ndarray, target: np.ndarray) -> dict[str, int]:
    p = pred.astype(bool)
    t = target.astype(bool)
    assert p.shape == t.shape, (p.shape, t.shape)
    tp = int(np.logical_and(p, t).sum())
    fp = int(np.logical_and(p, ~t).sum())
    fn = int(np.logical_and(~p, t).sum())
    tn = int(np.logical_and(~p, ~t).sum())
    return {"TP": tp, "FP": fp, "FN": fn, "TN": tn}


def bbox(mask: np.ndarray) -> Box:
    """Maszkból xyxy doboz; üres maszkra a szentinel."""
    ys, xs = np.nonzero(np.squeeze(mask))
    if ys.size == 0:
        return EMPTY
    return validate_box(Box(int(xs.min()), int(ys.min()), int(xs.max()) + 1, int(ys.max()) + 1))


def box_area(b: Box) -> int:
    b = validate_box(b)
    return 0 if b == EMPTY else (b.xmax - b.xmin) * (b.ymax - b.ymin)


def box_iou(a: Box, b: Box) -> float:
    """bbox-IoU; két üres doboz 1, pontosan egy üres 0 (az n153 üreseset-szerződése)."""
    a, b = validate_box(a), validate_box(b)
    if a == EMPTY or b == EMPTY:
        return 1.0 if a == b else 0.0
    iw = max(0, min(a.xmax, b.xmax) - max(a.xmin, b.xmin))
    ih = max(0, min(a.ymax, b.ymax) - max(a.ymin, b.ymin))
    inter = iw * ih
    return inter / (box_area(a) + box_area(b) - inter)


def centroid(mask: np.ndarray) -> tuple[float, float] | None:
    ys, xs = np.nonzero(np.squeeze(mask))
    if ys.size == 0:
        return None
    return float(ys.mean()), float(xs.mean())


def metric_dict(pred: np.ndarray, target: np.ndarray) -> dict:
    c = conf_counts(pred, target)
    tp, fp, fn, tn = c["TP"], c["FP"], c["FN"], c["TN"]
    out = {**c,
           "accuracy": (tp + tn) / (tp + fp + fn + tn),
           "mask_iou": tp / (tp + fp + fn) if (tp + fp + fn) else 1.0,
           "dice": 2 * tp / (2 * tp + fp + fn) if (2 * tp + fp + fn) else 1.0,
           "bbox_iou": box_iou(bbox(pred), bbox(target)),
           "n_pixels": int(pred.size)}
    cp, ct = centroid(pred), centroid(target)
    out["centroid_error"] = (float(np.hypot(ct[0] - cp[0], ct[1] - cp[1]))
                             if cp is not None and ct is not None else None)
    return out


def main() -> None:
    target = np.array([
        [0,0,0,0,0,0,0,0],
        [0,0,1,1,0,0,0,0],
        [0,0,1,1,0,0,0,0],
        [0,0,1,1,0,0,0,0],
        [0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0]], dtype=np.uint8)
    pred = np.array([
        [0,0,0,0,0,0,0,0],
        [0,0,1,1,0,0,0,0],
        [0,0,0,1,0,0,0,0],
        [0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0]], dtype=np.uint8)

    out_dir = Path("n155_control")
    out_dir.mkdir(exist_ok=True)
    np.save(out_dir / "control_pred.npy", pred)
    np.save(out_dir / "control_target.npy", target)

    report = metric_dict(pred, target)
    assert abs(report["mask_iou"] - 0.5) < 1e-12
    assert abs(report["dice"] - 2/3) < 1e-12
    assert abs(report["accuracy"] - 45/48) < 1e-12
    assert abs(report["bbox_iou"] - 2/3) < 1e-12
    assert abs(report["centroid_error"] - np.sqrt(17)/6) < 1e-12

    # Aszimmetrikus doboz teljes lánca: maszk -> bbox -> terület -> export -> visszaolvasás.
    m = np.zeros((H, W), dtype=np.uint8)
    m[5:9, 2:7] = 1                                   # sorok 5..8, oszlopok 2..6
    b = bbox(m)
    assert b == Box(xmin=2, ymin=5, xmax=7, ymax=9) and box_area(b) == 20
    assert Box(*json.loads(json.dumps(list(b)))) == b
    assert box_iou(b, Box(2, 5, 7, 9)) == 1.0 and box_iou(b, Box(5, 2, 9, 7)) < 1.0

    # A metadata.csv xyxy oszlopai minden képre egyeznek a maszkból számolt dobozzal.
    masks = np.load(DATA / "masks.npy").astype(np.uint8)
    meta = pd.read_csv(DATA / "metadata.csv")
    for r in meta.itertuples():
        assert bbox(masks[r.index]) == Box(r.xmin, r.ymin, r.xmax, r.ymax), r.index
    report["metadata_bbox_check"] = f"{len(meta)} kép, mind egyezik"
    report["foreground_ratio_by_split"] = {
        split: float(masks[g["index"].to_numpy()].mean()) for split, g in meta.groupby("split")}

    (out_dir / "n155_control_report.json").write_text(
        json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
    print(json.dumps(report, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()

Futtatás:

cd lab/project3
python n155_geom_control.py

A kód a n155_control/control_pred.npy és n155_control/control_target.npy fájlokat menti, majd a n155_control/n155_control_report.json jelentést írja. Az ellenőrzés tűrése 10−1210^{-12}, mert a számítások egész alapúak, a hibák pedig kis nevezőjű racionális számok. Az aszimmetrikus kontrolldoboz a [2,7)×[5,9)[2,7)\times[5,9) téglalap, azaz Box(xmin=2, ymin=5, xmax=7, ymax=9), területe 5⋅4=205\cdot4=20; ha valaki a koordinátákat (xmin,xmax,ymin,ymax)(x_{\min},x_{\max},y_{\min},y_{\max}) sorrendben adná át, a validátor vagy az összevetés azonnal hibát jelez, nem csak a saját függvényei egymás közötti tesztje. Futási jegyzőkönyv (eredeti GlyphLab-adat): minden assert teljesült, a metadata_bbox_check értéke „360 kép, mind egyezik”, az előtérarány splitenként train 0,02010{,}0201, val 0,02110{,}0211, test 0,02080{,}0208. A valós predikciókra a modult a 39.4. Alkalmazási riport és átadás export_delivery.py és report_audit.py szkriptje alkalmazza.

39.3.7 Per-scene aggregálás, páros teszt és osztályarány

A GlyphLab megfigyelési egysége a scene_id: egy jelenethez két zajos nézet tartozik, és a két nézet nem független. A 38.3. Végső teszt és páros korrupció alfejezetben bizonyított csoportátlag-lemma szerint, ha minden csoportban azonos VV megfigyelésszám van, a csoportátlag megegyezik a per-image átlaggal. Itt minden scene-hez Vs=2V_s=2 view tartozik, tehát a per-scene és a per-image átlag numerikusan egybeesik; a scene-alapú megfogalmazás mégis kötelező, mert a bootstrap és a hibaintervallum csak scene-egységekre értelmes.

Legyen Js,vcleanJ_{s,v}^{\text{clean}} a tiszta tesztkép és Js,vcorruptJ_{s,v}^{\text{corrupt}} a korrupt pár IoU-ja. A korrupt teszt ugyanazon scene/view sorrendet követi, így a párosítás egyértelmű. A páros különbség

ds,v=Js,vclean−Js,vcorrupt, d_{s,v}=J_{s,v}^{\text{clean}}-J_{s,v}^{\text{corrupt}},

a scene-szintű különbség pedig

Ds=1Vs∑v=1Vsds,v. D_s=\frac{1}{V_s}\sum_{v=1}^{V_s}d_{s,v}.

Az átlagos hatás D‾=1S∑sDs\bar D=\frac1S\sum_s D_s. A párosítás azért lényeges, mert a két nézet közös jelenetbeli pozíciója és vastagsága korrelációt visz a mérésbe; ha a clean és corrupt méréseket független mintaként kezelnénk, a jelenetek közötti szórást tévesen a beavatkozás bizonytalanságához adnánk.

Az intervallumot a scene-szintű DsD_s értékek osztályonként rétegzett bootstrapjével számítjuk: minden orientációs osztályból külön-külön, visszatevéssel húzunk 10 scene-t, kiszámítjuk a D‾\bar D bootstrap-mintát, és a 2,5–97,5 percentilist adjuk. Az [S02] módszertani kerete szerint ez feltételes, ismételt mintavételi értelmezésű intervallum; nem ok-okozati garancia, és 30 scene mellett a szélek bizonytalanok.

Az osztályarány két szinten jelenik meg. A jelenetek három orientációs osztálya a generátor szerint kiegyensúlyozott, ezért a bootstrapet rétegezzük. A pixelosztály azonban nem az: az előtér aránya a 24×24-es képen lényegesen kisebb, mint a háttéré, ezért a plain BCE loss és a pixel accuracy háttértorzított lehet. A per-scene átlag minden jelenetet egyenlő súllyal vesz, a globális pixel-IoU viszont a nagyobb előtérterületű képek felé húz.

39.3.8 Tipikus tévedés ellenpéldával

A leggyakoribb tévedés a pixel accuracy önálló használata háttérdomináns szegmentálásnál. Ellenpélda: legyen a kép 24×24, a GT maszk 24 előtérpixelt tartalmaz, a predikció pedig az azonosan nulla maszk. Ekkor TP=0\text{TP}=0, FN=24\text{FN}=24, FP=0\text{FP}=0, TN=552\text{TN}=552, tehát

A=552576=0,9583,J=D=0. A=\frac{552}{576}=0{,}9583,\qquad J=D=0.

A 95,83%-os accuracy azt sugallhatná, hogy a modell kiváló, miközben a foreground szegmentálás teljesen használhatatlan: egyetlen előtérpixelt sem talál meg. A helyes eljárás az IoU/Dice, valamint a foreground recall TPTP+FN\frac{\text{TP}}{\text{TP}+\text{FN}} és foreground precision TPTP+FP\frac{\text{TP}}{\text{TP}+\text{FP}} együttes közlése.

A második ellenpélda a doboz és a maszk összekeverésére figyelmeztet: legyen a GT doboz [0,10)×[0,10)[0,10)\times[0,10), a pred doboz ugyanez, de a GT maszk a doboz bal, a pred maszk a doboz jobb felét fedi. Ekkor a box-IoU 11, a pixel-IoU 00. A doboz tehát lokalizációs összefoglaló, nem helyettesíti a pixelszintű metrikát.

39.3.9 Önellenőrzés és visszahívás

Ellenőrző kérdések a továbbhaladás előtt:

  1. Mi a négy hibakategória shape-je és viszonya NN-hez?
  2. Miért nem adunk +1+1-et a doboz szélességéhez?
  3. Mi a centroid, és miért marad a centroid-hiba változatlan a közös eltolásra?
  4. A GlyphLab-ban mi a független mintavételi egység a maszkmetrikák bootstrapjében?
  5. Mikor J=0J=0 akkor is, amikor a GT és a pred nem üres?

A válaszok röviden: a négy skalár összege H⋅WH\cdot W; a félig nyílt konvenció őrzi a metszet–unió formulát; a centroid az átlag linearitása miatt tolódik el, ahogy a 4.1. lemma mutatja; a mintavételi egység a scene_id; J=0J=0, ha TP=0\text{TP}=0 de FP+FN>0\text{FP}+\text{FN}>0, azaz a két maszk diszjunkt előterű.

Visszahívás: a 27.3. Dice, IoU és aggregálás alfejezetben bevezetett D=2J1+JD=\frac{2J}{1+J} azonosság és az üres konvenció itt is érvényes; a 38.3. Végső teszt és páros korrupció scene-bootstrapje adja a páros különbség intervallumát; a 35.3. Perturbáció és adversarial példa tanulsága pedig az, hogy a clean és a perturbált bemenet metrikája két külön kockázat, nem egyetlen „robusztusság” szám.

39.3.10 Feladatok

F1 (fogalmi, nehézség 1). Definiálja a half-open doboz területét, és számítsa ki a [3,8)×[2,6)[3,8)\times[2,6) doboz területét. Miért hibás a diszkrét ((8−3)+1)((6−2)+1)((8-3)+1)((6-2)+1) képlet? Elvárt eredmény: a helyes terület, a hibás képlet értéke és az indoklás.

F2 (levezetési, nehézség 2). A 4.1. lemma felhasználásával bizonyítsa a 4.2. következményt. Adjon olyan tt eltolást, amelyre a lemma nem alkalmazható egy 24×24-es képen, és indokolja a kimaradást. Elvárt eredmény: teljes levezetés és konkrét számértékű ellenpélda.

F3 (implementációs, nehézség 2). Írjon függvényt, amely két azonos alakú uint8 maszkra visszaadja a TP, FP, FN, TN, IoU, Dice ötöst. Tesztelje az üres–üres esetre és arra az esetre, amikor a pred minden pixelén 1, a GT pedig azonosan 0. Elvárt eredmény: futtatható kód és a két teszteset számértékei.

F4 (hibakeresési, nehézség 2). Egy mérést végző hallgató a szegmentáló modelljét „nagyon pontosnak” nevezi, mert a validációs accuracy 0,9375. A tényleges predikció minden képen üres maszk. Hogyan lehetséges ez? Számítsa ki a 4×4-es esetet, ahol a GT-ben egyetlen előtérpixel van, és adja meg a javítást. Elvárt eredmény: az ok, a TM/FP/FN/TM számok, valamint a javasolt metrika.

F5 (kísérlettervezési, nehézség 3). Tervezze meg a Projekt III. páros korrupciós kísérletét a szegmentáló modellre. Adja meg a kérdést, a megfigyelési egységet, a metrikát, a statisztikát, a bootstrap stratégiát és a következtetés érvényességi korlátját. Elvárt eredmény: protokoll, amely a 38.3. Végső teszt és páros korrupció és a jelen alfejezet jelöléseit használja.

39.3.11 Megoldások és indoklás

F1. A helyes terület (8−3)(6−2)=5⋅4=20(8-3)(6-2)=5\cdot 4=20. A hibás formula 6⋅5=306\cdot 5=30. A fél-nyílt intervallum a folytonos élhosszat méri: az intervallum hossza a végpontkülönbség, függetlenül attól, hogy a rácson hány egész index esik bele. A +1+1 a bennfoglalt rácspontok számát adná, ami a metszet–unió geometriai területformulához nem konzisztens.

F2. A 4.1. lemma szerint

c(Ĝ+t)−c(G+t)=(c(Ĝ)+t)−(c(G)+t)=c(Ĝ)−c(G). c(\hat G+t)-c(G+t) = \big(c(\hat G)+t\big)-\big(c(G)+t\big) = c(\hat G)-c(G).

A norma mindkét oldalon kifejezhető, ezért a hiba egyenlő. A feltételek: Ĝ\hat G és GG nem üres, és a G+tG+t, Ĝ+t\hat G+t halmazok a keretben maradnak. Például a 24×24-es kép esetén t=(0,20)t=(0,20) az xx irányba tol: ha a maszk maximális xx-indexe 12, akkor 12+20=32>2312+20=32>23, a keret csonkít. Az így megfigyelt halmaz nem M+tM+t, tehát a lemma előfeltétele sérül; a centroid-hiba már nem feltétlenül eltolás-invariáns.

F3. Az alábbi kód a labor conf_counts és metric_dict függvényeire építhető; a két teszteset a NumPy-ben:

def conf_counts(pred, target):
    p = pred.astype(bool); t = target.astype(bool)
    return (int((p & t).sum()), int((p & ~t).sum()),
            int((~p & t).sum()), int((~p & ~t).sum()))

# üres-üres: 2x2
tp, fp, fn, tn = conf_counts(np.zeros((2,2), np.uint8),
                             np.zeros((2,2), np.uint8))
iou = 1.0 if (tp+fp+fn) == 0 else tp/(tp+fp+fn)
dice = 1.0 if (2*tp+fp+fn) == 0 else 2*tp/(2*tp+fp+fn)
assert (tp,fp,fn,tn,iou,dice) == (0,0,0,4,1.0,1.0)

# pred minden 1, target 0
tp, fp, fn, tn = conf_counts(np.ones((2,2), np.uint8),
                             np.zeros((2,2), np.uint8))
iou = 1.0 if (tp+fp+fn) == 0 else tp/(tp+fp+fn)
dice = 1.0 if (2*tp+fp+fn) == 0 else 2*tp/(2*tp+fp+fn)
assert (tp,fp,fn,tn,iou,dice) == (0,4,0,0,0.0,0.0)

Az alakellenőrzés az assert pred.shape == target.shape; a maszk uint8, a műveletek bool-lá alakítás után egész összeget adnak.

F4. Az ok a háttérdominancia: 16 pixelből a GT-ben csak 1 az előtér, a pred üres. Ekkor TP=0, FP=0, FN=1, TN=15; accuracy 15/16=0,937515/16=0{,}9375, IoU 0/1=00/1=0, Dice 00. A pontosság a TN tömegétől függ, ezért egy triviális üres predikció is magas értéket kap. A javítás: a döntési metrika legyen IoU vagy Dice, és mindig közöljük a foreground recallt és precisiont; ha a modell üres maszkot ad, a recall 0, ami azonnal jelzi a hibát.

F5. Protokollterv. Kérdés: hogyan változik a befagyasztott szegmentáló modell IoU-ja a GlyphLab corrupt tesztjén? Hipotézis: a zaj és fényességgradiens az előtér határán rontja a maszkot. Megfigyelési egység: scene/view pár; a clean és corrupt image ugyanazon scene/view, ezért a párosítás rögzített. Metrika: Js,vJ_{s,v}; hatás: ds,v=Js,vclean−Js,vcorruptd_{s,v}=J_{s,v}^{\text{clean}}-J_{s,v}^{\text{corrupt}}, scene-átlag DsD_s. Statisztika: D‾=130∑sDs\bar D=\frac1{30}\sum_sD_s. Bootstrap: 1000 ismétlés, osztályonként 10 scene visszatevéssel, 2,5–97,5 percentilis. Korlát: a toy adat nem valós kép; a CI csak a rögzített corrupt előállításra vonatkozik; a modellválasztás a validációs spliten már lezárult.

39.3.12 Rövid összefoglaló

A szegmentáló modell kiértékelése nem egyetlen számon múlik. A TP, FP, FN, TN hibakategóriákból számított IoU és Dice érzékeny az előtérhibára, míg a pixel accuracy háttérdominancia mellett félrevezető. A half-open doboz a lokalizációt, a centroid a tömegközéppont-eltérést méri; utóbbiról beláttuk, hogy a maszkok közös eltolására invariáns. A GlyphLab értékelése scene-szinten aggregál, a clean–corrupt összevetés páros, a bootstrap egysége a scene_id. A korrekt protokoll az IoU/Dice mellett közli a hibakategóriákat és az osztályarányt, így a triviális üres predikció nem kaphat hamisan magas érdemjegyet.

A következő alkalmazás, 39.4. Alkalmazási riport és átadás, az alkalmazási riport és átadás: a most definiált metrikákat és páros intervallumot a modellkártya, a hibakorlátok és a felhasználási szerződés nyelvezetévé alakítja.

Felhasznált források: