Az affin rétegek eddig alakú bemenetet kaptak, ahol egy megfigyelés egyetlen vektorsor. A kép ezzel szemben két térbeli koordinátával és csatornákkal rendelkező diszkrét jel. Ennek az egységnek az a célja, hogy a képértéket, a színcsatornát, a koordinátát és a batchelemet pontosan elválassza, mert a következő konvolúciós egységekben minden levezetés ezen a tengelyszerződésen áll.
Előismeret: NumPy tömb és tengelyfogalom a 2.1. Python, NumPy és tenzoralak alfejezetből, mátrixindexelés a 3.2. Mátrixszorzás és affín réteg alfejezetből, valamint a 11.4. Egy teljes PyTorch tanítási iteráció alfejezetben rögzített input-szerződés: a modell első rétege a dokumentált alakkal és skálával dolgozik.
Induló becslés: egy GlyphLab-kép alakja . Egy kép hány skalárt tárol, és egy 360 képből álló batch mennyit? Hány koordináta, hány csatorna és hány megfigyelés van ebben a batchben? A választ a definíciók után pontos alakkal indokoljuk.
Definíció (mintavételi rács). Rögzített magasság és szélesség mellett a mintavételi rács (sampling grid) az indexpárok
halmaza. A pár a sor- és oszlopkoordináta; a könyv tömbkiíratási konvenciója szerint lefelé, jobbra növekszik. A rács pontot tartalmaz. A koordináta tehát index, nem pixelérték.
Definíció (digitális kép). Egy csatornás digitális kép egy
tensor, ahol a csatorna intenzitása a rácspontban. A index a csatornakoordináta, a térbeli koordináta, az skalár pedig a képérték.
Definíció (pixel). Ebben a könyvben a pixel a rács egy pontjához tartozó csatornaérték-vektor: a pontban
Grayscale képnél ez egyetlen skalár; RGB esetén három számból álló vektor.
Definíció (batch). Ha képet egyszerre tárolunk, a batch tenzor
és a batchelem indexe. Az tehát megfigyelést azonosít, a csatornát, a pár térbeli pozíciót. A célcímke, a maszk vagy a doboz nem a képtensor része; a GlyphLab-szerződésben ezek külön tömbök, amelyek a térbeli rácsot megosztják a képpel.
Definíció (NCHW és NHWC). A tengelyek szemantikáját két elterjedt sorrend rögzíti:
| Elrendezés | Tengelysorrend | Gyorsan változó index | A könyv főútja |
|---|---|---|---|
| NCHW | batch, channel, height, width | PyTorch Conv2d, [D10] |
|
| NHWC | batch, height, width, channel | gyakori NumPy/képi formátum |
A két elrendezés ugyanazt a képet tárolhatja, de a memóriában másképp
rendezi. A PyTorch hivatalos Conv2d-dokumentációja NCHW
bemenetet ír elő; a 13.2. Konvolúció és
cross-correlation alfejezetben bevezetett konvolúciós réteg is ezt
használja [D10].
Definíció (grayscale és RGB). A eset grayscale: minden pixel egy intenzitásskalár. A eset színkép; a jelen könyvben a sorrend a konvenció. Ez nem a könyvtár automatikus ténye: az OpenCV például BGR sorrendet használ, ezért külső modelleknél a csatornasorrendet mindig a forrás dokumentációjából kell rögzíteni.
Állítás 1 (NCHW offset-képlet és index-tartomány). Legyen és C-sorrendű, összefüggő tömbben tárolva. A lineáris elemindexre
Ez a leképezés bijekció az indexhalmaz és között, ahol .
Bizonyítás. (i) A C-sorrend definíciója szerint az utolsó tengely stride-ja , tehát lépésenként -et; stride-ja ; stride-ja ; stride-ja . A kezdőcímtől mért elemeltolás ezért a fenti lineáris kombináció.
Injektivitás. Ha , akkor mindkét oldal -vel vett maradéka , illetve (a többi tag többszöröse), így . Kivonás és -vel osztás után ; a -val vett maradékból , majd -vel való osztás után a maradékból , végül .
Szürjektivitás. Tetszőleges esetén az euklideszi osztások egymás utáni alkalmazása
érvényes indexeket ad, és a képletbe visszahelyettesítve -et kapunk. Így minden lineáris index pontosan egyszer keletkezik.
Megjegyzés (ellenőrzési típus). Az Állítás 1 tisztán egész-aritmetikai azonosság, nem empirikus állítás; ellenőrzése kódban egész indexszámítással történik. A képlet csak C-sorrendű, összefüggő tárolásra érvényes. Egy tükrözött vagy transzponált nézet stride-jai eltérhetnek (akár negatívak is lehetnek); az általános alak a tényleges stride-okkal.
Állítás 2 (NCHW–NHWC transzpozíció és inverze). Legyen a tengelypermutáció. A tömb elemeire
A permutáció inverz, tehát ; a kettős transzponálás az identitás. Következésképp , vagyis a pixel csatornavektora megmarad.
Bizonyítás. Ez a tengelypermutáció definíciójának közvetlen elemenkénti ellenőrzése: a tömb pozíciójába a permutáció pontosan az tömb elemét írja; -et alkalmazva az elem az eredeti pozícióba kerül vissza. A kétforma tehát ugyanazt a képfüggvényt reprezentálja, csak a csatornatengely helye változik.
Állítás 3 (uint8 tárolás és a „fehér = 1” skálaválasztás). Legyen normalizált intenzitás és a -bites tárolási érték. A visszaolvasás szigorúan monoton bijekció a halmazra; speciálisan a maximális tárolási érték visszaolvasva . A -tal való osztás ezzel szemben -ot adna, ezért a „0 fekete, 1 maximális intenzitás” konvenció a osztót rögzíti.
Bizonyítás. esetén . Ha , akkor . A leképezés tehát megőrzi a rendezést és injektív, két elemű halmaz között pedig bijektív. A második állítás a egyenlőtlenség közvetlen következménye.
A GyphLab-képek közvetlenül float32 [0,1] tartományban
tároltak, ezért ott nincs kvantálási lépés. Dataset-szintű
standardizáláskor a
csatornánkénti affin transzformáció csak
mellett értelmes; a per-image min-max normalizálás
konstans képen
()
nullával osztana, tehát ilyen bemenetre explicit leágazást kell
definiálni.
Tekintsünk egy alakú, azaz grayscale képet átlóval:
Az NCHW offset-képlet miatt . A nemnulla elemek lineáris indexei: , , . A C-sorrendű, lapított tömbben tehát a a , a az , a a pozícióban áll, a többi elem nulla.
RGB példa , csatornasorrend :
A pixel vektora . NCHW lapításban a elem címe ; az NHWC transzponáltban , tehát ugyanannak a pixelnek a két elrendezés más lineáris címet ad.
# munkakönyvtár: lab/n049_layout
import numpy as np
X = np.zeros((1, 3, 2, 2), dtype=np.float32) # NCHW, [0,1]
X[0, 0, 0, 0], X[0, 0, 0, 1] = 0.1, 0.2 # R sor
X[0, 0, 1, 0], X[0, 0, 1, 1] = 0.3, 0.4
X[0, 1, 0, 0], X[0, 1, 0, 1] = 0.5, 0.6 # G sor
X[0, 1, 1, 0], X[0, 1, 1, 1] = 0.7, 0.8
X[0, 2] = [[0.9, 1.0], [1.0, 1.0]] # B csatorna, explicit mátrix
Y = np.transpose(X, (0, 2, 3, 1)) # NCHW -> NHWC
print("NCHW:", X.shape, "NHWC:", Y.shape)
print("pixel (0,1):", Y[0, 0, 1, :]) # várt: [0.2 0.6 1. ]
# ugyanazon pixel három csatornája a transzponálás előtt és után
assert np.array_equal(X[0, :, 0, 1], Y[0, 0, 1, :])
print("G csatorna (0,1) NCHW lapított címe:", (1*2 + 0)*2 + 1) # 5
print("G csatorna (0,1) NHWC lapított címe:", ((0*2 + 0)*2 + 1)*3 + 1) # 4A lab-profilban (NumPy 2.3.5) mért kimenet:
NCHW: (1, 3, 2, 2) NHWC: (1, 2, 2, 3),
pixel (0,1): [0.2 0.6 1. ], a két lapított cím
és
;
az assert igazolja, hogy a pixel csatornavektora a
transzponálás előtt és után azonos.
A labor egy kis, osztálycélú mintára hasonlító mesterséges képpárt rajzol, majd NCHW és NHWC között transzponál és visszafelé is ellenőriz. Ez a GlyphLab-labor előtti gyakorlólépés; nem modellkísérlet.
# lab/n049_layout/lab_n049.py
import numpy as np
from pathlib import Path
out = Path("lab/n049_layout")
out.mkdir(parents=True, exist_ok=True)
RNG_SEED = 20261020
H, W = 6, 7
rng = np.random.default_rng(RNG_SEED) # csak a vágási tervet rögzíti; jelen példa determinisztikus
X = np.zeros((2, 1, H, W), dtype=np.float32) # NCHW, [0,1], CPU
X[0, 0, :, 2] = 1.0 # 0. kép: függőleges vonal
X[1, 0, 3, :] = np.array([0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 0.8],
dtype=np.float32) # 1. kép: vízszintes sáv
assert X.shape == (2, 1, H, W)
assert X.dtype == np.float32
assert X.min() >= 0.0 and X.max() <= 1.0
Y = np.transpose(X, (0, 2, 3, 1)) # NHWC nézet
assert Y.shape == (2, H, W, 1)
assert np.shares_memory(X, Y) # transzponálás: nézet, nem másolat
# NCHW offset kézzel: off(n,0,h,w)=((n*1+0)*H+h)*W+w
def off(n, h, w):
return ((n * 1 + 0) * H + h) * W + w
flat = X.reshape(-1)
assert flat[off(1, 3, 4)] == X[1, 0, 3, 4] # lapított cím ellenőrzése
assert Y[1, 3, 4, 0] == X[1, 0, 3, 4] # ugyanaz a pixel NHWC-ben
X2 = np.transpose(Y, (0, 3, 1, 2))
assert np.array_equal(X, X2) # kettős transzponálás = identitás
np.savez(out / "mini_images.npz", x_nchw=X, x_nhwc=Y, seed=np.int64(RNG_SEED))
print("NCHW:", X.shape, X.dtype, "min/max:", X.min(), X.max())
print("NHWC:", Y.shape)
print("ellenőrzött pixel X[1,0,3,4] =", X[1, 0, 3, 4])
print("mentve:", out / "mini_images.npz")Futtatás a könyv gyökérkönyvtárából:
python lab/n049_layout/lab_n049.pyA kódból számított, jelölt elvárt kimenet — nem egy dokumentált mérés jegyzőkönyve:
NCHW: (2, 1, 6, 7) float32 min/max: 0.0 1.0
NHWC: (2, 6, 7, 1)
ellenőrzött pixel X[1,0,3,4] = 1.0
mentve: lab/n049_layout/mini_images.npz
Értelmezés: a transzponálás nem változtatta a pixelértékeket, csak a
csatornatengely helyét. A np.shares_memory igaz volta azt
mutatja, hogy a két elrendezés ugyanazon az adaton osztozik; másolni
csak akkor kell, ha összefüggő NHWC-tömbre van szükség. A
mini_images.npz visszatöltése után a
np.array_equal összevetés a lemezre írás-újraolvasás
kontrollja is.
F1 (fogalmi; nehézség 1). A fenti RGB kézi példában . Írja fel a teljes NHWC tömböt elemenként, és adja meg, hogy a indexeiben hol található a koordinátájú, csatornájú érték. Indokolja, miért nem veszít információt a transzpozíció, és adjon rövid magyarázatot, miért nem azonos mégis a két lapított memóriakép.
F2 (levezetési; nehézség 2). Legyen . Az logikai pixelre vezesse le az NCHW és az NHWC C-sorrendű offset-képletet a stride-okból, és mutassa meg, hogy a két lineáris cím , illetve . Bizonyítsa be a két képlet különbségéből, hogy a két lapított sorrend akkor és csak akkor azonos minden indexre, ha vagy .
F3 (implementációs; nehézség 2). Írjon futtatható
függvényt nchw_to_nhwc_ellenorzott(x) néven, amely elfogad
egy 4 dimenziós float32/view float64 NumPy-tömböt,
ValueError-t dob rossz dimenziószámra,
TypeError-t egész dtype-ra, majd
np.transpose(x,(0,2,3,1)) módon NHWC-t ad. Adjon seedelt
véletlentesz tet a
alakkal: shape, egy ismert pixel, osztott memória, visszatranszponálás
(
abszolút hibával) és negatív uint8 teszt.
F4 (hibakeresési; nehézség 2). Az alábbi kód egy
grayscale glyph.png-t ad egy NCHW modellnek. Hol a hiba, mi
a következménye, és hogyan javítható?
from PIL import Image
import numpy as np, torch
x = np.asarray(Image.open("glyph.png")).astype(np.float32) # (H, W) szürke tömb
logits = model(torch.as_tensor(x)) # modell 4D (N,C,H,W)-t várF5 (kísérleti/transzfer; nehézség 3). Egy
GlyphLab-ra tanított osztályozót külső, saját rajzú vonalképekre akarunk
átvinni. Tervezzen protokollt annak eldöntésére, hogy a rögzített
/255 skálázás elegendő-e, vagy a külső képek változó
hátterén per-image min-max normalizálás kell. Adja meg: kérdés,
hipotézis, adat és forrás, megfigyelési egység, split, baseline, változó
tényező, metrika, erőforrás és a következtetés érvényességi határa. Ne
igényeljen meglévő lefutást; tervet és buktatókat várunk.
F1. A transzponált elemei . Tehát
A csatorna értéke -ben van: az első két térbeli index a koordináta, az utolsó index a csatorna. Az Állítás 2 szerint a két forma között invertálható index-permutáció áll, ezért adat nem vész el. A lapított memóriakép mégis eltér: az NCHW-ben a csatornastride , az NHWC-ben , ezért az elem lineáris címe , illetve .
F2. NCHW: . NHWC: . A különbség általánosan
(mert ). Ez pontosan akkor nulla minden indexre, ha minden -re. esetén és , így . Ha , válasszuk : ekkor , tehát szükséges; ez az eset valóban nullát ad. Ezzel az „akkor és csak akkor” állítás kész. A megadott pontban , azaz .
F3. Futtatható minta; a kimenet jelölt, a futás a tanuló gépén végzendő.
import numpy as np
def nchw_to_nhwc_ellenorzott(x):
if x.ndim != 4:
raise ValueError(f"négy dimenzió kell, kapott {x.ndim}")
if x.dtype not in (np.float32, np.float64):
raise TypeError(f"float32/float64 kell, kapott {x.dtype}")
return np.transpose(x, (0, 2, 3, 1))
rng = np.random.default_rng(0)
x = rng.random((2, 3, 4, 5)).astype(np.float32)
y = nchw_to_nhwc_ellenorzott(x)
assert y.shape == (2, 4, 5, 3)
assert np.array_equal(y[1, 0, 2, :], x[1, :, 0, 2])
assert np.shares_memory(x, y)
vissza = np.transpose(y, (0, 3, 1, 2))
max_abs = float(np.max(np.abs(x - vissza)))
assert max_abs == 0.0
try:
nchw_to_nhwc_ellenorzott(np.zeros((1, 3, 2, 2), dtype=np.uint8))
except TypeError:
print("uint8 elutasítva: explicit float dtype-kérés")
print(f"Rendben: shape {y.shape}, dtype {y.dtype}, max abs hiba {max_abs:.1e}")Elvárt kimenet:
uint8 elutasítva: explicit float dtype-kérés, majd
Rendben: shape (2, 4, 5, 3), dtype float32, max abs hiba 0.0e+00.
A nulla hiba nem tolerancia-kérdés: a transzponálás bitazonos másolatot
ad, mert csak indexeket cserél, számot nem.
F4. Az Image.open(...) grayscale PNG-re
alakú tömböt ad. A Conv2d csak 3D, csatornás batch nélküli
vagy 4D
bemenetet fogad el; a kétdimenziós bemenetet elutasítja
(RuntimeError: Expected 3D (unbatched) or 4D (batched) input to conv2d, but got input of size: [24, 24]),
tehát ez a hiba nem csendes. A javítás: adjunk batch- és
csatornadimenziót, és alkalmazzuk a [0,1] skálakonvenciót.
x = np.asarray(Image.open("glyph.png")).astype(np.float32) # (H, W)
t = torch.from_numpy(x / 255.0).unsqueeze(0).unsqueeze(0) # (1, 1, H, W)
assert t.shape == (1, 1, x.shape[0], x.shape[1])
assert torch.equal(torch.as_tensor(np.float32(x[0, 0] / 255.0)), t[0, 0, 0, 0])
logits = model(t)A valóban csendes hiba az azonos dimenziószámú
tengelycsere. Ha a
tensort valahol x.transpose(-1, -2) alakban, a
és
felcserélésével adjuk a modellnek, az alak továbbra is négydimenziós, a
Conv2d hiba nélkül lefut, a kép viszont a főátlójára
tükröződik: egy függőleges vonalból vízszintes lesz, a GlyphLabben tehát
a 0 és 1 címke jelentése felcserélődik. Ezt csak tartalmi kontroll szűri
ki, például egy ismert orientációjú tesztkép predikciója, vagy egy pixel
helyének összevetése a transzformáció előtt és után.
Színes fájl esetén előbb rögzíteni kell a csatornasorrendet, például
Image.open(p).convert("RGB") után
permute(2,0,1) az (R,G,B)→NCHW átálláshoz; az OpenCV BGR-t
ad, ott a permutáció nem felcserélhető.
F5. Egy lehetséges terv. Kérdés: változó
hátterű külső vonalképeken a rögzített /255 baseline-hoz
képest a per-image min-max javítja-e a validációs pontosságot, és nem
okoz-e degenerált esetet? Hipotézis: a per-image transzformáció
erősítheti a háttérzajt, konstans foltokon pedig osztás előtt leágazás
kell. Adat: 60 saját, annotált külső kép (a GlyphLab-laborból
származó split nem módosítható); megfigyelési egység scene_id,
class-stratified split. Baseline: modell rögzített, input
/255, CPU. Változó: csak a preprocessing lépés (a
modell súlya és seed rögzített). Metrika: val accuracy; a
bizonytalanság per-scene bootstrap CI, nem képenkénti.
Erőforrás: CPU, kis batch. Következtetési határ: az
eredmény erre a saját külső rajztartományra érvényes; egyetlen seed nem
általánosítás, és a külső val spliten tilos a modellt újrahangolni;
konstans képek arányát a jelentésben fel kell tüntetni.
Tévhit: a konstanssal való átskálázás csak „mértékegységet” cserél, ezért nem változtatja a döntést. Ez akkor igaz, ha a modell paramétereit a skálának megfelelően értelmezzük; egy fix, [0,1] bemenetre tanított modellnél nem.
Ellenpélda (számított, nem mért). Legyen , , bemenet . Az affin pontszám . Ugyanezt a képet nyers skálán tárolva , és . Egy -döntő osztályozó előjele negatívból pozitívba vált, tehát a skálaváltoztatás pusztán az adatbeadásnál megváltoztatja a modell inputjelentését. Ugyanígy az NCHW–NHWC csere nem no-op: az Állítás 2 megőrzi a pixelvektort, de a lapított memóriaképet átrendezi.
Önellenőrző lista. Minden tömbhöz felírtam-e az
alakot, a dtype-ot és a skálakorlátot? A kettős transzponálást
np.array_equal-rel ellenőriztem? A lapított offset-képletet
egész indexszámítással teszteltem, nem pixelérték-heurisztikával? A
normalizálásnál kezeltem a konstans kép nullosztóját?
Visszahívás. A 4.4. I. ellenőrző
pont: matematika és kód megfelelése alfejezetből: numerikus egyezést
dtype-, shape- és hibahatár-megadással ellenőrzünk; jelen egységben a
transzpozíció bitazonos, ezért a hiba pontosan
.
A 11.4. Egy teljes PyTorch tanítási iteráció
alfejezetből: a modell első rétegének in_channels
várakozása és a betöltött kép csatornatengelye ugyanaz a szerződés; a
dimenziórendezési hiba gyakran nem dob kivételt, hanem csendesen
félreértelmezi a koordinátákat.
A mintavételi rács a indexhalmaz; a pixel az egy rácsponthoz tartozó csatornavektor, az intenzitás a konvenció szerinti skalár. Az NCHW tensor indexeit , az NHWC-ét azonosítja; a permutáció invertálható, ezért adat nem vész, de a lineáris memóriacím megváltozik. Bizonyítottuk az NCHW C-sorrendű offset-képletét és bijektivitását, a kettős transzponálás identitását, valamint a skála monoton voltát. A labor kis mesterséges képpáron a két elrendezést alakkal, osztott memóriával és pixel-egyezéssel ellenőrzi.
A 13.2. Konvolúció és cross-correlation a kernelmegfordítás nélküli cross-correlationt és a matematikai konvolúciót vezeti be ugyanezen a rácson; az itt rögzített NCHW-indexelés a súly- és kimeneti indexek levezetésének alapja.
lab/ADATOK.md,
data/manifest.json; az images.npy alakja
float32, értékkészlete [0,1].