12.10 Adam
Diskusijose, vedusiose į šį skyrelį, susidūrėme su nemažai efektyvaus optimizavimo metodų. Čia juos išsamiai apibendrinkime:
- Matėme, kad 12.4 skyrelis yra veiksmingesnis nei gradientinis nusileidimas sprendžiant optimizavimo uždavinius, pavyzdžiui, dėl savaiminio atsparumo perteklinei duomenų informacijai.
- Matėme, kad 12.5 skyrelis suteikia reikšmingą papildomą efektyvumą dėl vektorizavimo, kai viename minirinkinyje naudojamos didesnės stebėjimų aibės. Tai yra esminis efektyvaus daugiamašinio, daugelio GPU ir apskritai lygiagretaus apdorojimo veiksnys.
- 12.6 skyrelis pridėjo mechanizmą praeities gradientų istorijai agreguoti, kad paspartintų konvergavimą.
- 12.7 skyrelis naudojo koordinatėms specifinį mastelio keitimą, kad būtų galima gauti skaičiavimo požiūriu efektyvų pradinių sąlygų tikrinimą.
- 12.8 skyrelis atskyrė koordinatėms specifinį mastelio keitimą nuo mokymosi greičio koregavimo.
Adam (Kingma ir Ba, 2014) sujungia visus šiuos metodus į vieną efektyvų mokymosi algoritmą. Kaip ir tikėtasi, tai algoritmas, kuris tapo gana populiarus kaip vienas tvirčiausių ir veiksmingiausių giliajame mokymesi naudojamų optimizavimo algoritmų. Tačiau jis nėra be trūkumų. Visų pirma (Reddi ir kt., 2019) parodo, kad yra situacijų, kai Adam gali diverguoti dėl prastos dispersijos kontrolės. Tolesniame darbe (Zaheer ir kt., 2018) pasiūlė Adam skubų pataisymą, vadinamą Yogi, kuris sprendžia šias problemas. Apie tai vėliau. Kol kas apžvelkime Adam algoritmą.
12.10.1 Algoritmas
Vienas pagrindinių Adam komponentų yra tas, kad jis naudoja eksponentiškai svertinius slankiuosius vidurkius (dar žinomus kaip nutekėjęs vidurkinimas), kad gautų ir momento, ir gradiento antrojo momento įverčius. Tai yra, jis naudoja būsenos kintamuosius
Čia ir yra neneigiami svorio parametrai. Įprasti jų pasirinkimai yra ir . Tai yra, dispersijos įvertis juda daug lėčiau nei momento narys. Atkreipkite dėmesį, kad jei inicializuojame , pradžioje turime didelį poslinkį link mažesnių reikšmių. Tai galima ištaisyti pasinaudojus faktu, kad , ir pernormuoti narius. Atitinkamai normuoti būsenos kintamieji yra
Turėdami tinkamus įverčius, galime užrašyti atnaujinimo lygtis. Pirmiausia pakeičiame gradiento mastelį labai panašiai kaip RMSProp ir gauname
Skirtingai nei RMSProp, mūsų atnaujinimas naudoja momentą , o ne patį gradientą. Be to, yra nedidelis kosmetinis skirtumas, nes mastelio keitimas vyksta naudojant vietoj . Pirmasis, galima teigti, praktikoje veikia kiek geriau, todėl nukrypstama nuo RMSProp. Paprastai pasirenkame kaip gerą kompromisą tarp skaitinio stabilumo ir tikslumo.
Dabar turime visas dalis atnaujinimams apskaičiuoti. Tai šiek tiek antiklimaksinė dalis, ir turime paprastą atnaujinimą:
Apžvelgus Adam dizainą, jo įkvėpimas aiškus. Momentas ir mastelis aiškiai matomi būsenos kintamuosiuose. Jų gana savotiškas apibrėžimas verčia šalinti narių poslinkį (tai būtų galima ištaisyti šiek tiek kitokia inicializacija ir atnaujinimo sąlyga). Antra, abiejų narių derinimas yra gana paprastas, turint RMSProp. Galiausiai aiškus mokymosi greitis leidžia valdyti žingsnio ilgį, kad būtų sprendžiamos konvergavimo problemos.
12.10.2 Įgyvendinimas
Adam įgyvendinti nuo nulio nėra labai baisu. Patogumui laiko žingsnio skaitiklį saugome žodyne hyperparams. Be to, viskas paprasta.
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import np, npx
npx.set_np()
def init_adam_states(feature_dim):
v_w, v_b = d2l.zeros((feature_dim, 1)), d2l.zeros(1)
s_w, s_b = d2l.zeros((feature_dim, 1)), d2l.zeros(1)
return ((v_w, s_w), (v_b, s_b))
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = beta2 * s + (1 - beta2) * np.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (np.sqrt(s_bias_corr) + eps)
hyperparams['t'] += 1
%matplotlib inline
from d2l import torch as d2l
import torch
def init_adam_states(feature_dim):
v_w, v_b = d2l.zeros((feature_dim, 1)), d2l.zeros(1)
s_w, s_b = d2l.zeros((feature_dim, 1)), d2l.zeros(1)
return ((v_w, s_w), (v_b, s_b))
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
with torch.no_grad():
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = beta2 * s + (1 - beta2) * torch.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr)
+ eps)
p.grad.data.zero_()
hyperparams['t'] += 1
%matplotlib inline
from d2l import tensorflow as d2l
import tensorflow as tf
def init_adam_states(feature_dim):
v_w = tf.Variable(d2l.zeros((feature_dim, 1)))
v_b = tf.Variable(d2l.zeros(1))
s_w = tf.Variable(d2l.zeros((feature_dim, 1)))
s_b = tf.Variable(d2l.zeros(1))
return ((v_w, s_w), (v_b, s_b))
def adam(params, grads, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s), grad in zip(params, states, grads):
v[:].assign(beta1 * v + (1 - beta1) * grad)
s[:].assign(beta2 * s + (1 - beta2) * tf.math.square(grad))
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:].assign(p - hyperparams['lr'] * v_bias_corr
/ tf.math.sqrt(s_bias_corr) + eps)
Esame pasirengę naudoti Adam modeliui apmokyti. Naudojame mokymosi greitį .
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adam, init_adam_states(feature_dim),
{'lr': 0.01, 't': 1}, data_iter, feature_dim);
Glaustesnis įgyvendinimas yra paprastas, nes adam yra vienas iš algoritmų, pateikiamų kaip Gluon trainer optimizavimo bibliotekos dalis. Todėl įgyvendinimui Gluon tereikia perduoti konfigūracijos parametrus.
d2l.train_concise_ch11('adam', {'learning_rate': 0.01}, data_iter)
trainer = torch.optim.Adam
d2l.train_concise_ch11(trainer, {'lr': 0.01}, data_iter)
trainer = tf.keras.optimizers.Adam
d2l.train_concise_ch11(trainer, {'learning_rate': 0.01}, data_iter)
12.10.3 Yogi
Viena Adam problemų yra ta, kad jis gali nekonverguoti net iškiliosiose aplinkose, kai antrojo momento įvertis išauga. Kaip sprendimą (Zaheer ir kt., 2018) pasiūlė patobulintą atnaujinimą (ir inicializaciją). Kad suprastume, kas vyksta, perrašykime Adam atnaujinimą taip:
Kai tik turi didelę dispersiją arba atnaujinimai reti, gali per greitai pamiršti praeities reikšmes. Galimas šios problemos sprendimas yra pakeisti . Dabar atnaujinimo dydis nebepriklauso nuo nuokrypio dydžio. Taip gauname Yogi atnaujinimus
Autoriai be to pataria momentą inicializuoti didesniame pradiniame rinkinyje, o ne tik pradiniu taškiniu įverčiu. Šias detales praleidžiame, nes jos nėra esminės diskusijai ir nes net be to konvergavimas išlieka gana geras.
def yogi(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-3
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = s + (1 - beta2) * np.sign(
np.square(p.grad) - s) * np.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (np.sqrt(s_bias_corr) + eps)
hyperparams['t'] += 1
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(yogi, init_adam_states(feature_dim),
{'lr': 0.01, 't': 1}, data_iter, feature_dim);
def yogi(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-3
for p, (v, s) in zip(params, states):
with torch.no_grad():
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = s + (1 - beta2) * torch.sign(
torch.square(p.grad) - s) * torch.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr)
+ eps)
p.grad.data.zero_()
hyperparams['t'] += 1
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(yogi, init_adam_states(feature_dim),
{'lr': 0.01, 't': 1}, data_iter, feature_dim);
def yogi(params, grads, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s), grad in zip(params, states, grads):
v[:].assign(beta1 * v + (1 - beta1) * grad)
s[:].assign(s + (1 - beta2) * tf.math.sign(
tf.math.square(grad) - s) * tf.math.square(grad))
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:].assign(p - hyperparams['lr'] * v_bias_corr
/ tf.math.sqrt(s_bias_corr) + eps)
hyperparams['t'] += 1
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(yogi, init_adam_states(feature_dim),
{'lr': 0.01, 't': 1}, data_iter, feature_dim);
12.10.4 Santrauka
- Adam sujungia daugelio optimizavimo algoritmų ypatybes į gana tvirtą atnaujinimo taisyklę.
- Sukurtas RMSProp pagrindu, Adam taip pat naudoja EWMA minirinkinio stochastiniam gradientui.
- Adam naudoja poslinkio korekciją, kad kompensuotų lėtą pradžią vertinant momentą ir antrąjį momentą.
- Gradientams su didele dispersija galime susidurti su konvergavimo problemomis. Jas galima ištaisyti naudojant didesnius minirinkinius arba pereinant prie patobulinto įverčio. Yogi siūlo tokią alternatyvą.
12.10.5 Pratimai
- Keiskite mokymosi greitį ir stebėkite bei analizuokite eksperimentų rezultatus.
- Ar galite perrašyti momento ir antrojo momento atnaujinimus taip, kad nereikėtų poslinkio korekcijos?
- Kodėl konverguojant reikia mažinti mokymosi greitį ?
- Pabandykite sukonstruoti atvejį, kai Adam diverguoja, o Yogi konverguoja.
