Tensorflow(三) 自动微分

2022/5/5 23:21:17

本文主要是介绍Tensorflow(三) 自动微分,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本章学习TensorFlow中的自动微分:
https://tensorflow.google.cn/guide/autodiff

自动微分的含义

参考:

  • https://zhuanlan.zhihu.com/p/61103504
  • https://en.wikipedia.org/wiki/Automatic_differentiation

深度学习中的反向传播的实现借助于自动微分。 让计算机实现微分功能, 有以下四种方式:

  • 手工计算出微分, 然后编码进代码
  • 数值微分 (numerical differentiation)
  • 符号微分 (symbolic differentiation)
  • 自动微分

自动微分是将一个复杂的数学运算过程分解为一系列简单的基本运算, 每一项基本运算都可以通过查表得出来。自动微分有两种形式

  • 前向模式 (forward mode)

前向模式是在计算图前向传播的同时计算微分。
image

  • 反向模式 (reverse mode)

反向模式需要对计算图进行一次正向计算, 得出输出值,再进行反向传播
image

前向模式的一次正向传播能够计算出输出值以及导数值, 而反向模式需要先进行正向传播计算出输出值, 然后进行反向传播计算导数值,所以反向模式的内存开销要大一点, 因为它需要保存正向传播中的中间变量值,这些变量值用于反向传播的时候计算导数。
当输出的维度大于输入的时候,适宜使用前向模式微分;当输出维度远远小于输入的时候,适宜使用反向模式微分。、
从矩阵乘法次数的角度来看,前向模式和反向模式的不同之处在于矩阵相乘的起始之处不同。当输出维度小于输入维度,反向模式的乘法次数要小于前向模式。

TensorFlow中的自动微分

自动微分对于实现机器学习算法非常有用,例如用于训练神经网络的反向传播。
为了自动区分,TensorFlow 需要记住在正向传递期间以什么顺序发生的操作。然后,在向后传递期间,TensorFlow 以反向顺序遍历此操作列表以计算梯度。

Gradient tapes

TensorFlow 提供了 tf.GradientTapeAPi来实现自动微分。对与数据输入(通常是tf.Variable),TensorFlow会将其操作记录记录到TensorFlow的 tf.GradientTape库中的tape里。然后,TensorFlow使用该tape使用反向模式微分来计算“记录”计算的梯度。
示例:

x = tf.Variable(3.0)

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

记录完一些操作后,使用 GradientTape.gradient(target,source)计算某个目标相对于某个Source(通常是模型的Variable)的梯度(通常是Loss):

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()

6.0

用于二维的Tensor:

w = tf.Variable(tf.random.normal((3, 2)), name='w')
b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b')
x = [[1., 2., 3.]]

with tf.GradientTape(persistent=True) as tape:
  y = x @ w + b
  loss = tf.reduce_mean(y**2)

以下两种写法进行梯度计算:

# list
[dl_dw, dl_db] = tape.gradient(loss, [w, b])

# dictionary
my_vars = {
    'w': w,
    'b': b
}
grad = tape.gradient(loss, my_vars)
grad['b']

用于Model

通常将 tf.Variables 收集到 tf.Module 或其子类之一(layers.Layer、keras.Model)中,用于设置检查点和导出。
在大多数情况下,需要计算相对于模型的可训练变量的梯度。 由于 tf.Module 的所有子类都在 Module.trainable_variables 属性中聚合其变量,可以用几行代码计算这些梯度:

layer = tf.keras.layers.Dense(2, activation='relu')
x = tf.constant([[1., 2., 3.]])

with tf.GradientTape() as tape:
  # Forward pass
  y = layer(x)
  loss = tf.reduce_mean(y**2)

# Calculate gradients with respect to every trainable variable
grad = tape.gradient(loss, layer.trainable_variables)

for var, g in zip(layer.trainable_variables, grad):
  print(f'{var.name}, shape: {g.shape}')

dense/kernel:0, shape: (3, 2)
dense/bias:0, shape: (2,)

tape的监控

默认情况下,可以访问训练 tf.Variable后记录所有运算。原因如下:

  • tape需要知道在前向传递中记录哪些运算,以计算后向传递中的梯度。
  • 梯度带包含对中间输出的引用,因此应避免记录不必要的操作。
  • 最常见用例涉及计算损失相对于模型的所有可训练变量的梯度。
# A trainable variable
x0 = tf.Variable(3.0, name='x0')
# Not trainable
x1 = tf.Variable(3.0, name='x1', trainable=False)
# Not a Variable: A variable + tensor returns a tensor.
x2 = tf.Variable(2.0, name='x2') + 1.0
# Not a variable
x3 = tf.constant(3.0, name='x3')

with tf.GradientTape() as tape:
  y = (x0**2) + (x1**2) + (x2**2)

grad = tape.gradient(y, [x0, x1, x2, x3])

for g in grad:
  print(g)

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

可以使用 GradientTape.watched_variables 方法列出梯度带正在监视的变量:

[var.name for var in tape.watched_variables()]

tf.GradientTape 提供了hook,让用户可以控制被监视或不被监视的内容。
要记录相对于 tf.Tensor 的梯度,您需要调用 GradientTape.watch(x)

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

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
print(dy_dx.numpy())

6.0

相反,要停用监视所有 tf.Variables 的默认行为,需要在创建gradient tape时设置 watch_accessed_variables=False。此计算使用两个变量,但仅连接其中一个变量的梯度:

x0 = tf.Variable(0.0)
x1 = tf.Variable(10.0)

with tf.GradientTape(watch_accessed_variables=False) as tape:
  tape.watch(x1)
  y0 = tf.math.sin(x0)
  y1 = tf.nn.softplus(x1)
  y = y0 + y1
  ys = tf.reduce_sum(y)
  
# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1)
grad = tape.gradient(ys, {'x0': x0, 'x1': x1})

print('dy/dx0:', grad['x0'])
print('dy/dx1:', grad['x1'].numpy())

dy/dx0: None
dy/dx1: 0.9999546

中间结果

还可以请求输出相对于 tf.GradientTape 上下文中计算的中间值的梯度。

x = tf.constant(3.0)

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

# Use the tape to compute the gradient of z with respect to the
# intermediate value y.
# dz_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())

18.0

默认情况下,只要调用 GradientTape.gradient 方法,就会释放 GradientTape 保存的资源。要在同一计算中计算多个梯度,请创建一个 persistent=True的梯度带。这样一来,当梯度带对象作为垃圾回收时,随着资源的释放,可以对 gradient 方法进行多次调用。例如:

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())  # 108.0 (4 * x**3 at x = 3)
print(tape.gradient(y, x).numpy())  # 6.0 (2 * x)

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



这篇关于Tensorflow(三) 自动微分的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程