反向传播与神经网络
反向传播
上一节我们介绍了梯度下降法以及在线性分类器上的应用,并且介绍了两种求解方法,一个是数值梯度,另外一个是分析梯度,其中数值梯度计算量大但是准确,分析梯度通过求梯度的方法得到,计算量小,但是通常需要进行验证.
分析梯度可以应用到线性分类器上,因为损失函数形式比较简单,通常用一个数学表达式就可以表示,但是在神经网络这种含有多层神经元的比较复杂的形式,尤其是深度学习网络比如下图,那么损失函数就完全不能够用数学表达式进行表达了,那么进行分析梯度显然也是不可能的,所以需要一种新的方法取更新权值矩阵,在这里,我们将会介绍反向传播算法.
举一个例子,有一个函数如下所示:
f(x,y,z)=(x+y)z
,可以看出一共有三个输入,两个运算,一个加法一个乘法,可以把两个运算抽象成两个神经元,
q=x+y
代表着加法运算,
f=qz
代表乘法运算,现在要求f关于x,y,z的导数.
可以表示成如下图所示:
由链式法则可知, αfαx=αfαqαqαx
本例中,结果为-4:
一个神经元可以表示为下面的形式,其中绿色为前向的激励,红色为反向传播的梯度,用来权重调整:
还有一个比较复杂的例子,红色为梯度,绿色为输出,计算过程并不难,这里不再赘述:
其中一些操作可以合并,成为一个模块,比如,就是把一个Sigmoid模块化,导数的公式可以提前就计算出来.
反向传播实践
如果有以下函数:
先写代码实现forward:
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在前向传播时,使用了多次(比如说本例中x输入了三次,y输入了两次);那么在反向传播时要使用+=来代替=。我们要累积梯度,使用=会覆盖掉前面计算好的梯度。
在实际计算的时候,输入和输出都是向量,所以如果进行求导的结果是一个雅克比矩阵,不过雅克比矩阵太庞大,所以实际不会求出完整的雅克比矩阵,比如下面的例子,雅克比矩阵就基本上是一个单位矩阵,只不过对角线上元素有的为0.