深度学习(四)-前馈神经网络

  在前馈神经网络中,各神经元分别属于不同的层。每一层的神经元可以接收前一层神经元的信号,并产生信号输出到下一层。第 0 层叫输入层,最后一层叫输出层,其它中间层叫做隐藏层,相邻两层的神经元之间为全连接关系,也称为全连接神经网络( F N N FNN ),表现形式如下图所示。

  我们用下面的记号来描述一个前馈神经网络:
     L L :表示神经网络的层数
     m ( l ) m^{(l)} :表示第 l l 层神经元的个数
     f l ( ) f_l(·) :表示 l l 层神经元的激活函数
     W ( l ) W^{(l)} :表示 l 1 l−1 层到第 l l 层的权重矩阵
     b ( l ) b^{(l)} :表示 l 1 l−1 层到第 l l 层的偏置
     z ( l ) z^{(l)} :表示 l l 层神经元的净输入
     a ( l ) a^{(l)} :表示 l l 层神经元的输出

  前馈神经网络通过下面公式进行信息传播:
z ( l ) = W ( l ) a ( l 1 ) + b ( l ) z^{(l)} = W ^{(l)} * a^{(l−1)} + b^{(l)}
a ( l ) = f l ( z ( l ) ) a^{(l)} = f_l(z^{(l)})
  上面两式也可以得到:
z ( l ) = W ( l ) f l 1 ( z ( l 1 ) ) + b ( l ) z^{(l)} = W^{(l)}* f_{l−1}(z^{(l−1)}) + b^{(l)}
  或者
a ( l ) = f l ( W ( l ) a ( l 1 ) + b ( l ) ) a^{(l)} = f_l(W^{(l)}*a^{(l−1)} + b^{(l)})
  这样,前馈神经网络可以通过逐层的信息传递,得到网络最后的输出 a ( l ) a^{(l)} 。整个网络可以看作一个复合函数 ϕ ( x ; W , b ) ϕ(x; W, b) ,将向量 x x 作为第1层的输入 a ( 0 ) a(0) ,将第 L L 层的输出 a ( L ) a(L) 作为整个函数的输出。
x = a ( 0 ) z ( 1 ) a ( 1 ) z ( 2 ) a ( L 1 ) z ( L ) a ( L ) = φ ( x ; W , b ) ) x = a(0) → z(1) → a(1) → z(2) → · · · → a(L−1) → z(L) → a(L) = φ(x; W, b))
  其中 W , b W, b 表示网络中所有层的连接权重和偏置。

1. 反向传播算法

  上面简单的介绍了前馈神经网络,我们发现一个神经网络的输出好坏是由参数 W , b W,b 决定的,那么训练神经网络的过程其实就是不断的更新参数来得到最好的结果,在前面的pytorch学习(一)-线性回归中使用了损失函数 f f 来控制模型训练的结果,其本质就是需要计算出 f f 的梯度 f \nabla f ,反向传播算法就是一个有效地求解梯度的算法。

1.1 链式法则

  假设有一个函数 f ( x , y , z ) = ( x + y ) z f(x,y,z)=(x+y)z ,我们需要对其求微分,当然很多人一眼就可以看出 f ( x , y , z ) f(x,y,z) x x y y z z 的微分,但是如果函数复杂呢,所以我们换一种方式,假设 q = x + y q=x+y , 那么可以求得微分:
f q = z ; f z = q ; q x = 1 ; q y = 1 \frac{{\partial f}}{{\partial q}} = z;\frac{{\partial f}}{{\partial z}} = q;\frac{{\partial q}}{{\partial x}} = 1;\frac{{\partial q}}{{\partial y}} = 1
  但是我们关心的是 f x , f y , q z \frac{{\partial f}}{{\partial x}},\frac{{\partial f}}{{\partial y}},\frac{{\partial q}}{{\partial z}} ,所以可以得到:

f x = f q q x \frac{{\partial f}}{{\partial x}} = \frac{{\partial f}}{{\partial q}}\frac{{\partial q}}{{\partial x}}
f y = f q q y \frac{{\partial f}}{{\partial y}} = \frac{{\partial f}}{{\partial q}}\frac{{\partial q}}{{\partial y}}
f z = q \frac{{\partial f}}{{\partial z}} = q
  从上面我们可以看出,如果需要对函数的元素求导,那么可以一层一层求导,然后将结果乘起来,这就是链式法则,同时也是反向传播算法的核心。

1.2 反向传播算法

  给定一个样本 ( x , y ) (x, y) ,将其输入到神经网络模型中,得到网络输出为 y y' 。假设损失函数为 L ( y , y ) L(y, y') ,要进行
参数学习就需要计算损失函数关于每个参数的导数。对第 l l 层中的参数权重 W ( l ) W^{(l)} 和偏置 b ( l ) b^{(l)} 计算偏导数。
L ( y , y ) W i j ( l ) = L ( y , y ) z l ( z l W i j ( l ) ) \frac{{\partial L(y,y')}}{{\partial W_{ij}^{(l)}}} = \frac{{\partial L(y,y')}}{{\partial {z^l}}}(\frac{{\partial {z^l}}}{{\partial W_{ij}^{(l)}}})

L ( y , y ) b ( l ) = L ( y , y ) z l ( z l b ( l ) ) \frac{{\partial L(y,y')}}{{\partial {b^{(l)}}}} = \frac{{\partial L(y,y')}}{{\partial {z^l}}}(\frac{{\partial {z^l}}}{{\partial {b^{(l)}}}})

  其中 L ( y , y ) z l \frac{{\partial L(y,y')}}{{\partial {z^l}}} 为目标函数关于第 l l 层的神经元 z ( l ) z^{(l)} 的偏导数,称为误差项,所以需要计算三个偏导数,分别为 L ( y , y ) z l \frac{{\partial L(y,y')}}{{\partial {z^l}}} ( z l W i j ( l ) ) (\frac{{\partial {z^l}}}{{\partial W_{ij}^{(l)}}}) ( z l b ( l ) ) (\frac{{\partial {z^l}}}{{\partial {b^{(l)}}}}) ,可以得到:

z l W i j ( l ) = ( W ( l ) a ( l 1 ) + b ( l ) ) W i j ( l ) \frac{{\partial {z^l}}}{{\partial W_{ij}^{(l)}}} = \frac{{\partial ({W^{(l)}}{a^{(l - 1)}} + {b^{(l)}})}}{{\partial W_{ij}^{(l)}}}
z l b ( l ) = ( W ( l ) a ( l 1 ) + b ( l ) ) b ( l ) \frac{{\partial {z^l}}}{{\partial {b^{(l)}}}} = \frac{{\partial ({W^{(l)}}{a^{(l - 1)}} + {b^{(l)}})}}{{\partial {b^{(l)}}}}
  所以权重矩阵 W ( l ) W^(l) 的第 i i 行为 a j ( l 1 ) a_j^{(l - 1)} ,偏置为 1。
  因为 z ( l + 1 ) = W ( l + 1 ) a ( l ) + b ( l + 1 ) z^{(l+1)} = W ^{(l+1)}a^{(l)} + b^{(l+1)} , 且 a ( l ) = f l ( z ( l ) ) a^{(l)} = f_l(z^{(l)}) ,同时 f l ( ) fl(·) 为按位计算的函数,得到:
z ( l + 1 ) a l = ( W ( l + 1 ) ) T \frac{{\partial {z^{(l + 1)}}}}{{\partial {a^l}}}=(W^{(l+1)})^T
a l z l = d i a g ( f l ( z ( l ) ) ) \frac{{\partial {a^l}}}{{\partial {z^l}}}=diag(f_l'(z^{(l)}))

= L ( y l , y l ) z l = ( y l y l ) z l = ( y l W l a ( l 1 ) + b l ) z l = ( y l W l f l 1 ( z ( l 1 ) ) + b l ) z l =\frac{{\partial L({y_l},{y_l}')}}{{\partial {z^l}}} = \frac{{\partial ({y_l} - {y_l}')}}{{\partial {z^l}}} = \frac{{\partial ({y_l} - {W^l}{a^{(l - 1)}} + {b^l})}}{{\partial {z^l}}} = \frac{{\partial ({y_l} - {W^l}{f_{l - 1}}({z^{(l - 1)}}) + {b^l})}}{{\partial {z^l}}}

= > L ( y , y ) z l = L ( y , y ) z l + 1 z ( l + 1 ) a l a l z l =>\frac{{\partial L(y,y')}}{{\partial {z^l}}} = \frac{{\partial L(y,y')}}{{\partial {z^{l + 1}}}}\frac{{\partial {z^{(l + 1)}}}}{{\partial {a^l}}}\frac{{\partial {a^l}}}{{\partial {z^l}}}
  其中 d i a g ( x ) diag(x) 为对角矩阵,其对角线元素为 x x ,⊙ 是向量的点积运算符,表示每个元素相乘。我们用 δ l \delta^l 来定义第 l l 层神经元的误差项,那么 :
δ l = d i a g ( f l ( z ( l ) ) ) ( W ( l + 1 ) ) T δ ( l + 1 ) = f l ( z ( l ) ) ( ( W ( l + 1 ) ) T δ ( l + 1 ) ) \delta^l= diag(f_l'(z^{(l)})) · (W^{(l+1)})^T · δ^{(l+1)}=f_l'(z^{(l)})\odot ((W^{(l+1)})^T · δ^{(l+1)})
  从上式可以看到,第 l l 层的误差项可以通过第 l + 1 l+1 层的误差项计算得到,这就是误差的反向传播。反向传播算法的含义是:第 l l 层的一个神经元的误差项是所有与该神经元相连的第 l + 1 l+1 层的神经元的误差项的权重和,然后再乘上该神经元激活函数的梯度。计算出三个偏导之后,重新算出参数的梯度:
L ( y , y ) W i j ( l ) = δ i ( l ) i ( a j ( l 1 ) ) T = δ i ( l ) a j ( l 1 ) = > L ( y , y ) W ( l ) = δ ( l ) ( a ( l 1 ) ) T \frac{{\partial L(y,y')}}{{\partial W_{ij}^{(l)}}} = \delta _i^{(l)}\prod\nolimits_i {{{(a_j^{(l - 1)})}^T}} = \delta _i^{(l)}a_j^{(l - 1)} = > \frac{{\partial L(y,y')}}{{\partial W_{}^{(l)}}} = \delta _{}^{(l)}{(a_{}^{(l - 1)})^T}

L ( y , y ) b ( l ) = δ ( l ) \frac{{\partial L(y,y')}}{{\partial b_{}^{(l)}}} = \delta _{}^{(l)}

2. 自动梯度计算

  神经网络的参数主要通过梯度下降来进行优化的。当确定了风险函数以及网络结构后,我们就可以手动用链式法则来计算风险函数对每个参数的梯度,并用代码进行实现。但是手动求导并转换为计算机程序的过程非常琐碎并容易出错,导致实现神经网络变得十分低效。目前,几乎所有的主流深度学习框架都包含了自动梯度计算的功能,即我们可以只考虑网络结构并用代码实现,其梯度可以自动进行计算,无需人工干预。这样开发的效率就大大提高了,常见的梯度计算有三类:数值微分、符号微分、自动微分。想深究的同学可以看看邱锡鹏老师《神经网络与深度学习》第二张第4.5小结,讲述的很清楚。

3. 梯度优化

  深度学习中主要使用的梯度下降的方法, P y t o r c h Pytorch 中有封装 t o r c h . o p t i m torch.optim 模块来优化梯度,我这里下降梯度都用 1e-1 代替,具体使用具体设置。主要有以下几种:

3.1 SGD

  随机梯度下降法是梯度下降法的一个小变形,就是每次使用一批数据进行梯度的计算,而不是计算全部数据的梯度。因为现在深度学习的数据量都特别大, 所以每次都计算所有数据的梯度是不现实的,这样会导致运算时间特别长,同时每次都计算全部的梯度还失去了一些随机性, 容易陷入局部误差,所以使用随机梯度下降法可能每次都不是朝着真正最小的方向.但是这样反而容易跳出局部极小点。
   p y t o r c h pytorch 表示为: t o r c h . o p t i m . S G D ( p a r a m e t e r s , 1 e 1 ) torch.optim.SGD(parameters, 1e-1)

3.2 Momentum

  在随机梯度下降的同时,增加动量,其计算基于前面梯度,也就是说参数更新不仅仅基于当前的梯度,也基于之前的梯度
   p y t o r c h pytorch 表示为: t o r c h . o p t i m . S G D ( p a r a m e t e r s , 1 e 1 , m o m e n t u m = 0.9 ) torch.optim.SGD(parameters, 1e-1, momentum=0.9)

3.3 Adagrad

  自适应学习率,表现形式为:
w t η i = 0 t ( g i ) 2 + ε g t > w t + 1 {w^t} - \frac{\eta }{{\sqrt {\sum\nolimits_{i = 0}^t {{{({g^i})}^2} + \varepsilon } } }}{g^t} - > {w^{t + 1}}
  我们可以看到学习率在不断变小, 且受每次计算出来的梯度影响 ,对于梯度比较大的参数,它的学习率就会变得相对更小 ,里面的根号特别重要,没有这个根号算法表现非常差。同时 ε \varepsilon 是一个平滑参数,通常设置为 1e-4~1e-8,这是为了避免分母为 0。但是自适应学习率在某些情况下一直递减的学习率并不好,这样会造成学习过早停止。
   p y t o r c h pytorch 表示为: t o r c h . o p t i m . A d a g r a d ( p a r a m e t e r s , 1 e 1 ) torch.optim.Adagrad(parameters, 1e-1)

3.4 RMSprop

c a c h e t = α c a c h e t 1 + ( 1 α ) ( g t ) 2 cach{e^t} = \alpha *cach{e^{t - 1}} + (1 - \alpha ){({g^t})^2}
w t η c a c h e t + ε g t > w t + 1 {w^t} - \frac{\eta }{{\sqrt {cach{e^t} + \varepsilon } }}{g^t} - > {w^{t + 1}}
   R M S p r o p RMSprop 这里相比较 A d a g r a d Adagrad 多了一个衰减 α \alpha , 这样就不再会将前面所有的梯度平方求和,而是通过一个衰减率将其变小,使用了一种滑动平均的方式,越靠前面的梯度对自适应的学习率影响越小,这样就能更加有效地避免 A d a g r a d Adagrad 学习率一直递减太多的问题,能够更快地收敛 。
   p y t o r c h pytorch 表示为: t o r c h . o p t i m . R M S p r o p ( p a r a m e t e r s , 1 e 1 ) torch.optim.RMSprop(parameters, 1e-1)

3.5 Adam

  这是一种综合型的学习方法,可以看成是 R M S p r o p RMSprop 加上 M o m e n t u m Momentum 的学习方法,达到比 R M S P r o p RMSProp 更好的效果。
   p y t o r c h pytorch 表示为: t o r c h . o p t i m . A d a m ( p a r a m e t e r s , 1 e 1 ) torch.optim.Adam(parameters, 1e-1)

参考

  1. 邱锡鹏:《神经网络与深度学习》 https://nndl.github.io/
  2. 廖星宇:《深度学习之pytorch》