Tensorflow2.x 利用“GradientTape 梯度带”自动求梯度

本文介绍了Tensorflow2.x的tf.GradientTape API,用于自动微分和计算梯度。内容包括梯度带的使用、中间结果处理、多目标梯度、控制流中的梯度、获取None梯度的情况以及如何处理零梯度。通过实例展示了如何在控制流中正确计算梯度,并解释了为何有时会得到None或零梯度的情况。

自动微分和梯度

Tensorflow为自动微分提供了 tf.GradientTape API,将tf.GradientTape上下文内执行的相关运算记录到“条带”上,随后使用该条带通过反向传播模式计算梯度。

要实现自动微分,Tensorflow需要记忆前向传播过程中的运算顺序;
后向传播期间,Tensorflow以相反的顺序遍历此运算列表来计算梯度。

使用 GradientTape.gradient(target,sources) 计算某个目标相对于模型变量的梯度。
使用GradientTape.watch(x) 可以设置要监视的变量。

x = tf.constant(3.0)
with tf.GradientTape() as tape:
    tape.watch(x)
    y = x**2

dy_dx = tape.gradient(y,x)
dy_dx.numpy()

6.0

要停用监视所有 tf.Variables 的默认行为,请在创建梯度带时设置 watch_accessed_variables=False.

中间结果

如果要在统一计算中计算多个梯度,需要创建一个 persistent=True 的梯度带。

x = tf.constant([1,3.0])

with tf.GradientTape(persistent = True) as tape:
    tape.watch(x)
    y = x*x
    z = y*y

print(tape.gradient(z,x).numpy())
print(tape.gradient(y,x).numpy())

[ 4. 108.]
[2. 6.]

如果不设置 persistent=True,计算y对x梯度时会报错。
在这里插入图片描述

多目标的梯度

单目标的梯度:

x = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
    y0 = x**2;
    y1 = 1/x
print(tape.gradient(y0,x).numpy())
print(tape.gradient(y1,x).numpy())

4.0
-0.25

多目标的梯度:

tape.gradient({'y0':y0, 'y1':y1},x).numpy()

3.75

多个目标的梯度即:单目标梯度的总和

这样一来,就可以轻松获取损失集合总和的梯度。

控制流

梯度在控制语句(例如if while语句)中,仅记录使用到的变量。

x = tf.constant(1.0)
v0 = tf.Variable(2.0)
v1 = tf.Variable(2.0)

with tf.GradientTape(persistent=True) as tape:
    tape.watch(x)
    if x > 0.0:
        result = v0
    else:
        result = v1**2

dv0, dv1 = tape.gradient(result,[v0,v1])

print(dv0)
print(dv1)

tf.Tensor(1.0, shape=(), dtype=float32)
None

只有v0有梯度,v1未使用到,故梯度为None

获取None的梯度

当目标未连接到变量时,您将获得None的梯度。

x = tf.Variable(2.)
y = tf.Variable(3.)

with tf.GradientTape() as tape:
  z = y * y
print(tape.gradient(z, x))

None

此处 z 显然未连接到 x,因此梯度为None。

  1. 一种常见错误是不经意间用tensor替换了Variable,而得不到梯度。
# 1.使用张量替换变量, 梯度带自动监视tf.Variable,但不会监视tf.tensor
x = tf.Variable(2.0)

for epoch in range(2):
    with tf.GradientTape() as tape:
        y = x+1
    
    print(type(x).__name__, ":", tape.gradient(y,x))
    x = x+1

ResourceVariable : tf.Tensor(1.0, shape=(), dtype=float32)
EagerTensor : None

正确的做法是:用assign()更新 x.assign(x+1)

  1. 在tensorflow之外进行了计算
    如果计算退出 TensorFlow,梯度带将无法记录梯度路径。
import numpy as np
x = tf.Variable([[1.0,2.0],
                [3.0,4.0]], dtype=tf.float32)

with tf.GradientTape() as tape:
    x2 = x**2
    y = np.mean(x2, axis=0)    # 不在tensorflow内的运算
    y = tf.reduce_mean(y,axis=0)

print(tape.gradient(y,x))

None

  1. 通过整数或字符串获取梯度

整数字符串不可微分。如果计算路径使用这些数据类型,则不会出现梯度。

x = tf.constant(10)

with tf.GradientTape() as g:
    g.watch(x)
    y = x*x

print(g.gradient(y,x))

WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32
WARNING:tensorflow:The dtype of the target tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
None

上述例子只要把x替换为 10.0 即可正常运行。

  1. 通过有状态对象获取梯度。
    tf.Variable 具有内部状态,即它的值。使用变量时,会读取状态。计算相对于变量的梯度是正常操作,但是变量的状态改变会阻止梯度计算进一步向后移动。 例如:
x0 = tf.Variable(3.0)
x1 = tf.Variable(0.0)

with tf.GradientTape() as tape:
  # Update x1 = x1 + x0.
  x1.assign_add(x0)
  # The tape starts recording from x1.
  y = x1**2   # y = (x1 + x0)**2

# This doesn't work.
print(tape.gradient(y, x0))   #dy/dx0 = 2*(x1 + x0)

None

类似地,tf.data.Dataset 迭代器和 tf.queue 也有状态,会停止经过它们的张量上的所有梯度。

零而不是None

在某些情况下,对于未连接到的梯度,得到0而不是None比较方便。
您可以使用 unconnected_gradients 参数决定具有未连接的梯度时的返回值。

x = tf.Variable([2.0,2.0])
y = tf.Variable(3.0)

with tf.GradientTape() as tape:
    z = y**2

print(tape.gradient(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO))

tf.Tensor([0. 0.], shape=(2,), dtype=float32)

z未连接到x,得到梯度为0。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值