5.4 Skaitinis stabilumas ir inicializavimas
Iki šiol kiekvienam mūsų įgyvendintam modeliui reikėjo inicializuoti jo parametrus pagal tam tikrą iš anksto nurodytą skirstinį. Iki šiol inicializavimo schemą laikėme savaime suprantama, praleisdami detales, kaip šie pasirinkimai daromi. Galbūt net susidarėte įspūdį, kad šie pasirinkimai nėra ypač svarbūs. Priešingai, inicializavimo schemos pasirinkimas vaidina svarbų vaidmenį neuroninių tinklų mokymesi, ir jis gali būti lemiamas išlaikant skaitinį stabilumą. Be to, šie pasirinkimai gali būti įdomiais būdais susiję su netiesinės aktyvacijos funkcijos pasirinkimu. Kokią funkciją pasirenkame ir kaip inicializuojame parametrus, gali nulemti, kaip greitai konverguoja mūsų optimizavimo algoritmas. Prasti pasirinkimai čia gali lemti, kad apmokymo metu susidursime su sprogstančiais arba nykstančiais gradientais. Šiame skyrelyje išsamiau nagrinėjame šias temas ir aptariame keletą naudingų euristikų, kurios jums praverstų visoje jūsų giliojo mokymosi karjeroje.
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import autograd, np, npx
npx.set_np()
%matplotlib inline
from d2l import torch as d2l
import torch
%matplotlib inline
from d2l import tensorflow as d2l
import tensorflow as tf
%matplotlib inline
from d2l import jax as d2l
import jax
from jax import numpy as jnp
from jax import grad, vmap
5.4.1 Nykstantys ir sprogstantys gradientai
Panagrinėkime gilųjį tinklą su sluoksnių, įvestimi ir išvestimi . Kiekvieną sluoksnį apibrėžia transformacija , parametrizuota svoriais , kurio paslėptojo sluoksnio išvestis yra (tegul ), tada mūsų tinklą galima išreikšti taip:
Jei visos paslėptojo sluoksnio išvestys ir įvestis yra vektoriai, gradientą bet kurios parametrų aibės atžvilgiu galime užrašyti taip:
Kitaip tariant, šis gradientas yra matricų ir gradiento vektoriaus sandauga. Todėl esame jautrūs toms pačioms skaitinio nuopildymo (angl. underflow) problemoms, kurios dažnai iškyla dauginant per daug tikimybių. Dirbant su tikimybėmis, įprastas triukas yra pereiti į logaritminę erdvę, t. y. perkelti apkrovą nuo mantisės prie skaitinio vaizdavimo eksponentės. Deja, mūsų aukščiau minėta problema rimtesnė: iš pradžių matricos gali turėti labai įvairias tikrines reikšmes. Jos gali būti mažos arba didelės, ir jų sandauga gali būti labai didelė arba labai maža.
Nestabilių gradientų keliama rizika neapsiriboja skaitiniu vaizdavimu. Nenuspėjamo dydžio gradientai taip pat kelia grėsmę mūsų optimizavimo algoritmų stabilumui. Galime susidurti su parametrų atnaujinimais, kurie yra arba (i) pernelyg dideli, sugadinantys mūsų modelį (sprogstančių gradientų problema); arba (ii) pernelyg maži (nykstančių gradientų problema), dėl ko mokymasis tampa neįmanomas, nes parametrai vos juda per kiekvieną atnaujinimą.
5.4.1.1 (Nykstantys gradientai)
Vienas dažnas nykstančių gradientų problemos kaltininkas yra aktyvacijos funkcijos pasirinkimas, kuri pridedama po kiekvieno sluoksnio tiesinių operacijų. Istoriškai sigmoidinė funkcija (pristatyta 5.1 skyrelis) buvo populiari, nes primena slenkstinę funkciją. Kadangi ankstyvieji dirbtiniai neuroniniai tinklai buvo įkvėpti biologinių neuroninių tinklų, idėja apie neuronus, kurie šauna arba visiškai, arba visai nešauna (kaip biologiniai neuronai), atrodė patraukli. Pažvelkime į sigmoidinę funkciją atidžiau, kad pamatytume, kodėl ji gali sukelti nykstančius gradientus.
x = np.arange(-8.0, 8.0, 0.1)
x.attach_grad()
with autograd.record():
y = npx.sigmoid(x)
y.backward()
d2l.plot(x, [y, x.grad], legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.sigmoid(x)
y.backward(torch.ones_like(x))
d2l.plot(x.detach().numpy(), [y.detach().numpy(), x.grad.numpy()],
legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))
x = tf.Variable(tf.range(-8.0, 8.0, 0.1))
with tf.GradientTape() as t:
y = tf.nn.sigmoid(x)
d2l.plot(x.numpy(), [y.numpy(), t.gradient(y, x).numpy()],
legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))
x = jnp.arange(-8.0, 8.0, 0.1)
y = jax.nn.sigmoid(x)
grad_sigmoid = vmap(grad(jax.nn.sigmoid))
d2l.plot(x, [y, grad_sigmoid(x)],
legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))
Kaip matote, (sigmoidinės funkcijos gradientas išnyksta, kai jos įvestys yra ir didelės, ir mažos). Be to, atgalinio sklidimo per daugelį sluoksnių metu, nebent būtume „Auksaplaukės zonoje“, kurioje daugelio sigmoidinių funkcijų įvestys artimos nuliui, bendros sandaugos gradientai gali išnykti. Kai mūsų tinklas turi daug sluoksnių, jei nebūsime atsargūs, gradientas greičiausiai bus nutrauktas kuriame nors sluoksnyje. Iš tiesų ši problema anksčiau kamavo giliųjų tinklų apmokymą. Todėl ReLU, kurios stabilesnės (bet mažiau neuroniškai įtikimos), tapo numatytuoju specialistų pasirinkimu.
5.4.1.2 Sprogstantys gradientai
Priešinga problema, kai gradientai sprogsta, gali būti lygiai taip pat erzinanti. Kad tai šiek tiek geriau iliustruotume, generuojame 100 Gauso atsitiktinių matricų ir dauginame jas iš kokios nors pradinės matricos. Mūsų pasirinktam mastelio dydžiui (dispersijos pasirinkimas ) matricų sandauga sprogsta. Kai taip nutinka dėl gilaus tinklo inicializavimo, neturime jokių šansų priversti gradientinio nusileidimo optimizatoriaus konverguoti.
M = np.random.normal(size=(4, 4))
print('a single matrix', M)
for i in range(100):
M = np.dot(M, np.random.normal(size=(4, 4)))
print('after multiplying 100 matrices', M)
M = torch.normal(0, 1, size=(4, 4))
print('a single matrix \n',M)
for i in range(100):
M = M @ torch.normal(0, 1, size=(4, 4))
print('after multiplying 100 matrices\n', M)
M = tf.random.normal((4, 4))
print('a single matrix \n', M)
for i in range(100):
M = tf.matmul(M, tf.random.normal((4, 4)))
print('after multiplying 100 matrices\n', M.numpy())
get_key = lambda: jax.random.PRNGKey(d2l.get_seed()) # Generate PRNG keys
M = jax.random.normal(get_key(), (4, 4))
print('a single matrix \n', M)
for i in range(100):
M = jnp.matmul(M, jax.random.normal(get_key(), (4, 4)))
print('after multiplying 100 matrices\n', M)
5.4.1.3 Simetrijos laužymas
Kita neuroninių tinklų projektavimo problema yra jų parametrizavimui būdinga simetrija. Tarkime, kad turime paprastą MLP su vienu paslėptuoju sluoksniu ir dviem vienetais. Šiuo atveju galėtume sukeisti vietomis pirmojo sluoksnio svorius ir taip pat sukeisti išvesties sluoksnio svorius, kad gautume tą pačią funkciją. Nėra nieko ypatingo, kas skirtų pirmąjį ir antrąjį paslėptuosius vienetus. Kitaip tariant, turime keitinių simetriją tarp kiekvieno sluoksnio paslėptųjų vienetų.
Tai daugiau nei tik teorinis nepatogumas. Panagrinėkime minėtą vieno paslėptojo sluoksnio MLP su dviem paslėptaisiais vienetais. Iliustracijai tarkime, kad išvesties sluoksnis du paslėptuosius vienetus transformuoja tik į vieną išvesties vienetą. Įsivaizduokite, kas nutiktų, jei inicializuotume visus paslėptojo sluoksnio parametrus kaip su kokia nors konstanta . Šiuo atveju tiesioginio sklidimo metu abu paslėptieji vienetai gauna tas pačias įvestis ir parametrus, sukurdami tą pačią aktyvaciją, kuri paduodama išvesties vienetui. Atgalinio sklidimo metu diferencijavus išvesties vienetą parametrų atžvilgiu, gaunamas gradientas, kurio visi elementai įgyja tą pačią reikšmę. Taigi po gradientinės iteracijos (pvz., minirinkinio stochastinio gradientinio nusileidimo) visi elementai vis dar įgyja tą pačią reikšmę. Tokios iteracijos niekada savaime nesulaužytų simetrijos ir galbūt niekada nesugebėtume realizuoti tinklo raiškos galios. Paslėptasis sluoksnis elgtųsi taip, tarsi turėtų tik vieną vienetą. Atkreipkite dėmesį, kad nors minirinkinio stochastinis gradientinis nusileidimas šios simetrijos nesulaužytų, dropout reguliarizavimas (pristatytas vėliau) ją sulaužytų!
5.4.2 Parametrų inicializavimas
Vienas būdas spręsti – ar bent sušvelninti – aukščiau iškeltas problemas yra atsargus inicializavimas. Kaip pamatysime vėliau, papildomas atsargumas optimizavimo metu ir tinkamas reguliarizavimas gali dar labiau pagerinti stabilumą.
5.4.2.1 Numatytasis inicializavimas
Ankstesniuose skyreliuose, pvz., 3.5 skyrelis, naudojome normalųjį skirstinį savo svorių reikšmėms inicializuoti. Jei nenurodome inicializavimo metodo, karkasas naudos numatytąjį atsitiktinio inicializavimo metodą, kuris praktikoje dažnai gerai veikia vidutinio dydžio problemoms.
5.4.2.2 Xavier inicializavimas
Pažvelkime į išvesties mastelio skirstinį kokiame nors pilnai sujungtame sluoksnyje be netiesiškumų. Turint įvesčių ir su jomis susijusius svorius šiam sluoksniui, išvestis duodama
Svoriai visi traukiami nepriklausomai iš to paties skirstinio. Be to, tarkime, kad šis skirstinys turi nulinį vidurkį ir dispersiją . Atkreipkite dėmesį, kad tai nereiškia, jog skirstinys turi būti Gauso, tik kad vidurkis ir dispersija turi egzistuoti. Kol kas tarkime, kad sluoksnio įvestys taip pat turi nulinį vidurkį ir dispersiją ir kad jos yra nepriklausomos nuo bei viena nuo kitos. Šiuo atveju galime apskaičiuoti vidurkį:
ir dispersiją:
Vienas būdas išlaikyti dispersiją fiksuotą yra nustatyti . Dabar panagrinėkime atgalinį sklidimą. Ten susiduriame su panašia problema, nors gradientai sklinda iš sluoksnių, esančių arčiau išvesties. Naudodami tą patį samprotavimą kaip tiesioginiam sklidimui, matome, kad gradientų dispersija gali sprogti, nebent , kur yra šio sluoksnio išvesčių skaičius. Tai palieka mus dilemoje: negalime vienu metu patenkinti abiejų sąlygų. Vietoj to tiesiog stengiamės patenkinti:
Tai yra samprotavimas, kuriuo grindžiamas dabar jau standartinis ir praktiškai naudingas Xavier inicializavimas, pavadintas pirmojo jo kūrėjų autoriaus vardu (Glorot ir Bengio, 2010). Paprastai Xavier inicializavimas semia svorius iš Gauso skirstinio su nuliniu vidurkiu ir dispersija . Taip pat galime tai pritaikyti dispersijai pasirinkti, kai svoriai semiami iš tolygiojo skirstinio. Atkreipkite dėmesį, kad tolygusis skirstinys turi dispersiją . Įstatę į savo sąlygą , gauname, kad turėtume inicializuoti pagal
Nors prielaida apie netiesiškumų nebuvimą aukščiau pateiktame matematiniame samprotavime neuroniniuose tinkluose gali būti lengvai pažeista, Xavier inicializavimo metodas praktikoje veikia gerai.
5.4.2.3 Daugiau
Aukščiau pateiktas samprotavimas vos paliečia šiuolaikinių parametrų inicializavimo metodų paviršių. Giliojo mokymosi karkasas dažnai įgyvendina daugiau nei tuziną skirtingų euristikų. Be to, parametrų inicializavimas ir toliau yra karšta fundamentinių giliojo mokymosi tyrimų sritis. Tarp jų yra euristikos, specializuotos susietiems (bendriems) parametrams, superrezoliucijai, sekų modeliams ir kitoms situacijoms. Pavyzdžiui, (Xiao ir kt., 2018) parodė galimybę apmokyti 10 000 sluoksnių neuroninius tinklus be architektūrinių gudrybių, naudojant kruopščiai suprojektuotą inicializavimo metodą.
Jei ši tema jus domina, siūlome pasigilinti į šio modulio siūlomas galimybes, perskaityti straipsnius, pasiūliusius ir išanalizavusius kiekvieną euristiką, o tada panagrinėti naujausias publikacijas šia tema. Galbūt užtiksite ar net išrasite gudrią idėją ir prisidėsite įgyvendinimu prie giliojo mokymosi karkasų.
5.4.3 Santrauka
Nykstantys ir sprogstantys gradientai yra dažnos problemos giliuosiuose tinkluose. Reikia didelio atidumo inicializuojant parametrus, kad gradientai ir parametrai išliktų gerai kontroliuojami. Reikalingos inicializavimo euristikos, kad pradiniai gradientai nebūtų nei per dideli, nei per maži. Atsitiktinis inicializavimas yra esminis užtikrinant, kad simetrija būtų sulaužyta prieš optimizavimą. Xavier inicializavimas siūlo, kad kiekvieno sluoksnio bet kurios išvesties dispersijos neveiktų įvesčių skaičius, o bet kurio gradiento dispersijos neveiktų išvesčių skaičius. ReLU aktyvacijos funkcijos sušvelnina nykstančių gradientų problemą. Tai gali paspartinti konvergavimą.
5.4.4 Pratimai
- Ar galite sugalvoti kitų atvejų, kai neuroninis tinklas gali pasižymėti simetrija, kurią reikia laužyti, be keitinių simetrijos MLP sluoksniuose?
- Ar galime inicializuoti visus svorių parametrus tiesinėje regresijoje arba softmax regresijoje tokia pačia reikšme?
- Susiraskite analizines dviejų matricų sandaugos tikrinių reikšmių ribas. Ką tai pasako apie tai, kaip užtikrinti, kad gradientai būtų gerai sąlygoti?
- Jei žinome, kad kai kurie nariai diverguoja, ar galime tai ištaisyti vėliau? Įkvėpimo ieškokite straipsnyje apie sluoksnių adaptyvųjį sparto mastelio keitimą (You ir kt., 2017).
