Comment appliquer un découpage de dégradé dans TensorFlow?

96

Considérant l' exemple de code .

Je voudrais savoir comment appliquer l'écrêtage de gradient sur ce réseau sur le RNN où il y a une possibilité d'explosion de gradients.

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

Ceci est un exemple qui pourrait être utilisé, mais où dois-je introduire cela? Dans la définition de 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)

Mais cela n'a pas de sens car le tenseur _X est l'entrée et non le grad ce qui doit être écrêté?

Dois-je définir mon propre optimiseur pour cela ou existe-t-il une option plus simple?

Fanatique d'Arsenal
la source

Réponses:

143

L'écrêtage des dégradés doit avoir lieu après le calcul des dégradés, mais avant de les appliquer pour mettre à jour les paramètres du modèle. Dans votre exemple, ces deux choses sont gérées par la AdamOptimizer.minimize()méthode.

Pour découper vos dégradés, vous devez les calculer, les découper et les appliquer explicitement comme décrit dans cette section de la documentation API de TensorFlow . Plus précisément, vous devrez remplacer l'appel à la minimize()méthode par quelque chose comme ce qui suit:

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
la source
4
Styrke, merci pour le message. Savez-vous quelles sont les prochaines étapes pour exécuter une itération de l'optimiseur? En règle générale, un optimiseur est instancié au fur optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) et à mesure qu'une itération de l'optimiseur est effectuée, optimizer.run()mais l'utilisation optimizer.run()ne semble pas fonctionner dans ce cas?
applecider
6
Ok, ça optimizer.apply_gradients(capped_gvs)doit être assigné à quelque chose, x = optimizer.apply_gradients(capped_gvs)puis dans votre session, vous pouvez vous entraîner en tant quex.run(...)
applecider
3
Merci à @ remi-cuingnet pour la belle suggestion de modification . (Ce qui a malheureusement été rejeté par les critiques pressés)
Styrke
Cela me donne UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.donc en quelque sorte mes dégradés clairsemés sont convertis en denses. Une idée comment surmonter ce problème?
Pekka le
8
En fait, la bonne façon de découper les dégradés (selon les documents tensorflow, les informaticiens et la logique) est avec tf.clip_by_global_norm, comme suggéré par @danijar
gdelab
116

Malgré ce qui semble être populaire, vous voulez probablement découper tout le dégradé selon sa norme globale:

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))

Le découpage de chaque matrice de dégradé change individuellement leur échelle relative, mais est également possible:

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))

Dans TensorFlow 2, une bande calcule les dégradés, les optimiseurs proviennent de Keras, et nous n'avons pas besoin de stocker l'opération de mise à jour car elle s'exécute automatiquement sans la transmettre à une session:

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
la source
10
Bon exemple avec clip_by_global_norm()! Ceci est également décrit comme the correct way to perform gradient clippingdans la documentation tensorflow
MZHm
9
@Escachator C'est empirique et dépendra de votre modèle et éventuellement de la tâche. Ce que je fais est de visualiser la norme de gradient tf.global_norm(gradients)pour voir sa plage habituelle, puis de couper un peu au-dessus pour éviter que les valeurs aberrantes ne gâchent la formation.
danijar
1
appelleriez-vous toujours opt.minimize()après ou appelleriez-vous quelque chose de différent comme cela opt.run()est suggéré dans certains des commentaires sur d'autres réponses?
reese0106
3
@ reese0106 Non, optimizer.minimize(loss)c'est juste un raccourci pour calculer et appliquer les dégradés. Vous pouvez exécuter l'exemple dans ma réponse avec sess.run(optimize).
danijar
1
Donc, si j'utilisais tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)dans une fonction d'expérimentation, alors votre optimizeremplacerait mon train_opcorrect? En ce moment, train_op = optimizer.minimize(loss, global_step=global_step))j'essaye de m'assurer que je m'ajuste en conséquence ...
reese0106
10

Ceci est en fait correctement expliqué dans la documentation. :

L'appel de minimiser () prend en charge à la fois le calcul des gradients et leur application aux variables. Si vous souhaitez traiter les dégradés avant de les appliquer, vous pouvez à la place utiliser l'optimiseur en trois étapes:

  • Calculez les dégradés avec compute_gradients ().
  • Traitez les dégradés comme vous le souhaitez.
  • Appliquez les dégradés traités avec apply_gradients ().

Et dans l'exemple qu'ils fournissent, ils utilisent ces 3 étapes:

# 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)

Voici MyCappern'importe quelle fonction qui coiffe votre dégradé. La liste des fonctions utiles (autres que tf.clip_by_value()) est ici .

Salvador Dali
la source
appelleriez-vous toujours opt.minimize()après ou appelleriez-vous quelque chose de différent comme cela opt.run()est suggéré dans certains des commentaires sur d'autres réponses?
reese0106
@ reese0106 Non, vous devez attribuer le opt.apply_gradients(...)à une variable comme train_steppar exemple (comme vous le feriez pour opt.minimize(). Et dans votre boucle principale, vous l'appelez comme d'habitude pour vous entraînersess.run([train_step, ...], feed_dict)
dsalaj
Gardez à l'esprit que le gradient est défini comme le vecteur des dérivés de la perte par rapport à tous les paramètres du modèle. TensorFlow le représente sous la forme d'une liste Python contenant un tuple pour chaque variable et son dégradé. Cela signifie que pour couper la norme du gradient, vous ne pouvez pas couper chaque tenseur individuellement, vous devez considérer la liste à la fois (par exemple en utilisant tf.clip_by_global_norm(list_of_tensors)).
danijar le
8

Pour ceux qui voudraient comprendre l'idée de découpage en dégradé (par norme):

Chaque fois que la norme de gradient est supérieure à un seuil particulier, nous découpons la norme de gradient afin qu'elle reste dans le seuil. Ce seuil est parfois défini sur 5.

Soit le gradient g et le max_norm_threshold j .

Maintenant, si || g || > j , on fait:

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

C'est l'implémentation faite dans tf.clip_by_norm

kmario23
la source
si je dois sélectionner le seuil à la main, existe-t-il une méthode commune pour le faire?
ningyuwhut
C'est une sorte de magie noire suggérée dans certains articles. Sinon, vous devez faire beaucoup d'expériences et découvrir laquelle fonctionne le mieux.
kmario23
4

IMO, la meilleure solution consiste à envelopper votre optimiseur avec le décorateur d'estimateur de 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)

De cette façon, vous ne devez le définir qu'une seule fois et ne pas l'exécuter après chaque calcul de dégradés.

Documentation: https://www.tensorflow.org/api_docs/python/tf/contrib/estimator/clip_gradients_by_norm

Ido Cohn
la source
2

L'écrêtage de gradient aide essentiellement en cas d'explosion ou de disparition des gradients.Dis que votre perte est trop élevée, ce qui entraînera des gradients exponentiels à travers le réseau, ce qui peut entraîner des valeurs Nan. Pour surmonter cela, nous découpons les dégradés dans une plage spécifique (-1 à 1 ou toute plage selon les conditions).

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

où les grades _et_vars sont les paires de dégradés (que vous calculez via tf.compute_gradients) et leurs variables auxquelles ils seront appliqués.

Après le découpage, nous appliquons simplement sa valeur à l'aide d'un optimiseur. optimizer.apply_gradients(clipped_value)

Raj
la source