经典神经网络模型的介绍及应用

经典神经网络模型的介绍及应用

摘要:人工智能、深度学习、机器视觉等概念如今充斥我们生活的整个社会,小到小区内的车牌自动识别系统,大到火星探测车的实现都离不开深度学习的应用。读研究生之后一直在学习障碍物的目标识别算法,也走了很多弯路,从传统的基于图像特征的检测算法到现在最新的基于深度学习的目标检测算法,学习过程中也遇到了很多困难,所以也想写一篇学习笔记,涵盖从传统的BP神经网络模型到卷积神经网络分类模型再到现在state-of-art的Faster-RCNN,SSD, YOLO v3等目标检测算法的介绍,也算是自己最近时间的一个学习总结。

一、神经网络简介

首先,我们要明确图像分割、目标检测以及图像分类是计算机视觉领域的三大主要任务。传统的神经网络算法以及卷积神经网络算法都是针对图像分类而提出的解决方案,目标检测算法我们后面会提到,本篇主要针对神经网络模型进行介绍。

1.1 BP神经网络原理

动物学家通过对化石的研究发现,早在5亿4千万年前,经历了一次物种大爆炸----在短短一千万年间有成千上万种新物种产生。而也正是5亿4千万年前,生物首次进化出了视觉系统,是否这是一种巧合呢?也许这是生物学家的事情,但是,视觉在人类的生活当中扮演了不可或缺的角色,有研究表明人类的交谈中有将近70%的信息是通过视觉来获取的。神经网络模型正是计算机视觉的基础,通过神经网络模型,我们赋予计算一种通过图像识别物体信息的能力。许多先进的视觉检测算法本质上都是由BP神经网络模型演化而来的,其都具有BP神经网络模型的一些特征,如激活函数、损失函数以及各种不同的优化方法等。因此,为了入门深度学习和计算机视觉,让我们先从BP神经网络讲起。

1.1.1 BP神经网络结构

BP(back
propagation)神经网络又称后向传播神经网络,是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播算法训练的多层前馈神经网络,在数学上其本质就是一种用来寻找一组最优参数来拟合测试集中的输入输出数据方法,并且使其拥有一定的泛化能力。举个例子:我有如下的一组数据集

x 0.1 0.2 0.3 0.4
y 0.2 0.4 0.6 0.8

显然可以看出输出y=2x,对于一个不在数据集中的输入x=0.5,我们根据上面的数据集所得到的式子来进行预测的话,得到的输出就是y=1.0。当然,这是一个最简单的线性回归的例子,神经网络要做的事也一样。

了解了神经网络的目标,那们我们来看一下神经网络到底是怎么实现这一目标的。先从神经网络的结构说起。

如下图左所示,这是一个典型的神经网络结构。每个小圆圈代表一个神经元节点,每个神经元节点所做的就是将一组权值和输入数据相乘再加上其对应的偏置值,同时节点输出之前要经过一个激活函数,激活函数的作用之后会详细提到。图示结构包括一个输入层,两个隐藏层以及一个输出层。输入元素的个数为3个向量,第一个和第二个隐藏层的节点数都为4个,最后一个为输出层,输出数为1。

在这里插入图片描述

神经网络整体结构 神经元节点

在这里插入图片描述

图1 神经网络结构

之前提到神经网络的作用是用来拟合一组训练集的输入输出数据,现在我们已经知道了神经网络的结构,那么它到底是如何进行拟合的呢。由于神经网络中权值的个数实在是太多了(这也是为什么BP神经网络应用跟不上的原因),所以我们先对公式符号给出如下说明: ω j k [ l ] \omega_{jk}^{[l]} 表示从网络第( l 1 l -1 )层中第k个神经元指向第 l l 层中第j个神经元的连接权重; b j [ l ] b_{j}^{\left\lbrack l\right\rbrack} 表示第 l l 层中第j个神经元的偏置值; a j [ l ] a_{j}^{\left\lbrack l\right\rbrack} 表示第 l l 层中第j个神经元的输出结果,注意这个输出结果是经过激活函数的输出结果,而不是简单的线性输出 ω i x i + b \sum_{}^{}{\omega_{i}x_{i}+ b} ,对单个神经元而言,设激活函数为 f ( x ) f(x) ,则输出 a = f ( ω i x i + b ) a =f(\sum_{}^{}{\omega_{i}x_{i} + b}) 。构造一个如下图所示的神经网络结构:

在这里插入图片描述

图2 神经网络正向传播流程

需要明确的是输入x是一组向量,它具有一定的维度,我们知道向量相乘需要满足一定的条件,及x的行数应该等于 ω \omega 的列数。对从Layer0到Layer1层而言,用矩阵来表示计算过程:

(1) a [ 1 ] = f ( W [ 1 ] X + b [ 1 ] ) \mathbf{a}^{\left\lbrack \mathbf{1} \right\rbrack} = f(\mathbf{W}^{\left\lbrack \mathbf{1} \right\rbrack}\mathbf{X +}\mathbf{b}^{\left\lbrack \mathbf{1} \right\rbrack}) \tag{1}

其中 X = [ x 1 x 2 x 3 ] \mathbf{X} = \begin{bmatrix} \mathbf{x}_{\mathbf{1}} \\ \mathbf{x}_{\mathbf{2}} \\ \mathbf{x}_{\mathbf{3}} \\ \end{bmatrix} W [ 1 ] = [ W 11 [ 1 ] W 12 [ 1 ] W 13 [ 1 ] W 21 [ 1 ] W 22 [ 1 ] W 23 [ 1 ] ] \mathbf{W}^{\left\lbrack \mathbf{1} \right\rbrack} = \begin{bmatrix} \mathbf{W}_{\mathbf{11}}^{\left\lbrack \mathbf{1} \right\rbrack} & \mathbf{W}_{\mathbf{12}}^{\left\lbrack \mathbf{1} \right\rbrack} & \mathbf{W}_{\mathbf{13}}^{\left\lbrack \mathbf{1} \right\rbrack} \\ \mathbf{W}_{\mathbf{21}}^{\left\lbrack \mathbf{1} \right\rbrack} & \mathbf{W}_{\mathbf{22}}^{\left\lbrack \mathbf{1} \right\rbrack} & \mathbf{W}_{\mathbf{23}}^{\left\lbrack \mathbf{1} \right\rbrack} \\ \end{bmatrix} a [ 1 ] = [ a 1 [ 1 ] a 2 [ 1 ] ] \mathbf{a}^{\left\lbrack \mathbf{1} \right\rbrack}\mathbf{=}\begin{bmatrix} a_{1}^{\left\lbrack 1 \right\rbrack} \\ a_{2}^{\left\lbrack 1 \right\rbrack} \\ \end{bmatrix} b [ 1 ] = [ b 1 [ 1 ] b 2 [ 1 ] ] \mathbf{b}^{\left\lbrack \mathbf{1} \right\rbrack}\mathbf{=}\begin{bmatrix} b_{1}^{\left\lbrack 1 \right\rbrack} \\ b_{2}^{\left\lbrack 1 \right\rbrack} \\ \end{bmatrix} 。通常我们会对输入数据进行扁平化处理,使输入 x 1 \mathbf{x}_{\mathbf{1}} n × 1 n \times 1 的向量,那么 W 11 [ 1 ] \mathbf{W}_{\mathbf{11}}^{\left\lbrack \mathbf{1} \right\rbrack} 就应该是 1 × n 1 \times n 的向量,这样才能保证向量能够相乘。 b i [ 1 ] b_{i}^{\left\lbrack 1 \right\rbrack} 是一个标量,其个数等于本层神经元的个数。同样地,我们可以按照这样的方法一直计算到输出层,其实也就是简单的矩阵乘法,只不过在神经层输出之前加了个激活函数而已。这就是神经网络的前向传播过程。

1.1.2 损失函数及优化器

我们已经弄明白了神经网络的前向传播过程,就是给定一组权值,偏置值矩阵然后按照一定的路径计算得到最后的输出值。但是这一组权值和偏置值要怎么选择才能得到最优的解呢?也就是说怎样才能得到一组权值和偏置值,使其能够很好的拟合训练集数据,并且使其具有一定的泛化能力呢?这就是本节损失函数和优化器要做的事了。

1.1.2.1 损失函数

首先,我们要评价一个神经网络结构的好坏,就必须要设定一个评价准则,这个评价准则应该能较好地评价神经网络能不能很好地拟合训练集的数据。常见的损失函数有MSE均方误差损失函数、hinge损失函数以及交叉熵损失函数等。

1.hinge损失函数

损失函数是评价当前分类器好坏的标准,一般而言,若当前分类器的损失很大时我们认为分类效果不好。举个例子:

在这里插入图片描述

图3 神经网络的输出

假设这是通过一个神经网络输出的结果,我们选择得分最高的那一类作为网络输出的预测值。给定一个数据集 { ( x i , y i ) } i = 1 N \left\{\left( x_{i},y_{i} \right) \right\}_{i =1}^{N} ,其中 x i x_{i} 表示图像, y i y_{i} 表示图像标签(人工标定的真实分类)。那么整个数据集的损失是各个图像的损失的平均值:

(2) L = 1 N i L i ( f ( x i , W ) , y i ) L = \frac{1}{N} \sum_{i}^{}{L_{i}(f\left( x_{i},W \right),y_{i})} \tag{2}

其中 L i ( a , b ) L_{i}(a,b) 表示损失函数,则SVM(Hinge)损失函数有如下的表现形式:

L i = j y i m a x ( 0 , s j s y i + 1 ) L_{i} = \sum_{j \neq y_{i}}^{}{max(0,s_{j} - s_{y_{i}} + 1)}

其中 s = f ( x i , W ) s = f\left( x_{i},W\right) 为神经网络的预测得分, s y i s_{y_{i}} 为正确分类对应的得分,函数图像如下:

在这里插入图片描述

看上去公式很复杂是不是?让我结合图3来理解该损失函数时如何表现分类正确与否的,以第一幅图像猫的分类结果为例,正确分类的标签猫的得分 s y 1 = 3.2 s_{y_{1}}= 3.2

L i = j y i m a x ( 0 , s j s y i + 1 ) = max ( 0 , 5.1 3.2 + 1 ) + max ( 0 , 1.7 3.2 + 1 ) = 2.9 L_{i} = \sum_{j \neq y_{i}}^{}max(0,s_{j}-s_{y_{i}} + 1)\\ = \max\left( 0,5.1 - 3.2 + 1 \right) + \max\left( 0, - 1.7 - 3.2 + 1 \right)= 2.9

再来看看第二幅汽车的图像:
L i = j y i m a x ( 0 , s j s y i + 1 ) = m a x ( 0 , 1.3 4.9 + 1 ) + max ( 0 , 2.0 4.9 + 1 ) = 0 L_{i} = \sum_{j \neq y_{i}}^{}max(0,s_{j}-s_{y_{i}} + 1)\\ = max\left( 0,1.3 - 4.9 + 1 \right) + \max\left( 0,2.0 - 4.9 + 1 \right)= 0

同理可以计算第三幅青蛙的损失值为12.9。显然,当图像分类错误的时候,图像的分类损失不为0,并且分类损失值越大表示预测结果与正确分类之间的差值越大,这就是我们需要的。再代入式2求出整个数据集的损失为(2.9+0+12.9)/3=5.27。hinge损失还有很多值得学习的地方,这里只对其算法原理做简单介绍。

2.交叉熵损失函数

可以看到图3的网络输出分数没有界,且存在负数。Softmax分类器就是对输出的分数值做一个指数运算,将其值固定到[0,1]区间内。

P ( Y = k X = x i ) = e s k j e s j w h e r e s = f ( x i ; W ) P( Y = k | X = x_{i} ) = \frac{e^{s_{k}}}{\sum_{j}^{}e^{s_{j}}} \qquad\qquad where \qquad s = f(x_{i};W)

为了使分类损失为正数,并且使错误分类的损失尽可能大(而不是最大值为1),定义交叉熵损失函数为:

L i = l o g P ( Y = y i X = x i ) = l o g ( e s k j e s j ) L_{i} = - logP\left( Y = y_{i} \middle| X = x_{i} \right) = - log(\frac{e^{s_{k}}}{\sum_{j}^{}e^{s_{j}}})

交叉熵损失的计算过程如下:

在这里插入图片描述

一幅图看明白hinge loss和cross-entropy
loss的区别。现在,如果我们有一组训练集,那么我们能够通过神经网络的前向传播过程 s = f ( x ; W ) s=f(x;W) 得到分类结果,并且有能够评价分类结果的损失函数。但是,我们要怎么得到一组最优化的权值W呢?这就是优化器所解决的问题。

在这里插入图片描述

图4 hinge loss和cross-entropy loss的计算过程
1.1.2.2 优化器

反向传播神经网络(BP神经网络)之所以叫做反向传播神经网络就是因为其在神经网络模型的结构中引入了反馈回路,使得能够自动朝最优化方向更新权值,这也是神经网络模型从理论走向应用方面最重要的一步。

L = 1 N   i L i ( f ( x i , W ) , y i ) L = \frac{1}{N}\ \sum_{i}^{}{L_{i}(f\left( x_{i},W \right),y_{i})}

显然,模型的损失是权值W的函数,那么如何才能改变权值的值使得损失函数变小呢?答案就是梯度。梯度是高等数学中的概念,并不是很难理解,其本质是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。也就是说我们求出式(2)的梯度,然后再将权值W按照一定的学习率找梯度方向进行调整,就能达到调整权值以减小loss的目的,也就是我们需要求得的参数是 W L \mathbf{\nabla}_{\mathbf{W}}\mathbf{L} 。那么梯度又要怎么求呢?

我们知道,在数学上梯度的计算方式为:

L ( x , y , , z ) = ( L x ,   L y , L z ) \nabla L(x,y,\ldots,z) = (\frac{\partial L}{\partial x},\ \frac{\partial L}{\partial y}\ldots,\frac{\partial L}{\partial z})

应用到神经网络模型上来,还是举类似于图2所示的神经网络结构为例子进行讲解说明,多了一个输出神经元。这里假设里面的字母所代表的数据全部为标量:

在这里插入图片描述

若我们想对Laye1层的权值数据进行更新,其输出 a 1 [ 2 ] = f ( w [ 2 ] a [ 1 ] + b [ 2 ] ) a_{1}^{\left\lbrack 2\right\rbrack} = f(w^{\left\lbrack 2 \right\rbrack}a^{\left\lbrack 1 \right\rbrack} + b^{\left\lbrack 2\right\rbrack}) ,由于只有一个输出,损失函数 L = L ( a 1 [ 2 ] , y i ) L = L(a_{1}^{\left\lbrack 2\right\rbrack},y_{i}) 。想求 L / w 11 [ 2 ] \partial L/\partial w_{11}^{\left\lbrack 2\right\rbrack} ,有:

L w 11 [ 2 ] = L a 1 [ 2 ] a 1 [ 2 ] w 11 [ 2 ] = L a 1 [ 2 ] f ( Z ) Z Z w 11 [ 2 ] = L a 1 [ 2 ] f ( Z ) Z ( w 11 [ 2 ] a 1 [ 1 ] + w 12 [ 2 ] a 2 [ 1 ] + b [ 2 ] )   w 11 [ 2 ] = L a 1 [ 2 ] f ( Z ) Z a 1 [ 1 ] \frac{\partial L}{\partial w_{11}^{\left\lbrack 2 \right\rbrack}} = \frac{\partial L}{\partial a_{1}^{\left\lbrack 2 \right\rbrack}} \bullet \frac{\partial a_{1}^{\left\lbrack 2 \right\rbrack}}{\partial w_{11}^{\left\lbrack 2 \right\rbrack}} = \frac{\partial L}{\partial a_{1}^{\left\lbrack 2 \right\rbrack}} \bullet \frac{\partial f\left( Z \right)}{\partial Z} \bullet \frac{\partial Z}{\partial w_{11}^{\left\lbrack 2 \right\rbrack}}\\ = \frac{\partial L}{\partial a_{1}^{\left\lbrack 2 \right\rbrack}} \bullet \frac{\partial f\left( Z \right)}{\partial Z} \bullet \frac{\partial{(w}_{11}^{\left\lbrack 2 \right\rbrack}a_{1}^{\left\lbrack 1 \right\rbrack} + w_{12}^{\left\lbrack 2 \right\rbrack}a_{2}^{\left\lbrack 1 \right\rbrack} + b^{\left\lbrack 2 \right\rbrack})\ }{\partial w_{11}^{\left\lbrack 2 \right\rbrack}}\\ = \frac{\partial L}{\partial a_{1}^{\left\lbrack 2 \right\rbrack}} \bullet \frac{\partial f\left( Z \right)}{\partial Z} \bullet a_{1}^{\left\lbrack 1 \right\rbrack}

看到其他blog写的很复杂,又是海塞矩阵又是什么的,其实就是一个很简单的链式求导法则。要求Layer0层的参数也是一样的,通过链式求导法则一直对 ω \omega 求偏导就可以得到整个损失函数对权值 ω \omega 和偏置值 b b 的梯度了,然后再将权值矩阵按照一定的比率照梯度方向进行变化就是能使得函数的损失下降的最快。举个例子,假设上图中Layer1层的初始权值 ω [ 2 ] = [ 1 , 1 ] \omega^{\left\lbrack 2 \right\rbrack} = \lbrack 1,1\rbrack ,输入 a [ 1 ] = [ 2 , 2 ] a^{\left\lbrack 1 \right\rbrack}= \lbrack 2,2\rbrack ,初始偏置 b [ 2 ] = 0 b^{\left\lbrack 2 \right\rbrack} =0 ,经激活函数和softmax分类得到的输出 a 1 [ 2 ] = [ 0.87 , 0.13 ] a_{1}^{\left\lbrack 2 \right\rbrack} =\lbrack0.87,0.13\rbrack ,假设损失函数采用交叉熵损失函数(交叉熵损失函数的导数可以看这里),告诉大家是 ( a i [ th ] y i ) (a_{i}^{\left\lbrack\text{th} \right\rbrack} -y_{i}) ,yi为标签,正确分类时为1,假设第一个样本为错误分类即为0。激活函数先不讲,在下一节会具体讨论,先假设其偏导为1并假设学习率 η = 1 \eta= 1 。根据上式有

W [ 2 ] L = ( L a 1 [ 2 ] a 1 [ 1 ] , L a 1 [ 2 ] a 2 [ 1 ] ) = ( ( 0.87 0 ) 2 , ( 0.87 0 ) 2 ) = ( 1.74 , 1.74 ) \nabla_{W^{\left\lbrack 2 \right\rbrack}}L = \left( \frac{\partial L}{\partial a_{1}^{\left\lbrack 2 \right\rbrack}} \bullet a_{1}^{\left\lbrack 1 \right\rbrack},\frac{\partial L}{\partial a_{1}^{\left\lbrack 2 \right\rbrack}} \bullet a_{2}^{\left\lbrack 1 \right\rbrack} \right)\\ = \left( \left( 0.87 - 0 \right)*2,\left( 0.87 - 0 \right)*2 \right)\\ = (1.74,1.74)

更新权值W:

W [ 2 ] = W [ 2 ] W [ 2 ] L = [ 0.74 , 0.74 ] {W^{\left\lbrack 2 \right\rbrack}}^{'} = W^{\left\lbrack 2 \right\rbrack} - \nabla_{W^{\left\lbrack 2 \right\rbrack}}L = - \lbrack 0.74,0.74\rbrack

偏置值的更新也是同理,这样更新权值的方法我们叫做梯度下降法。目前基本上所有的优化方法都是基于梯度下降法(GD)的。下面对一些常用的优化方法进行介绍:

1.随机梯度下降法(SGD)

先对一些参数符号给出定义:W—要训练的参数;J(W)—代价函数; W J ( W ) \nabla_{W}J(W) —代价函数的梯度; η \eta —学习率。

在这里插入图片描述

梯度下降法在更新每一个参数时,都需要所有的训练样本(由代价函数决定的),所以训练过程会随着样本数量的加大而变得异常的缓慢。在SGD中,每次迭代可以只用一个训练数据来更新参数,如果样本量很大的情况(例如几十万),那么可能只用其中几万条或者几千条的样本,就已经将参数迭代到最优解了。但是,SGD伴随的一个问题是噪音较GD要多,使得SGD并不是每次迭代都向着整体最优化方向。

2. Momentum

在这里插入图片描述

当前权值的改变会受到上一次权值改变的影响,类似于小球向下滚动的时候带上了惯性。这样可以加快小球的向下的速度。

3. NAG(加速梯度下降法)

在这里插入图片描述

在Momentun中小球会盲目地跟从下坡的梯度,容易发生错误,所以我们需要一个更聪明的小球,这个小球提前知道它要去哪里,它还要知道走到坡底的时候速度慢下来而不是又冲上另一个坡。γvt−1会用来修改W的值,计算W−γvt−1可以表示小球下一个位置大概在哪里。从而我们可以提前计算下一个位置的梯度,然后使用到当前位置。

4.Adagrad

在这里插入图片描述

它是基于SGD的一种算法,它的核心思想是对比较常见的数据给予它比较小的学习率去调整参数,对于比较罕见的数据给予它比较大的学习率去调整参数。它很适合应用于数据稀疏的数据集(比如一个图片数据集,有10000张狗的照片,10000张猫的照片,只有100张大象的照片)。

Adagrad主要的优势在于不需要人为的调节学习率,它可以自动调节。它的缺点在于,随着迭代次数的增多,学习率也会越来越低,最终会趋向于0。

5.RMSprop(均方根反向传播)

在这里插入图片描述

RMSprop借鉴了一些Adagrad的思想,不过这里RMSprop只用到了前t-1次梯度平方的平均值加上当前梯度的平方的和的开平方作为学习率的分母。这样RMSprop不会出现学习率越来越低的问题,而且也能自己调节学习率,并且可以有一个比较好的效果。

6.Adadelta

在这里插入图片描述

使用Adadelta我们甚至不需要设置一个默认学习率,在Adadelta不需要使用学习率也可以达到一个非常好的效果。

7.Adam

在这里插入图片描述

就像Adadelta和RMSprop一样Adam会存储之前衰减的平方梯度,同时它也会保存之前衰减的梯度。经过一些处理之后再使用类似Adadelta和RMSprop的方式更新参数。

在这里插入图片描述

图5 不同优化方式的对比

有这么多优化方法,那么是不是说后面的优化方法就一定比前面的好呢?是否可以抛弃SGD、Momentum等早些时期的优化算法呢?其实不是的,当我们拿到一个任务时,我们可以先构建其框架,选定其中的一个优化方法,比如Adam,然后当框架构建好了再尝试不同的优化方法来进行对比,选择其中效果最好的一种作为该网络的优化器。

1.1.3 激活函数的原理及作用

之前一直在说激活函数,到底激活函数是什么东西?我们为什么要在神经元输出的时候加一个激活函数呢?其实,激活函数的作用就是为了解决线性不可分问题。如果我们没有激活函数,通过上节我们介绍的神经网络结构,那么整个神经网络的输出就是一堆的线性组合的结果,也就是说隐藏层没有起到作用,整个神经网络经过简化之后可以变成就只有一个中间层。做个简单推导,从图2中可以知道:

a 1 = W [ 1 ] X + b [ 1 ] a_{1} = W^{\left\lbrack 1 \right\rbrack}X + b^{\left\lbrack 1 \right\rbrack}

a 2 = W [ 2 ] a 1 + b [ 2 ] a_{2} = W^{\left\lbrack 2 \right\rbrack}a_{1} + b^{\left\lbrack 2 \right\rbrack}

那么:

a 2 = W [ 2 ] ( W [ 1 ] X + b [ 1 ] ) + b [ 2 ] = ( W [ 2 ] W [ 1 ] ) X + ( W [ 2 ] b [ 1 ] + b [ 2 ] ) a_{2} = W^{\left\lbrack 2 \right\rbrack}\left( W^{\left\lbrack 1 \right\rbrack}X + b^{\left\lbrack 1 \right\rbrack} \right) + b^{\left\lbrack 2 \right\rbrack} = {(W}^{\left\lbrack 2 \right\rbrack}W^{\left\lbrack 1 \right\rbrack})X + {(W}^{\left\lbrack 2 \right\rbrack}b^{\left\lbrack 1 \right\rbrack} + b^{\left\lbrack 2 \right\rbrack})

W = W [ 2 ] W [ 1 ] , B = ( W [ 2 ] b [ 1 ] + b [ 2 ] ) {W = W}^{\left\lbrack 2 \right\rbrack}W^{\left\lbrack 1 \right\rbrack},B ={(W}^{\left\lbrack 2 \right\rbrack}b^{\left\lbrack 1 \right\rbrack} +b^{\left\lbrack 2 \right\rbrack}) ,有 a 2 = W X + B a_{2} = WX + B

上式只能对线性函数进行拟合,然而大多数情况下我们的数据并不是线性的,所以为了解决上述问题,我们引入了激活函数。

常见的激活函数有Sigmoid、tanh、ReLU、LeakyReLU、Maxout、ELU等。下面来一一进行介绍:

1.Sigmoid激活函数:

在这里插入图片描述

Sigmoid激活函数可以将输入信号的值放缩到[0,1]区间内,我们知道对损失函数求梯度是要计算激活函数的导数的,且该激活函数的导数简单 f ( x ) = f ( x ) ( 1 f ( x ) ) f^{'}\left(x \right) = f(x)(1 -f(x)) 。过去该激活函数一度被视为是生物神经元的饱和“激活率”的展现。然而该激活函数具有一些问题:

①饱和的神经元会“杀死”梯度

②Sigmoid函数的输出并不是“zero-centered output”

③exp()是指数函数,计算时间较长(现在已经不是问题了)。

根据我们上一节的理论,可以知道对权值进行更新时要计算梯度,而梯度的计算又会要求计算激活函数的导数,可以看到Sigmoid函数存在饱和区,在饱和区的函数的导数是为0的,这样就会使得损失函数的梯度 L f ( Z ) f ( Z ) Z Z w \frac{\partial L}{\partial f(Z)} \bullet \frac{\partial f\left( Z \right)}{\partial Z} \bullet\frac{\partial Z}{\partial w} 等于0,导致权值无法更新,这就深度学习中的“梯度消失”问题。此外,当网络的输入全部为正时,并且Sigmoid的导数为正数,那么梯度只取决于 L f ( Z ) \frac{\partial L}{\partial f(Z)} ,这样就会导致权值W的梯度全部为正或全部为负,使得W更新的时候会沿锯齿状更新权值。如下图,这就是为什么我们想要零均值的输入数据

在这里插入图片描述

2.tanh激活函数

在这里插入图片描述

Tanh激活函数将输出值放缩到[-1,1]区间内,这解决了Sigmoid函数不以零点对称的问题,但是其在饱和时依然会“杀死”梯度。

3.ReLU激活函数

在这里插入图片描述

ReLU激活函数不存在饱和区,其最大值是正无穷。并且ReLU激活函数非常易于计算(使用ReLU激活函数要比Sigmoid和tanh函数快将近6倍)。事实上,ReLU激活函数更接近生物神经元的激活原理。然而ReLU的输出依然不是“zero-centered output”,同时ReLU在训练的时候很“脆弱”,一不小心有可能导致神经元“坏死”。例如,一个非常大的梯度流过一个ReLU神经元,更新过参数之后导致许多的权值w变成负数,那么这个神经元再也不会对任何数据有激活现象了,那么这个神经元的梯度就永远都会是0。不理解的看这里。

4.Leaky ReLU激活函数

在这里插入图片描述

Leaky ReLU激活函数没有饱和区,同样也十分易于计算,要比Sigmoid和tanh激活函数收敛的更快,而且不会导致神经元“坏死”。

5.ELU激活函数

在这里插入图片描述

ELU激活函数有所有ReLU激活函数的优点,并且还有近似零均值的输出。其在输入数据小于0时具有一定的饱和度,这能增加对噪声的鲁棒性(我也不知道为什么,cs231n的课件上是这么提的)。但是它需要计算指数函数exp()。

6.Maxout激活函数

在这里插入图片描述

Maxout是一个非线性函数,它不再使用 f ( w T x + b ) f(w^{T}x +b) 函数形式来表示权重和数据的内积,是综合了ReLU和Leaky ReLU激活函数的方法。ReLU和Leaky ReLU都是这个公式的特殊情况(比如ReLU就是当 w 1 = 0 , b 1 = 0 w_{1} = 0,b_{1} = 0 的时候)。Maxout激活函数线性操作不会饱和,而且不存在“坏死”的神经元。然而和ReLU对比,它每个神经元的参数数量增加了一倍,这就导致整体参数的数量激增。

介绍了这么多激活函数,我们在实际使用时应该怎么选用激活函数呢?一种说法是:优先选用ReLU激活函数,但是选用ReLU激活函数时,学习率不要设置的太大。当效果不好时,可以尝试Leaky ReLU/Maxout/ELU激活函数可以使用tanh激活函数,尽量不要使用Sigmoid激活函数

1.2 训练神经网络中的常见问题

1.2.1 梯度消失和梯度爆炸

梯度消失和梯度爆炸其实是一种情况,当神经网络深度很深或者激活函数选择不当时就容易出现梯度消失和梯度爆炸的问题。举个例子,下图是一个四层的BP神经网络:

在这里插入图片描述

根据我们之前说的,如果我们要更新输入层的权值,先求其梯度,有:

W 1 L = L W 1 = L ( f 4 ( W 4 f 3 ( W 3 f 2 ( W 2 f 1 ( W 1 X ) ) ) ) ) W 1 = L f 4 × f 4 f 3 W 4 × f 3 f 2 W 3 × f 2 f 1 W 2 × f 1 W 1 = L f 4 × f 4 f 3 W 4 × f 3 f 2 W 3 × f 2 f 1 W 2 × f 1 Z x i \nabla_{W_{1}}L = \frac{\partial L}{\partial W_{1}} = \frac{\partial L\left( f_{4}\left( W_{4}f_{3}\left( W_{3}f_{2}\left( W_{2}f_{1}\left( W_{1}X \right) \right) \right) \right) \right)}{\partial W_{1}}\\ = \frac{\partial L}{\partial f_{4}} \times \frac{\partial f_{4}}{\partial f_{3}}W_{4} \times \frac{\partial f_{3}}{\partial f_{2}}W_{3} \times \frac{\partial f_{2}}{\partial f_{1}}W_{2} \times \frac{\partial f_{1}}{\partial W_{1}}\\ = \frac{\partial L}{\partial f_{4}} \times \frac{\partial f_{4}}{\partial f_{3}}W_{4} \times \frac{\partial f_{3}}{\partial f_{2}}W_{3} \times \frac{\partial f_{2}}{\partial f_{1}}W_{2} \times \frac{\partial f_{1}}{\partial Z}x_{i}

其中 f n f_{n} 表示第n-1层经激活函数的输出, f n = f ( w n × f n 1 + b n ) f_{n} = f(w_{n} \times f_{n - 1} +b_{n}) ,所以 f n f n 1 \frac{\partial f_{n}}{\partial f_{n - 1}} 就是激活函数的导数,可以看到,当激活函数的导数小于1时,梯度最后一项 x i x_{i} 前面的系数就是一个小于1的小数的连乘,相当于是一个指数的增益,最终导致梯度变得很小,也就是梯度消失。同样地,当激活函数的导数大于1时,梯度的数值就会呈指数的上升,也就是梯度爆炸

此外,之前我们也提过部分损失函数如Sigmoid和tanh激活函数具有饱和区,这也是产生梯度消失的原因之一。还有,如果初始化权值很大,w大到乘以激活函数的导数都大于1,那么连乘后,可能会导致求导的结果很大,也会形成梯度爆炸。其实,在实际情况中,梯度消失要比梯度爆炸更容易出现

知道了产生梯度消失和梯度爆炸的原因之后再简单介绍一下如何避免产生梯度消失和梯度爆炸的方法:①既然梯度消失和激活函数有关,所以应该选择合适的激活函数如ReLU激活函数及其变体来代替Sigmoid激活函数;②减少网络层深度;③使用更合理的网络结构,如Resnet网络,RNN网络等;④合理的初始化权值等。

1.2.2 过拟合

我们在进行神经网络训练之前,通常将数据集分为三个部分,训练集(train)、测试集(test)以及验证集(validation)。训练集是指用于模型拟合的数据样本。验证集是模型训练过程中单独留出的样本集,它可以用于调整模型的超参数和用于对模型的能力进行初步评估,所谓超参数就是不会随训练过程而改变的参数,如学习率,迭代次数以及层数等。测试集用来评估模最终模型的泛化能力。但不能作为调参、选择特征等算法相关的选择的依据。过拟合是指为了得到与训练集完全匹配的参数而使训练过程变得过度严格。过拟合的具体表现就是最终模型在训练集上效果很好,但是在测试集上效果很差,也就是所说的模型泛化能力弱。如下图:显然整个网络的损失是不断下降的,然而,从右图中可以看到,训练集上的精度在不断上升但是在验证集上的精度却上升缓慢,而我们并不在意模型在训练集上的表现,我们真正在意的是模型能不能很好地去拟合未知的数据,这就是过拟合的具体表现形式。

在这里插入图片描述

产生过拟合根本原因是特征维度过多,模型假设过于复杂,参数过多,训练数据过少,噪声过多,导致拟合的函数完美的预测训练集,但对新数据的测试集预测结果差。解决过拟合的方法有:增加训练集样本、Batch Normalization方法以及正则化等。

1.增加训练样本

可以说,防止过拟合最好的方法就是对数据集进行扩增。在深度学习领域,有人曾说过:“训练模型有时候不是由于算法好赢了,而是由于拥有海量的数据才赢了。”从中可见训练数据有多么重要,特别是在深度学习方法中,海量的训练数据,意味着能够用更深的网络,训练出更好的模型。除了去收集更多的数据外,还可以通过旋转、缩放、裁切、引入随机噪声等方法来扩增原始的数据集

2.Batch Normalization(批标准化)方法

神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低;另外一方面,一旦每批训练数据的分布各不相同(batch 梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度。Batch Normalization就是为了解决上述问题而提出的一种算法。就像激活函数层、卷积层、全连接层、池化层一样,BN(Batch Normalization)也属于网络的一层。BN层会对输入的数据进行一个归一化变换,使其全部符合标准正态分布,并且BN算法还引入了两个可学习的参数 γ , β \gamma,\beta 来调整输出的均值和标准差。

BN算法首先需要分别计算输入数据的平均值和标准来代替样本均值和标准差,这在数据量很大的情况下是可行的,然后再对数据进行数据归一化处理,这是概率论和数理统计里面的知识:

在这里插入图片描述

这样就对输入数据固定成符合标准正态分布的类型了。一般我们将BN层插入到全连接层或卷积层之后,并在进行非线性变化(激活函数)之前。如下图:

在这里插入图片描述

但是在有些情况下,我们并不想将输入数据符合标准正态分布,比如在tanh函数的中间区域是类似一个线性的函数,如果我们全部将输入数据固定到标准正态分布,也就是将数据变换成分布于tanh激活函数的中间部分,就类似一个线性变换了。如之前提到的,对神经网络模型而言,深层的线性网络是没有意义的,我们总可以找到一组权值来等价于这一连串的深层网络。为此,我们引入了两个可学习的参数 γ , β \gamma,\beta 来调整输出的均值和标准差。

y ( k ) = γ ( k ) x ^ ( k ) + β ( k ) y^{(k)} = \gamma^{(k)}{\hat{x}}^{(k)} + \beta^{(k)}

注意:网络是可以通过学习使得 γ ( k ) = Var [ x ( k ) ] \gamma^{(k)} = \sqrt{\text{Var}\left\lbrack x^{(k)} \right\rbrack} β = E [ x ( k ) ] \beta = E\lbrack x^{(k)}\rbrack 来恢复原始输入的相同的分布的。BN算法的流程如下(下面的公式中m指的是mini-batch size):

在这里插入图片描述

通过对数据的归一化操作,使得输入激活函数的数据大致分布在非饱和区域,能够极大地提高收敛速度。添加BN层可以选择较大的学习率,实际上每层需要的学习率是不一样的,同一层不同维度的scale往往也需要不同大小的学习率,通常需要使用最小的那个学习率才能保证损失函数有效下降,Batch Normalization将每层、每维的scale保持一致,那么我们就可以直接使用较高的学习率进行优化。此外,BN层还能够减少超参数的选择,比如导致overfit的位置往往在数据边界处,如果初始化权重就已经落在数据内部,overfit现象就可以得到一定的缓解,这样就能降低对Dropout以及L2正则权值参数的依赖(后面会提)。总而言之,经过这么简单的变换,带来的好处多得很,这也是为何现在BN这么快流行起来的原因。

3.添加正则化损失项

正则化(Regularization)是机器学习中抑制过拟合问题的常用算法,常用的正则化方法是在损失函数(Cost Function)中添加一个系数的L1范数或L2范数项,用来抑制过大的模型参数,从而缓解过拟合现象。产生过拟合的原因有很多,其中一个就是原始数据集的变量太多。

对于神经网络模型而言,如果我们能够将部分变量的权重w置为0,也就能够限制参数量的个数,也就是减少了参数量,这样就能达到防止过拟合的目的(这也是Dropout的核心思想)这个下一小节再讲。这里我们用另一种思路,寻找更宽松的限制条件,将部分权值w的值设置成很小

j w j 2 C \sum_{j}^{}{w_{j}^{2} \leq C}

其中 w j 2 \sum w_{j}^{2} 就是L2范数,应用L2范数的边界条件我们叫做L2正则,若是L1正则,则应该满足 w j < C \sum\left| w_{j} \right| < C 。这种思想类似于高等数学中的拉格朗日边界条件,给原来的求最值函数附加一个边界条件,也就是在原来的损失函数上加入一个正则损失项:

L = L ( W ; x i , y i ) + λ R ( W ) L^{'} = L\left( W;x_{i},y_{i} \right) + \lambda R(W)

下面,我用一张图来说明如何在限定条件下,对原始函数进行最小化的优化,假设有一个二维的权值变量β1,β2:

在这里插入图片描述

如上图所示,左侧代表L1正则化损失,右图代表L2正则化损失。如果是权值是一个二维的数据,L1损失就可以表示为 w 1 + w 2 < C {|w}_{1}|+ |w_{2}| < C ,L2损失为 w 1 2 + w 2 2 < C w_{1}^{2} + w_{2}^{2} <C ,在图中就表示为一个菱形和圆形。以L2正则为例,红色的圆圈表示原来不加正则项的函数,它的最优解为 β ^ \hat{\beta} ,本来按照梯度速降法是要一直沿着红色圈半径减少的方向更新权值的,但是我们加入了正则向之后,就增加了一个约束条件,即是权值范围一定要固定到绿色的圆圈内,这样的解就不是原始函数的最优解了,能够在一定程度上减少过拟合现象(过拟合就是对训练集拟合过于完美),而且可以看到图中β1的值很小,这样又能起到减少参数变量的作用,进一步降低过拟合的影响。同时,还应该注意到,对与上图左所示的L1正则函数,他的 β 1 \beta1 参数等于0,相当于β1对应的变量在模型训练中完全不起作用,这就是我们所说的L1正则具有稀疏性的原因(不展开讲,其实很简单,有兴趣的同学可以自己去了解)。这样,我们就从图像化的角度,分析了L1和L2正则的物理意义,下面我从数学角度给出更加直观的解释。

我们前面已经提到,如果有一组权值,减小它的值就能起到减少参数变量的作用,进而降低过拟合的影响(y=wi*x­i)。加入正则化的损失函数为:

C = C 0 + λ 2 n ω 2 C = C_{0} + \frac{\lambda}{2n}\sum\omega^{2}

其中C0代表原始的损失函数,后面那一项就是L2正则化项,λ就是正则项系数,权衡正则项与C0项的比重。另外还有一个系数1/2n主要是为了后面求导的结果方便。求损失函数的梯度:

C w = C 0 w + λ n w \frac{\partial C}{\partial w} = \frac{\partial C_{0}}{\partial w} + \frac{\lambda}{n}w

C b = C 0 b \frac{\partial C}{\partial b} = \frac{\partial C_{0}}{\partial b}

可以发现L2正则化项对b的更新没有影响,但是对于w的更新有影响:

w w η C 0 w η λ n w \ n = ( 1 ηλ n ) w η C 0 w {w \rightarrow w - \eta\frac{\partial C_{0}}{\partial w} - \eta\frac{\lambda}{n}w\backslash n}{= \left( 1 - \frac{\text{ηλ}}{n} \right)w - \eta\frac{\partial C_{0}}{\partial w}}

在不使用L2正则化时,求导结果中w前系数为1,现在w前面系数为1−ηλ/n,因为η、λ、n都是正的,所以1−ηλ/n小于1,它的效果是减小w,这也就是权重衰减(weight decay)的由来。当然考虑到后面的导数项,w最终的值可能增大也可能减小

4.Dropout方法

L1、L2正则化是通过修改代价函数来实现的,而Dropout则是通过修改神经网络本身来实现的,其实我们在进行构建神经网络模型的时候更多地是采用这种方式

在这里插入图片描述

图6 Dropout方法

Dropout方法是在训练阶段,在每次前向传播的过程中随机的将某些神经元置为0,dropping率是一个超参数,我们通常将其设置为0.5。之前说过产生过拟合的重要原因之一是要训练的参数过多,通过Dropout方法可以简单方便地减少参数量;在测试阶段再将每一个神经元的权重参数乘以概率p。

1.2.3 参数初始化

权值参数初始化也是构建神经网络模型中很重要的一步,如果权值初始化数值不合适会产生很多问题,常见的权值初始化方法有:常量初始化(constant)、高斯分布初始化(gaussian)、positive_unitball初始化、均匀分布初始化(uniform)、xavier初始化、msra初始化、双线性初始化(bilinear)等。下面举几个权值初始化不合适的例子:

1.将均值为0,标准差为0.01的正态分布作为权值初始值(初始化值很小)

这样的权值初始化策略对浅层网络或许还行,但是对深层网络却会出现一些问题。比如我们有一个10层,每层有500个神经元的神经网络模型,并使用tanh函数作为激活函数。采用上述初始化策略来初始化每层的权值,每层得到的输出如下:

在这里插入图片描述

可以看到,几乎所有的输出值都变成了0!由于每一层的权值都非常小,所以每一层都好像衰减运算一样,最后会导致输出值均值为0,方差也为0。这样就会导致权值根本无法更新,梯度不会传播,网络无法正常训练。

2.将均值为0,标准差为1的正态分布作为权值初始值(初始化值很大)

如果使用这样的初始化策略,那么激活所有的神经元都会饱和,输出值全部是-1或者1。梯度依然会是0,权值无法更新,如下图。

在这里插入图片描述

所以,过大或过小初始化权值策略都不合理,需要一个恰当的标准差来初始化参数,比如可以用Xavier初始化方法来进行权值初试化等(我没了解过)。目前还没有一种通用的方法来进行参数的初始化,有兴趣的读者可以看下面的论文:

在这里插入图片描述

二、基于神经网络模型的手写数字识别

讲了这么多,终于把BP神经网络的理论部分讲完了,这是目前深度学习网络的基础,包括后面会讲到的卷积神经网络、循环神经网络、目标检测神经网络都是以BP神经网络为框架的,只有深刻理解了BP神经网路的原理才能更好地进行后面部分的学习。下面给一个BP神经网络应用的例子,对MNIST手写数字数据集进行训练并使其达到98%以上的精度。编程语言使用python,框架采用Tensorflow(Tensorflow的安装我有在之前的博文中介绍,不会的可以去看看)。

MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。此数据集中,共有60000个训练样本,其中55000个用于训练,另外5000个用于验证,还有10000个测试样本。下面来搭建我们的神经网络:

如下图,我们构建了一个三层的神经网络模型,训练集有60000个28*28像素大小的手写数字图像,第一层有500个节点,第二层有300个节点,最后一层是输出层,采用一个softmax分类器输出10个数字对应的标签。激活函数采用tanh激活函数,为了防止过拟合,使用Dropout方法,dropping率为0.8,使用交叉熵损失函数,使用Adam方法来进行参数优化,权值初始值符合标准差为0.1,均值为0的标准正态分布,并且学习率会随着迭代的进行而降低(这样做是为了防止震荡),总共循环51个epoch后完成网络训练。

在这里插入图片描述

下面是具体的程序:

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 载入数据集
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

# 每个批次的大小
batch_size = 100
# 计算一共有多少个批次
n_batch = mnist.train.num_examples // batch_size

# 定义两个placeholder
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
keep_prob = tf.placeholder(tf.float32)
lr = tf.Variable(0.001, dtype=tf.float32)

# 创建一个简单的神经网络
W1 = tf.Variable(tf.truncated_normal([784, 500], stddev=0.1))
b1 = tf.Variable(tf.zeros([500]) + 0.1)
L1 = tf.nn.tanh(tf.matmul(x, W1) + b1)
L1_drop = tf.nn.dropout(L1, keep_prob)

W2 = tf.Variable(tf.truncated_normal([500, 300], stddev=0.1))
b2 = tf.Variable(tf.zeros([300]) + 0.1)
L2 = tf.nn.tanh(tf.matmul(L1_drop, W2) + b2)
L2_drop = tf.nn.dropout(L2, keep_prob)

W3 = tf.Variable(tf.truncated_normal([300, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros([10]) + 0.1)
prediction = tf.nn.softmax(tf.matmul(L2_drop, W3) + b3)

# 交叉熵代价函数
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=prediction))
# 训练
train_step = tf.train.AdamOptimizer(lr).minimize(loss)

# 初始化变量
init = tf.global_variables_initializer()

# 结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))  # argmax返回一维张量中最大的值所在的位置
# 求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

with tf.Session() as sess:
    sess.run(init)
    for epoch in range(51):
        sess.run(tf.assign(lr, 0.001 * (0.95 ** epoch)))
        for batch in range(n_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 0.8})

        learning_rate = sess.run(lr)
        acc = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})
        print("Iter " + str(epoch) + ", Testing Accuracy= " + str(acc) + ", Learning Rate= " + str(learning_rate))

如果对某个具体的函数不是很明白,说明你还没有掌握Python/Tensorflow的用法,我在这方面也是刚入门,遇到那个语句不懂去去百度搜吧:)

输出结果如下:

在这里插入图片描述

可以看到,在第28次迭代的时候就已经达到了98%的精度,迭代51次后精度可以达到98.1%。但是BP神经网络依然具有很多问题,对于复杂、多目标场景下的分类难以达到预期效果,这时就轮到基于卷积神经网络的先进算法粉墨登场了。下一篇文章我将介绍从AlexNet、VGG16、GoogleNet以及ResNet等一众当下流行的卷积神经网络架构,这些架构是实现目标检测的基础。

猜你喜欢

转载自blog.csdn.net/LIT_Elric/article/details/86477639