.. _sec_adam:
Adam
====
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:
- :numref:`sec_sgd` 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.
- :numref:`sec_minibatch_sgd` 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.
- :numref:`sec_momentum` yakınsamayı hızlandırmak için geçmiş
gradyanların tarihçesini bir araya getirmeye yönelik bir mekanizma
ekledi.
- :numref:`sec_adagrad` hesaplama açısından verimli bir ön koşul
sağlamak için koordinat başına ölçekleme kullanıldı.
- :numref:`sec_rmsprop` bir öğrenme hızı ayarlaması ile koordinat
başına ölçeklendirmeyi ayrıştırdı.
Adam :cite:`Kingma.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, :cite:`Reddi.Kale.Kumar.2019`, Adam'ın zayıf
varyans kontrolü nedeniyle ıraksayabileceği durumlar olduğunu
göstermektedir. Bir takip çalışması
:cite:`Zaheer.Reddi.Sachan.ea.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.
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
.. math::
\begin{aligned}
\mathbf{v}_t & \leftarrow \beta_1 \mathbf{v}_{t-1} + (1 - \beta_1) \mathbf{g}_t, \\
\mathbf{s}_t & \leftarrow \beta_2 \mathbf{s}_{t-1} + (1 - \beta_2) \mathbf{g}_t^2.
\end{aligned}
Burada :math:`\beta_1` ve :math:`\beta_2` negatif olmayan
ağırlıklandırma parametreleridir. Onlar için yaygın seçenekler
:math:`\beta_1 = 0.9` ve :math:`\beta_2 = 0.999`'dur. Yani, varyans
tahmini momentum teriminden *çok daha yavaş* hareket eder.
:math:`\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, :math:`\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:
.. math:: \hat{\mathbf{v}}_t = \frac{\mathbf{v}_t}{1 - \beta_1^t} \text{ ve } \hat{\mathbf{s}}_t = \frac{\mathbf{s}_t}{1 - \beta_2^t}.
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:
.. math:: \mathbf{g}_t' = \frac{\eta \hat{\mathbf{v}}_t}{\sqrt{\hat{\mathbf{s}}_t} + \epsilon}.
RMSProp'un aksine güncellememiz gradyanın kendisi yerine
:math:`\hat{\mathbf{v}}_t` momentumunu kullanır. Ayrıca, yeniden
ölçeklendirme :math:`\frac{1}{\sqrt{\hat{\mathbf{s}}_t + \epsilon}}`
yerine :math:`\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 :math:`\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:
.. math:: \mathbf{x}_t \leftarrow \mathbf{x}_{t-1} - \mathbf{g}_t'.
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ı :math:`\eta`, yakınsama sorunlarını çözmek için adım
uzunluğunu kontrol etmemizi sağlar.
Uygulama
--------
Adam'ı sıfırdan uygulama çok da göz korkutucu değil. Kolaylık sağlamak
için ``hyperparams`` sözlüğünde :math:`t` zaman adımı sayacını
saklıyoruz. Bunun ötesinde her şey basittir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%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
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%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
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%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)
.. raw:: html
.. raw:: html
Modelini eğitmek için Adam'ı kullanmaya hazırız. :math:`\eta = 0.01`
öğrenim oranını kullanıyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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);
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.246, 0.119 sec/epoch
.. figure:: output_adam_f5876e_15_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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);
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.242, 0.016 sec/epoch
.. figure:: output_adam_f5876e_18_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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);
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.242, 0.152 sec/epoch
.. figure:: output_adam_f5876e_21_1.svg
.. raw:: html
.. raw:: html
``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.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
d2l.train_concise_ch11('adam', {'learning_rate': 0.01}, data_iter)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.244, 0.056 sec/epoch
.. figure:: output_adam_f5876e_27_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer = torch.optim.Adam
d2l.train_concise_ch11(trainer, {'lr': 0.01}, data_iter)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.243, 0.015 sec/epoch
.. figure:: output_adam_f5876e_30_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
trainer = tf.keras.optimizers.Adam
d2l.train_concise_ch11(trainer, {'learning_rate': 0.01}, data_iter)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.245, 0.120 sec/epoch
.. figure:: output_adam_f5876e_33_1.svg
.. raw:: html
.. raw:: html
Yogi
----
Adam'ın sorunlarından biri, :math:`\mathbf{s}_t`'teki ikinci moment
tahmini patladığında dışbükey ayarlarda bile yakınsamayabilmesidir. Bir
düzeltme olarak :cite:`Zaheer.Reddi.Sachan.ea.2018`
:math:`\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:
.. math:: \mathbf{s}_t \leftarrow \mathbf{s}_{t-1} + (1 - \beta_2) \left(\mathbf{g}_t^2 - \mathbf{s}_{t-1}\right).
:math:`\mathbf{g}_t^2` yüksek varyansa sahip olduğunda veya
güncellemeler seyrek olduğunda, :math:`\mathbf{s}_t` geçmiş değerleri
çok çabuk unutabilir. Bunun için olası bir düzeltme
:math:`\mathbf{g}_t^2 - \mathbf{s}_{t-1}`'yı
:math:`\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:
.. math:: \mathbf{s}_t \leftarrow \mathbf{s}_{t-1} + (1 - \beta_2) \mathbf{g}_t^2 \odot \mathop{\mathrm{sgn}}(\mathbf{g}_t^2 - \mathbf{s}_{t-1}).
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.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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);
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.242, 0.133 sec/epoch
.. figure:: output_adam_f5876e_39_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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);
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.243, 0.017 sec/epoch
.. figure:: output_adam_f5876e_42_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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);
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
loss: 0.242, 0.160 sec/epoch
.. figure:: output_adam_f5876e_45_1.svg
.. raw:: html
.. raw:: html
Ö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
:math:`\mathbf{s}_t` için geliştirilmiş bir tahmine geçilerek
değiştirilebilir. Yogi böyle bir alternatif sunuyor.
Alıştırmalar
------------
1. Öğrenme oranını ayarlayın ve deneysel sonuçları gözlemleyip analiz
edin.
2. Eğer momentum ve ikinci moment güncellemeleri, ek girdi düzeltme
gerektirmeyecek şekilde yeniden yazabilir misiniz?
3. Neden yakınsadığımızda :math:`\eta` öğrenme oranını düşürmeniz
gerekiyor?
4. Adam'ın ıraksadığında ve Yogi'nin yakınsadığı bir durum oluşturmaya
mı çalışın.
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html