Ebben az alfejezetben a nn.Conv2d két korlátozott
rokonát definiáljuk: a depthwise (csatornánkénti,
mélységi) és a pointwise
(-es,
pontonkénti) réteget, majd belőlük a depthwise
separable (csatornaszétválasztott) blokkot. A cél az, hogy a
teljes konvolúció egyszerre végzett térbeli és csatornakeverését
szétválasszuk, és a paraméter-, illetve MAC-költséget pontosan,
meghatározott alakokra kiszámoljuk. A szükséges előismeret a 14.4. Csoportos és depthwise konvolúció
alfejezetből a groups paraméter értelmezése és oszthatósági
feltétele, a 17.4. Architektúra összehasonlítása
kontrollált keretben alfejezetből a költségkeretek egyeztetésének
szükségessége, a 15.1. Súly- és biasgradiens
alfejezetből a súlymegosztásból következő gradiens-összegzés. A
[D10] dokumentáció szerint a PyTorch
Conv2d térbeli cross-correlationt számol
kernelmegfordítás nélkül; a depthwise és pointwise változatok is ezt
teszik, tehát a „konvolúció” elnevezés ellenére ebben a fejezetben végig
cross-correlationt definiálunk.
Induló becslés. Legyen , , , stride 1, padding 0, bemenet. A teljes réteg súlyszáma . A separable blokk depthwise súlya , pointwise súlya , összesen , tehát a súlyarány . A levezetés után látni fogjuk, hogy ez nem véletlen: bias nélkül az arány mindig .
Legyen a bemeneti képbatch , NCHW elrendezésben.
1. definíció (depthwise konvolúció). A depthwise
konvolúció olyan cross-correlation, amelyben groups = C_in,
tehát minden bemeneti csatornához külön csatornacsoport tartozik. A
kernelsúly alakja
ahol a depthwise multiplier: egy bemeneti csatornához darab kimeneti szűrő tartozik. A kimeneti csatornák száma emiatt . Zéró alapú indexeléssel a kimenetet jelöli, és a kimeneti csatornához egyértelműen tartozik és . Adott stride , zero padding és dilation mellett
azzal a konvencióval, hogy a
indexen kívüli bemenet nulla. Az
esetben
,
tehát egy bemeneti csatornából pontosan egy köztes csatorna lesz. A
depthwise groups=C_in beállítás miatt az oszthatósági
feltétel automatikusan teljesül, a gyakorlatban azonban a PyTorch a
groups értéket csak akkor fogadja el, ha
és
egyaránt osztható groups értékkel; itt
ezt garantálja.
2. definíció (pointwise konvolúció). A pointwise
konvolúció
-es
kernelű, groups=1 csatornakeverő cross-correlation:
Mivel a kernel térbeli kiterjedése 1, stride 1 és padding 0 esetén a térbeli felbontás változatlan: a pointwise csak az egyazon pozícióban lévő csatornákat keveri (channel mixing). A teljes konvolúció ezzel szemben minden kimeneti skalárhoz az összes bemeneti csatornát és az összes kernelpozíciót egyszerre használja.
3. definíció (depthwise separable blokk). A depthwise separable blokk a két réteg kompozíciója:
A térbeli szűrés és a csatornakeverés tehát szekvenciálisan, külön operátorokkal történik. A „separable” szó itt nem a matematikai tenzorszorzat-szeparálhatóságot jelenti, hanem a térbeli és csatorna-feladat szétválasztását; ezt a fogalmi különbséget az F1 feladat vizsgálja.
4. állítás (saját levezetés, elemi leszámlálás).
Legyen
,
,
stride
,
padding
,
dilation
,
és tegyük fel, hogy a teljes konvolúció (groups=1, kimeneti
csatornaszám
)
és a separable blokk
(
depthwise + pointwise) azonos
térbeli kimenetet ad. A MAC definíciója: egy kimeneti skalárhoz tartozó
egy szorzás-plusz-akkumulálás; a bias-hozzáadás nem MAC. Ekkor bias
nélkül a súlyparaméterek aránya, és mindig a MAC-ek aránya
Bizonyítás. A teljes réteg egyetlen kimeneti skalárjához súly–bemenet szorzat tartozik, tehát
A depthwise réteg () kimeneti skalárjaihoz bemeneti csatorna tartozik, tehát egy kimeneti skalár MAC-et igényel; a kimeneti skalárok száma , ezért
A pointwise réteg egy kimeneti skalárja szorzatot igényel, a kimeneti skalárok száma , így
A separable blokk MAC-je a kettő összege, mert a pointwise a depthwise kimenetén dolgozik, és a két réteg költsége additív:
A bias nélküli súlyparaméterekre ugyanez a három szorzat jelenik meg: , , , tehát az arány azonos.
Megjegyzések.
a különbség . Például , esetén a két érték és . Aki paraméterköltséget közöl, mondja meg, számol-e bias-t. 2. Multiplier esetén -re , így -szeres, a pointwise pedig bemeneti csatornán dolgozik, ezért
Példánkban , , , , , , , , ezért .
| Operátor | Súly+bias | MAC |
|---|---|---|
Teljes Conv2d(3,4,3) |
||
Depthwise Conv2d(3,3,3,groups=3) |
||
Pointwise Conv2d(3,4,1) |
||
| Separable összeg |
Bias nélkül a súlyarány , a MAC-arány , pontosan . A bias-t is tartalmazó paraméterarány , tehát a bias elhanyagolása itt nem ártalmatlan.
A labor egyetlen függvénnyel számolja ki tetszőleges
nn.Conv2d paraméter- és MAC-költségét a fenti képletekből,
és a bemeneti térbeli méretből. A kód a lab/n073_lab.py
fájlba kerül; a gyökérkönyvtár a fejezet lab mappája.
# lab/n073_lab.py
# Futtatás: python n073_lab.py
# CPU, float32; a kimenet a képletekből számolt kontroll, nem futásidő-mérés.
import math
import torch
from torch import nn
def out_len(L, p, d, k, s):
return math.floor((L + 2*p - d*(k-1) - 1)/s) + 1
def count_conv(conv, N, H, W):
# Csak forward cross-correlation Conv2d; transposed conv nem tartozik ide.
assert conv.in_channels % conv.groups == 0
cin_per_group = conv.in_channels // conv.groups
Ho = out_len(H, conv.padding[0], conv.dilation[0], conv.kernel_size[0], conv.stride[0])
Wo = out_len(W, conv.padding[1], conv.dilation[1], conv.kernel_size[1], conv.stride[1])
params = sum(p.numel() for p in conv.parameters()) # súly + bias
mac = N * conv.out_channels * Ho * Wo * cin_per_group * conv.kernel_size[0] * conv.kernel_size[1]
return params, mac, (Ho, Wo)
device = torch.device("cpu")
dtype = torch.float32
full = nn.Conv2d(3, 4, 3, stride=1, padding=0, dilation=1, groups=1, bias=True).to(device)
dw = nn.Conv2d(3, 3, 3, stride=1, padding=0, dilation=1, groups=3, bias=True).to(device)
pw = nn.Conv2d(3, 4, 1, stride=1, padding=0, dilation=1, groups=1, bias=True).to(device)
pF, mF, shapeF = count_conv(full, 1, 5, 5)
pD, mD, _ = count_conv(dw, 1, 5, 5)
pP, mP, _ = count_conv(pw, 1, 5, 5)
print("full ", pF, mF, shapeF)
print("dw ", pD, mD)
print("pw ", pP, mP)
print("sep ossz ", pD+pP, mD+mP)
print("MAC arany ", (mD+mP)/mF)
print("sulyarany bias nelkul ", (3*9 + 4*3)/(4*3*9))
with torch.no_grad():
Y = pw(dw(torch.zeros(1, 3, 5, 5, dtype=dtype, device=device)))
print("output shape:", tuple(Y.shape))Várható, képletből számolt kimenet (a kézirat ezt nem futtatta; a tanulólabor futtatja és jegyzi a tényleges értékeket):
full (112, 972, (3, 3))
dw (30, 243)
pw (16, 108)
sep ossz (46, 351)
MAC arany 0.3611111111111111
sulyarany bias nelkul 0.3611111111111111
output shape: (1, 4, 3, 3)
Ellenőrzés és értelmezés. A két arány bias nélkül az
ötödik tizedesjegyig egyezik a
értékkel; a bias-os paraméterarány különbözik, ahogy a megjegyzés
mondta. Az output shape
megegyezik a teljes réteg kimenetével, tehát a térbeli illesztés
teljesül. A számítás nem futásidő-mérés: a count_conv csak
a zárt képleteket ismétli meg, így a numerikus egyezés a levezetés belső
konzisztenciáját ellenőrzi, nem a könyvtár optimalizálását.
F1 — fogalmi (nehézség 1). Adja meg saját szavaival,
miben korlátozott a depthwise réteg a teljes konvolúcióhoz képest, miért
nevezzük a
-es
réteget channel mixingnek, és miért nem „szeparálható” a szó matematikai
tenzorszorzat-értelmében. Adjon ellenpéldát:
-es
kernel groups=2 beállítással mely csatornák keverednek,
melyek nem. Átadandó: legfeljebb egy oldal,
képletekkel.
F2 — levezetési/bizonyítási (nehézség 2). Vezesse le a depthwise multiplier esetére a MAC-arányt, és határozza meg: és mellett mi a legkisebb pozitív egész , amelyre a separable blokk már nem csökkenti a MAC-et a teljes réteghez képest. Átadandó: lépésről lépésre levezetés és az értéke.
F3 — implementációs (nehézség 2). Egészítse ki a
labor count_conv függvényét úgy, hogy hibát dobjon, ha
in_channels % groups != 0, majd tesztelje három esettel: a
labor full, dw, pw rétegén kívül
egy Conv2d(6, 4, 3, groups=2) rétegen is,
,
bemenettel. Átadandó: futtatható kód,
assert-ekkel, és a várt egész MAC-értékek.
F4 — hibakeresési (nehézség 2). Az alábbi érvelés hibás: „A -as separable blokk a teljes konvolúcióhoz képest -ére csökkenti a számítást, mert a pointwise elhanyagolható.” Lokalizálja a hibát, mutassa meg a fenti , példán a tényleges arányt, és adjon olyan szélsőesetet (), ahol a separable blokk MAC-je nagyobb a teljesnél. Átadandó: az ok, a következmény és a javított állítás.
F5 — kísérleti/transzfer (nehézség 3). Tervezzen GlyphLab-protokollt, amely egy teljes konvolúciós blokk és egy depthwise separable blokk hatását hasonlítja össze egyeztetett MAC-keret mellett, a 17.4. Architektúra összehasonlítása kontrollált keretben matched-recipe elvét követve. Írja elő a hipotézist, a modellfejek azonos tartalmát, a scene_id szerinti splitet, a seed-készletet, a best-val-loss választást, a záró clean/korrupt tesztet és a következtetési határt. Futtatás nem elvárt. Átadandó: egyoldalas protokoll.
F1. A teljes konvolúció kimeneti skalárja
,
tehát a csatorna- és térbeli indexek egyszerre, tetszőlegesen
összekapcsolódnak. A depthwise rétegben az összegzés csak
szerint fut, és minden kimeneti csatorna pontosan egy bemeneti
csatornához kötődik; a groups=C_in beállítás ezt a
blokk-diagonális kapcsolatot valósítja meg. A pointwise
-es
réteg ezzel szemben ugyanazon
pozíció összes csatornáját keveri, de térben nem hat, ezért nevezzük
csatornakeverésnek. A „separable” elnevezés itt operátorok szekvenciális
szétválasztását jelenti, nem azt, hogy a
-as
kernel két
+
kernelszorzatként állna elő; utóbbi tenzorszorzat-szeparálhatóság esetén
a kernel rangja 1 volna. Ellenpélda:
Conv2d(6, 4, 1, groups=2) a bemenetet két háromcsatornás
csoportra osztja; a 0–2. csatornák keveredhetnek egymással, a 3–5.
csatornák egymással, de a két csoport között nincs út,
tehát ez nem teljes channel mixing.
F2. Az -es depthwise kimeneti csatornaszáma , MAC-je . A pointwise bemenete csatorna, MAC-je . A teljes réteg MAC-je változatlan . A hányadosból kiesik:
A feltétel , azaz , tehát . A legkisebb pozitív egész megoldás . esetén az arány , tehát a separable változat több MAC-et igényel, mint a teljes réteg. Ez a szélsőeset mutatja, hogy a depthwise separable nem minden beállításban takarékos.
F3. A teljes függvény:
# lab/n073_f3.py — futtatás: python n073_f3.py
import math
import torch
from torch import nn
def out_len(L, p, d, k, s):
return math.floor((L + 2*p - d*(k-1) - 1)/s) + 1
def count_conv(conv, N, H, W):
if conv.in_channels % conv.groups != 0:
raise ValueError("in_channels nem osztható groups értékkel")
cin_per_group = conv.in_channels // conv.groups
Ho = out_len(H, conv.padding[0], conv.dilation[0], conv.kernel_size[0], conv.stride[0])
Wo = out_len(W, conv.padding[1], conv.dilation[1], conv.kernel_size[1], conv.stride[1])
mac = N * conv.out_channels * Ho * Wo * cin_per_group * conv.kernel_size[0] * conv.kernel_size[1]
return mac
full = nn.Conv2d(3, 4, 3); dw = nn.Conv2d(3, 3, 3, groups=3); pw = nn.Conv2d(3, 4, 1)
assert count_conv(full, 1, 5, 5) == 972
assert count_conv(dw, 1, 5, 5) == 243
assert count_conv(pw, 1, 3, 3) == 108
g2 = nn.Conv2d(6, 4, 3, groups=2)
# H=W=10, k=3, p=0, s=1, d=1 -> Hout=Wout=8; N=2; out=4; cin/group=3
assert count_conv(g2, 2, 10, 10) == 2*4*8*8*3*9
try:
nn.Conv2d(5, 4, 3, groups=2)
except ValueError:
print("group-oszthatósági hiba helyesen elkapva")
print("minden assert lefutott")A várt MAC a grouped esetre
.
A konstruktor már a modul létrehozásakor hibát dob a 5 % 2
esetben, ezért a count_conv assertje a közvetlen
modulépítés előtti védőháló.
F4. A hiba a pointwise tag elhagyása: az érvelés csak a depthwise szorzóját nézi, és csendben -t a levezetés nélkül kezeli. A példában a teljes MAC 972, a depthwise 243, a pointwise további 108, így a tényleges arány , nem pedig . A következmény túlzott megtakarítási ígéret. esetén a teljes réteg MAC , a separable pedig ? Várjunk: , pointwise MAC , tehát ; az arány . A javított állítás: a separable blokk akkor és csak akkor csökkenti a MAC-et az esetben, ha , ami és mellett teljesül. Az F4 hibakeresési megoldás tehát a hiányzó pointwise tételt azonosítja.
F5 lehetséges protokoll (terv, nem futás).
Hipotézis: azonos MAC-keretben a depthwise separable blokk validációs
cross-entropyja nem marad el szisztematikusan a teljes konvolúciós
blokkétól a GlyphLab háromosztályos feladatán. Felépítés: közös stem
Conv2d(1,8,3,stride=2,padding=1); majd A blokk: depthwise
separable, B blokk: teljes conv, úgy választva csatornaszámokat, hogy a
stem és a közös fej kivételével count_conv szerinti
MAC-keret 5%-on belül egyezzen; fej mindkettőn azonos
AvgPool+lineáris 3 logit. Adat: GlyphLab, train/val/test a
megadott split szerint, mintavételi egység scene_id;
augmentáció és egyszerű flip kizárt vagy mindkét ágon azonos. Választás:
5 seed (20261020, +1, +2, +3, +4), batch 64, AdamW, 30 epoch, early-stop
a best val lossra checkpointol; a tesztet csak a választott checkpointon
futólag értékeljük, clean és páros corrupt kimenettel. Metrika:
per-image accuracy és macro-tényezős 95% CI a scene-szintű bootstrapből
(lásd 5.4. Függő képek, seedek és resampling).
Következtetési határ: a toy adat és a kis CPU-s környezet miatt az
eredmény nem általánosítható természetes képekre vagy
GPU-kernel-hatékonyságra.
A leggyakoribb tévedés: „Kevesebb MAC automatikusan kisebb valós futási időt jelent.” A MAC-arány a fenti levezetésből következik, a futásidő viszont a hardver, kernelimplementáció, memória-hozzáférés és batchméret függvénye. Ellenpélda-váz: képzeljünk el egyetlen csatornán, nagy térbeli képen egy naiv Python-listás depthwise hurkot, mellette egy 64 csatornás pointwise 1x1-et, amelyet optimalizált GEMM hív meg; a depthwise MAC-száma lehet sokkal kisebb, a valós futás mégis a rossz aritmetikai intenzitású depthwise hurokban telhet el. Ez gondolatkísérlet, nem a jelen kéziratban mért állítás; a 24.2. CPU/GPU profiling és helyes időmérés mérési protokollja dönti el a konkrét gépen. A helyes állítás tehát megfordítva sem igaz: a MAC-arány csak számítási keret, nem sebességgarancia.
Önellenőrző kérdés és válasz. Milyen és kernelméret mellett nem csökken a MAC? A állítás alapján nem csökken, ha . Pozitív egészekre ez pontosan a eset; ekkor a depthwise + pointwise összeg nagyobb, mert a pointwise új költséget ad a csatornakeveréshez, miközben a teljes réteg a kevés kimeneti csatorna miatt eleve olcsó. Aki az arányt mellett is megtakarításként közli, azonnal ellenőrizhető hibát követ el.
Visszahívás. A 15.1. Súly- és biasgradiens alfejezetben levezetett súlygradiens-összegzés a depthwise blokkban is érvényes: minden kimeneti csatorna pontosan egy bemeneti csatornán dolgozik, de a súlygradienshez a batch és térbeli felhasználási helyek szerint továbbra is összeadódnak a hozzájárulások; az osztás hiba volna. A 18.1. Reziduális blokk és identitásút reziduális blokkja miatt, ha a separable blokkot shortcut mellett használjuk, a kimeneti csatornaszám illesztésére adott esetben -es projekció kell, ami további pointwise költség. Végül a 7.1. Entrópia, cross-entropy és KL cross-entropy felbontása emlékeztet arra, hogy a modellválasztást a validációs dönti el; a paraméter- vagy MAC-megtakarítás önmagában nem minőségi garancia.
A depthwise konvolúció groups=C_in beállítással minden
bemeneti csatornát külön szűr,
multiplier esetén
köztes csatornát adva. A pointwise
-es
réteg azonos térbeli pozíció csatornáit keveri; a kettő kompozíciója a
depthwise separable blokk, amely a térbeli és csatornakeverést
szétválasztja. A levezetett arány bias nélkül mind paraméterekre, mind
MAC-re
,
esetén ennek
-szerese.
A példában ez
;
esetén az arány meghaladja az 1-et, tehát a separable forma nem mindig
takarékos. A MAC-megtakarítás nem bizonyít futásidő-csökkenést. A kódos
ellenőrzés a képletek konzisztenciáját mutatja, a tényleges
teljesítményt csak külön mérés igazolhatja.
A 19.2. MobileNet és inverted residual a MobileNetV2 inverted residual-blokkját építi erre a separable alapra: ott a pointwise réteg először növeli, majd a kimeneti pointwise csökkenti a csatornaszámot, lineáris bottleneck-et hagyva; a [P09] tanulmány MobileNetV2-tervezési elvei ebben a kontextusban jelennek meg. A 19.2. MobileNet és inverted residual ezekből oktatási célú, egyszerűsített blokkot épít.
A 4. állítás és a példák saját levezetések és saját számítások; a labor kimenete a képletekből előre számolt kontroll, nem a szerző által elvégzett futtatás.