神经网络算法实践
钟汉昌 19057011 2023年2月
第一部分 fashion_mnist数据集
在图像识别领域,有一个著名的手写数字数据集叫做mnist,随着研究的深入,一个算法可以在mnist数据集上表现好已经不稀奇了。由此,一家德国的时尚科技公司Zalando旗下的研究部门提供了fashion_minst,作为替代mnist的图像数据集。fashion_minst包含来自10种类别的共7万张不同商品的正面图片。
fashion_mnist数据集的大小、格式和训练集/测试集的划分与mnist数据集完全一致,都是60000/10000的训练集/测试集划分,28x28的灰度图片。因此,以mnist数据集为基础编写的算法,可以不改动任何代码,直接改用fashion_mnist测试算法性能。本代码通过keras包导入fashion_mnist数据集,并将训练集的前3张图片展示出来。
每个图像都是元素为0~255的整数值的28x28矩阵,每个图像都有一个相应的值为0~9的类别数据(蓝色)
第二部分 两层前馈神经网络模型
2.1 数据和模型准备工作
用于识别的两层前馈神经网络是把28x28的图像数据当作长度为784的向量处理。因此,要用代码把60000x28x28的数组转换为60000x28的数组。同时,要把输入值转换为0~1的float值,把输出值用keras包的函数转换为1-of-K(哑编码)表示法的编码。
以上完成了数据准备工作,接下来考虑核心的网络模型。
使用keras的Sequential(序贯模型)定义了model,并定义了中间层和输出层。
2.2 训练模型
Epoch 1/10
60/60 [==============================] - 1s 7ms/step - loss: 1.9428 - accuracy: 0.4391 - val_loss: 1.6696 - val_accuracy: 0.6349
Epoch 2/10
60/60 [==============================] - 0s 6ms/step - loss: 1.4965 - accuracy: 0.6884 - val_loss: 1.3585 - val_accuracy: 0.7099
Epoch 3/10
60/60 [==============================] - 0s 5ms/step - loss: 1.2383 - accuracy: 0.7293 - val_loss: 1.1514 - val_accuracy: 0.7263
Epoch 4/10
60/60 [==============================] - 0s 5ms/step - loss: 1.0645 - accuracy: 0.7483 - val_loss: 1.0107 - val_accuracy: 0.7443
Epoch 5/10
60/60 [==============================] - 0s 5ms/step - loss: 0.9423 - accuracy: 0.7671 - val_loss: 0.9077 - val_accuracy: 0.7654
Epoch 6/10
60/60 [==============================] - 0s 5ms/step - loss: 0.8503 - accuracy: 0.7836 - val_loss: 0.8275 - val_accuracy: 0.7777
Epoch 7/10
60/60 [==============================] - 0s 5ms/step - loss: 0.7783 - accuracy: 0.7953 - val_loss: 0.7644 - val_accuracy: 0.7879
Epoch 8/10
60/60 [==============================] - 0s 5ms/step - loss: 0.7206 - accuracy: 0.8056 - val_loss: 0.7145 - val_accuracy: 0.7976
Epoch 9/10
60/60 [==============================] - 0s 5ms/step - loss: 0.6739 - accuracy: 0.8145 - val_loss: 0.6730 - val_accuracy: 0.8038
Epoch 10/10
60/60 [==============================] - 0s 6ms/step - loss: 0.6351 - accuracy: 0.8196 - val_loss: 0.6388 - val_accuracy: 0.8112
Test loss: 0.6387856006622314
Test accuracy: 0.8112000226974487
Computation time:4.086 sec
(一)参数解释
verbose为日志显示参数,verbose=1为输出进度条记录(默认为1)。因此,以上代码运行后会显示每轮训练的评估值,并在最后显示根据测试数据评估的交叉熵误差(Test loss)、正确率(Test accuracy)和计算时间(Computation time)。
通常,每次更新时对误差函数的梯度计算都是以整个数据集为对象进行的,但如果数据量很大,计算就会非常花时间。在这种情况下,可以使用只根据部分数据计算误差函数梯度,即随机梯度法。一次更新使用的数据的大小叫作批大小(batch_size)。一般的梯度法即使碰到较浅的极小值,也会陷入泥潭无法脱身。而随机梯度法如果碰到较浅的极小值,依然有可能从容脱身。轮数(epochs)是决定训练的更新次数的参数。
(二)结果分析
结果显示,计算机大约4.09秒就计算完成了。输出信息表明模型对81.12%的测试数据回答正确,这个结果显然是不理想的。
不过在改善模型前,还需确认是否发生了过拟合(overfitting),即为了得到一致假设而使假设变得过度严格。可以理解成想要估计到每个点而使得拟合曲线拐来拐去。为了确认是否发生了过拟合,需要观察测试数据的误差随时间的变化情况。
由于测试数据的误差也是单调减小的,所以可以判断没有发生过拟合。正确率也顺利地提高了。
313/313 [==============================] - 0s 875us/step
显示在右下角的数字是网络的输出。蓝色横线表示的是识别错误的数据
第三部分 改进后的模型——终极神经网络UNN
3.1 激活函数
以前人们常用Sigmoid函数作为激活函数,近年来ReLU(Rectified Linear Unit, 线性整流函数)作为激活函数非常受欢迎。2015年Yann LeCun、Yoshua Bengio和Geoffrey Hinton三人在《自然》杂志发表论文,其中将ReLU评为最好的激活函数。
将刚才网络中间层的激活函数换为ReLU并运行
Epoch 1/10
60/60 [==============================] - 1s 7ms/step - loss: 1.4790 - accuracy: 0.5222 - val_loss: 0.9322 - val_accuracy: 0.6827
Epoch 2/10
60/60 [==============================] - 0s 6ms/step - loss: 0.7687 - accuracy: 0.7329 - val_loss: 0.6922 - val_accuracy: 0.7626
Epoch 3/10
60/60 [==============================] - 0s 5ms/step - loss: 0.6272 - accuracy: 0.7900 - val_loss: 0.6058 - val_accuracy: 0.7951
Epoch 4/10
60/60 [==============================] - 0s 5ms/step - loss: 0.5567 - accuracy: 0.8151 - val_loss: 0.5534 - val_accuracy: 0.8112
Epoch 5/10
60/60 [==============================] - 0s 5ms/step - loss: 0.5141 - accuracy: 0.8284 - val_loss: 0.5239 - val_accuracy: 0.8206
Epoch 6/10
60/60 [==============================] - 0s 5ms/step - loss: 0.4881 - accuracy: 0.8361 - val_loss: 0.5031 - val_accuracy: 0.8267
Epoch 7/10
60/60 [==============================] - 0s 5ms/step - loss: 0.4685 - accuracy: 0.8422 - val_loss: 0.4874 - val_accuracy: 0.8326
Epoch 8/10
60/60 [==============================] - 0s 5ms/step - loss: 0.4540 - accuracy: 0.8468 - val_loss: 0.4788 - val_accuracy: 0.8345
Epoch 9/10
60/60 [==============================] - 0s 5ms/step - loss: 0.4422 - accuracy: 0.8505 - val_loss: 0.4716 - val_accuracy: 0.8392
Epoch 10/10
60/60 [==============================] - 0s 5ms/step - loss: 0.4332 - accuracy: 0.8522 - val_loss: 0.4617 - val_accuracy: 0.8400
Test loss: 0.4617328643798828
Test accuracy: 0.8399999737739563
Computation time:4.085 sec
正确率由使用Sigmoid时的81.12%提高到了83.99%。有大约2.87%的提升。
313/313 [==============================] - 0s 824us/step
至此已经知道,简单的前馈网络模型的正确率约83%,离目标还比较遥远。那么如何使得正确率更上一层楼呢?增加中间层神经元是一个可行的方法,不过有更根本的问题需要解决。这个模型实际上忽视了输入是二维图像,根本没有使用二维空间信息。网络构造是全连接型的,所有输入元素与不相邻的输入元素在数学式上完全平等。
3.2 空间过滤器
空间信息是直线、弯曲的曲线、圆形及四边形等表示形状的信息。可以使用被称为空间过滤器的图像处理方法来提炼这样的形状信息。移动图像,求出图像的一部分与过滤器元素的乘积之和,直至完成在整个图像上的计算,这样的计算称为卷积计算(convolution)
Don't know how to reset #重置内存, please run `%reset?` for details
再次读取fashion_mnist数据,格式上做修改。
对训练数据应用检测纵边和横边的2个过滤器,实际上通过改变过滤器数值,还可以实现识别斜边、图像平滑化、识别细微部分等各种各样的处理。不过,上图中的过滤器的设计是所有元素之和为0。这样一来,没有任何空间构造的值全部相同的部分就会转换为0,而过滤器想要抽取的具有构造的部分会被转换为大于0的值,最终把0作为检测基准即可,非常方便。
原图白为0,黑为1,应用横向过滤器把横线下侧的值变为大值,应用纵向过滤器会把纵线左侧的值变为大值
然而,应用过滤器之后,输出图像的大小比原来小了一圈,这在有些场景下就不太方便。比如连续应用各种过滤器时,图像会越来越小。针对这个问题,可以通过填充(padding)来解决。填充是在应用过滤器之前,使用0等固定值在图像周围附加元素的方法。
除了填充之外,与过滤器有关的参数还有步长。之前过滤器都是错开1个间隔移动的,其实任意的间隔都可以,这个间隔被称为步长(stride)。步长越长,输出图像越小。当通过库使用卷积网络时,填充和步长值会被作为参数传入。
3.3 卷积神经网络
使用了过滤器的神经网络称为卷积神经网络(Convolution Neural Network,CNN)。
通过向过滤器嵌入不同的数值,可以进行各种图像处理,而CNN可以学习过滤器本身。先创建1个使用了8个过滤器的简单的CNN。对输入图像应用8个大小为3x3、填充为1、步长为1的过滤器。由于1个过滤器的输出为28x28的数组,所以全部输出合在一起是28x28x8的三维数组,把它展开为一维的长度为6272的数组,并与10个输出层神经元全连接。
Epoch 1/10
60/60 [==============================] - 4s 59ms/step - loss: 1.0583 - accuracy: 0.7294 - val_loss: 0.5776 - val_accuracy: 0.7984
Epoch 2/10
60/60 [==============================] - 3s 58ms/step - loss: 0.5034 - accuracy: 0.8303 - val_loss: 0.4827 - val_accuracy: 0.8319
Epoch 3/10
60/60 [==============================] - 3s 58ms/step - loss: 0.4359 - accuracy: 0.8519 - val_loss: 0.4436 - val_accuracy: 0.8442
Epoch 4/10
60/60 [==============================] - 3s 58ms/step - loss: 0.4014 - accuracy: 0.8619 - val_loss: 0.4215 - val_accuracy: 0.8532
Epoch 5/10
60/60 [==============================] - 3s 58ms/step - loss: 0.3792 - accuracy: 0.8704 - val_loss: 0.4042 - val_accuracy: 0.8564
Epoch 6/10
60/60 [==============================] - 3s 58ms/step - loss: 0.3624 - accuracy: 0.8752 - val_loss: 0.3918 - val_accuracy: 0.8628
Epoch 7/10
60/60 [==============================] - 3s 57ms/step - loss: 0.3488 - accuracy: 0.8799 - val_loss: 0.3869 - val_accuracy: 0.8601
Epoch 8/10
60/60 [==============================] - 3s 58ms/step - loss: 0.3380 - accuracy: 0.8832 - val_loss: 0.3724 - val_accuracy: 0.8686
Epoch 9/10
60/60 [==============================] - 3s 58ms/step - loss: 0.3269 - accuracy: 0.8863 - val_loss: 0.3675 - val_accuracy: 0.8689
Epoch 10/10
60/60 [==============================] - 3s 57ms/step - loss: 0.3179 - accuracy: 0.8893 - val_loss: 0.3590 - val_accuracy: 0.8730
Test loss: 0.35904553532600403
Test accuracy: 0.8730000257492065
Computation time:35.623 sec
向model中增加了卷积层Conv2D()。“8, (3,3)”的意思是使用了8个3x3过滤器。padding = 'same' 的意思是增加1个使输出大小不变的填充。input_shape = (28, 28, 1) 是输入图像大小,最后一个参数黑白图像为1,彩色图像为3。除此之外,还指定了偏置输入为默认值,每个过滤器被分配1个偏置变量。在训练开始前,过滤器的初始值是随机设置的,而偏置的初始值被设置为0。
卷积层的输出是四维的,其形式为“(小批量大小, 过滤器数量,输出图像的高度,输出图像的高度)”。在把这个数据作为输入传给之后的输出层(Dense层)之前,必须先将其转换为二维的形式“(小批量大小, 过滤器数量 x 输出图像的高度 x 输出图像的宽度)”。这个转换通过model.add(Flatten())进行。
卷积神经网络计算大约花费35.6秒,正确率居然达到了87.30%,与之前的两层ReLU网络的正确率83.10%相比,改善非常显著。
313/313 [==============================] - 1s 2ms/step
下面通过代码看一下经过学习的8个过滤器
能够比较明显地看出,第4个过滤器似乎识别了横线下侧的边,第6个过滤器似乎识别了横线上侧的边。这种能够自动学习得到过滤器的能力真让人着迷(amazing)。
3.4 池化
通过卷积层,我们得以利用二维图像拥有的特征,但是在图像识别的情况下,模型要尽量不受图像平移的影响,这一点很重要。假如输入是一个平移后的图像,即使只平移1个像素,各个数组也将完全改变。在人眼看来几乎完全相同的输入,网络却会识别为完全不同的结果。在使用CNN时也会碰到这个问题。解决这个问题的一种方法就是池化处理。
以2x2的最大池化(max pooling)举例。这种方法着眼于输入图像内的2x2的小区域,并输出区域内最大的数值。然后以步长2来平移小区域,重复同样的处理。最终输出图像的长和宽的大小将变为输入图像的一半。除了最大池化之外,还有平均池化(average pooling)的方法。这种方法中小区域的输出值是区域内数值的平均值。
3.5 Dropout
Nitish Srivastava,Geoffrey Hinton等人在发表的论文中提出了一种名为Dropout的改善网络学习的方法。在许多应用场景中,这个方法都带来了较好的效果。Dropout在训练时以概率p(p<1)随机选择输入层的单元和中间层的神经元,并使其他神经元无效。无效的神经元被当作不存在,然后在这样的状态下进行训练。为每个小批量重新选择神经元,重复这个过程。
训练完成后,在进行预测时使用全部的神经元。由于在训练时仅使用了以概率p选择的神经元,所以在预测时使用全部神经元会使输出变大(1/p)倍。为了更符合逻辑,需要将权重变小,因此在预测时,将应用了Dropout的层的输出的权重乘以p(由于p小于1,所以会变小)。DRopout方法会分别训练多个网络,并在预测时取多个网络的平均值,因此具有综合了多个网络的预测值的效果。
3.6 终极神经网络UNN
在卷积神经网络中引入池化和Dropout,并增加层数,构建一个具备各种特性的网络。首先,第1层和第2层是连续的卷积层。第1层卷积层使用了16个过滤器,那么输出就是16张26x26的图像(没有填充),可以看作26x26x16的三维数组的数据。下一层对这个三维数据进行卷积。1个3x3的过滤器实质上被定义3x3x16的数组,输出为24x24的二维数组(没有填充)。分别分配了16个不同的过滤器,在对它们分别进行处理后,将输出汇总。第2层卷积层有32个这样的大小为3x3x16的过滤器。因此,输出为24x24x32的三维数组。如果不算偏置,那么用于定义过滤器的参数数量为3x3x16x32。
经过第3层的2x2的最大池化层之后,图像大小缩小了一半,变为12x12。之后的第4层又是卷积层,该层的过滤器有64个,参数数量为3x3x32x64。在第5层再次进行最大池化之后,图像大小变为5x5。之后的第6层是神经元数量为128个的全连接层,最后的第7层是输出为10个的全连接层。第5层和第6层还引入了Dropout。
Epoch 1/30
60/60 [==============================] - 34s 558ms/step - loss: 0.9822 - accuracy: 0.6450 - val_loss: 0.5473 - val_accuracy: 0.7947
Epoch 2/30
60/60 [==============================] - 33s 554ms/step - loss: 0.5233 - accuracy: 0.8078 - val_loss: 0.4479 - val_accuracy: 0.8351
Epoch 3/30
60/60 [==============================] - 33s 556ms/step - loss: 0.4442 - accuracy: 0.8388 - val_loss: 0.3957 - val_accuracy: 0.8582
Epoch 4/30
60/60 [==============================] - 33s 553ms/step - loss: 0.3984 - accuracy: 0.8562 - val_loss: 0.3600 - val_accuracy: 0.8698
Epoch 5/30
60/60 [==============================] - 33s 556ms/step - loss: 0.3686 - accuracy: 0.8661 - val_loss: 0.3322 - val_accuracy: 0.8799
Epoch 6/30
60/60 [==============================] - 33s 556ms/step - loss: 0.3394 - accuracy: 0.8756 - val_loss: 0.3264 - val_accuracy: 0.8799
Epoch 7/30
60/60 [==============================] - 33s 554ms/step - loss: 0.3240 - accuracy: 0.8810 - val_loss: 0.3030 - val_accuracy: 0.8878
Epoch 8/30
60/60 [==============================] - 33s 554ms/step - loss: 0.3071 - accuracy: 0.8892 - val_loss: 0.2946 - val_accuracy: 0.8932
Epoch 9/30
60/60 [==============================] - 33s 556ms/step - loss: 0.2946 - accuracy: 0.8918 - val_loss: 0.2779 - val_accuracy: 0.8978
Epoch 10/30
60/60 [==============================] - 33s 556ms/step - loss: 0.2795 - accuracy: 0.8990 - val_loss: 0.2690 - val_accuracy: 0.8994
Epoch 11/30
60/60 [==============================] - 33s 558ms/step - loss: 0.2690 - accuracy: 0.9015 - val_loss: 0.2825 - val_accuracy: 0.8945
Epoch 12/30
60/60 [==============================] - 33s 554ms/step - loss: 0.2658 - accuracy: 0.9030 - val_loss: 0.2596 - val_accuracy: 0.9064
Epoch 13/30
60/60 [==============================] - 33s 551ms/step - loss: 0.2537 - accuracy: 0.9066 - val_loss: 0.2503 - val_accuracy: 0.9090
Epoch 14/30
60/60 [==============================] - 33s 553ms/step - loss: 0.2450 - accuracy: 0.9100 - val_loss: 0.2461 - val_accuracy: 0.9108
Epoch 15/30
60/60 [==============================] - 33s 556ms/step - loss: 0.2379 - accuracy: 0.9116 - val_loss: 0.2463 - val_accuracy: 0.9110
Epoch 16/30
60/60 [==============================] - 33s 554ms/step - loss: 0.2315 - accuracy: 0.9147 - val_loss: 0.2377 - val_accuracy: 0.9114
Epoch 17/30
60/60 [==============================] - 33s 554ms/step - loss: 0.2238 - accuracy: 0.9181 - val_loss: 0.2337 - val_accuracy: 0.9138
Epoch 18/30
60/60 [==============================] - 33s 552ms/step - loss: 0.2156 - accuracy: 0.9199 - val_loss: 0.2294 - val_accuracy: 0.9169
Epoch 19/30
60/60 [==============================] - 33s 554ms/step - loss: 0.2129 - accuracy: 0.9221 - val_loss: 0.2290 - val_accuracy: 0.9171
Epoch 20/30
60/60 [==============================] - 33s 556ms/step - loss: 0.2087 - accuracy: 0.9228 - val_loss: 0.2261 - val_accuracy: 0.9182
Epoch 21/30
60/60 [==============================] - 33s 556ms/step - loss: 0.2017 - accuracy: 0.9257 - val_loss: 0.2235 - val_accuracy: 0.9188
Epoch 22/30
60/60 [==============================] - 33s 554ms/step - loss: 0.1970 - accuracy: 0.9276 - val_loss: 0.2226 - val_accuracy: 0.9196
Epoch 23/30
60/60 [==============================] - 33s 556ms/step - loss: 0.1915 - accuracy: 0.9285 - val_loss: 0.2243 - val_accuracy: 0.9201
Epoch 24/30
60/60 [==============================] - 33s 554ms/step - loss: 0.1865 - accuracy: 0.9309 - val_loss: 0.2190 - val_accuracy: 0.9206
Epoch 25/30
60/60 [==============================] - 33s 557ms/step - loss: 0.1872 - accuracy: 0.9297 - val_loss: 0.2182 - val_accuracy: 0.9213
Epoch 26/30
60/60 [==============================] - 33s 556ms/step - loss: 0.1789 - accuracy: 0.9330 - val_loss: 0.2145 - val_accuracy: 0.9201
Epoch 27/30
60/60 [==============================] - 33s 553ms/step - loss: 0.1745 - accuracy: 0.9351 - val_loss: 0.2123 - val_accuracy: 0.9237
Epoch 28/30
60/60 [==============================] - 33s 554ms/step - loss: 0.1688 - accuracy: 0.9373 - val_loss: 0.2105 - val_accuracy: 0.9261
Epoch 29/30
60/60 [==============================] - 33s 553ms/step - loss: 0.1677 - accuracy: 0.9376 - val_loss: 0.2152 - val_accuracy: 0.9251
Epoch 30/30
60/60 [==============================] - 33s 551ms/step - loss: 0.1633 - accuracy: 0.9405 - val_loss: 0.2151 - val_accuracy: 0.9228
Test loss: 0.21514172852039337
Test accuracy: 0.9228000044822693
Computation time:1000.026 sec
终极神经网络终于使得正确率达到了90%以上,来到了92.28%。这个终极神经网络是为了尝试所有技术而设计的,事实上应该还可以创建出更加简单且正确率更高的网络。不过,通过实验,也可看出在处理比fashion_mnist图像大小还要大的自然图像,以及必须处理多个类别时,层的深化、卷积、池化和Dropout等技术必定会发挥更强大的作用。