这节介绍反向传播的直观理解。使用链式法则递归求导
函数 f(x) ,其中 x 是输入变量,我们需要计算 x 的导数 ∇f(x) 。这个函数 f(x) 可以是loss函数,计算loss函数关于权重和偏置 W,b 的导数,依次更新它们。
函数有两个变量 f(x,y)=xy ,可以得到
一个变量的导数,表示它的值变化时对整个表示式影响的大小。
∇f 表示偏导数向量, ∇f=[∂f∂x,∂f∂y]=[y,x]
对加法求导:
对max算子求导
以一个稍微复杂一点的计算为例 f(x,y,z)=(x+y)z ,可以把这个函数分为2个 q=x+y , f=qz 。先对 q,z 求导得到 ∂f∂q=z,∂f∂z=q ,在用 q 对 x,y 求导 ∂q∂x=1,∂q∂y=1 ,根据链式法则 ∂f∂x=∂f∂q∂q∂x
上面求解过程,可以用下图表示
上图中,绿色表示前向传播,红色表示反向传播。
反向传播是局部处理过程。每个神经单元在得到输入后,可以立即计算两件事1、输出,2、关于输入的局部梯度。神经单元不需要知道整个网络的结构就可以完成这两件事。前向传播完成后,在反向传播时,只需要对路径上的局部梯度相乘就可以得到最终输出对于某个变量的梯度值。
任何函数都可以看做一个神经元,可以把多个神经元组成一个,或把一个函数拆分为几个门单元。看下面的表达式
sigmoid函数是常用的激活函数,对它的求导,计算如下
实现提示:分段反向传播。把前向传播分段进行,方便计算导数和反向传播。
有如下函数
x = 3 # example values
y = -4
# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # sigmoid in numerator #(1)
num = x + sigy # numerator #(2)
sigx = 1.0 / (1 + math.exp(-x)) # sigmoid in denominator #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # denominator #(6)
invden = 1.0 / den #(7)
f = num * invden # done! #(8)
在实现时,创建了一些中间变量sigy, num, sigx, xpy, xpysqr, den, invden
,这些都是简单的表达式。在反向传播时,在这些变量前面加上d
表示梯度
# backprop f = num * invden
dnum = invden # gradient on numerator #(8)
dinvden = num #(8)
# backprop invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# backprop xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)
# backprop num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
# done! phew
注意:
1、对前向传播过程中的变量进行缓存,因为在反向传播时也会用到。
2、如果 x,y 在前向传播时,使用了多次;那么在反向传播时要使用+=
来代替=
。我们要累积梯度,使用=
会覆盖掉前面计算好的梯度。
反向传播中的梯度可以直观的解释一下。神经网络中最常用的三个门单元为 add,mul,max ,可以直观的看一下它们三个在反向传播中,对梯度的影响。
add:加法是把梯度不变的分到下一个门单元。
max:max算子是把梯度分到值比较大的一个单元。
mul:乘法是把梯度乘以一个值传到下一个门单元。 f=xy ,那么传到 x 的梯度要乘以 y ,同理 y 的梯度。
注意,权重和数据的乘积 wTxi ,如果有某个权重很大,那么某个维度的输入数据梯度会很大,这样这个维度的数据对结果有很大影响。因此要正则化权重,限制梯度幅度。
上面的例子都是单个变量的,神经网络中的运算都是以矩阵或向量为单位的。求梯度以及梯度反向传播,都可以用矩阵或向量乘法。
# forward pass
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)
# now suppose we had the gradient on D from above in the circuit
dD = np.random.randn(*D.shape) # same shape as D
dW = dD.dot(X.T) #.T gives the transpose of the matrix
dX = W.T.dot(dD)
注意,在分析梯度是,不用记住它的表达式,因为可以通过维度推导出来。dW
维度和W
维度大小一样,它等于dD
和X
的乘积。
1、对梯度有了直观理解,知道梯度如何在网络中传播,如何影响输出。
2、对梯度计算时,使用分段计算。把函数分为更小的模块,可以更好地计算梯度,使用链式法则得到最终梯度。
Automatic differentiation in machine learning: a survey