《动手学习深度学习》- Tensorflow自动求梯度

《动手学习深度学习》- Tensorflow自动求梯度

Scroll Down
import tensorflow as tf
import numpy as np
tf.__version__
'2.3.0'

1. 自动求梯度简介

  • 在深度学习中,我们经常需要对函数求梯度(gradient)。本节将介绍如何使用tensorflow2.0提供的GradientTape来自动求梯度。
    GradientTape 可以理解为“梯度流 记录磁带”:

  • 在记录阶段:记录被 GradientTape 包裹的运算过程中,依赖于 source node (被 watch “监视”的变量)的关系图。

  • 在求导阶段:通过搜索 source node 到 target node 的路径,进而计算出偏微分。

  • source node 在记录运算过程之前进行指定:

  • 自动“监控”所有可训练变量:GradientTape 默认(watch_accessed_variables=True)将所有可训练变量(created by tf.Variable, where trainable=True)视为需要“监控”的 source node 。

  • 对于不可训练的变量(比如tf.constant)可以使用tape.watch()对其进行“监控”。

  • 此外,还可以设定watch_accessed_variables=False,然后使用tf.watch()精确控制需要“监控”哪些变量

2. 简单示例

  • 对函数 $y = 2x^Tx$ 求关于向量 $x$ 的梯度
# 创建变量并赋初值

x = tf.reshape(tf.constant(range(4),dtype='float32'), (4,1))
x
<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[0.],
       [1.],
       [2.],
       [3.]], dtype=float32)>
  • 函数 $y = 2x^Tx$ 的梯度应该是 4$x$
  • t.watch(x) : 对于Variable类型的变量,一般不用加此监控 (source node)
with tf.GradientTape() as t:
    t.watch(x) #对于Variable类型的变量,一般不用加此监控 (source node)
    y = 2 * tf.matmul(tf.transpose(x),x)

g_dx = t.gradient(y,x)
g_dx
<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[ 0.],
       [ 4.],
       [ 8.],
       [12.]], dtype=float32)>

3. 训练模式和预测模式

  • 默认情况下, GradientTape 持有的资源会在 GradientTape.gradient() 方法被调用后立即释放。 要在同一计算中计算多个梯度,设置tf.GradientTape(persistent=True)。 他可以让 Tape 对象被垃圾回收时释放资源时多次调用 gradient() 方法。
with tf.GradientTape(persistent=True) as g:
    g.watch(x)
    y = x * x
    z = y * y 
    dz_dx = g.gradient(z,x)  # 108.0 (4*x^3 at x = 3) 注意这个不是矩阵乘积
    dy_dx = g.gradient(y,x)

dz_dx,dy_dx
WARNING:tensorflow:Calling GradientTape.gradient on a persistent tape inside its context is significantly less efficient than calling it outside the context (it causes the gradient ops to be recorded on the tape, leading to increased CPU and memory usage). Only call GradientTape.gradient inside the context if you actually want to trace the gradient in order to compute higher order derivatives.
WARNING:tensorflow:Calling GradientTape.gradient on a persistent tape inside its context is significantly less efficient than calling it outside the context (it causes the gradient ops to be recorded on the tape, leading to increased CPU and memory usage). Only call GradientTape.gradient inside the context if you actually want to trace the gradient in order to compute higher order derivatives.





(<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
 array([[  0.],
        [  4.],
        [ 32.],
        [108.]], dtype=float32)>,
 <tf.Tensor: shape=(4, 1), dtype=float32, numpy=
 array([[0.],
        [2.],
        [4.],
        [6.]], dtype=float32)>)

4. 对Python函数控制流求梯度

  • 即使函数的计算图包含了Python的控制流(如条件和循环控制),我们也有可能对变量求梯度。
  • 需要强调的是,这里循环(while循环)迭代的次数和条件判断(if语句)的执行都取决于输入a的值。
def func(a):
    b = a * 2
    # 计算b向量的范数
    while tf.norm(b) < 1000:
        b = b * 2
    # 对b中所有元素求和
    if tf.reduce_sum(b) > 0:
        c = b
    else:
        c = 100 * b
    return c
a = tf.random.normal((1,1),dtype=tf.float32)
a
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[-0.5131821]], dtype=float32)>
  • 我们来分析一下上面定义的f函数。事实上,给定任意输入a,其输出必然是 f(a) = x * a的形式,其中标量系数x的值取决于输入a。由于c = f(a)有关a的梯度为x,且值为c / a,我们可以像下面这样验证对本例中控制流求梯度的结果的正确性。
with tf.GradientTape() as t:
    t.watch(a)
    c = func(a)

t.gradient(c,a) == (c / a) 
<tf.Tensor: shape=(1, 1), dtype=bool, numpy=array([[ True]])>

5. 文档查阅

  • 当我们想知道一个模块里面提供了哪些可以调用的函数和类的时候,可以使用dir函数。下面我们打印dtypes和random模块中所有的成员或属性。
dir(tf.dtypes)
['DType',
 'QUANTIZED_DTYPES',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 '_sys',
 'as_dtype',
 'bfloat16',
 'bool',
 'cast',
 'complex',
 'complex128',
 'complex64',
 'double',
 'float16',
 'float32',
 'float64',
 'half',
 'int16',
 'int32',
 'int64',
 'int8',
 'qint16',
 'qint32',
 'qint8',
 'quint16',
 'quint8',
 'resource',
 'saturate_cast',
 'string',
 'uint16',
 'uint32',
 'uint64',
 'uint8',
 'variant']
  • 通常我们可以忽略掉由__开头和结尾的函数(Python的特别对象)或者由_开头的函数(一般为内部函数)。通过其余成员的名字我们大致猜测出这个模块提供了各种随机数的生成方法,包括从均匀分布采样(uniform)、从正态分布采样(normal)、从泊松分布采样(poisson)等。
dir(tf.random)
['Algorithm',
 'Generator',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 '_sys',
 'all_candidate_sampler',
 'categorical',
 'create_rng_state',
 'experimental',
 'fixed_unigram_candidate_sampler',
 'gamma',
 'get_global_generator',
 'learned_unigram_candidate_sampler',
 'log_uniform_candidate_sampler',
 'normal',
 'poisson',
 'set_global_generator',
 'set_seed',
 'shuffle',
 'stateless_binomial',
 'stateless_categorical',
 'stateless_gamma',
 'stateless_normal',
 'stateless_parameterized_truncated_normal',
 'stateless_poisson',
 'stateless_truncated_normal',
 'stateless_uniform',
 'truncated_normal',
 'uniform',
 'uniform_candidate_sampler']
  • 想了解某个函数或者类的具体用法时,可以使用help函数。让我们以ones函数为例,查阅它的用法。更详细的信息,可以通过Tensorflow的 API文档 版本选择页,选择与自己环境中的 tensorflow 版本一致的 API 版本进行查询。
help(tf.ones)
Help on function ones in module tensorflow.python.ops.array_ops:

ones(shape, dtype=tf.float32, name=None)
    Creates a tensor with all elements set to one (1).
    
    See also `tf.ones_like`, `tf.zeros`, `tf.fill`, `tf.eye`.
    
    This operation returns a tensor of type `dtype` with shape `shape` and
    all elements set to one.
    
    >>> tf.ones([3, 4], tf.int32)
    <tf.Tensor: shape=(3, 4), dtype=int32, numpy=
    array([[1, 1, 1, 1],
           [1, 1, 1, 1],
           [1, 1, 1, 1]], dtype=int32)>
    
    Args:
      shape: A `list` of integers, a `tuple` of integers, or
        a 1-D `Tensor` of type `int32`.
      dtype: Optional DType of an element in the resulting `Tensor`. Default is
        `tf.float32`.
      name: Optional string. A name for the operation.
    
    Returns:
      A `Tensor` with all elements set to one (1).