简单来说,反向传播是一种通过链式法则递归计算表达式的梯度的方法,本节主要介绍了给定 f(x) ,求解梯度 ∇f(x) 的方法。理解反向传播,对神经网络的调整和优化优很大的帮助。
在神经网络中,对应的是损失函数
L
,输入
偏导公式和理解梯度
考虑
f(x,y)=xy
,则
x,y
的偏导分别为
详细理解
导数就是变量变化导致的函数在该方向上的变化率。
以上公式中的
df(x)dx
作用在
f
上,表示对
函数关于每个变量的导数指明了整个表达式对于该变量的敏感程度。
梯度是偏导数的向量,所以有
∇f=[∂f∂x,∂f∂y]=[y,x]
。
我们也可以对加法和
max
函数求导:
上式
max
函数,如果该变量比另一个变量大,那么梯度是1,反之为0。例如,若
x=4,y=2
,那么结果
4
,所以函数对于
链式法则计算复合函数偏导
考虑
f(x,y,z)=(x+y)z
。当然,我们可以直接求偏导。但是为了帮助我们直观理解反向传播,使用换元法,把原函数拆成两个部分
q=x+y
和
f=qz
。对于这两个部分,我们知道怎么求解它们变量上的偏导:
∂f∂q=z,∂f∂z=q,∂q∂x=1,∂q∂y=1
,当然
q
的偏导
通过链式法则,上述的偏导数:
∂f∂x=∂f∂q∂q∂x
看下图
x = -2; y = 5; z = -4
# 前向计算
q = x + y # q becomes 3
f = q * z # f becomes -12
# 类反向传播:
# 先算到了 f = q * z
dfdz = q # df/dz = q
dfdq = z # df/dq = z
# 再算到了 q = x + y
dfdx = 1.0 * dfdq # dq/dx = 1 恩,链式法则
dfdy = 1.0 * dfdq # dq/dy = 1
链式法则的结果是,只剩下我们感兴趣的dfdx,dfdy,dfdz
,也就是原函数在
x,y,z
上的偏导。这是一个简单的例子,之后的程序里面我们为了简洁,不会完整写出dfdq
,而是用dq
代替。
上图的真实值计算线路展示了计算的视觉化过程。前向传播从输入计算到输出(绿色),反向传播从尾部开始,根据链式法则递归地向前计算梯度(显示为红色),一直到网络的输入端。可以认为,梯度是从计算链路中回流。
反向传播的直观理解
在整个计算线路图中,每个门单元都会得到一些输入并立即计算两个值:这个门的输出值和其输出值关于输入值的局部梯度。门单元完成这两件事是完全独立的。一旦前向传播完毕,在反向传播的过程中,门单元将最终获得整个网络的最终输出值在自己的输出值上的梯度。链式法则指出,门单元应该将回传的梯度乘以它的局部梯度,从而得到整个网络的输出对该门单元的每个输入值的梯度。
这里对于每个输入的乘法操作是基于链式法则的。该操作让一个相对独立的门单元变成复杂计算线路中不可或缺的一部分,这个复杂计算线路可以是神经网络等。
下面还以上面例子来对这一过程进行理解。加法门收到了输入
[−2,5]
,计算输出是
3
。这个门是加法操作,两个输入的局部梯度都是
因此,反向传播可看为门单元之间在通过梯度信号相互通信,只要让它们的输入沿着梯度方向变化,则整个网络的输出值更高。
Sigmoid例子
这个看似复杂的函数,其实可以看做一些基础函数的组合,这些基础函数及他们的偏导如下:
整个计算流程如下:
使用sigmoid激活函数的2维神经元的例子。输入是 [x0,x1] ,可学习的权重是 [w0,w1,w2] 。这个神经元对输入数据做点积运算,然后其激活数据被sigmoid函数挤压到 0−1 之间。
上面的例子中,
w
与
举个例子,sigmoid表达式输入为 1.0 ,则在前向传播中计算出输出为 0.73 。根据上面的公式,局部梯度为 (1−0.73)∗0.73≈0.2 。
该神经元反向传播的代码实现如下:
w = [2,-3,-3] # 假设一些随机数据和权重
x = [-1, -2]
# 前向传播
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid函数
# 对神经元反向传播
ddot = (1 - f) * f # 点积变量的梯度, 使用sigmoid函数求导
dx = [w[0] * ddot, w[1] * ddot] # 回传到x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # 回传到w
# 完成!得到输入的梯度
工程实现提示:分段反向传播。在实际操作中,为了使反向传播过程更加简洁,把向前传播分成不同的阶段将是很有帮助的。比如我们创建了一个中间变量dot
,它装着w
和x
的点乘结果。在反向传播的时,就可以(反向地)计算出装着w
和x
等的梯度的对应的变量(比如ddot
,dx
和dw
)。
反向传播实战:复杂函数
我们把这个函数分解成小部分,进行前向和反向传播计算,即可得到结果,前向传播计算的代码如下:
x = 3 # 例子
y = -4
# 前向传播
sigy = 1.0 / (1 + math.exp(-y)) # 单值上的sigmoid函数
num = x + sigy
sigx = 1.0 / (1 + math.exp(-x))
xpy = x + y
xpysqr = xpy**2
den = sigx + xpysqr
invden = 1.0 / den
f = num * invden # 完成!
我们并没有一次性把前向传播最后结果算出来,而是刻意留出了很多中间变量,它们都是我们可以直接求解局部梯度的简单表达式。因此,计算反向传播就变得简单了:我们从最后结果往前看,前向运算中的每一个中间变量sigy, num, sigx, xpy, xpysqr, den, invden
我们都会用到,只不过后向传回的偏导值乘以它们,得到反向传播的偏导值。反向传播计算的代码如下:
# 局部函数表达式为 f = num * invden
dnum = invden
dinvden = num
# 局部函数表达式为 invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden
# 局部函数表达式为 den = sigx + xpysqr
dsigx = (1) * dden
dxpysqr = (1) * dden
# 局部函数表达式为 xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# 局部函数表达式为 xpy = x + y
dx = (1) * dxpy
dy = (1) * dxpy
# 局部函数表达式为 sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # 注意到这里用的是 += !!
# 局部函数表达式为 num = x + sigy
dx += (1) * dnum
dsigy = (1) * dnum
# 局部函数表达式为 sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy
# 完事!
注意:
对前向传播变量进行缓存:在计算反向传播时,前向传播过程中得到的一些中间变量非常有用。在实际操作中,最好代码实现对于这些中间变量的缓存,这样在反向传播的时候也能用上它们。如果这样做过于困难,也可以(但是浪费计算资源)重新计算它们。
在不同分支的梯度要相加:如果变量
x
,
回传流中的模式
虽然神经网络结构形式和使用的神经元都不同,但是后向计算中的梯度计算大多可以归到几种常见的模式上。比如,最常见的三种简单运算门(加、乘、最大),他们在反向传播运算中的作用是非常简单和直接的。比如下面这个简单的神经网:
上图里有我们提到的三种门:
- 加运算门在反向传播运算中,不管输入值是多少,取得它output传回的梯度(gradient)然后均匀地分给两条输入路径。因为加法运算的偏导都是 +1.0 。
-
max
最大门,在反向传播计算中,它只会把传回的梯度回传给一条输入路径。因为
max(x,y)
只对
x
和
y 中较大的那个数,偏导为 +1.0 ,而另一个数上的偏导是 0 。 - 乘法门就更好理解了,因为
x∗y 对 x 的偏导为y ,而对 y 的偏导为x ,因此在上图中 x 的梯度是−8.0 ,即 −4.0∗2.0 。
用向量化操作计算梯度
上述内容考虑的都是单个变量情况,但是所有概念都适用于矩阵和向量操作。然而,在操作的时候要注意关注维度和转置操作。
矩阵相乘的梯度:可能最有技巧的操作是矩阵相乘(也适用于矩阵和向量,向量和向量相乘)的乘法操作:
# 前向传播
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)
# 假设我们得到了D的梯度
dD = np.random.randn(*D.shape) # 和D一样的尺寸
dW = dD.dot(X.T) #.T就是对矩阵进行转置
dX = W.T.dot(dD)
提示:要分析维度!注意不需要去记忆
dW
和
dX
的表达,因为它们很容易通过维度推导出来。例如,权重的梯度
dW
的尺寸肯定和权重矩阵
W
的尺寸是一样的,而这又是由dD.dot(X.T)
。
使用小而具体的例子:如果觉得向量化操作的梯度计算比较困难,建议是写出一个很小很明确的向量化例子,在纸上演算梯度,然后对其一般化,得到一个高效的向量化操作形式。
最后我们用一组图来说明实际优化过程中的正向传播与反向残差传播:
参考资料
链接:http://cs231n.github.io/optimization-2/
链接:https://zhuanlan.zhihu.com/p/21407711
链接:http://blog.csdn.net/han_xiaoyang/article/details/50321873