7.3 Papildymas ir žingsnis
Prisiminkite konvoliucijos pavyzdį 7.2.1 pav.. Įvesties aukštis ir plotis buvo 3, o konvoliucijos branduolio aukštis ir plotis – 2, gaunant išvesties atvaizdą, kurio dimensija . Tarkime, kad įvesties forma yra , o konvoliucijos branduolio forma – , tuomet išvesties forma bus : konvoliucijos branduolį galime pastumti tik tiek, kol pritrūksta pikselių, kuriems taikyti konvoliuciją.
Toliau išnagrinėsime keletą technikų, įskaitant papildymą ir konvoliucijas su žingsniu, suteikiančių daugiau kontrolės išvesties dydžiui. Kaip motyvaciją atkreipkite dėmesį, kad kadangi branduolių plotis ir aukštis paprastai didesni nei , pritaikius daug nuoseklių konvoliucijų, dažniausiai gauname išvestis, kurios yra gerokai mažesnės už įvestį. Jei pradedame nuo pikselių vaizdo, dešimt konvoliucijų sluoksnių sumažina vaizdą iki pikselių, nupjaudami vaizdo ir kartu sunaikindami bet kokią įdomią informaciją originalaus vaizdo pakraščiuose. Papildymas yra populiariausia priemonė šiai problemai spręsti. Kitais atvejais galime norėti drastiškai sumažinti dimensiją, pvz., jei originali įvesties skyra mums pasirodo nepatogi. Konvoliucijos su žingsniu yra populiari technika, galinti padėti tokiais atvejais.
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
import torch
from torch import nn
import tensorflow as tf
from d2l import jax as d2l
from flax import linen as nn
import jax
from jax import numpy as jnp
7.3.1 Papildymas
Kaip aprašyta aukščiau, viena keblumo priežastis taikant konvoliucinius sluoksnius yra ta, kad prarandame pikselius vaizdo perimetre. Panagrinėkite 7.3.1 pav., vaizduojantį pikselių panaudojimą kaip konvoliucijos branduolio dydžio ir vietos vaizde funkciją. Kampuose esantys pikseliai beveik nenaudojami.
7.3.1 pav. Pikselių panaudojimas , ir dydžio konvoliucijoms.
Kadangi paprastai naudojame mažus branduolius, bet kuriai konkrečiai konvoliucijai galime prarasti tik kelis pikselius, bet tai gali susikaupti, kai taikome daug nuoseklių konvoliucinių sluoksnių. Vienas paprastas šios problemos sprendimas yra pridėti papildomų užpildo pikselių aplink įvesties vaizdo kraštą, taip padidinant efektyvų vaizdo dydį. Paprastai papildomų pikselių reikšmes nustatome lygias nuliui. 7.3.2 pav. papildome įvestį, padidindami jos dydį iki . Atitinkama išvestis tada padidėja iki matricos. Pažymėtos dalys yra pirmasis išvesties elementas ir įvesties bei branduolio tenzoriaus elementai, naudoti išvesčiai skaičiuoti: .
7.3.2 pav. Dvimatė kryžminė koreliacija su papildymu.
Apskritai, jei pridedame iš viso papildymo eilučių (maždaug pusę viršuje ir pusę apačioje) ir iš viso papildymo stulpelių (maždaug pusę kairėje ir pusę dešinėje), išvesties forma bus
Tai reiškia, kad išvesties aukštis ir plotis padidės atitinkamai ir .
Daugeliu atvejų norėsime nustatyti ir , kad įvestis ir išvestis turėtų tą patį aukštį ir plotį. Tai palengvins kiekvieno sluoksnio išvesties formos numatymą konstruojant tinklą. Darant prielaidą, kad čia yra nelyginis, papildysime eilučių abiejose aukščio pusėse. Jei yra lyginis, viena galimybė yra papildyti eilučių įvesties viršuje ir eilučių apačioje. Abi pločio puses papildysime tuo pačiu būdu.
CNN dažniausiai naudoja konvoliucijos branduolius su nelyginėmis aukščio ir pločio reikšmėmis, tokiomis kaip 1, 3, 5 ar 7. Nelyginių branduolių dydžių pasirinkimas turi tą privalumą, kad galime išlaikyti dimensiją, papildydami vienodu eilučių skaičiumi viršuje ir apačioje bei vienodu stulpelių skaičiumi kairėje ir dešinėje.
Be to, ši praktika naudoti nelyginius branduolius
ir papildymą, kad tiksliai išlaikytume dimensiją,
suteikia raštvedybinės naudos.
Bet kuriam dvimačiam tenzoriui X,
kai branduolio dydis yra nelyginis,
o papildymo eilučių ir stulpelių skaičius
visose pusėse yra vienodas,
taip gaunant išvestį, kurios aukštis ir plotis yra tokie patys kaip įvesties,
žinome, kad išvestis Y[i, j] apskaičiuojama
įvesties ir konvoliucijos branduolio kryžmine koreliacija,
kai langas yra centruotas ties X[i, j].
Toliau pateiktame pavyzdyje sukuriame dvimatį konvoliucinį sluoksnį, kurio aukštis ir plotis yra 3, ir (visose pusėse pritaikome 1 pikselio papildymą.) Turint įvestį, kurios aukštis ir plotis yra 8, randame, kad išvesties aukštis ir plotis taip pat yra 8.
# We define a helper function to calculate convolutions. It initializes
# the convolutional layer weights and performs corresponding dimensionality
# elevations and reductions on the input and output
def comp_conv2d(conv2d, X):
conv2d.initialize()
# (1, 1) indicates that batch size and the number of channels are both 1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# Strip the first two dimensions: examples and channels
return Y.reshape(Y.shape[2:])
# 1 row and column is padded on either side, so a total of 2 rows or columns are added
conv2d = nn.Conv2D(1, kernel_size=3, padding=1)
X = np.random.uniform(size=(8, 8))
comp_conv2d(conv2d, X).shape
# We define a helper function to calculate convolutions. It initializes the
# convolutional layer weights and performs corresponding dimensionality
# elevations and reductions on the input and output
def comp_conv2d(conv2d, X):
# (1, 1) indicates that batch size and the number of channels are both 1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# Strip the first two dimensions: examples and channels
return Y.reshape(Y.shape[2:])
# 1 row and column is padded on either side, so a total of 2 rows or columns
# are added
conv2d = nn.LazyConv2d(1, kernel_size=3, padding=1)
X = torch.rand(size=(8, 8))
comp_conv2d(conv2d, X).shape
# We define a helper function to calculate convolutions. It initializes
# the convolutional layer weights and performs corresponding dimensionality
# elevations and reductions on the input and output
def comp_conv2d(conv2d, X):
# (1, 1) indicates that batch size and the number of channels are both 1
X = tf.reshape(X, (1, ) + X.shape + (1, ))
Y = conv2d(X)
# Strip the first two dimensions: examples and channels
return tf.reshape(Y, Y.shape[1:3])
# 1 row and column is padded on either side, so a total of 2 rows or columns
# are added
conv2d = tf.keras.layers.Conv2D(1, kernel_size=3, padding='same')
X = tf.random.uniform(shape=(8, 8))
comp_conv2d(conv2d, X).shape
# We define a helper function to calculate convolutions. It initializes
# the convolutional layer weights and performs corresponding dimensionality
# elevations and reductions on the input and output
def comp_conv2d(conv2d, X):
# (1, X.shape, 1) indicates that batch size and the number of channels are both 1
key = jax.random.PRNGKey(d2l.get_seed())
X = X.reshape((1,) + X.shape + (1,))
Y, _ = conv2d.init_with_output(key, X)
# Strip the dimensions: examples and channels
return Y.reshape(Y.shape[1:3])
# 1 row and column is padded on either side, so a total of 2 rows or columns are added
conv2d = nn.Conv(1, kernel_size=(3, 3), padding='SAME')
X = jax.random.uniform(jax.random.PRNGKey(d2l.get_seed()), shape=(8, 8))
comp_conv2d(conv2d, X).shape
Kai konvoliucijos branduolio aukštis ir plotis skiriasi, galime padaryti, kad išvestis ir įvestis turėtų tą patį aukštį ir plotį, nustatydami skirtingus papildymo skaičius aukščiui ir pločiui.
# We use a convolution kernel with height 5 and width 3. The padding on
# either side of the height and width are 2 and 1, respectively
conv2d = nn.Conv2D(1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
# We use a convolution kernel with height 5 and width 3. The padding on either
# side of the height and width are 2 and 1, respectively
conv2d = nn.LazyConv2d(1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
# We use a convolution kernel with height 5 and width 3. The padding on
# either side of the height and width are 2 and 1, respectively
conv2d = tf.keras.layers.Conv2D(1, kernel_size=(5, 3), padding='same')
comp_conv2d(conv2d, X).shape
# We use a convolution kernel with height 5 and width 3. The padding on
# either side of the height and width are 2 and 1, respectively
conv2d = nn.Conv(1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
7.3.2 Žingsnis
Skaičiuodami kryžminę koreliaciją, pradedame nuo konvoliucijos lango įvesties tenzoriaus viršutiniame kairiajame kampe, o tada slenkame jį per visas vietas žemyn ir į dešinę. Ankstesniuose pavyzdžiuose pagal numatytuosius nustatymus slinkome po vieną elementą. Tačiau kartais, arba dėl skaičiavimo efektyvumo, arba todėl, kad norime sumažinti diskretizavimo dažnį, savo langą judiname daugiau nei vienu elementu vienu metu, praleisdami tarpines vietas. Tai ypač naudinga, jei konvoliucijos branduolys yra didelis, nes jis apima didelę pagrindinio vaizdo sritį.
Eilučių ir stulpelių skaičių, kurį peržengiame per vieną slinkimą, vadiname žingsniu. Iki šiol naudojome 1 žingsnius – ir aukščiui, ir pločiui. Kartais galime norėti naudoti didesnį žingsnį. 7.3.3 pav. parodyta dvimatė kryžminės koreliacijos operacija su žingsniu 3 vertikaliai ir 2 horizontaliai. Pažymėtos dalys yra išvesties elementai ir įvesties bei branduolio tenzoriaus elementai, naudoti išvesčiai skaičiuoti: , . Matome, kad generuojant antrąjį pirmojo stulpelio elementą, konvoliucijos langas slenka žemyn per tris eilutes. Konvoliucijos langas slenka dviem stulpeliais į dešinę, kai generuojamas antrasis pirmosios eilutės elementas. Kai konvoliucijos langas toliau slenka dviem stulpeliais į dešinę įvestyje, išvesties nėra, nes įvesties elementas negali užpildyti lango (nebent pridėtume dar vieną papildymo stulpelį).
7.3.3 pav. Kryžminė koreliacija su žingsniais 3 ir 2 atitinkamai aukščiui ir pločiui.
Apskritai, kai aukščio žingsnis yra , o pločio žingsnis – , išvesties forma yra
Jei nustatome ir , išvesties formą galima supaprastinti iki . Žengiant dar žingsnį, jei įvesties aukštis ir plotis dalijasi iš aukščio ir pločio žingsnių, tuomet išvesties forma bus .
Toliau nustatome ir aukščio, ir pločio žingsnius lygius 2, taip perpus sumažindami įvesties aukštį ir plotį.
conv2d = nn.Conv2D(1, kernel_size=3, padding=1, strides=2)
comp_conv2d(conv2d, X).shape
conv2d = nn.LazyConv2d(1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
conv2d = tf.keras.layers.Conv2D(1, kernel_size=3, padding='same', strides=2)
comp_conv2d(conv2d, X).shape
conv2d = nn.Conv(1, kernel_size=(3, 3), padding=1, strides=2)
comp_conv2d(conv2d, X).shape
Pažvelkime į (šiek tiek sudėtingesnį pavyzdį).
conv2d = nn.Conv2D(1, kernel_size=(3, 5), padding=(0, 1), strides=(3, 4))
comp_conv2d(conv2d, X).shape
conv2d = nn.LazyConv2d(1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
conv2d = tf.keras.layers.Conv2D(1, kernel_size=(3,5), padding='valid',
strides=(3, 4))
comp_conv2d(conv2d, X).shape
conv2d = nn.Conv(1, kernel_size=(3, 5), padding=(0, 1), strides=(3, 4))
comp_conv2d(conv2d, X).shape
7.3.3 Santrauka ir diskusija
Papildymas gali padidinti išvesties aukštį ir plotį. Tai dažnai naudojama, kad išvestis turėtų tokį patį aukštį ir plotį kaip įvestis, siekiant išvengti nepageidaujamo išvesties susitraukimo. Be to, tai užtikrina, kad visi pikseliai naudojami vienodai dažnai. Paprastai renkamės simetrišką papildymą abiejose įvesties aukščio ir pločio pusėse. Šiuo atveju kalbame apie papildymą. Dažniausiai nustatome , ir tada tiesiog sakome, kad pasirenkame papildymą .
Panaši konvencija taikoma žingsniams. Kai horizontalus žingsnis ir vertikalus žingsnis sutampa, tiesiog kalbame apie žingsnį . Žingsnis gali sumažinti išvesties skyrą, pavyzdžiui, sumažindamas išvesties aukštį ir plotį iki tik įvesties aukščio ir pločio, kai . Pagal numatytuosius nustatymus papildymas yra 0, o žingsnis – 1.
Iki šiol visas aptartas papildymas tiesiog išplėsdavo vaizdus nuliais. Tai turi didelį skaičiavimo privalumą, nes jį padaryti elementaru. Be to, operatorius galima suprojektuoti taip, kad jie netiesiogiai pasinaudotų šiuo papildymu, nereikalaujant skirti papildomos atminties. Tuo pat metu tai leidžia CNN užkoduoti numanomą pozicijos informaciją vaizde, tiesiog išmokstant, kur yra „tuščia vieta“. Yra daug alternatyvų papildymui nuliais. (Alsallakh ir kt., 2020) pateikė išsamią jų apžvalgą (nors be aiškaus atvejo, kada naudoti nenulinį papildymą, nebent atsiranda artefaktų).
7.3.4 Pratimai
- Turint paskutinį šio skyrelio kodo pavyzdį su branduolio dydžiu , papildymu ir žingsniu , apskaičiuokite išvesties formą, kad patikrintumėte, ar ji atitinka eksperimento rezultatą.
- Garso signalams, ką atitinka žingsnis 2?
- Įgyvendinkite veidrodinį papildymą, t. y. papildymą, kai kraštų reikšmės tiesiog atspindimos tenzoriams išplėsti.
- Kokia yra skaičiavimo nauda iš didesnio nei 1 žingsnio?
- Kokia gali būti statistinė nauda iš didesnio nei 1 žingsnio?
- Kaip įgyvendintumėte žingsnį ? Ką jis atitinka? Kada tai būtų naudinga?
