项目报告:使用numpy搭建神经网络,进行手写数字识别-2019

使用numpy搭建神经网络,进行手写数字识别

1 实验目的与要求

实验要求使用神经网络来完成基于MNIST的手写数字的分类。其中MNIST数据库是由Yann提供的手写数字数据库文件。这个数据库主要包含了60000张的训练图像和10000张的测试图像,囊括了各种手写数字图片。数据样本如下图所示。
在这里插入图片描述
实验要求使用手动搭建神经网络,实现前后向,参数更新。神经网络结构不限,建议使用CNN,并利用训练好的神经网络对于测试集进行分类,依据初步的分类结果,对所设计的网络结构进行分析与改进,最后对于结果进行分析,并与前两次实验(模式识别第一次实验和第二次实验)结果进行对比分析。

2 实验原理与过程

2.1 使用numpy搭建卷积层以及反向传播算法

首先这里约定下面的字母参数的含义:(1)卷积层的输入量为N;输入的深度为C;输入高度为H;输入宽度为W。(2)对于卷积核,filter的数量为K;空间的范围(边长)为F;步幅为S,零填充的个数为P。所以最终的输出空间的大小为:
在这里插入图片描述
首先考虑正向传播:卷积层用卷积运算代替矩阵乘法,计算ijth 的非线性层,我们有如下的公式:
在这里插入图片描述
所以根据上面的公式,在每个图像、每个通道上为我们的每个过滤器在每个F×F位置做点积。即:
在这里插入图片描述
在这里插入图片描述
为了提高效率和简化计算,我们需要做的是收集进行卷积运算所需的所有位置,并在每个位置进行点积运算。下面举一个具体的例子:
假设这里有一个图像,其尺寸为1×1×4×4,还有一个1×1×2×2的过滤器,这里我们令S=1(S为步幅),p=1(p为零填充的数量)。所以在进行padding后,我们的图片形状变为1×1×6×6。即图片的矩阵如下图:
在这里插入图片描述
所以现在我们的卷积核可以在长宽均为4-2/1+1=5的移动,其中在第一行和第二行中可以与卷积核计算的位置是:
在这里插入图片描述
这样我们对于整个图像的25个可进行卷积运算区域进行运算,这里使用1×2×2的卷积和,并将其扩展为4×1的列向量。由此我们在25个区域中,最终得到4×25大小的感受野矩阵,如下所示:
在这里插入图片描述
接下来是反向传播:对于误差的计算和网络参数的更新,根据公式,我们必须找到每一个权重的梯度变化以及偏置的变化.根据微分的性质,上式的表达式可写为如下的公式:
∂C/(∂W_ab^l )=∑_(i=0)(N-m)▒∑_(j=0)(N-m)▒〖∂C/(∂Z_ij^l )×(∂Z_ijl)/(∂W_abl )〗=∑_(i=0)(N-m)▒∑_(j=0)(N-m)▒〖∂C/(∂Z_ij^l )×a_(i+a)(j+b)^(l-1) 〗
其中为了计算反向传播的误差,我们还需要计算∂C/(∂a_ij^(l-1) )的值,计算公式如下:
在这里插入图片描述
对于偏置的更新梯度来说,我们只需像全连接层进行反向传播一样累积梯度即可。具体的计算公式如下:
在这里插入图片描述
对于每一个神经元进行权重以及偏置的梯度变化计算,并逐层“反馈”到前一层,这样最终就可以完成一轮的反向转播计算。
对于在网络中选取不同大小的batch_size,我们可以将多个数据,同时放入网络进行计算,其计算步骤完全相同,计算误差时,直接将多个数据的误差累加即可。
对于卷积的反向传播算法,主要分为4个步骤:首先是对于输入量进行padding操作,接着对于卷积核参数进行翻转,然后对卷积核进行转置操作;最后对翻转后的卷积核与padding后的pad_delta进行卷积计算。其中的计算方法与上面描述的正向传播的方法相同。
具体的实现代码见neuralnet.py中的convolutionBackward函数以及network.py中的conv函数。

2.2 使用numpy完成relu等激活函数

在前向传播中,为了增加网络的非线性,一般会在每一层的结果输出前让结果通过非线性的激活函数。这里我实现了Relu、Sigmoid以及Tanh等激活函数。
Relu函数:
在这里插入图片描述
实现代码:np.where(x>0,x,0)
Sigmoid函数:
在这里插入图片描述
实现代码:1/(1+np.exp(-x))
Tanh函数:
在这里插入图片描述
实现代码:s=(np.exp(x)- np.exp(-x))/( np.exp(x)+ np.exp(-x))

2.3 使用numpy完成池化层

在这里我们只考虑maxpool,即取区域内的最大值作为池化后区域的新数值。所谓的池化,即为降采样。这种机制能够有效地原因在于,在发现一个特征之后,它的精确位置远不及它和其他特征的相对位置的关系重要。池化层会不断地减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合。通常来说,CNN的卷积层之间都会周期性地插入池化层。池化层通常会分别作用于每个输入的特征并减小其大小。当前最常用形式的池化层是每隔2个元素从图像划分出22的区块,然后对每个区块中的4个数取最大值。这将会减少75%的数据量。基于22的区块的maxpool的示意图如下:
在这里插入图片描述
代码实现如下:
在这里插入图片描述
对于池化层的反向误差传播,是不可导的,因为Pooling操作使得feature map的尺寸变化,假如做2×2的池化,假设那么第l+1层的feature map有16个梯度,那么第l层就会有64个梯度,这使得梯度无法对位的进行传播下去。其实解决这个问题的思想也很简单,就是把1个像素的梯度传递给4个像素,但是需要保证传递的loss(或者梯度)总和不变。根据这条原则,mean pooling和max pooling的反向传播也是不同的。我们在这里只考虑maxpooling。
对于maxpooling需要满足误差计算前后梯度之和不变的原则。max pooling的前向传播是把patch中最大的值传递给后一层,而其他像素的值直接被舍弃掉。那么反向传播也就是把梯度直接传给前一层某一个像素,而其他像素不接受梯度,也就是为0。具体的maxpooling的反向传播的实现代码如下:
在这里插入图片描述

2.4 全连接层的实现

这里的卷积层是为了将使用卷积计算得到的数据特征进行分类,其中输出神经元的个数就是类别数10。这里直接使用numpy自带的矩阵点乘的函数(.dot()函数)进行了全连接层的实现。例如w3为此全连接层的矩阵,fc为此全连接层的输入向量,b3为此全连接层的偏置:
在这里插入图片描述

2.5 Adam、SGD等优化器以及的softmax实现

目前优化算法主要用的就是梯度下降算法,在原始梯度下降的基础上变化出很多更加优秀的算法。发展历史为:BGD ⇒ SGD ⇒SGDM ⇒ NAG ⇒ AdaGrad ⇒ AdaDelta ⇒ Adam ⇒ Nadam。其中,固定学习率优化算法有:BGD、SGD、SGDM、NAG;自适应学习率优化算法有:AdaGrad、AdaDelta、Adam、Nadam。他们的优劣如下表:

算法 优点 缺点 适用情况
BGD 目标函数为凸函数时,可以找到全局最优值 收敛速度慢,需要用到全部数据,内存消耗大 不适用于大数据集,不能在线更新模型
SGD 避免冗余数据的干扰,收敛速度加快,能够在线学习 更新值的方差较大,收敛过程会产生波动,可能落入极小值(卡在鞍点),选择合适的学习率比较困难(需要不断减小学习率) 适用于需要在线更新的模型,适用于大规模训练样本情况
Adagrad 实现学习率的自动更改 仍依赖于人工设置一个全局学习率,学习率设置过大,对梯度的调节太大。中后期,梯度接近于0,使得训练提前结束 需要快速收敛,训练复杂网络时;适合处理稀疏梯度
Momentum 能够在相关方向加速SGD,抑制振荡,从而加快收敛 需要人工设定学习率 适用于有可靠的初始化参数
Adadelta 不需要预设一个默认学习率,训练初中期,加速效果不错,很快,可以避免参数更新时两边单位不统一的问题 在局部最小值附近震荡,可能不收敛 需要快速收敛,训练复杂网络时
Adam 速度快,对内存需求较小,为不同的参数计算不同的自适应学习率 在局部最小值附近震荡,可能不收敛 需要快速收敛,训练复杂网络时;善于处理稀疏梯度和处理非平稳目标的优点,适用于大数据集和高维空间

所以在这里我测试了两种优化器,分别是固定学习率优化的SGD(实验推荐)以及自适应学习率优化的Adam。
其中固定学习率优化的SGD(随机梯度下降),这个算法提出的来由就是因为BGD的迭代速度在大数据量的情况下会变得非常慢。SGD每一次迭代只使用一个样本,根据这一个样本来计算梯度。其迭代公式如下:
θ_j=θ_j-α(h_θ (x^i )-y^i ) x^i
具体的实现代码如下:
在这里插入图片描述
当然也有很多的对于SGD的改进优化器,例如带动量的随机梯度下降(Momentum-SGD),因为SGD只依赖于当前迭代的梯度,十分不稳定,加一个“动量”的话,相当于有了一个惯性在里面,梯度方向不仅与这次的迭代有关,还与之前一次的迭代结果有关。“当前一次效果好的话,就加快步伐;当前一次效果不好的话,就减慢步伐”;而且在局部最优值处,没有梯度但因为还存在一个动量,可以跳出局部最优值。迭代公式是:
在这里插入图片描述
其中的momentum参数就是冲量(一个常数)。
对于自适应学习率的Adam优化器,在Momentum一阶矩估计的基础上加入了二阶矩估计。它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。为了解决Adagrad算法出现学习率趋向0的问题,还加入了偏置校正,这样每一次迭代学习率都有个确定范围,使得参数比较平稳。其迭代公式为:
在这里插入图片描述
其具体的实现代码如下:
在这里插入图片描述
对于softmax函数,其在多分类的场景中使用广泛。他把一些输入映射为0-1之间的实数,并且归一化保证和为1,因此多分类的概率之和也刚好为1,从而进行多分类。这里我们假设有一个数组V,V_i表示V中的第i个元素,那么这个元素的softmax值就是:
在这里插入图片描述
在这里插入图片描述
下面是使用代码实现softmax函数:
在这里插入图片描述

2.6 损失函数以及正则化

这里因为是多分类问题所以需要使用交叉熵的损失函数,交叉熵代价函数(Cross-entropy cost function)是用来衡量人工神经网络(ANN)的预测值与实际值的一种方式。与二次代价函数相比,它能更有效地促进ANN的训练。其实现公式为:
在这里插入图片描述
这里的实现代码如下:
在这里插入图片描述
为了增加模型的鲁棒性,我这里还加入了正则化项,来减小过拟合的程度。正则化的思路就是保留所有变量,但减小特征变量的数量级。网络中的参数的值越小,通常对应于越光滑的函数,也就是更加简单的函数。因此 就不易发生过拟合的问题。
其中我们常见的正则化模型有L1正则化以及L2正则化。L1正则化的模型又叫做Lasso Regularization(Lasso回归),直接在原来的损失函数基础上加上权重参数的绝对值的倍数,计算公式如下:
在这里插入图片描述
L2正则化的模型又叫做岭回归(ridge regression),直接在原来的损失函数基础上加上权重参数的平方的倍数,计算公式如下:
在这里插入图片描述
两个公式中的λ为正则化系数,一般都会在正则化项之前添加这个系数,这个系数需要用户设定,系数越大,正则化作用越明显。根据从前的经验可知,lasso (L1范数)可以用来做 feature selection,而 ridge(L2范数) 不行。或者说,lasso 更容易使得权重变为 0,而 ridge 更容易使得权重接近 0。这里我为了减小计算的复杂度,统一选用L2范数进行正则化的操作。

3 实验过程与结果

3.1 本次实验结果

这里我自己手动搭建了卷积神经网络,没有使用Keras、tensorflow或者pytorch等现成神经网络构架,并分别使用不同的网络结构进行尝试,选择不同的优化器进行训练。这里我使用了三种不同的网络结构,并分别与不用的优化器和激活函数进行组合。
这里的基本网络为:卷积层conv3x3 -> 非线性层relu ->池化层pool2x2 ->全连接层fc ->分类层softmax。。下面的图是我使用(http://alexlenail.me/NN-SVG)画的网络结构图:
在这里插入图片描述
另外一个网络结构是LeNet-5网络结构,LeNet-5模型是Yann LeCun教授与1998年在论文Gradient-based learning applied to document recognition中提出的,它是第一个成功应用于数字识别问题的卷积神经网络。这个网络一共有7层。下面的图是我使用(http://alexlenail.me/NN-SVG)画的LeNet-5的网络结构图:
在这里插入图片描述
除此之外,我还尝试了Alexnet网络,在2012年,Hinton的学生Alex Krizhevsky提出了深度卷积神经网络模型AlexNet,它可以算是LeNet的一种更深更宽的版本。AlexNet以显著的优势赢得了竞争激烈的ILSVRC 2012比赛,top-5的错误率降低至了16.4%,远远领先第二名的26.2%的成绩。其网络的模型如下图:
在这里插入图片描述
不过由于使用numpy手动实现卷积神经网络的话,无法调用GPU,导致计算时间特别长,所以这里直接使用了tensorflow架构,复现了Alexnet网络,并且与上面的结果做了对比。
在调试代码的过程中,我发现使用numpy进行网络的运算速度比较慢,大约一个epoch需要40分钟左右的时间,经过查阅资料以后,我发现使用im2col函数可以来对于卷积运算进行优化,即im2col的核心是将卷积核感受野的转化成一行(列)来存储,优化运算速度,减少内存访问时间。caffe等框架中使用了这种计算方式(im2col + gemm矩阵运算)来优化卷积计算。示意图如下图:
在这里插入图片描述
使用im2col后的代码速度得到了明显的提升。
下面是实验的结果示意图(我选择了两个比较有代表性的网络结构进行图示):
首先是LeNet-5+ SGD+Sigmoid的结果:
在这里插入图片描述
下面是不同数字类别的召回率以及误差随epoch的变化曲线:

在这里插入图片描述
在这里插入图片描述
然后是LeNet-5+ Adam+Sigmoid的结果:
在这里插入图片描述
下面是不同数字类别的召回率以及误差随epoch的变化曲线:
在这里插入图片描述
在这里插入图片描述
最终的结果如下表:

方法 测试集的准确率
基本网络+SGD+Relu(L2范数) 96.2%
基本网络+SGD+Sigmoid(L2范数) 95.8%
基本网络+Adam+Relu(L2范数) 97.8%
基本网络+Adam+Sigmoid(L2范数) 97.6%
LeNet-5+ SGD+Relu(L2范数) 98.2%
LeNet-5+ SGD+Sigmoid(L2范数) 98.0%
LeNet-5+ Adam+Relu(L2范数) 98.8%
LeNet-5+ Adam+Sigmoid(L2范数) 98.4%
AlexNet 99.4%

3.2 与实验一和实验二的结果对比

在实验一中,我们使用概率模型对于mnist手写数字数据集进行分类,最终的分类效果如下:

在这里插入图片描述
混淆矩阵如下:
在这里插入图片描述
可以看出,首先对于数据降维,再使用基于概率的分类器进行分类的准确率最高只能达到96%,远远不及CNN的98%乃至99%,而且对于实验一方法中分类效果较差的2和7,使用CNN进行识别的准确率也有大幅度的提升。
在实验二中,我们使用线性感知器对于mnist手写数字数据集进行分类,最终的分类效果如下:
首先是准确率的对比:
在这里插入图片描述
在这里插入图片描述
混淆矩阵的结果如下:
在这里插入图片描述
从结果可以看出,与使用CNN可以得到比纯线性感知机更好的结果,尤其是对于线性感知机分错样本有了更好的分类效果,例如使用线性感知机时,数字3和5的F1-score只有0.93,但是使用了CNN可以更好的对于数字的特征进行提取,从而使得最终的3和5的F1-score提升到了0.96。而且对于线性感知器来说,其仅仅可以对于样本进行线性的分析,并不能探究其中的非线性特征,而本次实验所使用的CNN可以很好的对于非线性特征乃至“隐藏”的特征进行提取,再配合全连接层和softmax进行分类,从而提高了识别的准确率。

4 实验的思考与创新总结

首先是网络的选择(算法的选择),使用的卷积层数越多对于手写数字的特征提取的越完整,但是会加大运算的开销,尤其是对于本次实验不能使用成熟的神经网络的架构,导致计算的速度特别慢。所以这里为了加快运算的速度,使用了im2col函数对于卷积的运算进行了加速。另一个角度来说,我们有很多分类的算法,卷积神经网络中卷积的作用是提取有效的特征,而后面的全连接层才是分类的算法、网络,除此之外,SVM也可以认为是一种比较好的分类算法。所以现在有很多人也使用卷积来提取特征并在后面接上SVM分类器实现分类效果。
另外,并不是卷积层数越多越好,由于网络层数变多,也代表训练网络的难度上升了,受限制于目前训练方法的局限性,很多时候网络层变多了反而更容易陷入局部最优。另外的话,其实可以这样考虑,如果一个浅层的网络已经达到最优了,在原来浅层的网络上加上额外的层数,这些额外的层数如果权值都是1,那么至少这个浅层和深层的表现应该是一样的,因为额外的层数并没有对于网络造成影响,但是问题在于,我们很难将这些额外的层数的权值全部训练为1,那么这个在浅层基础上加入的额外层数的网络,反而没有办法使得网络达到最优。所以对于MNIST这个比较小的图像数据集,并不一定使用深度网络的效果比使用例如LeNet-5这种比较浅层的网络效果好,甚至综合计算的算力要求以及分类的准确性上,使用浅层网络的综合得分要比深层网络还要好。
然后是优化器的选择。上面我使用了两种优化器对于梯度下降进行优化,这里分别使用了固定学习率的SGD以及自适应学习率的Adam最终发现,自适应学习率的Adam无论与什么激活函数还是与不同的网络结构相配合,效果都比固定学习率的SGD算法要好。
另外对于激活函数的选择,激活函数主要目的是增加整个网络结构的非线性。

总结如下:我在本次实验中,首先使用了实验ppt中推荐的网络结构以及优化器等等方法,得到了一个baseline。然后我分别在网络结构、优化器、激活函数方面进行了探索与思考,在网络结构方面,我又尝试了使用LeNet-5以及AlexNet卷积神经网络结构进行尝试,另外我还单独测试了基于KNN和线性、非线性的SVM对于MNIST分类的效果。另外,在优化器和激活函数等方面我也尝试使用固定学习率的SGD以及自适应学习率的Adam来分别进行梯度下降法的梯度“传递”,在激活函数方面,我也尝试了使用sigmoid以及Relu等激活函数为网络提供了非线性的拟合效果,并对比了以上的操作的效果。
这里将最终我所有尝试的结构和最终的准确率绘制如下表:

方法 测试集准确率
基本网络+SGD+Relu(L2范数) 96.2%
基本网络+SGD+Sigmoid(L2范数) 95.8%
基本网络+Adam+Relu(L2范数) 97.8%
基本网络+Adam+Sigmoid(L2范数) 97.6%
LeNet-5+ SGD+Relu(L2范数) 98.2%
LeNet-5+ SGD+Sigmoid(L2范数) 98.0%
LeNet-5+ Adam+Relu(L2范数) 98.8%
LeNet-5+ Adam+Sigmoid(L2范数) 98.4%
AlexNet 99.4%
SVM(线性核) 96.8%
SVM(RBF) 99.0%
KNN 98.2%
实验一:(降到30维) 96.0%
实验二:(降到50维) 98.0%

5 总结与感想

自己手动而且没有使用成熟的神经网络架构搭建了神经网络,并实现了前后向的参数更新,并实现了基于MNIST的手写数字数据集的分类,在测试集中达到了比较好的效果。
实现了PPT中的参考方法后,我也在网络结构、激活函数以及梯度下降优化器等方面考虑了不同的方法也一一做出了尝试,另外还使用了除了神经网络之外的SVM和KNN进行了数据集的分类。通过对比以上的所有算法,可以发现卷积神经网络可以更好的提取数据中的非线性特征以及相邻像素间的关系,通过全连接的网络结构可以进行基于卷积提取的特征的分类,最终取得了所有分类算法中最优的结果。
我从底层完全自己搭建了卷积神经网络,并进行了前向传播与反向传播的编程,不仅提高了自己的编程能力,更提高了自己对于神经网络尤其是卷积神经网络以及卷积运算、梯度下降算法的认识,可谓收获颇多。

猜你喜欢

转载自blog.csdn.net/qq_39867051/article/details/106981409
今日推荐