7.4 Keli įvesties ir keli išvesties kanalai

Nors 7.1.4 skyrelis aprašėme kelis kanalus, sudarančius kiekvieną vaizdą (pvz., spalvoti vaizdai turi standartinius RGB kanalus, nurodančius raudonos, žalios ir mėlynos kiekį), ir konvoliucinius sluoksnius keliems kanalams, iki šiol supaprastinome visus savo skaitinius pavyzdžius, dirbdami tik su vienu įvesties ir vienu išvesties kanalu. Tai leido mums savo įvestis, konvoliucijos branduolius ir išvestis laikyti dvimačiais tenzoriais.

Kai pridedame kanalus, mūsų įvestys ir paslėptieji atvaizdai tampa trimačiais tenzoriais. Pavyzdžiui, kiekvieno RGB įvesties vaizdo forma yra 3×h×w3\times h\times w. Šią ašį, kurios dydis 3, vadiname kanalų dimensija. Kanalų sąvoka yra tokia pat sena kaip patys CNN: pavyzdžiui, LeNet-5 (LeCun ir kt., 1995) jas naudoja. Šiame skyrelyje giliau pažvelgsime į konvoliucijos branduolius su keliais įvesties ir keliais išvesties kanalais.

from d2l import mxnet as d2l
from mxnet import np, npx
npx.set_np()
from d2l import torch as d2l
import torch
from d2l import jax as d2l
import jax
from jax import numpy as jnp
from d2l import tensorflow as d2l
import tensorflow as tf

7.4.1 Keli įvesties kanalai

Kai įvesties duomenys turi kelis kanalus, turime sukonstruoti konvoliucijos branduolį su tokiu pat įvesties kanalų skaičiumi kaip ir įvesties duomenys, kad jis galėtų atlikti kryžminę koreliaciją su įvesties duomenimis. Tarkime, kad įvesties duomenų kanalų skaičius yra cic_\textrm{i}, tada konvoliucijos branduolio įvesties kanalų skaičius taip pat turi būti cic_\textrm{i}. Jei mūsų konvoliucijos branduolio lango forma yra kh×kwk_\textrm{h}\times k_\textrm{w}, tai, kai ci=1c_\textrm{i}=1, savo konvoliucijos branduolį galime laikyti tiesiog dvimačiu tenzoriumi, kurio forma kh×kwk_\textrm{h}\times k_\textrm{w}.

Tačiau, kai ci>1c_\textrm{i}>1, mums reikia branduolio, kuriame yra formos kh×kwk_\textrm{h}\times k_\textrm{w} tenzorius kiekvienam įvesties kanalui. Sujungus šiuos cic_\textrm{i} tenzorių gaunamas konvoliucijos branduolys, kurio forma ci×kh×kwc_\textrm{i}\times k_\textrm{h}\times k_\textrm{w}. Kadangi įvestis ir konvoliucijos branduolys turi po cic_\textrm{i} kanalų, galime atlikti kryžminės koreliacijos operaciją su dvimačiu įvesties tenzoriumi ir dvimačiu konvoliucijos branduolio tenzoriumi kiekvienam kanalui, sudėdami cic_\textrm{i} rezultatus (sumuodami per kanalus), kad gautume dvimatį tenzorių. Tai yra dvimatės kryžminės koreliacijos rezultatas tarp daugiakanalės įvesties ir konvoliucijos branduolio su keliais įvesties kanalais.

7.4.1 pav. pateiktas dvimatės kryžminės koreliacijos su dviem įvesties kanalais pavyzdys. Pažymėtos dalys yra pirmasis išvesties elementas ir įvesties bei branduolio tenzoriaus elementai, naudoti išvesčiai skaičiuoti: (1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56(1\times1+2\times2+4\times3+5\times4)+(0\times0+1\times1+3\times2+4\times3)=56.

Kryžminės koreliacijos skaičiavimas su dviem įvesties kanalais.

7.4.1 pav. Kryžminės koreliacijos skaičiavimas su dviem įvesties kanalais.

Kad įsitikintume, jog tikrai suprantame, kas čia vyksta, patys galime (įgyvendinti kryžminės koreliacijos operacijas su keliais įvesties kanalais). Atkreipkite dėmesį, kad viskas, ką darome, yra kryžminės koreliacijos operacija kiekvienam kanalui, o tada rezultatų sudėjimas.

def corr2d_multi_in(X, K):
    # Iterate through the 0th dimension (channel) of K first, then add them up
    return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
def corr2d_multi_in(X, K):
    # Iterate through the 0th dimension (channel) of K first, then add them up
    return tf.reduce_sum([d2l.corr2d(x, k) for x, k in zip(X, K)], axis=0)

Galime sukonstruoti įvesties tenzorių X ir branduolio tenzorių K, atitinkančius reikšmes 7.4.1 pav., kad (patvirtintume) kryžminės koreliacijos operacijos (išvestį).

X = d2l.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
               [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = d2l.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])

corr2d_multi_in(X, K)

7.4.2 Keli išvesties kanalai

Nepriklausomai nuo įvesties kanalų skaičiaus, iki šiol visada gaudavome vieną išvesties kanalą. Tačiau, kaip aptarėme 7.1.4 skyrelis, paaiškėja, kad kiekviename sluoksnyje būtina turėti kelis kanalus. Populiariausiose neuroninių tinklų architektūrose iš tikrųjų didiname kanalų dimensiją, eidami gilyn į neuroninį tinklą, paprastai sumažindami diskretizavimo dažnį, kad iškeistume erdvinę skyrą į didesnį kanalų gylį. Intuityviai galite įsivaizduoti, kad kiekvienas kanalas reaguoja į skirtingą požymių rinkinį. Tikrovė yra šiek tiek sudėtingesnė. Naivi interpretacija leistų manyti, kad atvaizdai išmokstami nepriklausomai kiekvienam pikseliui ar kiekvienam kanalui. Vietoj to kanalai optimizuojami taip, kad būtų naudingi kartu. Tai reiškia, kad užuot priskyrus vieną kanalą kraštų detektoriui, gali tiesiog reikšti, kad tam tikra kryptis kanalų erdvėje atitinka kraštų aptikimą.

Tegul cic_\textrm{i} ir coc_\textrm{o} žymi atitinkamai įvesties ir išvesties kanalų skaičių, o khk_\textrm{h} ir kwk_\textrm{w} – branduolio aukštį ir plotį. Kad gautume išvestį su keliais kanalais, galime sukurti formos ci×kh×kwc_\textrm{i}\times k_\textrm{h}\times k_\textrm{w} branduolio tenzorių kiekvienam išvesties kanalui. Juos sujungiame išvesties kanalų dimensijoje, taip kad konvoliucijos branduolio forma būtų co×ci×kh×kwc_\textrm{o}\times c_\textrm{i}\times k_\textrm{h}\times k_\textrm{w}. Kryžminės koreliacijos operacijose rezultatas kiekviename išvesties kanale apskaičiuojamas pagal tą išvesties kanalą atitinkantį konvoliucijos branduolį ir gauna įvestį iš visų įvesties tenzoriaus kanalų.

Įgyvendiname kryžminės koreliacijos funkciją, kad apskaičiuotume kelių kanalų išvestį, kaip parodyta žemiau.

def corr2d_multi_in_out(X, K):
    # Iterate through the 0th dimension of K, and each time, perform
    # cross-correlation operations with input X. All of the results are
    # stacked together
    return d2l.stack([corr2d_multi_in(X, k) for k in K], 0)

Sukonstruojame trivialų konvoliucijos branduolį su trimis išvesties kanalais, sujungdami K branduolio tenzorių su K+1 ir K+2.

K = d2l.stack((K, K + 1, K + 2), 0)
K.shape

Toliau atliekame kryžminės koreliacijos operacijas su įvesties tenzoriumi X ir branduolio tenzoriumi K. Dabar išvestyje yra trys kanalai. Pirmojo kanalo rezultatas sutampa su ankstesnio įvesties tenzoriaus X ir branduolio su keliais įvesties kanalais bei vienu išvesties kanalu rezultatu.

corr2d_multi_in_out(X, K)

7.4.3 1×11\times 1 konvoliucinis sluoksnis

Iš pirmo žvilgsnio 1×11 \times 1 konvoliucija, t. y. kh=kw=1k_\textrm{h} = k_\textrm{w} = 1, neatrodo labai prasminga. Juk konvoliucija koreliuoja gretimus pikselius. 1×11 \times 1 konvoliucija akivaizdžiai to nedaro. Vis dėlto tai populiarios operacijos, kurios kartais įtraukiamos į sudėtingų giliųjų tinklų dizainą (Lin ir kt., 2013; Szegedy ir kt., 2017). Pažiūrėkime šiek tiek išsamiau, ką ji iš tikrųjų daro.

Kadangi naudojamas mažiausias langas, 1×11\times 1 konvoliucija praranda didesnių konvoliucinių sluoksnių gebėjimą atpažinti šablonus, sudarytus iš sąveikų tarp gretimų elementų aukščio ir pločio dimensijose. Vienintelis 1×11\times 1 konvoliucijos skaičiavimas vyksta kanalų dimensijoje.

7.4.2 pav. parodytas kryžminės koreliacijos skaičiavimas naudojant 1×11\times 1 konvoliucijos branduolį su 3 įvesties kanalais ir 2 išvesties kanalais. Atkreipkite dėmesį, kad įvestys ir išvestys turi tą patį aukštį ir plotį. Kiekvienas išvesties elementas gaunamas iš įvesties vaizdo elementų toje pačioje pozicijoje tiesinio derinio. 1×11\times 1 konvoliucinį sluoksnį galite laikyti pilnai sujungtu sluoksniu, taikomu kiekvienoje pikselio vietoje, kad cic_\textrm{i} atitinkamų įvesties reikšmių transformuotų į coc_\textrm{o} išvesties reikšmių. Kadangi tai vis dar yra konvoliucinis sluoksnis, svoriai yra susieti per pikselių vietas. Taigi 1×11\times 1 konvoliuciniam sluoksniui reikia co×cic_\textrm{o}\times c_\textrm{i} svorių (plius poslinkis). Taip pat atkreipkite dėmesį, kad po konvoliucinių sluoksnių paprastai eina netiesiškumai. Tai užtikrina, kad 1×11 \times 1 konvoliucijų negalima tiesiog sulieti su kitomis konvoliucijomis.

Kryžminės koreliacijos skaičiavimas naudoja 1\times 1 konvoliucijos branduolį su trimis įvesties kanalais ir dviem išvesties kanalais. Įvestis ir išvestis turi tą patį aukštį ir plotį.

7.4.2 pav. Kryžminės koreliacijos skaičiavimas naudoja 1×11\times 1 konvoliucijos branduolį su trimis įvesties kanalais ir dviem išvesties kanalais. Įvestis ir išvestis turi tą patį aukštį ir plotį.

Patikrinkime, ar tai veikia praktikoje: įgyvendiname 1×11 \times 1 konvoliuciją naudodami pilnai sujungtą sluoksnį. Vienintelis dalykas – turime šiek tiek pakoreguoti duomenų formą prieš matricų daugybą ir po jos.

def corr2d_multi_in_out_1x1(X, K):
    c_i, h, w = X.shape
    c_o = K.shape[0]
    X = d2l.reshape(X, (c_i, h * w))
    K = d2l.reshape(K, (c_o, c_i))
    # Matrix multiplication in the fully connected layer
    Y = d2l.matmul(K, X)
    return d2l.reshape(Y, (c_o, h, w))

Atliekant 1×11\times 1 konvoliucijas, aukščiau pateikta funkcija yra ekvivalenti anksčiau įgyvendintai kryžminės koreliacijos funkcijai corr2d_multi_in_out. Patikrinkime tai su keliais pavyzdiniais duomenimis.

X = d2l.normal(0, 1, (3, 3, 3))
K = d2l.normal(0, 1, (2, 3, 1, 1))
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(d2l.reduce_sum(d2l.abs(Y1 - Y2))) < 1e-6
X = d2l.normal((3, 3, 3), 0, 1)
K = d2l.normal((2, 3, 1, 1), 0, 1)
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(d2l.reduce_sum(d2l.abs(Y1 - Y2))) < 1e-6
X = jax.random.normal(jax.random.PRNGKey(d2l.get_seed()), (3, 3, 3)) + 0 * 1
K = jax.random.normal(jax.random.PRNGKey(d2l.get_seed()), (2, 3, 1, 1)) + 0 * 1
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(d2l.reduce_sum(d2l.abs(Y1 - Y2))) < 1e-6

7.4.4 Diskusija

Kanalai leidžia mums sujungti geriausias abiejų pasaulių savybes: MLP, leidžiančius reikšmingus netiesiškumus, ir konvoliucijas, leidžiančias lokalizuotą požymių analizę. Visų pirma kanalai leidžia CNN samprotauti su keliais požymiais, tokiais kaip kraštų ir formų detektoriai vienu metu. Jie taip pat siūlo praktišką kompromisą tarp drastiško parametrų sumažinimo, atsirandančio dėl postūmio invariantiškumo ir lokalumo, ir raiškių bei įvairių modelių poreikio kompiuteriniame matyme.

Tačiau atkreipkite dėmesį, kad šis lankstumas kainuoja. Turint (h×w)(h \times w) dydžio vaizdą, k×kk \times k konvoliucijos skaičiavimo kaina yra O(h⋅w⋅k2)\mathcal{O}(h \cdot w \cdot k^2). Atitinkamai cic_\textrm{i} įvesties ir coc_\textrm{o} išvesties kanalams tai padidėja iki O(h⋅w⋅k2⋅ci⋅co)\mathcal{O}(h \cdot w \cdot k^2 \cdot c_\textrm{i} \cdot c_\textrm{o}). 256×256256 \times 256 pikselių vaizdui su 5×55 \times 5 branduoliu ir atitinkamai 128128 įvesties ir išvesties kanalų tai sudaro daugiau nei 53 milijardus operacijų (daugybą ir sudėtį skaičiuojame atskirai). Vėliau susidursime su efektyviomis strategijomis kainai sumažinti, pvz., reikalaujant, kad operacijos pagal kanalus būtų blokinės įstrižainės, o tai veda prie tokių architektūrų kaip ResNeXt (Xie ir kt., 2017).

7.4.5 Pratimai

  1. Tarkime, kad turime du konvoliucijos branduolius, kurių dydžiai atitinkamai k1k_1 ir k2k_2 (be netiesiškumo tarp jų).
    1. Įrodykite, kad operacijos rezultatą galima išreikšti viena konvoliucija.
    2. Kokia yra ekvivalentiškos vienos konvoliucijos dimensija?
    3. Ar teisingas atvirkštinis teiginys, t. y. ar visada galima konvoliuciją išskaidyti į dvi mažesnes?
  2. Tarkime, kad įvesties forma yra ci×h×wc_\textrm{i}\times h\times w, konvoliucijos branduolio forma – co×ci×kh×kwc_\textrm{o}\times c_\textrm{i}\times k_\textrm{h}\times k_\textrm{w}, papildymas – (ph,pw)(p_\textrm{h}, p_\textrm{w}), o žingsnis – (sh,sw)(s_\textrm{h}, s_\textrm{w}).
    1. Kokia yra tiesioginio sklidimo skaičiavimo kaina (daugybos ir sudėties)?
    2. Kokia yra atminties apimtis?
    3. Kokia yra atminties apimtis atgaliniam skaičiavimui?
    4. Kokia yra atgalinio sklidimo skaičiavimo kaina?
  3. Kiek kartų padidėja skaičiavimų skaičius, jei padvigubiname ir įvesties kanalų skaičių cic_\textrm{i}, ir išvesties kanalų skaičių coc_\textrm{o}? Kas nutinka, jei padvigubiname papildymą?
  4. Ar kintamieji Y1 ir Y2 paskutiniame šio skyrelio pavyzdyje yra visiškai vienodi? Kodėl?
  5. Išreikškite konvoliucijas kaip matricų daugybą, net kai konvoliucijos langas nėra 1×11 \times 1.
  6. Jūsų užduotis – įgyvendinti greitas konvoliucijas su k×kk \times k branduoliu. Vienas iš algoritmo kandidatų yra skenuoti horizontaliai per šaltinį, skaitant kk pločio juostą ir apskaičiuojant 11 pločio išvesties juostą po vieną reikšmę. Alternatyva yra nuskaityti k+Δk + \Delta pločio juostą ir apskaičiuoti Δ\Delta pločio išvesties juostą. Kodėl pastaroji yra geresnė? Ar yra riba, kokį didelį Δ\Delta turėtumėte rinktis?
  7. Tarkime, kad turime c×cc \times c matricą.
    1. Kiek greičiau dauginti iš blokinės įstrižainės matricos, jei matrica išskaidyta į bb blokų?
    2. Koks yra bb blokų turėjimo trūkumas? Kaip jį galėtumėte ištaisyti, bent iš dalies?

Diskusijos

Diskusijos

Diskusijos

Diskusijos