Ez az egység a NumPy ndarray objektumán keresztül
rögzíti a skalár, vektor, mátrix és négydimenziós képtensor közötti
mozgás szabályait. A cél nem a NumPy API katalógusa, hanem az, hogy
minden művelet előtt és után egyértelmű legyen a shape, a
dtype és a redukált tengely. Az objektumok alakja a modell
bemeneti és kimeneti szerződésének része 1.1.
Modell, tanulás és felhasználási szerződés; a tévesen értelmezett
alak csendben hibás számolást adhat.
Indító becslés: legyen
alakja (4,),
alakja (4,1). Az x + y kifejezés a
NumPy-értelmezés szerint milyen alakot ad? Becsülje meg, mielőtt tovább
olvas, és jegyezze fel a tippjét. Ha (4,4)-et tippelt, a
broadcasting szabályát már érzi; ha (4,)-et, akkor éppen ez
az egység fogja tisztázni, miért nem az.
ndarray,
dtype, shape és axisDefiníció (ndarray). A NumPy
ndarray homogén, rögzített alakú, többdimenziós tömb:
minden elemének azonos a típusa, az alakot a létrehozás után
megváltoztatni csak új tömb létrehozásával lehet. A memóriában az elemek
lineáris sorrendben tárolódnak; a többdimenziós indexelést a
shape és a hozzá tartozó strides írja le. Az
ndarray.ndim a dimenziók száma, ndarray.size
az elemek darabszáma, ndarray.shape pedig egy tuple,
amelynek
-edik
eleme az
-edik
tengely hossza.
Definíció (dtype). A dtype
az elemi tárolási típus: np.float32,
np.float64, np.int64, np.uint8,
np.bool_ stb. A dtype nem tartalmaz
mértékegységet, és a shape sem; mindkettő dimenzió nélküli
tárolási és szerkezeti információ 1.4. Tanulási
diagnózis és kísérleti napló. A jelentést (például „intenzitás a
intervallumban”) a programozó dokumentálja, nem a tömb hordozza. A könyv
főútja float32 a tanításhoz, mert ez a tipikus CPU/GPU numerikus típus;
az ellenőrzésekhez helyenként float64-et használunk.
Definíció (axis). Az axis
a redukciós műveletek tengelyparamétere. Az axis=0 az első,
axis=1 a második tengely mentén összegez; negatív index a
végétől számol. axis=None az összes elem feletti redukciót
jelenti. Például
esetén axis=0 a batch, axis=1 a csatorna
tengely.
A Python-szintaxis egyetlen pontja, amely itt félreérthető: a
(n,) egyelemű tuple, tehát
hosszú egydimenziós vektor; a (n,1) kétdimenziós
oszlopvektor. A kettő nem ugyanaz az objektum, és a broadcasting
eltérően kezeli őket.
Definíció (broadcasting alakszabálya, [D04] szerinti megfogalmazás). Legyen a két operandus alakja és . A rövidebb alak elé írjunk 1-eseket, amíg a két tuple hossza egyenlő nem lesz, majd jobbról balra igazítsuk a méreteket. Az elemenkénti bináris művelet akkor és csak akkor értelmezett, ha minden igazított pozícióban , vagy , vagy . Az eredmény alakja pozíciónként a nagyobbik méret. A végrehajtáskor az 1 méretű dimenzió virtuálisan ismétlődik az eredmény méretére.
Ez a szabály a NumPy API definíciója, nem tétel; a stride-0 implementáció a [D04] hivatalos leírásában ellenőrizendő. Az alábbi lemma a szabály első, gyakran félreértett következménye.
Lemma (külső összeg). Legyen
alakja (n,) és
alakja (n,1). Ekkor az a + b művelet eredménye
(n,n) alakú, és az eredmény
.
Bizonyítás. A jobbról igazított alakok: esetén — a hiányzó első dimenziót 1-nek tekintjük —, esetén . Az első pozícióban és szerepel, tehát broadcastelhető; a másodikban és , szintén broadcastelhető. Az eredmény alakja . A virtuális ismétlés következménye: a második tengelyen hosszú, ezért a kimenet minden sorában megismétlődik, vagyis az elemhez járul; az első tengelyen hosszú, a második tengelyen 1, tehát minden oszlopban ismétlődik, vagyis az elemhez járul. Az összeadás elemenkénti, így . ∎
A lemma mutatja, hogy a (n,) vektor és a
(n,1) oszlopvektor összeadása nem a kívánt komponensenkénti
összeadás, hanem az összes lehetséges pár összegét tartalmazó mátrix. Ez
a későbbi hibajavító labor központi esete.
Legyen a vizsgált képtensor
ahol az egydimenziós csatornát az áttekinthetőség kedvéért elhagytuk
a leírásból, de a shape (2,1,2,3) és a
dtype float32. A matematikai indexelés 1-től, a Python
0-tól számol; az alábbiakban a Python-tengelyeket használjuk.
A teljes átlag:
amely 0-dimenziós tömb, alakja (). A batch-átlag
axis=0 mentén redukál, tehát a két kép pixelenkénti
átlaga:
alakja (1,2,3). A csatornaátlag a batch, magasság és
szélesség felett átlagol, axis=(0,2,3), eredménye
(1,) és értéke
.
A képenkénti átlag axis=(1,2,3), eredménye
(2,), értékei
,
mert a két kép elemeinek összege 15, illetve 51, egyenként 6 elemmel
osztva.
Vegyük észre a tengelyek szerepét: ugyanaz a
más-más kérdésre más alakú eredményt ad. A modellkódban a
mean tengelyparamétere ezért része a szerződésnek.
A labor egy kis, helyi CPU-s NumPy-szkript. A device
paraméter itt nem értelmes; az izolált uv-projektet a
lockfájl szerint szinkronizáljuk [D01]. A munkakönyvtár
lab/n005, a futtatási parancs:
uv sync
python lab/n005/lab_batch_mean.pyA szkript:
# lab/n005/lab_batch_mean.py
"""Kézi és NumPy batch-átlag, valamint a (n,)+(n,1) hiba javítása."""
import numpy as np
X = np.array([[1.0, 0.0, 2.0],
[0.0, 2.0, 1.0],
[2.0, 1.0, 0.0],
[1.0, 1.0, 2.0]], dtype=np.float32)
# Kézi átlag oszloponként: shape (3,)
manual_mean = np.array([X[:, j].mean() for j in range(X.shape[1])],
dtype=np.float32)
np_mean = X.mean(axis=0)
print("X shape:", X.shape)
print("manual_mean:", manual_mean)
print("np_mean:", np_mean)
# Hiba: (3,) és (3,1) összeadása (3,3) mátrixot ad
x = np.array([1.0, 2.0, 3.0], dtype=np.float32) # (3,)
y = np.array([[10.0], [20.0], [30.0]], dtype=np.float32) # (3,1)
bad = x + y # (3,3) -- csendes, de rossz művelet
good = x + y[:, 0] # (3,) -- explicit 1D vektor
print("bad shape:", bad.shape)
print("good shape:", good.shape, good)A kimenet kézi számolásból származó várt érték, nem futtatott mérés:
X shape: (4, 3)
manual_mean: [1. 1. 1.25]
np_mean: [1. 1. 1.25]
bad shape: (3, 3)
good shape: (3,) [11. 22. 33.]
Az átadandó eredmény: a kézi és a NumPy-átlag egyezése, a
bad és good alakok megkülönböztetése, valamint
a futtatott szkript kimenete. A seed itt nem szükséges, mert az adatok
determinisztikusan vannak megadva; a véletlen adatok generálásakor
viszont a [D02] szerint az azonos seed nem garantál platformok közötti
bitazonosságot, ezért a protokoll a seed mellett a NumPy-verziót is
rögzíti.
F1 (fogalmi, nehézség 1). Magyarázza el, miért nem
felcserélhető a (n,), (n,1) és
(1,n) alak. Konkrétan: legyen
alakja (5,8),
alakja (8,),
alakja (1,8),
alakja (8,1). Mely X+b_i kifejezések
értelmesek, és mi az eredmény alakja?
F2 (levezetési, nehézség 2). Legyen
alakja (3,4),
alakja (4,),
alakja (3,1). A broadcasting szabályból vezesse le, hogy
A+b alakja (3,4) és eleme
;
mutassa meg azt is, hogy b[:,None] + c.T alakja
(4,3) és eleme
.
Mindkét állítást igazolja az alakszabály pozíciónkénti
alkalmazásával.
F3 (implementációs, nehézség 2). Írjon
batch_and_channel_mean(x) függvényt, amely x
alak (N,C,H,W) bemenetre visszaadja a batch-átlagot
((C,H,W)) és a csatornaátlagot ((C,)) float32
típusban. Ellenőrizze a fenti 2×1×2×3 példán az
np.testing.assert_allclose eszközzel; a tűrés
rtol=1e-7, atol=1e-7.
F4 (hibakeresési, nehézség 2). Az alábbi függvény
hibát okoz, amikor a hívó (4,3) kimenetet vár:
def add_bias(X, b):
return X + b # X.shape == (4,3), b.shape == (3,1)Azonosítsa a hiba okát a broadcasting szabály alapján, adja meg a
következményt, javítsa a függvényt úgy, hogy a kimenet
(4,3) legyen, és ellenőrizze egy konkrét tömbön.
F5 (kísérlettervezési, nehézség 3). Tervezzen kis
kísérletet annak eldöntésére, hogy egy adott
preprocess(images) függvény a csatornaátlag kivonását
batch-szintű statisztikával végzi-e, vagy rögzített, előre kiszámolt
átlaggal. Adja meg a kérdést, a két összehasonlítandó útvonalat, a
használt GlyphLab-állományt, a metrikát és a következtetés érvényességi
korlátját.
F1 megoldás. A (n,) egydimenziós, a
(n,1) kétdimenziós oszlop-, a (1,n)
kétdimenziós sorvektor.
:
jobbról igazítva
alakja (1,8), az első pozíció
és
broadcastelhető, a második
és
egyenlő, eredmény (5,8).
:
alakok (5,8) és (1,8), ugyancsak
(5,8).
:
alakok (5,8) és (8,1); jobbról a második
pozíció
és
broadcastelhető, de az első pozíció
és
sem nem egyenlő, sem nem 1, tehát ValueError. Az
oszlopvektor-bias nem adható hozzá soronként; csak akkor lenne jó, ha
első dimenziója is 8 lenne.
F2 megoldás. A+b: a b
alakja (4,), helyettesítve (1,4). Az első
pozíció
és
→ broadcast, a második
és
→ egyenlő. Az eredmény (3,4). Az
kimeneti elemhez
járul;
stride-0 első dimenziója miatt minden sorban ugyanaz a
ismétlődik, tehát a hozzájárulás
.
b[:,None] + c.T: a bal oldal alakja (4,1), a
jobb oldalé (1,3). Az első pozíció
és
,
a második
és
,
mindkettő broadcastelhető; eredmény (4,3). A bal oldal a
második tengelyen ismétlődik, ezért eleme az
pontban
;
a jobb oldal az első tengelyen ismétlődik, ezért
;
az összeg
.
F3 megoldás.
import numpy as np
def batch_and_channel_mean(x: np.ndarray):
assert x.ndim == 4, f"Várt shape (N,C,H,W), kapott: {x.shape}"
batch_mean = x.mean(axis=0) # (C,H,W)
channel_mean = x.mean(axis=(0, 2, 3)) # (C,)
return batch_mean.astype(np.float32), channel_mean.astype(np.float32)
X = np.arange(12, dtype=np.float32).reshape(2, 1, 2, 3)
batch, channel = batch_and_channel_mean(X)
expected_batch = np.array([[[3, 4, 5], [6, 7, 8]]], dtype=np.float32)
expected_channel = np.array([5.5], dtype=np.float32)
np.testing.assert_allclose(batch, expected_batch, rtol=1e-7, atol=1e-7)
np.testing.assert_allclose(channel, expected_channel, rtol=1e-7, atol=1e-7)
print("OK", batch.shape, channel.shape) # OK (1, 2, 3) (1,)A szélsőeset: esetén a batch-átlag maga a kép, a csatornaátlag a képenkénti átlaggal egyezik.
F4 megoldás. A hiba oka: (4,3) és
(3,1) jobbról igazítva az első pozícióban
és
áll, amelyek sem egyenlők, sem 1, tehát a broadcasting szabály szerint a
művelet nem értelmezett; a NumPy ValueError-t dob. A
következmény az, hogy a hívó egyáltalán nem kap kimenetet. A javítás:
alakítsuk a bias-t egydimenzióssá, például b.reshape(-1)
vagy b[:, 0]; ekkor az alak (3,), a
broadcasting (1,3)-ként értelmezi, és az eredmény
(4,3). Ellenőrzés:
def add_bias(X, b):
return X + b.reshape(-1)
X = np.ones((4, 3), dtype=np.float32)
b = np.array([[1.0], [2.0], [3.0]], dtype=np.float32)
out = add_bias(X, b)
assert out.shape == (4, 3)
np.testing.assert_allclose(out[0], [2.0, 3.0, 4.0], rtol=1e-7, atol=1e-7)F5 megoldásminta. Kérdés: a preprocess
a csatornaátlag kivonásakor a batch statisztikáját használja-e, ami
eltérő tanítási és inference-viselkedést okozhat. Protokoll: a GlyphLab
data/images.npy állomány (360,1,24,24) alakú
float32 tömbjéből vegyünk egy rögzített train batch-et, számítsuk ki a
csatornaátlagot, és futtassuk a preprocess első útvonalát
ezzel a rögzített átlaggal. A második útvonal ugyanarra a bemenetre
batch-szintű átlagot számol, majd kivon. Metrika: a két kimenet
maximális abszolút eltérése. Ha az eltérés 0, a függvény nem
batch-függő; ha pozitív, a batch-szintű normalizálás jelen van.
Érvényességi korlát: a mérés egy implementációs tulajdonságot mutat, nem
a modellminőséget; a tanítás és kiértékelés közötti eltérés hatását
külön, rögzített protokollú kísérletben kell mérni.
A legveszélyesebb eset nem az, amikor a broadcasting hibát dob, hanem
amikor lefut, de más eredményt ad. A fenti lemmában x
(3,) és y (3,1) összeadása
(3,3) mátrixot hoz létre; ha a kód egy (3,)
vektort vár, a hiba csak akkor derül ki, amikor egy későbbi függvény
rossz alakot kap, vagy ami rosszabb, a modell csendben rossz értékekkel
tanul.
Ellenpélda a „mindig működik, tehát jó” érvelésre: az
batch és a
bias összeadása soronkénti torzítást ad, ahogyan az F4 javítása mutatja;
ha viszont valaki tévedésből a bias-t (4,1) alakban adja
meg, a broadcasting csak akkor fut le, ha a batch mérete éppen 3, és az
eredmény (4,3) helyett (4,3)? Nem:
(4,3) és (4,1) kompatibilis, mert a második
pozíció
és
;
az eredmény (4,3), és a bias oszloponként ismétlődik, ami a
szándékkal ellentétes lehet, ha a bias soronkénti volt. A shape-assert
és a kimenet explicit ellenőrzése ezért nem adminisztratív teher, hanem
a modell bemeneti szerződésének része 1.1. Modell,
tanulás és felhasználási szerződés.
A nézet/másolat különbsége is hasonló csendes hibát okozhat: az
alapszeletelés nézetet (view) ad, amely osztozik a
memórián, ezért
x = np.arange(6, dtype=np.float32).reshape(2, 3)
row = x[0] # view
row[0] = -1.0 # x[0,0] is megváltozikHa független másolatra van szükség, explicit x.copy()
kell. A Python-listák szeletelése ezzel szemben másolatot ad; a két
viselkedés összekeverése nehezen lokalizálható hibákhoz vezet.
np.ones((3,1)) + np.arange(4)
kifejezésnek? Válasz: ValueError, mert a jobbról igazított
első méret
és
nem kompatibilis.(2,1,2,3). Mi az alakja a X.mean(axis=0)
és a X.mean(axis=1) kifejezésnek? Válasz:
(1,2,3), illetve (2,2,3).x[0] NumPy esetén nézet vagy másolat? Válasz: nézet,
hacsak a kód nem készít explicit másolatot.Visszahívás: az 1.1. Modell, tanulás és felhasználási szerződés alfejezetben a modell bemenete batch, a hipotézisosztály pedig a paraméterezett leképezések halmaza; a shape mindkettő szerződésének része. Az 1.4. Tanulási diagnózis és kísérleti napló diagnózisban a tensor alakját és dtype-ját különválasztottuk: az alak a tengelyek hossza, a dtype a tárolás típusa. A seed a generátor kezdőállapota, a futásazonosító a kísérleti napló kulcsa; ebben a laborban determinisztikus adat miatt seed nem kellett, de a jövőbeli véletlen adatgeneráláshoz a protokoll rögzíti [D02].
Az ndarray homogén, rögzített alakú többdimenziós tömb;
a dtype a tárolási típust, a shape a
tengelyhosszakat, az axis a redukciós irányt adja meg. A
broadcasting alakszabálya jobbról balra igazítva engedi meg az 1 méretű
dimenziók kiterjesztését; a (n,) és (n,1)
alakok összeadása ezért (n,n) mátrixot ad, nem vektort. A
labor kézi és NumPy batch-átlagot számolt, majd a téves broadcastingból
származó hibát explicit átalakítással javította. A nézet osztozik a
memórián, a másolat független; alapszeletelésnél ez eltér a Python-lista
viselkedésétől. A shape-ellenőrzés a modell bemeneti szerződésének része
1.1. Modell, tanulás és felhasználási
szerződés.
A 2.2. Lebegőpont és stabil elemi számítás egység a lebegőpontos ábrázolás és a stabil elemi számítások felé lép: a float32 kerekítés, a katasztrofális törlés és a log-sum-exp alapjai ott épülnek erre a tensoralapra.