动机 在这个部分我们通过一些“直觉”来理解反向传播,也就是一种使用链式(求导)法则递归地计算梯度表达式的方法。理解其中的精妙之处非常重要,帮助理解,高效地开发,设计,调试神经网络。
问题陈述 前面学到的核心问题是:我们有一个函数
动机 我们之所以对这个问题如此感兴趣的原因是神经网络,
如果你在上课之前就已经知道如何使用链式法则得到梯度,我仍旧希望你可以留在这里,因为我们展示了作为实值传递圈逆流的反向传播的一些很成熟的想法,你可能从其中得到很多帮助。
我们由浅入深地学习一下。想象一个简单的乘积函数
解释 导数的意义是:在某个点的无限小的距离内,函数值沿一个变量变化方向的变化率:
等式左边的除号和等式右边的除号不一样,它不是除号。这里的操作
每个变量的导数表明了这个值对整个表达式的敏感度
像上面提到的,梯度
我们也可以导出加法函数的偏导:
也就是说,
直觉上来说,如果输入是
现在我们考虑更加复杂包括多种操作的表达式,例如
# set some inputs x = -2; y = 5; z = -4 # perform the forward pass q = x + y # q becomes 3 f = q * z # f becomes -12 # perform the backward pass (backpropagation) in reverse order: # first backprop through f = q * z dfdz = q # df/dz = q, so gradient on z becomes 3 dfdq = z # df/dq = z, so gradient on q becomes -4 # now backprop through q = x + y dfdx = 1.0 * dfdq # dq/dx = 1. And the multiplication here is the chain rule! dfdy = 1.0 * dfdq # dq/dy = 1
最后我们将梯度保存在变量中[dfdx,dfdy,dfdz],也就是告诉我们
x,y,z对于函数
f的敏感度。这是最简单的反向传播的例子。我们想要一些更加简明的标记方法,这样我们就不用一直写
df这种东西了。现在我们将
dfdq简化成
dq,并且约定这个梯度始终对应最终输出。
计算过程也可以很好的用电路图表达出来
上图中显示了计算过程的电路图。前向传播从输入开始计算输出(绿色)。反向传播则从最后开始递归地为每一个输入应用链式法则计算梯度(红色)。梯度可以看做沿着电路图反向流动。
直观理解反向传播
反向传播是非常精妙的操作,电路图中的每个门都接收一些输入,然后立刻计算两个东西:1、输出值,2、计算输出对于输入值的梯度。这些门的计算都是完全独立的,不必了解整个电路图中的分布情况。但是,一旦前向传播结束,在反向传播的过程中,这些门会习得自己的输出对于整个电路图的输出的梯度。链式法则中指出,这些门应将梯度乘进所有输入梯度中。
这额外的乘操作(对每个输入)是因为链式法则可以将一个简单的相对无用的门转变成一个复杂电路例如整个神经网络中的一个替代品。
我们再从例子里理解这一切如何运作。“和”门接受两个输入[-2,5],计算输出为3。由于这个门的计算为和运算,所以对每个输入的梯度都是+1。剩下的部分则进行积运算,结果为-12。在反向递归计算梯度的过程中,和门(积门的一个输入)学习到他对于输出的梯度为-4。如果我们将这个电路图人格化为想要输出更高的值,那么我们就希望和门输出的结果要小一些,并且是4倍关系。接下来,和门将所有输入都乘上梯度-4。如果x,y减小,在减小,和门输出的结果是减小的,但是总输出是增大的。
反向传播可以认为是不同的门之间的通信,以决定他们是想让输出更高还是更低(还有多快地增高或降低),以影响最终输出。
模块化:以Sigmoid为例
上面我们说到的门是胡编乱造的。任何可识别的函数都可以像门一样运作,我们也可以将许多门放到一个门中去,或者将一个函数分解为若干个门。我们看下一个例子:
后面我们可以看到,这个式子描述了使用sigmoid的二维神经元(有输入x以及权重w),但是现在我们把他想像成简单的w,x输入都是单个数字。这个函数由几个门组成。上面只介绍了和,积以及最大操作,下面有一些其他的操作:
函数
以sigmoid为激活函数的二维神经元电路图。输入是[x0,x1],可学习的 权重[w0,w1,w2]。后面我们会看到,神经元进行点积计算,然后激活函数sigmoid将结果温柔地压进0到1之间。
在上面的例子中,我们看到了依据w,x点乘结果的一长串操作。这些操作实现的是叫sigmoid函数
梯度变得异常的简单。比如,sigmoid在前向传播的时候获得的输入时1.0,得到的输出是0.73。领域梯度(local gradient)就是(1-0.73)*0.73~=0.2,在前面曾经计算过一个表达式。所以,在实际操作中,将这些操作整合在一个门中是非常有效的。我们看这个神经元的反向传播实现:
w = [2,-3,-3] # assume some random weights and data
x = [-1, -2]
# forward pass
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid function
# backward pass through the neuron (backpropagation)
ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation
dx = [w[0] * ddot, w[1] * ddot] # backprop into x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w
# we're done! we have the gradients on the inputs to the circuit
实现过程的提示:分段反向传播 我们前面提到过,将前向传播的计算结果保存下来可以让返向传播更加容易实现。比如我们创建了中间变量
dot,其中保存了
w和
x的点积结果。在反向传播的过程中再依次(反向地)计算各个对应的保存了各个梯度的变量(比如
ddot和
dw,dx)。