介绍
在前面一篇文章中,我们初步了解了什么是简单的神经网络,也通过keras构建了简单的神经网络,通过训练,我们实现了一个简单的预测,接下来就要进入到我们的重头戏卷积神经网络。
卷积神经网络
上周的文章给大家推荐了一门好课cs231n,本篇文章也是按照这个思路来的。
基本结构
首先解释一下什么是卷积,这个卷积当然不是数学上的卷积,这里的卷积其实表示的是一个三维的权重,这么解释起来可能不太理解,我们先看看卷积网络的基本结构。
通过上面的图我们清楚地了解到卷积网络和一般网络结构上面的差别,也可以理解为卷积网络是立体的,而一般的网络结构是平面的。
卷积层
了解完了基本的结构之后,我们就要了解cnn最重要的一个部分,也是起最为创新的一个部分,卷积层。首先用一张图片来比较一下卷积网络到底创新在什么地方。
我们通过这个结构就可以清晰地看到卷积网络到底是怎么实现的。首先右边是传统的网络结构,在上一篇文章中我们已经详细的解释过了。而左边的图片,我们首先看看图中最左边的结构,你肯定会好奇为什么是32x32x3的一块立体方块。这个32x32代表的是像素点,说白了也就是图片的大小,这个大小是你可以设置的,你可以设置为50x50,也可以是256x256,这都取决与图片的大小,那么3表示什么呢?3其实表示的是RGB的三个通道,RGB也是什么?RGB表示red,green,blue,这三种颜色的各种组合叠加可以形成各种各样的颜色,所以任何一张照片都可以用左边这种图形来表示。
那么中间这个小方块又表示什么呢?这个就是我们要重点讲的卷积。所谓的卷积,就是这种小方块,我们设置一个小方块的大小,但是这个小方块的厚度必须和左边的这个大方块的厚度是一样的,大方块每一个像素点由一个0到255的数字表示,这样我们就可以赋予小方块权重,比如我们取小方块的大小是3x3,我们要求起厚度必须要和左边的大方块厚度一样,那么小方块的的大小就为3x3x3,我们就可以赋予其3x3x3个权重,然后我们就可以开始计算卷积的结果,将小方块从大方块的左上角开始,一个卷积小方块所覆盖的范围是3x3x3,然后我们将大方块中3x3x3的数字和小方块中的权重分别相乘相加,再加上一个偏差,就可以得到一个卷积的接过,可以抽象的写成Wx+b这种形式,这就是图上所显示的接过,然后我们可以设置小方块的滑动距离,每次滑动就可以形成一个卷积的计算结果,然后讲整张大图片滑动覆盖之后就可以形成一层卷积的结果,我们看到图中的卷积结果是很厚的,也就是设置了很多层卷积。总结来说,就是每层卷积就是一个卷积核在图片上滑动求值,然后设置多个卷积核就可以形成多层的卷积层。
池化层
讲完卷积层,接下来就要讲一下池化层。为什么会有池化层的出现呢?是因为不断的做卷积,得到的中间结果会越来越厚,卷积就相当于提取图片中的特征,所以卷积层一般会设置得越来越厚,不然你就无法从前面的接过来提取更多的特征。这样就会导致中间的结果会越来越大,计算会越来越慢,所以提出了池化层。
所谓的池化层,就是将图片的大小缩小的一种处理方式。我们可以先看看下面的图片。
通过这个图片,我们可以清楚地看到池化层是怎么处理的。池化层也是需要先设置一个窗口,但是这个小窗口的厚度是1,而不再是前一层输出的结果的厚度。然后有两种处理方式,一种是取这个小窗口里面所有元素的最大值来代表这个小窗口,一种是取平均值,然后将小窗口滑动,在第二的位置再做同样的处理,上层网络输出方块的每一层做完之后就进入这个大方块的下一层做同样的操作,这个处理办法就可以让整个大方块的大小变小,可以看看上面的图片的左边。右边是一个简单的一层厚度,取最大值的例子。
实现Lenet
讲完了卷积网络的基本结构之后,你是不是已经迫不及待希望能够实现一个简单的神经网络了呢?卷积网络发展的特别迅速,最早是由Lecun提出来的Lenet成为cnn的鼻祖,接下来他的学生Alex提出了层数更深的Alexnet,然后2013年又提出了VGGnet,有16层和19层两种,这些都只是在层数上面的加深,并没有什么其他的创新,二之后google提出了inception net在网络结构上实现了创新,提出了一种inception的机构,facebook ai 实验室又提出了resnet,残差网络,实现了150层的网络结构可训练化,这些我们之后会慢慢讲到。
接下来我们就来实现一下最简单的Lenet,使用mnist手写子体作为训练集。
import keras
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
导入必要的库和数据集
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)
x_train = x_train / 255.
x_test = x_test / 255.
y_train = keras.utils.to_categorical(y_train)
y_test = keras.utils.to_categorical(y_test)
处理数据,让数据的shape是(28, 28, 1),然后label做一个one-hot encoding处理,比如类别是3,那么变成[0, 0, 1 ,0, 0, 0, 0, 0, 0, 0]。
from keras.layers import Conv2D, MaxPool2D, Dense, Flatten
from keras.models import Sequential
lenet = Sequential()
lenet.add(Conv2D(6, kernel_size=3, strides=1, padding='same', input_shape=(28, 28, 1)))
lenet.add(MaxPool2D(pool_size=2, strides=2))
lenet.add(Conv2D(16, kernel_size=5, strides=1, padding='valid'))
lenet.add(MaxPool2D(pool_size=2, strides=2))
lenet.add(Flatten())
lenet.add(Dense(120))
lenet.add(Dense(84))
lenet.add(Dense(10, activation='softmax'))
构建lenet
lenet.compile('sgd', loss='categorical_crossentropy', metrics=['accuracy'])
编译
lenet.fit(x_train, y_train, batch_size=64, epochs=50, validation_data=[x_test, y_test])
训练50次,得到结果如下
lenet.save('myletnet.h5')
可以保存训练好的模型
总结
OK, 这就是我们写的一个超级简单的Lenet,训练50次得到的训练准确率已经达到0.9939,测试准确率达到0.9852。
如果有疑问,可以访问我的github,这篇文章的代码都已传到了github上。
https://github.com/SherlockLiao/lenet