深度学习在计算机图像识别上的应用非常成功。利用深度学习,我们能够对图片进行高精度识别,实现这一功能的,主要依靠神经网络中的一种分支,名为卷积网络。卷积网络与我们前面实现的网络不通之处在于,它可以直接接受多维向量,而我们以前实现的网络只能接收一维向量。
我们在开始时,实现了一个能够识别手写数字图片的网络,网络接收数据时,必须把一张28*28的灰度图转换为784长的一维向量。在深入解析卷积网络前,我们直接用代码将其实现出来,通过卷积网络实现手写数字识别功能,先获得一个感性认识,为后续的深入研究打下基础,我们看看一个能直接接收手写数字图片的卷积网络是什么样子的:
from keras import layers from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)))
model.add(layers.MaxPooling2D(2,2))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.summary()
上面代码运行后结果如下:
上面实现网络与以往不同在于,网络层使用了Conv2D和MaxPooling,而不是以往的Dense,同时Conv2D网络层可以直接接收二维向量(28,28,1),这对应的就是手写数字灰度图。卷积网络主要作用是对输入数据进行一系列运算加工,它输出的是中间形态的结果,该结果不能直接用来做最终结果,要得到最终结果,我们需要为上面的卷积网络添加一层输出层,代码如下:
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
卷积网络在最后一层输出的是(3,3,64)的二维向量,Flatten()把它压扁成3364的一维向量,然后再传入一个包含64个神经元的网络层,由于我们要识别图片中的手写数字,其对应的结果有10种,也就是0到9,因此最后我们还添加了一个含有10个神经元的网络层。
我们把图片数据输入网络,对网络进行训练:
from keras.datasets import mnist
from keras.utils import to_categorical(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(train_images, train_labels, epochs = 5, batch_size=64)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(test_acc)
上面代码运行后,输出结果如下:
我们构造的卷积网络对手写数字图片的识别准确率为99%,而我们最开始使用的网络对图片识别的准确率是97%,也就是说最简单的卷积网络,对图片的识别效果也要比普通网络好得多。能取得这种好效果,主要是网络进行了两种特殊操作,分别是Conv2D和MaxPooling2D,接下来我们看看这两种操作的细节。
卷积操作,其实是把一张大图片分解成好多个小部分,然后一次对这些小部分进行识别,我们最开始实现的网络是一下子识别整张大图片,这是两种网络对图片识别精确度不一样的重要原因。通常我们会把一张图片分解成多个33或55的”小片“,然后分别识别这些小片段,最后把识别的结果集合在一起输出给下一层网络,例如下图:
上图中小方格圈中的区域就是我们抠出来的33小块。这种做法其实是一种分而治之的策略,如果一个整体很难攻克,那么我就把整体瓦解成多个弱小的局部,然后把每个局部攻克了,那么整体就攻克了。这种做法在图象识别中很有效就在于它能对不同区域进行识别,假设识别的图片是猫脸,那么我们就可以把猫脸分解成耳朵,嘴巴,眼睛,胡子等多个部位去各自识别,然后再把各个部分的识别结果综合起来作为对猫脸的识别。
每一小块识别后,会得到一个结果向量,例如语句:
layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1))
它表示把一个2828的灰度图片,(1表示颜色深度,对于灰度图其深度用一个数字就可以表示,对于RGB图,颜色深度需要用3个数字[R,G,B]表示),分解成多个33的小块,每个33小块识别后输出一个含有32个元素的一维向量。这个一维向量我们成为过滤器Filter,它蕴含着对图片的识别信息,例如“这部分对应猫脸的嘴巴”。
对于2828的图片,把它分解成3</em>3小块时,这些小块总共有2626个,我们先看看分解方法,假定我有一个55的大图片,那么我们可以将它如下分解成多个33的小块:
如果你看不出分解规律,我们把左边大图片的每个小格标号后就清楚了,左边图片编号如下:
1,2,3,4,5
6,7,8,9,10
11,12,13,14,15,
16,17,18,19,20
21,22,23,24,25
于是右边第一行第一小块对应的编号为:
1,2,3
6,7,8
11,12,13
第一行第二小块为:
2,3,4
7,8,9
12,13,14
以此类推,一个2828的图片可以分解成26个33小块,每个小块又计算出一个含有32个元素的向量,这个计算其实是将33矩阵乘以一个链路参数矩阵,它跟我们前面讲过的数据层上一层网络经过神经元链路输入下一层网络的原理是一样的,于是第一层网络输出结果是262632的三维矩阵。我们可以用下图形象的表示输出结果:
上面描述的操作流程就叫卷积,接下来我们看看另一种操作:max pooling。从我们的代码中看到,第一层网络叫Conv2D,第二层就是MaxPooling2D,max pooling 的目标是把卷积操作得到的结果进一步“挤压”出更有用的信息,有点类似于用力拧毛巾,把不必要的水分给挤兑掉。max pooling 其实是把一个二维矩阵进行22的分块,这部分跟前面描述的卷积很像,具体操作如下图:
一定要注意上面分块与卷积分块的区别,上面分出的块与块之间是没有重叠的,而卷积分出的33小块之间是相互重叠的!22分块后,把每块中的最大值抽出来最后组合成右边的小块,注意看完成后矩阵的维度缩减了一半,原来44的矩阵变成了22的矩阵。
回到我们的代码例子,第一层卷积网络输出了262632的结果,我们可以看成由32 个 2626个二维矩阵的集合。每个2626的二维矩阵都经过上面的max pooling处理变成1313的二维矩阵,因此经过第二层max pooling后,输出的结果是 131332的矩阵集合,也就是下面代码产生了32个13*13的矩阵集合:
layers.MaxPooling2D(2,2)
其他代码以此类推。卷积操作产生了太多的数据,如果没有max pooling对这些数据进行压缩,那么网络的运算量将会非常巨大,而且数据参数过于冗余就非常容易导致过度拟合。
以上就是我们这节需要介绍的内容,有了卷积网络,我们可以通过很少的训练数据就能教会计算机识别图片里面的物体,所以它的功能非常强大,下一节我们将运用卷积网络识别猫狗图片。