用计算图进行自动微分
思考反向传播的一种有用方法是利用计算图(compu- tation graph)。计算图是TensorFlow和深度学习革命的核心数据结构。它是一种由运算(比如我们用到的张量运算)构成的有向无环图。下图给出了一个模型的计算图表示。
计算图是计算机科学中一个非常成功的抽象概念。有了计算图,我们可以将计算看作数据:将可计算的表达式编码为机器可读的数据结构,然后作为另一个程序的输入或输出。举个例子,你可以想象这样一个程序:接收一个计算图作为输入,并返回一个新的计算图,新计算图可实现相同计算的大规模分布式版本。这意味着你可以对任意计算实现分布式,而无须自己编写分布式逻辑。或者想象这样一个程序:接收一个计算图作为输入,然后自动计算它所对应表达式的导数。如果将计算表示为一个明确的图数据结构,而不是.py文件中的几行ASCII字符,那么做这些事情就容易多了。为了解释清楚反向传播的概念,我们来看一个非常简单的计算图示例,只有一个线性层,所有变量都是标量。我们将取两个标量变量w和b,以及一个标量输入x,然后对它们做一些运算,得到输出y。最后,我们使用绝对值误差损失函数:loss_val = abs(y_true - y)。我们希望更新w和b以使loss_val最小化,所以需要计算grad(loss_val, b)和grad(loss_val, w)。
我们为图中的“输入节点”(输入x、目标y_true、w和b)赋值。我们将这些值传入图中所有节点,从上到下,直到loss_val。这就是前向传播过程(如下图)。
下面我们“反过来”看这张图。对于图中从A到B的每条边,我们都画一条从B到A的反向边,如果A发生变化,那么B会怎么变?也就是说,grad(B, A)是多少?我们在每条反向边上标出这个值。这个反向图表示的是反向传播过程(见下图)。
我们得到以下结果。grad(loss_val, x2) = 1。随着x2变化一个小量epsilon,loss_val = abs(4 - x2)的变化量相同。grad(x2, x1) = 1。随着x1变化一个小量epsilon,x2 = x1 + b = x1 + 1的变化量相同。grad(x2, b) = 1。随着b变化一个小量epsilon,x2 = x1 + b = 6 + b的变化量相同。grad(x1, w) = 2。随着w变化一个小量epsilon,x1 = x * w = 2 * w的变化量为2 * epsilon。链式法则告诉我们,对于这个反向图,想求一个节点相对于另一个节点的导数,可以把连接这两个节点的路径上的每条边的导数相乘。比如,grad(loss_val, w) =grad(loss_val, x2) * grad(x2, x1) * grad(x1, w),如下图所示。
对该图应用链式法则,我们可以得到如下想要的结果。grad(loss_val, w) = 1 * 1 * 2 = 2grad(loss_val, b) = 1 * 1 = 1注意 在反向图中,如果两个节点a和b之间有多条路径,那么grad(b, a)就是将所有路径的值相加。刚刚,你看到的就是反向传播的具体过程。反向传播就是将链式法则应用于计算图,仅此而已。反向传播从最终损失值开始,自下而上反向运行,计算每个参数对损失值的贡献。这就是“反向传播”这个名称的由来:我们“反向传播”计算图中不同节点对损失值的贡献。人们利用能够自动微分的现代框架来实现神经网络,比如TensorFlow。自动微分是利用前文所述的计算图来实现的。自动微分可以计算任意可微张量运算组合的梯度,只需要写出前向传播,而无须做任何额外工作。
TensorFlow的梯度带
GradientTape是一个API,让你可以充分利用TensorFlow强大的自动微分能力。它是一个Python作用域(scope),能够以计算图[有时也被称为“条带”(tape)]的形式“记录”在其中运行的张量运算。计算图可用来获取任意输出相对于任意变量或变量集的梯度,这些变量或变量集都是tf.Variable类的实例。tf.Variable是一类用于保存可变状态的张量,比如神经网络的权重就是tf.Variable的实例。
import tensorflow as tf
#将标量Variable的值初始化为0
x = tf.Variable(0.