Backpropagation反向传播
反向传播是一种利用链式法则chain rule去递归计算表达式梯度的方法
训练集的
xi
的梯度仍然是有用的,例如在将神经网络所做的事可视化以便于理解的时候
参考资料:CS231n课程笔记翻译:反向传播笔记-知乎智能单元-杜客
ChainRule链式法则
上图的真实值计算线路展示了计算的视觉化过程。前向传播(Forward Pass)从输入计算到输出(绿色),反向传播(Backward Pass)从尾部开始,根据链式法则(Chain Rule)递归地向前计算梯度(显示为红色),一直到网络的输入端。可以认为,梯度是从计算链路中回流。
在整个计算线路图中,每个门单元都会得到一些输入并立即计算两个东西:1. 这个门的输出值,和2.其输出值关于输入值的局部梯度。门单元完成这两件事是完全独立的,它不需要知道计算线路中的其他细节。然而,一旦前向传播完毕,在反向传播的过程中,门单元门将最终获得整个网络的最终输出值在自己的输出值上的梯度。
这里对于每个输入的乘法操作是基于链式法则的。该操作让一个相对独立的门单元变成复杂计算线路中不可或缺的一部分,这个复杂计算线路可以是神经网络等。
下面通过例子来对这一过程进行理解。加法门收到了输入[-2, 5],计算输出是3。既然这个门是加法操作,那么对于两个输入的局部梯度都是+1。网络的其余部分计算出最终值为-12。在反向传播时将递归地使用链式法则,算到加法门(是乘法门的输入)的时候,知道加法门的输出的梯度是-4。如果网络如果想要输出值更高,那么可以认为它会想要加法门的输出更小一点(因为负号),而且还有一个4的倍数。继续递归并对梯度使用链式法则,加法门拿到梯度,然后把这个梯度分别乘到每个输入值的局部梯度(就是让-4乘以x和y的局部梯度,x和y的局部梯度都是1,所以最终都是-4)。可以看到得到了想要的效果:如果x,y减小(它们的梯度为负),那么加法门的输出值减小,这会让乘法门的输出值增大。
反向传播因此可以认为是门单元之间在通过梯度信号相互通信
gate门单元其实可以是任意可导函数。我们可以把多个gates集合成一个gates,也可以根据需要分解一个函数拆分成多个gates
Sigmoid Function
因为Sigmoid导数的性质在实际中把这些操作集中在一个gates会使计算大大简化
在实际中,如果知道表达式中哪部分的局部梯度计算比较简洁,那就可以把这部分chain在一块,让代码量更少效率更高。
Backprop in practice: Staged computation反向传播的实际运用:分段计算
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!
在前向传播中,用这种拆分的方式得到这些局部梯度容易计算的中间变量(sigy, num, sigx, xpy, xpysqr, den, invden),这样就使得反向传播的过程变得简单了许多
# 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!
这里在BP的时候是先默认 df=1 再进行计算
- 对前向传播变量进行缓存
- 在不同分支的梯度要相加:如果变量x,y在前向传播的表达式中出现多次,那么进行反向传播的时候就要非常小心,使用+=而不是=来累计这些变量的梯度。这是遵循了在微积分中的multivariable chain rule多元链式法则,该法则指出如果变量在线路中分支走向不同的部分,那么梯度在回传的时候,就应该进行累加(具体看上面的代码)
数据预处理十分重要,因为如果对数据样本 xi 乘以1000,那么 W <script type="math/tex" id="MathJax-Element-10">W</script>的梯度就会增大1000倍,就只能降低LearningRates去补偿
Gradients for vectorized operations 求梯度的向量化操作
矩阵乘法梯度
# 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)
这里在BP的时候会先随机初始化D
其他
深度学习的框架例如Torch,就是一个门运算的集合,然后包含了门之间的运算关系