首页 > 程序开发 > 综合编程 > 其他综合 >

卷积神经网络:反向传播

2016-12-02

介绍:动机,在这个部分我们通过一些“直觉”来理解反向传播,也就是一种使用链式(求导)法则递归地计算梯度表达式的方法。理解其中的精妙之处非常重要,帮助理解,高效地开发,设计,调试神经网络。

介绍

动机 在这个部分我们通过一些“直觉”来理解反向传播,也就是一种使用链式(求导)法则递归地计算梯度表达式的方法。理解其中的精妙之处非常重要,帮助理解,高效地开发,设计,调试神经网络。

问题陈述 前面学到的核心问题是:我们有一个函数f(x),其中x是一个输入向量,我们希望计算fx处的梯度(也就是?f(x))。

动机 我们之所以对这个问题如此感兴趣的原因是神经网络,f对应的是损失函数(L),输入x会包括训练数据和神经网络的权值。例如,使用SVM损失函数计算损失,输入包括训练数据(xi,yi),i=1...N和权重与偏置W,b。注意到(在机器学习中很常见的)我们认为训练数据是给定的,并且不可改变,我们能修改的只有权重。因此,即使我们可以很轻易地使用反向传播计算输入xi对应的梯度,我们也只会计算参数的梯度(例如W,b)以用来更新参数。但是我们后面会看到,xi的梯度有的时候仍然是有用的,比如我们想要将神经网络正在做的事情可视化出来的时候。
如果你在上课之前就已经知道如何使用链式法则得到梯度,我仍旧希望你可以留在这里,因为我们展示了作为实值传递圈逆流的反向传播的一些很成熟的想法,你可能从其中得到很多帮助。

简单的表达式和梯度的理解

我们由浅入深地学习一下。想象一个简单的乘积函数f(x,y)=xy。求出他们的偏导数非常简单
f(x,y)=xy→?f?x=y→?f?y=x
解释 导数的意义是:在某个点的无限小的距离内,函数值沿一个变量变化方向的变化率:
df(x)dx=limh→0f(x+h)?f(x)h
等式左边的除号和等式右边的除号不一样,它不是除号。这里的操作ddx作用在f上,返回一个不同的函数(导数)。一个很好的思路就是,当h很小的时候,这个函数很好的近似了一个直线,而导数就是它的斜率。也就是说,每个变量上的导数给出了这个值附近整个表达式的敏感度。例如,如果x=4,y=?3,那么f(x,y)=?12,并且导数x?f?x=?3。这告诉我们如果我们将这个变量增加一点点,对于整个表达式来说,是减少的(因为前面的负号),并且按照增加量的三倍减少。我们重新整理上面的等式可以看到f(x+h)=f(x)+hdf(x)dx。类似的,由于?f?y=4,我们在y上添加一个非常小的值h,最终整个输出会增加4h

每个变量的导数表明了这个值对整个表达式的敏感度

像上面提到的,梯度?f是向量的偏导数,所以我们有?f=[?f?x,?f?y]=[y,x]。 虽然梯度事实上是一个向量,为了简化表达我们仍然会用“x上的梯度”而不是“x上的偏导”。

我们也可以导出加法函数的偏导:
f(x,y)=x+y→?f?x=1→?f?y=1
也就是说,x,y对应的导数都与x,y的值无关,这是因为增加x,y的值都会使得输出f增加,而增长率和x,y事实上是多少是无关的。最后我们用的比较多的是最大操作:
f(x,y)=(x,y)→?f?x=1(x>=y)→?f?y=1(y>=x)
直觉上来说,如果输入是x=4,y=2,那么最大值是4,而函数对y的值并不敏感。如果我们增加一个很小的h,函数仍旧输出4,所以梯度是0,也就是无影响。当然我们如果给y加上一个很大的数(比如比2大),f的值就会改变,但是导数并不会描述输入在这么大的变化下有什么影响。他们只描述非常小的,接近于0的输入,也就是定义中的limh→0

用链式求导法则混合表达式

现在我们考虑更加复杂包括多种操作的表达式,例如f(x,y,z)=(x+y)z。这个表达式仍然简单到可以直接区分出来,但是我们只是为理解反向传播特别设计一个方法。这个表达式可以分成两个表达式:q=x+y,f=qz。我们知道如何独立地求出两个表达式的导数。f只是qz的积,所以?f?q=z?f?z=q,并且qx,y的和,所以?q?x=1?q?y=1。但是我们并不会关心中间两q的梯度,我们非常关心f以及他的输入x,y,z的梯度。链式法则告诉我们如何正确地将梯度表达式合在一起。例如,?f?x=?f?q?q?x。在实际操作中,这只是一个简单的两个数相乘的例子。我们看下例:

# 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为例

上面我们说到的门是胡编乱造的。任何可识别的函数都可以像门一样运作,我们也可以将许多门放到一个门中去,或者将一个函数分解为若干个门。我们看下一个例子:
f(w,x)=11+e?(w0x0+w1x1+w2)
后面我们可以看到,这个式子描述了使用sigmoid的二维神经元(有输入x以及权重w),但是现在我们把他想像成简单的w,x输入都是单个数字。这个函数由几个门组成。上面只介绍了和,积以及最大操作,下面有一些其他的操作:
f(x)=1x→dfdx=1/x2
fc(x)=c+x→dfdx=1
f(x)=ex→dfdx=ex
fa(x)=ax→dfdx=a
函数fa,fc用常量a倍乘了输入,用常量c扩大了输入。理论上他们是加与乘运算的特殊形式,但是我们以新的一元门引入他们,因为我们确实需要常量c和a的梯度。下面是完整的电路图:


以sigmoid为激活函数的二维神经元电路图。输入是[x0,x1],可学习的 权重[w0,w1,w2]。后面我们会看到,神经元进行点积计算,然后激活函数sigmoid将结果温柔地压进0到1之间。

在上面的例子中,我们看到了依据w,x点乘结果的一长串操作。这些操作实现的是叫sigmoid函数σ(x)。事实上,我们可以使用函数本身化简它的导数:
σ(x)=11+e?x→dσ(x)dx=e?x(1+e?x)2=(1+e?x?11+e?x)(11+e?x)=(1?σ(x))σ(x)
梯度变得异常的简单。比如,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)。

相关文章
最新文章
热点推荐