注意:本文所提的神经网络在这里特质多层前馈神经网络
神经网络的numpy实现代码:Neural Network with Numpy

1. 神经网络的基础结构

神经网络 线性 神经网络 线性映射_激活函数

一个简单的神经网络基础结构包括三个,线性映射,激活层,隐藏层。
如图,输入层的输入向量经过一个线性映射,在经过一个激活层,到达了第一个隐藏层,随着网络的加深,重复线性映射,激活层,隐藏层的过程直至到达输出层。流程很简单,但是需要理解几个问题。

  • 线性映射是什么?
    假设如图所示,输入向量为四维向量神经网络 线性 神经网络 线性映射_映射函数_02,则经过图中的权重矩阵神经网络 线性 神经网络 线性映射_神经网络_03便是一次线性映射,即神经网络 线性 神经网络 线性映射_神经网络 线性_04
  • 2. 为什么要有激活层?
    如果不添加激活层,则一个多层的网络则可以看做是多个线性映射的复合,即上一层网络的输出作为下一层的输入,假设第一层线性映射函数为神经网络 线性 神经网络 线性映射_激活函数_05,则第二层神经网络 线性 神经网络 线性映射_神经网络 线性_06, 则最后的输出为神经网络 线性 神经网络 线性映射_映射函数_07。可以看到,多层的线性映射可以用单层的线性映射表示,失去了拟合非线性的能力。所以我们需要对每层线性映射的输出加入非线性的激活函数。

我们以矩阵的运算形式表示,对于 神经网络 线性 神经网络 线性映射_映射函数_08层:
经过线性映射:神经网络 线性 神经网络 线性映射_激活函数_09
经过激活层:神经网络 线性 神经网络 线性映射_神经网络_10

2. 优化

神经网络并没有解析解,我们需要借助梯度下降的方式进行优化。那么,我们如何求每层的梯度呢?

2.1 反向传播算法(Backpropagation)(觉得太麻烦可以直接看2.1.3)

2.1.1 传统方法

反向传播的基础是链式法则,这里就不多介绍。
现在有一个多层的神经网络,我们第 神经网络 线性 神经网络 线性映射_映射函数_08 层为例,看一下他的前向传播。
来自上一层的输入神经网络 线性 神经网络 线性映射_映射函数_12经过线性映射的第i个输出:
神经网络 线性 神经网络 线性映射_激活函数_13
然后经过激活函数f(x),然后得到l+1层的第i个输出:
神经网络 线性 神经网络 线性映射_深度学习_14

根据链式法则,便可以很容易求得:
神经网络 线性 神经网络 线性映射_映射函数_15
神经网络 线性 神经网络 线性映射_映射函数_16

其中神经网络 线性 神经网络 线性映射_深度学习_17很明显与激活函数的导数有关。

所以呢,反向传播算法可以理解为利用第l+1层传递过来的梯度,求解Loss对第l层层参数(W,b)的梯度,并传递给l-1层

2.1.2 矩阵运算

由上面的计算不难看出,实际上神经网络的计算都是矩阵运算,我们从矩阵的角度来理解反向传播的计算会更加容易一些。
不同于标量运算,矩阵运算不满足交换律(神经网络 线性 神经网络 线性映射_激活函数_18), 且矩阵必须形状匹配。
维数相容原则:若一个神经网络 线性 神经网络 线性映射_深度学习_19矩阵x经过f(x)变换为标量y,则神经网络 线性 神经网络 线性映射_深度学习_20必定也是一个神经网络 线性 神经网络 线性映射_深度学习_19大小矩阵。

所以我们的对矩阵求导时,可以先把矩阵看做标量正常求导,然后调整结果使得结果满足维数相容原则和矩阵相乘配型。

每一层的前向传播为:
神经网络 线性 神经网络 线性映射_激活函数_22
神经网络 线性 神经网络 线性映射_神经网络_10
神经网络 线性 神经网络 线性映射_激活函数_24

神经网络 线性 神经网络 线性映射_神经网络 线性_25
神经网络 线性 神经网络 线性映射_神经网络 线性_26

2.1.3 以Loss对具体某个w的梯度为例

神经网络 线性 神经网络 线性映射_神经网络 线性_27

损失函数为均方误差: 神经网络 线性 神经网络 线性映射_激活函数_28

Net:的输入输出为sigmoid: 神经网络 线性 神经网络 线性映射_映射函数_29

神经网络 线性 神经网络 线性映射_映射函数_30

神经网络 线性 神经网络 线性映射_神经网络_31

神经网络 线性 神经网络 线性映射_激活函数_32

神经网络 线性 神经网络 线性映射_激活函数_33

可以看到,梯度跟中间的权重 (w5),激活函数(神经网络 线性 神经网络 线性映射_神经网络 线性_34) ,还有损失函数(神经网络 线性 神经网络 线性映射_神经网络_35)有关

神经网络 线性 神经网络 线性映射_神经网络 线性_34激活函数的导数神经网络 线性 神经网络 线性映射_深度学习_37最大为0.25,又因为通常我们的w初始化通常小于1,所以神经网络 线性 神经网络 线性映射_神经网络 线性_38

2.2 梯度消失与激活函数

由2.1我们知道,实际上反向传播算法便是利用前一层的梯度求本层的梯度,并传递给下一层。当网络层数很深,且每层梯度都小于1的时候,反向传播的越深,随着梯度的累乘,浅层的网络梯度会变得非常小,这边是梯度消失;同理当梯度大于1,便会出现梯度爆炸的问题。
由2.1.1可以知道,每一层的梯度实际上跟激活函数有着很大的关系,所以我们可以通过改变激活函数,控制激活函数的导数大小,从而一定程度上解决梯度消失的问题。

2.2.1 常见的激活函数

  • Sigmoid函数

神经网络 线性 神经网络 线性映射_激活函数_39

函数表达式为神经网络 线性 神经网络 线性映射_神经网络_40
导数为神经网络 线性 神经网络 线性映射_神经网络_41

特点
作为以前很常用的激活函数之一,sigmoid的特点便是可以将输入值统一压缩在[0,1]的值域内,对于极大的正值,经过sigmoid后值为1,极小的的负值,经过sigmoid后为0,这一性质使得它在“门”结构中得到了广泛使用(例如LSTM中的门),同样,也使得它很容易与概率对应起来,在二分类问题在也很常用。
缺点:

  1. 由sigmoid的导数也可以看到,每过一层,梯度就会变成原来的0.25倍甚至更小,这很容易导致梯度消失问题
  2. 经过sigmoid的输出并不是0均值的输出。意思是单样本训练中,经过sigmoid到下一层的输入x>0或者x<0,这使得f(x)=wx+b求w局部梯度要么都为正,要么都为负,也就意味着此次所有的w要么全往正向更新,要么全往负向更新,降低了训练速度。当然,按batch进行训练一定程度上可以解决这个问题。
  3. sigmoid的解析解中的幂运算对于计算机来说求解比较麻烦。
  • tanh函数

神经网络 线性 神经网络 线性映射_神经网络 线性_42

函数表达式为神经网络 线性 神经网络 线性映射_深度学习_43
导数为神经网络 线性 神经网络 线性映射_神经网络 线性_44
特点几乎与sigmoid相同,不同点在于将sigmoid的值域拉伸到了[-1,1],解决了sigmoid的输出不以0均值问题,但幂运算和梯度消失的问题仍未解决。

  • ReLu
  • 神经网络 线性 神经网络 线性映射_神经网络 线性_45

  • Relu特点很明显, 对于负值梯度一律取0,对于正值梯度一律为1,所以不存在梯度消失的问题,但是同时会带来梯度死亡问题,即一旦某个神经元的想x<0, 梯度便为0,从此该神经元参数便永远不会被更新。当然,我们可以通过合理的初始化和学习率减少这样的概率。
    同时,Relu的计算简单,只需要判断阈值即可,计算速度很快。并且x<0时的输出为0,达到了类似于dropout的产生稀疏性的效果。
    唯一的问题与sigmoid类似,输出不是以为0为均值中心,但是它的优点实在太明显,所以至今为止仍然被广泛使用。
  • 其他激活函数
    还有一些激活函数是针对relu的改进,比如PReLu,ELU, 理论上效果好于Relu,但实际应用中并未发现如此。如果感兴趣的朋友可以自行查阅。

2.3 其他解决梯度消失与梯度爆炸的方法

梯度爆炸

  • 梯度剪切/截断: 设置梯度最大值的阈值
  • 正则化

梯度消失

  • Batch Normolization(BN):将每一层的输出规范化为0均值1方差的正态分布,使得激活函数的输入值落在更敏感的区域,从而使得损失函数变化更大,梯度更大,一定程度上避免梯度消失,而且加快训练收敛速度。
  • 残差结构:
  • LSTM