Je voudrais remplacer ou modifier le gradient d'un op ou d'une partie du graphique dans tensorflow. Ce serait idéal si je pouvais utiliser le gradient existant dans le calcul.
D'une certaine manière, c'est l'inverse de ce que fait tf.stop_gradient()
: au lieu d'ajouter un calcul qui est ignoré lors du calcul des gradients, je souhaite un calcul utilisé uniquement lors du calcul des gradients.
Un exemple simple serait quelque chose qui simplement échelle les gradients en les multipliant avec une constante (mais ne multiplie pas le calcul en avant par une constante). Un autre exemple serait quelque chose qui coupe les dégradés dans une plage donnée.
Pour tensorflow 1.7 ou plus récent, examinez le montage.
Définissez d'abord votre dégradé personnalisé:
@tf.RegisterGradient("CustomGrad")
def _const_mul_grad(unused_op, grad):
return 5.0 * grad
Puisque vous souhaitez que rien ne se passe dans le passage en aval, remplacez le dégradé d'une opération d'identité par votre nouveau dégradé:
g = tf.get_default_graph()
with g.gradient_override_map({"Identity": "CustomGrad"}):
output = tf.identity(input, name="Identity")
Voici un exemple de travail avec un calque qui coupe les dégradés dans la passe en arrière et ne fait rien dans la passe en avant, en utilisant la même méthode:
import tensorflow as tf
@tf.RegisterGradient("CustomClipGrad")
def _clip_grad(unused_op, grad):
return tf.clip_by_value(grad, -0.1, 0.1)
input = tf.Variable([3.0], dtype=tf.float32)
g = tf.get_default_graph()
with g.gradient_override_map({"Identity": "CustomClipGrad"}):
output_clip = tf.identity(input, name="Identity")
grad_clip = tf.gradients(output_clip, input)
# output without gradient clipping in the backwards pass for comparison:
output = tf.identity(input)
grad = tf.gradients(output, input)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print("with clipping:", sess.run(grad_clip)[0])
print("without clipping:", sess.run(grad)[0])
éditer pour TensorFlow 1.7
Depuis la version 1.7, il existe un nouveau moyen de redéfinir le dégradé avec une syntaxe plus courte. (Cela permet également de redéfinir le gradient de plusieurs opérations en même temps, ce qui n'est pas nécessaire pour cette question). Voici les exemples ci-dessus, réécrits pour TensorFlow 1.7:
Couche qui met à l'échelle les gradients dans la passe arrière:
@tf.custom_gradient
def scale_grad_layer(x):
def grad(dy):
return 5.0 * dy
return tf.identity(x), grad
Exemple avec un calque qui coupe les dégradés dans la passe en arrière:
import tensorflow as tf
input = tf.Variable([3.0], dtype=tf.float32)
@tf.custom_gradient
def clip_grad_layer(x):
def grad(dy):
return tf.clip_by_value(dy, -0.1, 0.1)
return tf.identity(x), grad
output_clip = clip_grad_layer(input)
grad_clip = tf.gradients(output_clip, input)
# output without gradient clipping in the backwards pass for comparison:
output = tf.identity(input)
grad = tf.gradients(output, input)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print("with clipping:", sess.run(grad_clip)[0])
print("without clipping:", sess.run(grad)[0])
utilisation optimizer.compute_gradients
ou tf.gradient
pour obtenir des dégradés originaux
alors faites ce que vous voulez
Enfin, utilisez optimizer.apply_gradients
J'ai trouvé un exemple de github
En supposant que le calcul en avant est
y = f(x)
Et vous voulez qu'il se propage comme
y = b(x)
Un simple hack sera:
y = b(x) + tf.stop_gradient(f(x) - b(x))
La manière la plus générale de le faire est d'utiliser https://www.tensorflow.org/api_docs/python/tf/RegisterGradient
Ci-dessous, j'ai implémenté un écrêtage en dégradé rétroprojecté, qui peut être utilisé avec matmul
, comme indiqué ici, ou tout autre op:
import tensorflow as tf
import numpy as np
# from https://Gist.github.com/harpone/3453185b41d8d985356cbe5e57d67342
def py_func(func, inp, Tout, stateful=True, name=None, grad=None):
# Need to generate a unique name to avoid duplicates:
rnd_name = 'PyFuncGrad' + str(np.random.randint(0, 1E+8))
tf.RegisterGradient(rnd_name)(grad)
g = tf.get_default_graph()
with g.gradient_override_map({"PyFunc": rnd_name}):
return tf.py_func(func, inp, Tout, stateful=stateful, name=name)
def clip_grad(x, clip_value, name=None):
""""
scales backpropagated gradient so that
its L2 norm is no more than `clip_value`
"""
with tf.name_scope(name, "ClipGrad", [x]) as name:
return py_func(lambda x : x,
[x],
[tf.float32],
name=name,
grad=lambda op, g : tf.clip_by_norm(g, clip_value))[0]
Exemple d'utilisation:
with tf.Session() as sess:
x = tf.constant([[1., 2.], [3., 4.]])
y = tf.constant([[1., 2.], [3., 4.]])
print('without clipping')
z = tf.matmul(x, y)
print(tf.gradients(tf.reduce_sum(z), x)[0].eval())
print('with clipping')
z = tf.matmul(clip_grad(x, 1.0), clip_grad(y, 0.5))
print(tf.gradients(tf.reduce_sum(z), x)[0].eval())
print('with clipping between matmuls')
z = tf.matmul(clip_grad(tf.matmul(x, y), 1.0), y)
print(tf.gradients(tf.reduce_sum(z), x)[0].eval())
Sortie:
without clipping
[[ 3. 7.]
[ 3. 7.]]
with clipping
[[ 0.278543 0.6499337]
[ 0.278543 0.6499337]]
with clipping between matmuls
[[ 1.57841039 3.43536377]
[ 1.57841039 3.43536377]]
Pour TensorFlow r1.13 actuel, utilisez tf.custom_gradient .
La fonction décorée (les arguments d'entrée est une liste x
) devrait retourner
x
.Voici un exemple avec une variable:
@tf.custom_gradient
def non_differentiable(x):
f = tf.cast(x > 0, tf.float32)
def grad(dy):
return tf.math.maximum(0., 1 - tf.abs(x))
return f, grad
Et une avec deux:
@tf.custom_gradient
def non_differentiable2(x0, x1):
f = x0 * tf.cast(x1 > 0, tf.float32)
def grad(dy):
df_dx0 = tf.cast(x1 > 0, tf.float32)
return dy*df_dx0, tf.zeros_like(dy)
return f, grad