11.10. Adam¶ Open the notebook in SageMaker Studio Lab
Bu bölüme kadar uzanan tartışmalarda etkili optimizasyon için bir dizi teknikle karşılaştık. Onları burada ayrıntılı olarak özetleyelim:
Section 11.4 içinde optimizasyon problemlerini çözerken, örneğin bol veriye olan doğal dayanıklılığı nedeniyle gradyan inişinden daha etkili olduğunu gördük.
Section 11.5 içinde bir minigrup içinde daha büyük gözlem kümeleri kullanarak vektörleştirmeden kaynaklanan önemli ek verimlilik sağladığını gördük. Bu, verimli çoklu makine, çoklu GPU ve genel paralel işleme için anahtardır.
Section 11.6 yakınsamayı hızlandırmak için geçmiş gradyanların tarihçesini bir araya getirmeye yönelik bir mekanizma ekledi.
Section 11.7 hesaplama açısından verimli bir ön koşul sağlamak için koordinat başına ölçekleme kullanıldı.
Section 11.8 bir öğrenme hızı ayarlaması ile koordinat başına ölçeklendirmeyi ayrıştırdı.
Adam (Kingma and Ba, 2014), tüm bu teknikleri tek bir verimli öğrenme algoritmasına birleştirir. Beklendiği gibi, bu, derin öğrenmede kullanılacak daha sağlam ve etkili optimizasyon algoritmalarından biri olarak oldukça popüler hale gelen bir algoritmadır. Yine de sorunları yok değildir. Özellikle, (Reddi et al., 2019), Adam’ın zayıf varyans kontrolü nedeniyle ıraksayabileceği durumlar olduğunu göstermektedir. Bir takip çalışması (Zaheer et al., 2018) Adam için bu sorunları gideren Yogi denilen bir düzeltme önerdi. Bunun hakkında daha fazlasını birazdan vereceğiz. Şimdilik Adam algoritmasını gözden geçirelim.
11.10.1. Algoritma¶
Adam’ın temel bileşenlerinden biri, hem momentumu hem de gradyanın ikinci momentini tahmin etmek için üstel ağırlıklı hareketli ortalamaları (sızdıran ortalama olarak da bilinir) kullanmasıdır. Yani, durum değişkenleri kullanır
Burada \(\beta_1\) ve \(\beta_2\) negatif olmayan ağırlıklandırma parametreleridir. Onlar için yaygın seçenekler \(\beta_1 = 0.9\) ve \(\beta_2 = 0.999\)’dur. Yani, varyans tahmini momentum teriminden çok daha yavaş hareket eder. \(\mathbf{v}_0 = \mathbf{s}_0 = 0\) şeklinde ilklersek, başlangıçta daha küçük değerlere karşı önemli miktarda taraflı olduğumuzu unutmayın. Bu, \(\sum_{i=0}^t \beta^i = \frac{1 - \beta^t}{1 - \beta}\)’nin terimleri yeniden normalleştirmesi gerçeğini kullanarak ele alınabilir. Buna göre normalleştirilmiş durum değişkenleri şu şekilde verilir:
Uygun tahminlerle donanmış olarak şimdi güncelleme denklemlerini yazabiliriz. İlk olarak, RMSProp’a çok benzer bir şekilde gradyanı yeniden ölçeklendirerek aşağıdaki ifadeyi elde ederiz:
RMSProp’un aksine güncellememiz gradyanın kendisi yerine \(\hat{\mathbf{v}}_t\) momentumunu kullanır. Ayrıca, yeniden ölçeklendirme \(\frac{1}{\sqrt{\hat{\mathbf{s}}_t + \epsilon}}\) yerine \(\frac{1}{\sqrt{\hat{\mathbf{s}}_t} + \epsilon}\) kullanarak gerçekleştiği için hafif bir kozmetik fark vardır. Sonraki, pratikte tartışmasız olarak biraz daha iyi çalışır, dolayısıyla RMSProp’tan bir sapmadır. Sayısal kararlılık ve aslına uygunluk arasında iyi bir denge için tipik olarak \(\epsilon = 10^{-6}\)’yı seçeriz.
Şimdi güncellemeleri hesaplamak için tüm parçalara sahibiz. Bu biraz hayal kırıcıdır ve formun basit bir güncellemesi vardır:
Adam’ın tasarımı gözden geçirildiğinde ilham kaynağı açıktır. Momentum ve ölçek durum değişkenlerinde açıkça görülebilir. Oldukça tuhaf tanımları bizi terimleri yansızlaştırmaya zorlar (bu biraz farklı bir ilkleme ve güncelleme koşuluyla düzeltilebilir). İkincisi, her iki terim kombinasyonu RMSProp göz önüne alındığında oldukça basittir. Son olarak, açık öğrenme oranı \(\eta\), yakınsama sorunlarını çözmek için adım uzunluğunu kontrol etmemizi sağlar.
11.10.2. Uygulama¶
Adam’ı sıfırdan uygulama çok da göz korkutucu değil. Kolaylık sağlamak
için hyperparams
sözlüğünde \(t\) zaman adımı sayacını
saklıyoruz. Bunun ötesinde her şey basittir.
%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 = np.zeros((feature_dim, 1)), np.zeros(1)
s_w, s_b = np.zeros((feature_dim, 1)), np.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
import torch
from d2l import torch as d2l
def init_adam_states(feature_dim):
v_w, v_b = torch.zeros((feature_dim, 1)), torch.zeros(1)
s_w, s_b = torch.zeros((feature_dim, 1)), torch.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
import tensorflow as tf
from d2l import tensorflow as d2l
def init_adam_states(feature_dim):
v_w = tf.Variable(tf.zeros((feature_dim, 1)))
v_b = tf.Variable(tf.zeros(1))
s_w = tf.Variable(tf.zeros((feature_dim, 1)))
s_b = tf.Variable(tf.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)
Modelini eğitmek için Adam’ı kullanmaya hazırız. \(\eta = 0.01\) öğrenim oranını kullanıyoruz.
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);
loss: 0.246, 0.119 sec/epoch
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);
loss: 0.242, 0.016 sec/epoch
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);
loss: 0.242, 0.152 sec/epoch
adam
Gluon trainer
optimizasyon kütüphanesinin bir parçası
olarak sağlanan algoritmalardan biri olduğundan daha kısa bir uygulama
barizdir. Bu nedenle, Gluon’daki bir uygulama için yalnızca yapılandırma
parametrelerini geçmemiz gerekiyor.
d2l.train_concise_ch11('adam', {'learning_rate': 0.01}, data_iter)
loss: 0.244, 0.056 sec/epoch
trainer = torch.optim.Adam
d2l.train_concise_ch11(trainer, {'lr': 0.01}, data_iter)
loss: 0.243, 0.015 sec/epoch
trainer = tf.keras.optimizers.Adam
d2l.train_concise_ch11(trainer, {'learning_rate': 0.01}, data_iter)
loss: 0.245, 0.120 sec/epoch
11.10.3. Yogi¶
Adam’ın sorunlarından biri, \(\mathbf{s}_t\)’teki ikinci moment tahmini patladığında dışbükey ayarlarda bile yakınsamayabilmesidir. Bir düzeltme olarak (Zaheer et al., 2018) \(\mathbf{s}_t\) için arıtılmış bir güncelleme (ve ilkleme) önerdi. Neler olup bittiğini anlamak için, Adam güncellemesini aşağıdaki gibi yeniden yazalım:
\(\mathbf{g}_t^2\) yüksek varyansa sahip olduğunda veya güncellemeler seyrek olduğunda, \(\mathbf{s}_t\) geçmiş değerleri çok çabuk unutabilir. Bunun için olası bir düzeltme \(\mathbf{g}_t^2 - \mathbf{s}_{t-1}\)’yı \(\mathbf{g}_t^2 \odot \mathop{\mathrm{sgn}}(\mathbf{g}_t^2 - \mathbf{s}_{t-1})\) ile değiştirmektir. Artık güncellemenin büyüklüğü sapma miktarına bağlı değil. Bu Yogi güncellemelerini verir:
Yazarlar ayrıca momentumu sadece ilk noktasal tahminden ziyade daha büyük bir ilk toplu iş ile ilklemeyi tavsiye ediyor. Tartışmada önemli olmadıkları ve bu yakınsama olmasa bile oldukça iyi kaldığı için ayrıntıları atlıyoruz.
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);
loss: 0.242, 0.133 sec/epoch
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);
loss: 0.243, 0.017 sec/epoch
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);
loss: 0.242, 0.160 sec/epoch
11.10.4. Özet¶
Adam, birçok optimizasyon algoritmasının özelliklerini oldukça sağlam bir güncelleme kuralı haline getirir.
RMSProp temelinde oluşturulan Adam ayrıca minigrup rasgele gradyan üzerinde EWMA kullanır.
Adam, momentumu ve ikinci bir momenti tahmin ederken yavaş başlatmayı ayarlamak için ek girdi düzeltmesini kullanır.
Önemli varyansa sahip gradyanlar için yakınsama sorunlarıyla karşılaşabiliriz. Bunlar, daha büyük minigruplar kullanılarak veya \(\mathbf{s}_t\) için geliştirilmiş bir tahmine geçilerek değiştirilebilir. Yogi böyle bir alternatif sunuyor.
11.10.5. Alıştırmalar¶
Öğrenme oranını ayarlayın ve deneysel sonuçları gözlemleyip analiz edin.
Eğer momentum ve ikinci moment güncellemeleri, ek girdi düzeltme gerektirmeyecek şekilde yeniden yazabilir misiniz?
Neden yakınsadığımızda \(\eta\) öğrenme oranını düşürmeniz gerekiyor?
Adam’ın ıraksadığında ve Yogi’nin yakınsadığı bir durum oluşturmaya mı çalışın.