11.9. Adadelta¶ Open the notebook in SageMaker Studio Lab
Adadelta é outra variante do AdaGrad (Section 11.7). A principal diferença reside no fato de que diminui a quantidade pela qual a taxa de aprendizagem é adaptável às coordenadas. Além disso, tradicionalmente é referido como não tendo uma taxa de aprendizagem, uma vez que usa a quantidade de mudança em si como calibração para mudanças futuras. O algoritmo foi proposto em [Zeiler, 2012]. É bastante simples, dada a discussão de algoritmos anteriores até agora.
11.9.1. O Algoritmo¶
Em poucas palavras, Adadelta usa duas variáveis de estado, \(\mathbf{s}_t\) para armazenar uma média de vazamento do segundo momento do gradiente e \(\Delta\mathbf{x}_t\) para armazenar uma média de vazamento do segundo momento da mudança de parâmetros no próprio modelo. Observe que usamos a notação original e a nomenclatura dos autores para compatibilidade com outras publicações e implementações (não há outra razão real para usar variáveis gregas diferentes para indicar um parâmetro que serve ao mesmo propósito em momentum, Adagrad, RMSProp e Adadelta)
Aqui estão os detalhes técnicos do Adadelta. Dado que o parâmetro du jour é \(\rho\), obtemos as seguintes atualizações vazadas de forma semelhante a Section 11.8:
A diferença para Section 11.8 é que realizamos atualizações com o gradiente redimensionado \(\mathbf{g}_t'\), ou seja,
Então, qual é o gradiente redimensionado \(\mathbf{g}_t'\)? Podemos calculá-lo da seguinte maneira:
onde \(\Delta \mathbf{x}_{t-1}\) é a média de vazamento dos gradientes redimensionados ao quadrado \(\mathbf{g}_t'\). Inicializamos \(\Delta \mathbf{x}_{0}\) para ser \(0\) e atualizamos em cada etapa com \(\mathbf{g}_t'\), ou seja,
e \(\epsilon\) (um pequeno valor como \(10^{-5}\)) é adicionado para manter a estabilidade numérica.
11.9.2. Implementação¶
Adadelta precisa manter duas variáveis de estado para cada variável, \(\mathbf{s}_t\) e \(\Delta\mathbf{x}_t\). Isso produz a seguinte implementação.
%matplotlib inline
from mxnet import np, npx
from d2l import mxnet as d2l
npx.set_np()
def init_adadelta_states(feature_dim):
s_w, s_b = np.zeros((feature_dim, 1)), np.zeros(1)
delta_w, delta_b = np.zeros((feature_dim, 1)), np.zeros(1)
return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta) in zip(params, states):
# In-place updates via [:]
s[:] = rho * s + (1 - rho) * np.square(p.grad)
g = (np.sqrt(delta + eps) / np.sqrt(s + eps)) * p.grad
p[:] -= g
delta[:] = rho * delta + (1 - rho) * g * g
%matplotlib inline
import torch
from d2l import torch as d2l
def init_adadelta_states(feature_dim):
s_w, s_b = torch.zeros((feature_dim, 1)), torch.zeros(1)
delta_w, delta_b = torch.zeros((feature_dim, 1)), torch.zeros(1)
return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta) in zip(params, states):
with torch.no_grad():
# In-place updates via [:]
s[:] = rho * s + (1 - rho) * torch.square(p.grad)
g = (torch.sqrt(delta + eps) / torch.sqrt(s + eps)) * p.grad
p[:] -= g
delta[:] = rho * delta + (1 - rho) * g * g
p.grad.data.zero_()
%matplotlib inline
import tensorflow as tf
from d2l import tensorflow as d2l
def init_adadelta_states(feature_dim):
s_w = tf.Variable(tf.zeros((feature_dim, 1)))
s_b = tf.Variable(tf.zeros(1))
delta_w = tf.Variable(tf.zeros((feature_dim, 1)))
delta_b = tf.Variable(tf.zeros(1))
return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, grads, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta), grad in zip(params, states, grads):
s[:].assign(rho * s + (1 - rho) * tf.math.square(grad))
g = (tf.math.sqrt(delta + eps) / tf.math.sqrt(s + eps)) * grad
p[:].assign(p - g)
delta[:].assign(rho * delta + (1 - rho) * g * g)
Escolher \(\rho = 0,9\) equivale a um tempo de meia-vida de 10 para cada atualização de parâmetro. Isso tende a funcionar muito bem. Obtemos o seguinte comportamento.
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adadelta, init_adadelta_states(feature_dim),
{'rho': 0.9}, data_iter, feature_dim);
loss: 0.244, 0.219 sec/epoch
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adadelta, init_adadelta_states(feature_dim),
{'rho': 0.9}, data_iter, feature_dim);
loss: 0.244, 0.015 sec/epoch
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adadelta, init_adadelta_states(feature_dim),
{'rho': 0.9}, data_iter, feature_dim);
loss: 0.244, 0.107 sec/epoch
Para uma implementação concisa, simplesmente usamos o algoritmo
adadelta
da classe Trainer
. Isso produz o seguinte one-liner
para uma invocação muito mais compacta.
d2l.train_concise_ch11('adadelta', {'rho': 0.9}, data_iter)
loss: 0.245, 0.216 sec/epoch
trainer = torch.optim.Adadelta
d2l.train_concise_ch11(trainer, {'rho': 0.9}, data_iter)
loss: 0.243, 0.013 sec/epoch
# adadelta is not converging at default learning rate
# but it's converging at lr = 5.0
trainer = tf.keras.optimizers.Adadelta
d2l.train_concise_ch11(trainer, {'learning_rate':5.0, 'rho': 0.9}, data_iter)
loss: 0.244, 0.076 sec/epoch
11.9.3. Sumário¶
Adadelta não tem parâmetro de taxa de aprendizagem. Em vez disso, ele usa a taxa de mudança nos próprios parâmetros para adaptar a taxa de aprendizado.
Adadelta requer duas variáveis de estado para armazenar os segundos momentos de gradiente e a mudança nos parâmetros.
Adadelta usa médias vazadas para manter uma estimativa contínua das estatísticas apropriadas.
11.9.4. Exercícios¶
Ajuste o valor de \(\rho\). O que acontece?
Mostre como implementar o algoritmo sem o uso de \(\mathbf{g}_t'\). Por que isso pode ser uma boa ideia?
A taxa de aprendizagem Adadelta é realmente gratuita? Você conseguiu encontrar problemas de otimização que quebram o Adadelta?
Compare Adadelta com Adagrad e RMS prop para discutir seu comportamento de convergência.