注意:本文所提的神经网络在这里特质多层前馈神经网络 神经网络的numpy实现代码:Neural Network with Numpy
一个简单的神经网络基础结构包括三个,线性映射,激活层,隐藏层。 如图,输入层的输入向量经过一个线性映射,在经过一个激活层,到达了第一个隐藏层,随着网络的加深,重复线性映射,激活层,隐藏层的过程直至到达输出层。流程很简单,但是需要理解几个问题。
线性映射是什么? 假设如图所示,输入向量为四维向量 X = [ x 1 , x 2 , x 3 , x 4 ] X=[x_1,x_2,x_3,x_4] X=[x1,x2,x3,x4],则经过图中的权重矩阵 W 1 W_1 W1便是一次线性映射,即 W 1 X + b 1 W_1X+b_1 W1X+b12. 为什么要有激活层? 如果不添加激活层,则一个多层的网络则可以看做是多个线性映射的复合,即上一层网络的输出作为下一层的输入,假设第一层线性映射函数为 f 1 ( x ) = W 1 X + b 1 f_1(x) = W_1X+b_1 f1(x)=W1X+b1,则第二层 f 2 ( x ) = W 2 X + b 2 f_2(x) = W_2X+b_2 f2(x)=W2X+b2, 则最后的输出为 f 2 ( f 1 ( x ) ) = W 2 ( W 1 X + b 1 ) + b 2 = ( W 1 W 2 ) X + ( W 2 b 1 + b 1 ) f_2(f_1(x)) = W_2(W_1X+b_1)+b_2 = (W_1W_2)X + (W_2b_1+b_1) f2(f1(x))=W2(W1X+b1)+b2=(W1W2)X+(W2b1+b1)。可以看到,多层的线性映射可以用单层的线性映射表示,失去了拟合非线性的能力。所以我们需要对每层线性映射的输出加入非线性的激活函数。我们以矩阵的运算形式表示,对于 l + 1 l+1 l+1层: 经过线性映射: z l + 1 = W l a l + b l z^{l+1} = W^la^l+b^l zl+1=Wlal+bl 经过激活层: a l + 1 = f ( z l + 1 ) a^{l+1} = f(z^{l+1}) al+1=f(zl+1)
神经网络并没有解析解,我们需要借助梯度下降的方式进行优化。那么,我们如何求每层的梯度呢?
反向传播的基础是链式法则,这里就不多介绍。 现在有一个多层的神经网络,我们第 l + 1 l+1 l+1 层为例,看一下他的前向传播。 来自上一层的输入 ∑ j a j l \underset {j}{\sum}a_j^l j∑ajl经过线性映射的第i个输出: z i l + 1 = ∑ j W i j l a j l + b i l z_i^{l+1} = \underset {j}{\sum}W_{ij}^{l}a_j^l +b_i^l zil+1=j∑Wijlajl+bil 然后经过激活函数f(x),然后得到l+1层的第i个输出: a i l + 1 = f ( z i l + 1 ) a_i^{l+1} = f(z_i^{l+1}) ail+1=f(zil+1)
根据链式法则,便可以很容易求得: ∂ L ∂ W i j l = ∂ L ∂ z i l + 1 ∂ z i l + 1 ∂ W i j l = ∂ L ∂ z i l + 1 a j l \frac{\partial L}{\partial W_{ij}^{l}} = \frac{\partial L}{\partial z_i^{l+1}} \frac{\partial z_i^{l+1}}{\partial W_{ij}^{l}} = \frac{\partial L}{\partial z_i^{l+1}}a_j^l ∂Wijl∂L=∂zil+1∂L∂Wijl∂zil+1=∂zil+1∂Lajl ∂ L ∂ b i l = ∂ L ∂ z i l + 1 \frac{\partial L}{\partial b_i^l} = \frac{\partial L}{\partial z_i^{l+1}} ∂bil∂L=∂zil+1∂L
其中 ∂ L ∂ z i l + 1 \frac{\partial L}{\partial z_i^{l+1}} ∂zil+1∂L很明显与激活函数的导数有关。
所以呢,反向传播算法可以理解为利用第l+1层传递过来的梯度,求解Loss对第l层层参数(W,b)的梯度,并传递给l-1层。
由上面的计算不难看出,实际上神经网络的计算都是矩阵运算,我们从矩阵的角度来理解反向传播的计算会更加容易一些。 不同于标量运算,矩阵运算不满足交换律( A B ≠ B A AB \neq BA AB=BA), 且矩阵必须形状匹配。 维数相容原则:若一个 m × n m\times n m×n矩阵x经过f(x)变换为标量y,则 ∂ f ( x ) ∂ x \frac{\partial f(x)}{\partial x} ∂x∂f(x)必定也是一个 m × n m\times n m×n大小矩阵。
所以我们的对矩阵求导时,可以先把矩阵看做标量正常求导,然后调整结果使得结果满足维数相容原则和矩阵相乘配型。
每一层的前向传播为: z l + 1 = W l a l + b l z^{l+1} = W^{l}a^l + b^l zl+1=Wlal+bl a l + 1 = f ( z l + 1 ) a^{l+1} = f(z^{l+1}) al+1=f(zl+1) 设 δ l = ∂ J ( W , b ) ∂ z l \delta^l = \frac{\partial J(W,b)}{\partial z^l} δl=∂zl∂J(W,b)
∂ J ( W , b ) ∂ W l = ∂ J ( W , b ) ∂ z l + 1 ∂ z l + 1 ∂ W l = δ l + 1 ( a l ) T \frac{\partial J(W,b)}{\partial W^l} = \frac{\partial J(W,b)}{\partial z^{l+1}}\frac{\partial z^{l+1}}{\partial W^{l}} = \delta^{l+1}(a^l)^T ∂Wl∂J(W,b)=∂zl+1∂J(W,b)∂Wl∂zl+1=δl+1(al)T ∂ J ( W , b ) ∂ b l = ∂ J ( W , b ) ∂ z l + 1 ∂ z l + 1 ∂ b l = δ l + 1 \frac{\partial J(W,b)}{\partial b^l} = \frac{\partial J(W,b)}{\partial z^{l+1}}\frac{\partial z^{l+1}}{\partial b^{l}} = \delta^{l+1} ∂bl∂J(W,b)=∂zl+1∂J(W,b)∂bl∂zl+1=δl+1
损失函数为均方误差: L o s s ( o u t p u t ) = 1 2 ∑ ( t a r g e t − o u t p u t ) 2 = 1 2 ( t 1 − o 1 o u t ) 2 + 1 2 ( t 2 − o 2 o u t ) 2 Loss(output) = \frac{1}{2} \sum(target - output)^2 = \frac{1}{2}(t1-o1_{out})^2 + \frac{1}{2}(t2-o2_{out})^2 Loss(output)=21∑(target−output)2=21(t1−o1out)2+21(t2−o2out)2
Net:的输入输出为sigmoid: n e t o u t = σ ( n e t i n ) net_{out} = \sigma(net_{in}) netout=σ(netin)
∂ L o s s ( o u t p u t ) ∂ w 1 \frac{\partial{Loss(output)}}{\partial{w1}} ∂w1∂Loss(output)
= ∂ L o s s ( o u t p u t ) ∂ o 1 o u t ∂ o 1 o u t ∂ o 1 i n ∂ o 1 i n ∂ h 1 o u t ∂ h 1 o u t ∂ h 1 i n ∂ h 1 i n ∂ w 1 = \frac{\partial{Loss(output)}}{\partial{o1_{out}}}\frac{\partial{o1_{out}}}{\partial{o1_{in}}}\frac{\partial{o1_{in}}}{\partial{h1_{out}}}\frac{\partial{h1_{out}}}{\partial{h1_{in}}}\frac{\partial{h1_{in}}}{\partial{w1}} =∂o1out∂Loss(output)∂o1in∂o1out∂h1out∂o1in∂h1in∂h1out∂w1∂h1in
= ∂ [ 1 2 ( t 1 − o 1 o u t ) 2 + 1 2 ( t 2 − o 2 o u t ) 2 ] ∂ o 1 o u t × σ ( o 1 i n ) ( 1 − σ ( o 1 i n ) ) × w 5 × σ ( h 1 i n ) ( 1 − σ ( h 1 i n ) ) × i 1 = \frac{\partial[\frac{1}{2}(t1-o1_{out})^2 + \frac{1}{2}(t2-o2_{out})^2]}{\partial{o1_{out}}} \times \sigma(o1_{in})(1-\sigma(o1_{in})) \times w5 \times \sigma(h1_{in})(1-\sigma(h1_{in})) \times i1 =∂o1out∂[21(t1−o1out)2+21(t2−o2out)2]×σ(o1in)(1−σ(o1in))×w5×σ(h1in)(1−σ(h1in))×i1
= ( t 1 − o 1 o u t ) × σ ( o 1 i n ) ( 1 − σ ( o 1 i n ) ) × w 5 × σ ( h 1 i n ) ( 1 − σ ( h 1 i n ) ) × i 1 = (t1-o1_{out}) \times \sigma(o1_{in})(1-\sigma(o1_{in})) \times w5 \times \sigma(h1_{in})(1-\sigma(h1_{in})) \times i1 =(t1−o1out)×σ(o1in)(1−σ(o1in))×w5×σ(h1in)(1−σ(h1in))×i1
可以看到,梯度跟中间的权重 (w5),激活函数( σ \sigma σ) ,还有损失函数( 1 2 ( t 1 − o 1 o u t ) 2 + 1 2 ( t 2 − o 2 o u t ) 2 \frac{1}{2}(t1-o1_{out})^2 + \frac{1}{2}(t2-o2_{out})^2 21(t1−o1out)2+21(t2−o2out)2)有关
σ \sigma σ激活函数的导数 σ ( o 1 i n ) ( 1 − σ ( o 1 i n ) ) \sigma(o1_{in})(1-\sigma(o1_{in})) σ(o1in)(1−σ(o1in))最大为0.25,又因为通常我们的w初始化通常小于1,所以 w × σ ( n e t i n ) ( 1 − σ ( n e t i n ) ) w \times \sigma(net_{in})(1-\sigma(net_{in})) w×σ(netin)(1−σ(netin)) 通常小于0.25,当层数很深,就是许多0.25相乘,梯度将会越来越小,最后消失,此时,w1便不会再更新,同理如果初始化权重过大,层数越多,梯度越来越大,直至爆炸(不常发生)。
由2.1我们知道,实际上反向传播算法便是利用前一层的梯度求本层的梯度,并传递给下一层。当网络层数很深,且每层梯度都小于1的时候,反向传播的越深,随着梯度的累乘,浅层的网络梯度会变得非常小,这边是梯度消失;同理当梯度大于1,便会出现梯度爆炸的问题。 由2.1.1可以知道,每一层的梯度实际上跟激活函数有着很大的关系,所以我们可以通过改变激活函数,控制激活函数的导数大小,从而一定程度上解决梯度消失的问题。
函数表达式为 f ( z ) = 1 1 + e − z f(z)=\frac{1}{1+e^{-z}} f(z)=1+e−z1 导数为 f ′ ( z ) = f ( z ) − f 2 ( z ) = e − x ( 1 + e − x ) 2 f'(z) = f(z) - f^2(z) = \frac{e^{-x}}{(1+e^{-x})^2} f′(z)=f(z)−f2(z)=(1+e−x)2e−x
特点: 作为以前很常用的激活函数之一,sigmoid的特点便是可以将输入值统一压缩在[0,1]的值域内,对于极大的正值,经过sigmoid后值为1,极小的的负值,经过sigmoid后为0,这一性质使得它在“门”结构中得到了广泛使用(例如LSTM中的门),同样,也使得它很容易与概率对应起来,在二分类问题在也很常用。 缺点:
由sigmoid的导数也可以看到,每过一层,梯度就会变成原来的0.25倍甚至更小,这很容易导致梯度消失问题经过sigmoid的输出并不是0均值的输出。意思是单样本训练中,经过sigmoid到下一层的输入x>0或者x<0,这使得f(x)=wx+b求w局部梯度要么都为正,要么都为负,也就意味着此次所有的w要么全往正向更新,要么全往负向更新,降低了训练速度。当然,按batch进行训练一定程度上可以解决这个问题。sigmoid的解析解中的幂运算对于计算机来说求解比较麻烦。 tanh函数函数表达式为 f ( z ) = e z − e − z e x + e − x f(z) = \frac{e^z-e^{-z}}{e^x+e^{-x}} f(z)=ex+e−xez−e−z 导数为 f ′ ( z ) = 1 − f 2 ( z ) f'(z)=1-f^2(z) f′(z)=1−f2(z) 特点几乎与sigmoid相同,不同点在于将sigmoid的值域拉伸到了[-1,1],解决了sigmoid的输出不以0均值问题,但幂运算和梯度消失的问题仍未解决。
ReLu Relu特点很明显, 对于负值梯度一律取0,对于正值梯度一律为1,所以不存在梯度消失的问题,但是同时会带来梯度死亡问题,即一旦某个神经元的想x<0, 梯度便为0,从此该神经元参数便永远不会被更新。当然,我们可以通过合理的初始化和学习率减少这样的概率。 同时,Relu的计算简单,只需要判断阈值即可,计算速度很快。并且x<0时的输出为0,达到了类似于dropout的产生稀疏性的效果。 唯一的问题与sigmoid类似,输出不是以为0为均值中心,但是它的优点实在太明显,所以至今为止仍然被广泛使用。
其他激活函数 还有一些激活函数是针对relu的改进,比如PReLu,ELU, 理论上效果好于Relu,但实际应用中并未发现如此。如果感兴趣的朋友可以自行查阅。
参考:
常用激活函数(激励函数)理解与总结