Como aplicar o recorte de gradiente no TensorFlow?

96

Considerando o código de exemplo .

Gostaria de saber como aplicar gradiente nessa rede no RNN onde existe a possibilidade de explodir gradientes.

tf.clip_by_value(t, clip_value_min, clip_value_max, name=None)

Este é um exemplo que poderia ser usado, mas onde posso apresentá-lo? Na defesa do RNN

    lstm_cell = rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0)
    # Split data because rnn cell needs a list of inputs for the RNN inner loop
    _X = tf.split(0, n_steps, _X) # n_steps
tf.clip_by_value(_X, -1, 1, name=None)

Mas isso não faz sentido porque o tensor _X é a entrada e não o grad que deve ser recortado?

Tenho que definir meu próprio Otimizador para isso ou existe uma opção mais simples?

Fanático do Arsenal
fonte

Respostas:

143

O recorte do gradiente precisa acontecer depois de computar os gradientes, mas antes de aplicá-los para atualizar os parâmetros do modelo. Em seu exemplo, ambas as coisas são tratadas pelo AdamOptimizer.minimize()método.

Para cortar seus gradientes, você precisará computar, cortar e aplicá-los explicitamente conforme descrito nesta seção na documentação da API do TensorFlow . Especificamente, você precisará substituir a chamada ao minimize()método por algo como o seguinte:

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
gvs = optimizer.compute_gradients(cost)
capped_gvs = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gvs]
train_op = optimizer.apply_gradients(capped_gvs)
Styrke
fonte
4
Styrke, obrigado pelo post. Você sabe quais são as próximas etapas para realmente executar uma iteração do otimizador? Normalmente, um otimizador é instanciado como optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) e, em seguida, uma iteração do otimizador é feita como, optimizer.run()mas o uso optimizer.run()não parece funcionar neste caso?
applecider
6
Ok, entendi optimizer.apply_gradients(capped_gvs)que precisa ser atribuído a algo, x = optimizer.apply_gradients(capped_gvs)então dentro da sua sessão você pode treinar comox.run(...)
aplicador
3
Chame @ remi-cuingnet pela boa sugestão de edição . (O que infelizmente foi rejeitado por revisores precipitados)
Styrke
Isso me dá UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.Então, de alguma forma, meus gradientes esparsos são convertidos em densos. Alguma ideia de como superar esse problema?
Pekka,
8
Na verdade, a maneira certa de recortar gradientes (de acordo com documentos do tensorflow, cientistas da computação e lógica) é com tf.clip_by_global_norm, conforme sugerido por @danijar
gdelab
116

Apesar do que parece ser popular, você provavelmente deseja cortar todo o gradiente por sua norma global:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimize = optimizer.apply_gradients(zip(gradients, variables))

Recortar cada matriz de gradiente individualmente altera sua escala relativa, mas também é possível:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients = [
    None if gradient is None else tf.clip_by_norm(gradient, 5.0)
    for gradient in gradients]
optimize = optimizer.apply_gradients(zip(gradients, variables))

No TensorFlow 2, uma fita calcula os gradientes, os otimizadores vêm de Keras e não precisamos armazenar a operação de atualização porque ela é executada automaticamente sem passá-la para uma sessão:

optimizer = tf.keras.optimizers.Adam(1e-3)
# ...
with tf.GradientTape() as tape:
  loss = ...
variables = ...
gradients = tape.gradient(loss, variables)
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimizer.apply_gradients(zip(gradients, variables))
danijar
fonte
10
Bom exemplo com clip_by_global_norm()! Isso também é descrito como the correct way to perform gradient clippingnos documentos do tensorflow
MZHm
9
@Escachator É empírico e dependerá do seu modelo e possivelmente da tarefa. O que faço é visualizar a norma de gradiente tf.global_norm(gradients)para ver seu alcance normal e, em seguida, cortar um pouco acima disso para evitar que outliers atrapalhem o treinamento.
danijar
1
você ainda ligaria opt.minimize()depois ou ligaria para algo diferente, como opt.run()sugerido em alguns dos comentários em outras respostas?
reese0106
3
@ reese0106 Não, optimizer.minimize(loss)é apenas uma forma abreviada para calcular e aplicar gradientes. Você pode executar o exemplo em minha resposta com sess.run(optimize).
danijar
1
Portanto, se eu estivesse usando tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)em uma função de experimento, seu optimizesubstituiria o train_opcorreto? No momento, train_op = optimizer.minimize(loss, global_step=global_step))estou tentando ter certeza de ajustar de acordo ...
reese0106
10

Na verdade, isso é explicado de maneira adequada na documentação. :

Chamar minimize () cuida de computar os gradientes e aplicá-los às variáveis. Se quiser processar os gradientes antes de aplicá-los, você pode usar o otimizador em três etapas:

  • Calcule os gradientes com compute_gradients ().
  • Processe os gradientes como desejar.
  • Aplique os gradientes processados ​​com apply_gradients ().

E no exemplo que eles fornecem, eles usam estas 3 etapas:

# Create an optimizer.
opt = GradientDescentOptimizer(learning_rate=0.1)

# Compute the gradients for a list of variables.
grads_and_vars = opt.compute_gradients(loss, <list of variables>)

# grads_and_vars is a list of tuples (gradient, variable).  Do whatever you
# need to the 'gradient' part, for example cap them, etc.
capped_grads_and_vars = [(MyCapper(gv[0]), gv[1]) for gv in grads_and_vars]

# Ask the optimizer to apply the capped gradients.
opt.apply_gradients(capped_grads_and_vars)

Aqui MyCapperestá qualquer função que limite seu gradiente. A lista de funções úteis (exceto tf.clip_by_value()) está aqui .

Salvador Dalí
fonte
você ainda ligaria opt.minimize()depois ou ligaria para algo diferente, como opt.run()sugerido em alguns dos comentários em outras respostas?
reese0106
@ reese0106 Não, você precisa atribuir o opt.apply_gradients(...)a uma variável como train_steppor exemplo (exatamente como faria para opt.minimize(). E no seu loop principal você chama como de costume para treinarsess.run([train_step, ...], feed_dict)
dsalaj
Lembre-se de que o gradiente é definido como o vetor das derivadas da perda em relação a todos os parâmetros do modelo. O TensorFlow o representa como uma lista Python que contém uma tupla para cada variável e seu gradiente. Isso significa que para cortar a norma gradiente, você não pode cortar cada tensor individualmente, você precisa considerar a lista de uma vez (por exemplo, usando tf.clip_by_global_norm(list_of_tensors)).
danijar,
8

Para quem gostaria de entender a ideia de recorte gradiente (por norma):

Sempre que a norma do gradiente é maior do que um determinado limite, recortamos a norma do gradiente para que permaneça dentro do limite. Esse limite às vezes é definido como 5.

Seja o gradiente ge o max_norm_threshold seja j .

Agora, se || g || > j , nós fazemos:

g = ( j * g ) / || g ||

Esta é a implementação feita em tf.clip_by_norm

kmario23
fonte
se eu precisar selecionar o limite manualmente, existe algum método comum para fazer isso?
ningyuwhut
É uma espécie de magia negra sugerida em alguns jornais. Caso contrário, você terá que fazer muitos experimentos e descobrir qual funciona melhor.
kmario23 de
4

IMO, a melhor solução é envolver seu otimizador com o decorador de estimadores do TF tf.contrib.estimator.clip_gradients_by_norm:

original_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
optimizer = tf.contrib.estimator.clip_gradients_by_norm(original_optimizer, clip_norm=5.0)
train_op = optimizer.minimize(loss)

Dessa forma, você só precisa definir isso uma vez e não executá-lo após cada cálculo de gradiente.

Documentação: https://www.tensorflow.org/api_docs/python/tf/contrib/estimator/clip_gradients_by_norm

Ido Cohn
fonte
2

O recorte de gradiente basicamente ajuda no caso de gradientes explodindo ou desaparecendo. Diga que sua perda é muito alta, o que resultará em gradientes exponenciais para fluir pela rede, o que pode resultar em valores Nan. Para superar isso, recortamos gradientes dentro de um intervalo específico (-1 a 1 ou qualquer intervalo conforme a condição).

clipped_value=tf.clip_by_value(grad, -range, +range), var) for grad, var in grads_and_vars

onde grads _and_vars são os pares de gradientes (que você calcula via tf.compute_gradients) e suas variáveis ​​às quais eles serão aplicados.

Após o recorte, simplesmente aplicamos seu valor usando um otimizador. optimizer.apply_gradients(clipped_value)

Raj
fonte