卷积神经网络
卷积神经网络这个词,应该在你开始学习人工智能不久后就听过了,那究竟什么叫卷积神经网络,今天我们就聊一聊这个问题。
不用思考,左右两张图就是两只可爱的小狗狗,但是两张图中小狗狗所处的位置是不同的,左侧图片小狗在图片的左侧,右侧图片小狗在图片的右下方,这样如果去用图片特征识别出来的结果,两张图的特征很大部分是不同的,这不是我们希望的,那思考一下,为什么我们人就可以把它们都看成是可爱的小狗狗呢?这是因为平移不变性和空间层次结构,这两个概念是卷积神经网络中的概念。
平移不变性与模式的空间层次结构
这很好理解,我们要观察或者识别的物体,在图片上平行移动,我们都可以识别出来,因为无论他们在任何地方,都有相同的特征;我们识别物体的时候,先识别物体的局部特征信息,然后再脑袋中将局部信息组合起来,组合而成更高层次的特征信息,最终形成整体信息。比如上图,我们认出他们是可爱的狗狗,但脑袋在实际运转的过程中,是先看到了一些像素点(黑白红等),然后将像素点连接起来形成轮廓或特征(耳朵、眼睛、舌头),最后组合这些特征形成最后的结论(可爱的狗狗)。这就给我们了启发,我们在计算机图片识别的识别的时候,是不是可以借鉴这种机制呢,不一定需要图片全部的信息,而是识别图片的特征信息,再由这些特征,我们会将其组合成更大的特征,再组合,最终得出整体的特征信息,如下看一个经典的图:
我们的人脑在识别人脸的时候,脑神经不同部分也处理的是不同的信息,像素点-线条边缘-对象部分-对象整体。
上面这种识别方式就与之前我们采用的各种识别方式不同了,我们之前都是将每张图作为一个整体,去识别其特征,这里更多的分析局部信息,这种方式叫卷积运算。
卷积运算
给了我们一张彩色图片,我们用长、宽和深度(用于存储每个像素点 RGB 三种颜色的值)三个维度的张量去表示,用一个小的过滤器分别去取特征值:
这里面的黄色的小框是一种过滤器,我们在做卷积运算的时候,往往需要选择多种过滤器,这样就可以得到不同的卷积特征,这里的过滤器是一个权重矩阵,与每个图片小块做张量积,得到的就是一维向量,过滤器在卷积神经网络中的术语叫卷积核。我们还可以看到,原图的尺寸是 5x5,处理后的尺寸是 3x3,缩小了一些,如果我们每次把黄色的小格子向右移动两个格子,那我们就会得到 2x2 的输出,这种移动几个格子的影响输出尺寸的术语叫做步幅。
我们这里卷积层可以处理学习图片特征的问题,但是这些特征如何去进行学习得到这是一只可爱的小狗狗的结论,还是需要以前我们用到的全连接的方法,可是对于图片来说,全连接的方法要用到的参数太多了,看下图中密密麻麻的线,这每一层还不到十个节点神经元,换成图片,动辄几千个元素,几百万几千万个参数,这样训练出的网络,只会得出过拟合的结果,需要调整,改变步幅可以,但是效果不好,因此这里我们引入一个新的概念——池化。
池化
池化可以理解为某种情况的采样。比如最大池化的操作方法是:从输入的特征值中,提取窗口,输出每个通道的最大值,这样说有点不好理解,可以理解为就是卷积层输出的结果,再用一个上面黄色的 2x2 的小窗口,步幅是 2,每次取窗口中最大值,这样就可以减少很大一部分数据量。当然,还有很多池化的方法,比如最大池化改为平均值等,当然其目的都是一样的,减少数据量。
随后,数据就可以交给全连接层,进行学习,输出结果了。
这里我们说明几个问题:我们的网络一般不会只有一个卷积层和池化层,就像全连接层不会只有一个 Dense 层,往往是:卷积层-池化层-卷积层-池化层-卷积层-全连接层(多个 Dense);我们上面虽然拿图片识别举例子,但是卷积神经网络的应用可不仅仅是图片识别,还包括目标定位、人脸识别和目标分割等,应用非常广泛;卷积神经网络的简称是 CNN,平常与他人交流的时候,很多人喜欢用 CNN。
手写数字图片识别例子优化
我们之前多分类问题中就提到了手写图片数字识别的例子,当时的准确率不到 97.8%,这里我们将其优化为卷积神经网络的写法,精度会达到 99.3%,这个提升还是很可观的呀,具体的代码解释上面都已经将理论讲清楚了,这里我就不写那么多代码的解释了,直接写在注释中,还有很多代码是之前代码,不再赘述了,看网络架构:
看代码:
#!/usr/bin/env python3
import time
from keras import layers
from keras import models
from keras.datasets import mnist
from keras.utils import to_categorical
def cat():
model = models.Sequential()
# filters 输出空间的维数
# kernel_size 窗口大小,就是上面黄色窗口的大小
# activation 激活函数
# input_shape 输入
model.add(layers.Conv2D(filters=32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))
# 最大池化
# pool_size 窗口大小
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
# 展平一个张量
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
# 查看神经网络架构
# model.summary()
(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') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_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)
if __name__ == "__main__":
time_start = time.time()
cat()
time_end = time.time()
print('Time Used: ', time_end - time_start)