Tensorflow:如何替换或修改渐变?

时间:2017-05-08 03:43:38

标签: python tensorflow neural-network

我想在tensorflow中替换或修改op的梯度或图的一部分。如果我可以在计算中使用现有的渐变,那将是理想的。

在某些方面,这与tf.stop_gradient()的作用相反:在计算渐变时,我想要一个仅在计算渐变时使用的计算,而不是添加一个被忽略的计算。

一个简单的例子是通过将它们与常数相乘(但不会将正向计算乘以常数)来简单地缩放渐变。另一个例子是将渐变剪辑到给定范围。

6 个答案:

答案 0 :(得分:48)

对于tensorflow 1.7或更新版本,请查看编辑打击。

首先定义自定义渐变:

@tf.RegisterGradient("CustomGrad")
def _const_mul_grad(unused_op, grad):
  return 5.0 * grad

由于您不希望在前向传递中发生任何事情,请使用新渐变覆盖身份操作的渐变:

g = tf.get_default_graph()
with g.gradient_override_map({"Identity": "CustomGrad"}):
  output = tf.identity(input, name="Identity")

这是一个工作示例,其中一个图层使用相同的方法在向后传递中剪切渐变并且在前向传递中不执行任何操作:

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

编辑TensorFlow 1.7

从1.7开始,有一种新方法可以用更短的语法重新定义渐变。 (它还允许同时重新定义多个操作的梯度,这个问题不需要)。以下是上面的示例,为TensorFlow 1.7重写:

在后向传递中缩放渐变的图层:

@tf.custom_gradient
def scale_grad_layer(x):
  def grad(dy):
    return 5.0 * dy
  return tf.identity(x), grad

在后向传递中剪切渐变的图层示例:

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

答案 1 :(得分:15)

使用optimizer.compute_gradientstf.gradient获取原始渐变 然后做你想做的事 最后,使用optimizer.apply_gradients

我从github找到了example

答案 2 :(得分:11)

假设正向计算

y = f(x)

你希望它像

一样反向传播
y = b(x)

一个简单的黑客将是:

y = b(x) + tf.stop_gradient(f(x) - b(x))

答案 3 :(得分:8)

最常见的方法是使用 https://www.tensorflow.org/api_docs/python/tf/RegisterGradient

下面,我实现了反向传播的渐变剪辑,可以与matmul一起使用,如此处所示,或者任何其他操作:

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]

使用示例:

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

输出:

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

答案 4 :(得分:2)

对于TensorFlow 2,您应按以下方式使用tf.custom_gradient装饰器:

@tf.custom_gradient
def func(x):
    f = # calculate forward pass
    def grad(dy):
        gradient = # calculate custom gradient of func
        return dy * gradient
    return f, grad

请注意,您必须将梯度乘以上游梯度。不过要小心!

如果您在创建Keras功能模型并使用tf.GradientTape时将此函数称为函数,则仍将进行自动微分,并且您的自定义渐变将被忽略。

相反,您必须将函数放入一个层:

class func_layer(tf.keras.layers.Layer):
    def __init__(self):
        super(func_layer, self).__init__()

    def call(self, x):
        return func(x)

现在,当您向功能模型添加func_layer时,将适当地计算出反向传递。

答案 5 :(得分:0)

对于当前的TensorFlow r1.13,请使用tf.custom_gradient

修饰的函数(输入参数是列表x)应返回

  • 前进通过的结果,
  • 返回梯度列表的函数,x中的每个元素一个。

这是一个带有一个变量的示例:

@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

一加二:

@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