我正在尝试切换到TensorFlow急切模式,并且发现GradientTapeimplicit_gradientsgradients_functionimplicit_value_and_gradients的文档令人困惑。

它们之间有什么区别?我什么时候应该在另一个上使用?

intro point in the documentation根本没有提到隐式*函数,但是TensorFlow存储库中的几乎所有示例似乎都使用该方法来计算梯度。

最佳答案

启用急切执行时,有4种自动计算梯度的方法(实际上,它们也可在图形模式下工作):

  • tf.GradientTape上下文记录了计算,因此您可以调用tfe.gradient()来获取在记录任何可训练变量时计算出的任何张量的梯度。
  • tfe.gradients_function()接受一个函数(例如f())并返回一个梯度函数(例如fg()),该函数可以根据f()(或其子集)的参数计算f()输出的梯度。
  • tfe.implicit_gradients()非常相似,但是fg()会根据这些输出所依赖的所有可训练变量来计算f()输出的梯度。
  • tfe.implicit_value_and_gradients()几乎相同,但是fg()也返回f()函数的输出。

  • 通常,在机器学习中,您将需要针对模型参数(即变量)计算损失的梯度,并且您通常也会对损失本身的值(value)感兴趣。对于此用例,最简单,最有效的选项是tf.GradientTapetfe.implicit_value_and_gradients()(其他两个选项不给您损失值本身,因此,如果您需要它,则将需要进行额外的计算)。在编写生产代码时,我个人更喜欢tfe.implicit_value_and_gradients(),而在Jupyter笔记本中进行实验时,我更喜欢tf.GradientTape

    编辑:在TF 2.0中,似乎只保留了tf.GradientTape。也许其他功能会重新添加,但我不会指望它。

    详细的例子

    让我们创建一个小函数来突出显示差异:
    import tensorflow as tf
    import tensorflow.contrib.eager as tfe
    tf.enable_eager_execution()
    
    w1 = tfe.Variable(2.0)
    w2 = tfe.Variable(3.0)
    ​
    def weighted_sum(x1, x2):
        return w1 * x1 + w2 * x2
    
    s = weighted_sum(5., 7.)
    print(s.numpy()) # 31
    

    使用tf.GradientTape
    GradientTape上下文中,记录了所有操作,然后您可以针对任何可训练变量,计算在上下文中计算出的任何张量的梯度。例如,此代码在s上下文中计算GradientTape,然后计算关于sw1的梯度。从s = w1 * x1 + w2 * x2开始,s关于w1的梯度为x1:
    with tf.GradientTape() as tape:
        s = weighted_sum(5., 7.)
    ​
    [w1_grad] = tape.gradient(s, [w1])
    print(w1_grad.numpy()) # 5.0 = gradient of s with regards to w1 = x1
    

    使用tfe.gradients_function()
    该函数返回另一个函数,该函数可以根据函数的参数计算返回值的梯度。例如,我们可以使用它来定义一个函数,该函数将针对sx1计算x2的梯度:
    grad_fn = tfe.gradients_function(weighted_sum)
    x1_grad, x2_grad = grad_fn(5., 7.)
    print(x1_grad.numpy()) # 2.0 = gradient of s with regards to x1 = w1
    

    在优化的上下文中,对于我们可以调整的变量,计算梯度是更有意义的。为此,我们可以更改weighted_sum()函数,使其也接受w1w2作为参数,并告诉tfe.gradients_function()仅考虑名为"w1""w2"的参数:
    def weighted_sum_with_weights(w1, x1, w2, x2):
        return w1 * x1 + w2 * x2
    
    grad_fn = tfe.gradients_function(weighted_sum_with_weights, params=["w1", "w2"])
    [w1_grad, w2_grad] = grad_fn(w1, 5., w2, 7.)
    print(w2_grad.numpy()) # 7.0 = gradient of s with regards to w2 = x2
    

    使用tfe.implicit_gradients()
    该函数返回另一个函数,该函数可以针对函数所依赖的所有可训练变量来计算函数返回值的梯度。回到weighted_sum()的第一个版本,我们可以使用它来计算s关于w1w2的梯度,而不必显式传递这些变量。注意,梯度函数返回梯度/变量对的列表:
    grad_fn = tfe.implicit_gradients(weighted_sum)
    [(w1_grad, w1_var), (w2_grad, w2_var)] = grad_fn(5., 7.)
    print(w1_grad.numpy()) # 5.0 = gradient of s with regards to w1 = x1
    
    assert w1_var is w1
    assert w2_var is w2
    

    该函数似乎是最简单,最有用的选项,因为通常我们对模型参数(即变量)的损耗梯度进行计算很感兴趣。
    注意:尝试使w1不可训练(w1 = tfe.Variable(2., trainable=False))并重新定义weighted_sum(),您将看到grad_fn仅返回s关于w2的梯度。

    使用tfe.implicit_value_and_gradients()
    此函数与implicit_gradients()几乎相同,除了它创建的函数还返回区分函数的结果(在这种情况下为weighted_sum()):
    grad_fn = tfe.implicit_value_and_gradients(weighted_sum)
    s, [(w1_grad, w1_var), (w2_grad, w2_var)] = grad_fn(5., 7.)
    print(s.numpy()) # 31.0 = s = w1 * x1 + w2 * x2
    

    当您同时需要函数的输出及其梯度时,此函数可以为您带来很好的性能提升,因为使用autodiff计算梯度时您可以免费获得函数的输出。

    10-06 01:55