本章学习TensorFlow中的自动微分:
https://tensorflow.google.cn/guide/autodiff
参考:
深度学习中的反向传播的实现借助于自动微分。 让计算机实现微分功能, 有以下四种方式:
自动微分是将一个复杂的数学运算过程分解为一系列简单的基本运算, 每一项基本运算都可以通过查表得出来。自动微分有两种形式
前向模式是在计算图前向传播的同时计算微分。
反向模式需要对计算图进行一次正向计算, 得出输出值,再进行反向传播
前向模式的一次正向传播能够计算出输出值以及导数值, 而反向模式需要先进行正向传播计算出输出值, 然后进行反向传播计算导数值,所以反向模式的内存开销要大一点, 因为它需要保存正向传播中的中间变量值,这些变量值用于反向传播的时候计算导数。
当输出的维度大于输入的时候,适宜使用前向模式微分;当输出维度远远小于输入的时候,适宜使用反向模式微分。、
从矩阵乘法次数的角度来看,前向模式和反向模式的不同之处在于矩阵相乘的起始之处不同。当输出维度小于输入维度,反向模式的乘法次数要小于前向模式。
自动微分对于实现机器学习算法非常有用,例如用于训练神经网络的反向传播。
为了自动区分,TensorFlow 需要记住在正向传递期间以什么顺序发生的操作。然后,在向后传递期间,TensorFlow 以反向顺序遍历此操作列表以计算梯度。
TensorFlow 提供了 tf.GradientTape
APi来实现自动微分。对与数据输入(通常是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']
通常将 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,)
默认情况下,可以访问训练 tf.Variable
后记录所有运算。原因如下:
# 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.]