1. 引入
卷积神经网络(CNN)是一种专门用来处理具有网格结构数据的神经网络.它属于前馈神经网络,它被定义为:至少在某一层用卷积代替了矩阵乘法的神经网络.最常见的应用场景是图像识别.
前篇我们自己动手,用Python实现了一个BP神经网络,本篇我们在Keras框架之下实现卷积神经网络(Keras框架详见《深度学习_工具》篇).Keras几乎是搭建CNN最简单的工具了,然而原理并不简单:除了基本的神经网络中用的误差函数,激活函数等概念以外(具体详见《深度学习_BP神经网络》),CNN还用到了卷积,池化,DropOut等方法.将在本文中逐一介绍.
2. 原理
1) 图像识别
先来看看图形学中的图像识别是如何实现的.
我们拿到了原图(图上左),一般先将其转换成灰度图(图上中).然后进行边缘检测,图像处理中常使用计算梯度方法(判断某像素与它相邻像素的差值)检测边缘.在CNN中我们用卷积来检测:先设计一个卷积核计算相邻像素的差值,然后用ReLU(f(x)=max(0,x))激活函数将那些差值小的置为0,即识别为非边缘(看到激活函数的厉害了吧,它把低于阈值的都扔掉了,这可是一般线性变换做不到的).于是得到了边缘图(图上右).处理后的图像是个稀疏的矩阵(多数点值都为0).
¬为了简化计算,我们希望将图片缩小,但在缩放的过程中,细的边缘线就会被弱化,甚至消失(图下左).在CNN中用池化解决这一问题,如果把3*3个点缩放到1点,”最大池化”会将这9个点中最大的值,作为新点的值,于是无论强边缘还是弱边缘都被保留了下来(图下右).
边缘检测只是图像识别的第一步,之后还将识别一些更上层的特征,比如拐角,车轮,汽车的轮廓等等,它们都可由卷积实现.越往后的层越抽象,最终一个神经网络模型建起来和图像处理中的"金字塔"有类似的结构,虽然下层的每个点都是局部的,但是上层的点具有全局性.
2) 卷积
下面来看看卷积到底是什么,它与之前学习的全连接神经网络有什么不同.
卷积定义是:通过两个函数f 和g 生成第三个函数的一种数学算子.呵呵,很抽象啊,形象地说,处理图像时,卷积就好像拿着一小块滤镜,把图像从左到右,从上到下,一格一格地扫一遍,如果在滤镜中看到想要的,就在下一层做个标记.看看下图就明白了.
图中是一个二维平面上的卷积运算,输入是一个4x3的矩阵,卷积核(Kernel)是2x2的矩阵,输出是一个3x2的矩阵(卷积不都是2维的).这就是卷积——一种特殊的线性运算,考虑以下几种情况:
如果上图中的w,x,y,z都为0.25,则该卷积实现了均值运算(图片的模糊处理)
如果上图中w+x+y+z=1,则该卷积实现了实现了加权平均.
这个运算是不是有点眼熟,如果把Input中各点排着一列,把Kernel看成权值参数,则它是一个神经网络的连接.输入层有12个元素,隐藏层1有6个元素(先不管其它层)
如图所示,左则是卷积层,右则是全连接的神经网络,全连接时,共有126=72个连接(输入输出),72种权值;而卷积层只有24个连接(核大小*输出),4个权值w,x,y,z,分别用四种颜色标出.此处,引出了两个概念:
共享权值(参数共享):共享权值就是多个连接使用同一权值,卷积神经网络中共享的权值就是卷积核的内容,这样不但减小了学习难度.还带来的”平移等变”的性质,比如集体合影中每张脸都可以使用相同的卷积核(一组权值)识别出来,无论它在图中的什么位置,这样学习一张脸后,就能对每张脸应用相同的处理了.此技术多用于同一状态重复出现的情况下.(若图中只有一张脸,脸相关的卷积核共享作用就不大了)
全连接,局部连接与卷积:全连接就是上一层的每个点都与下一层的每个点连接,每个连接都有其自已的权值;局部连接是只有部分点相互连续;卷积是在局部连续的基础上又共享了权值.
卷积核可以是指定的,也可以是用梯度向前推出来的,可以是聚类算出来的(无监督学习),还可以先取一小块训练,然后用这小块训练的结果定义卷积的核.这取决于我们设计的不同算法.
由此可见,卷积层是两层之间的连接方法,与全连接相比,它大大简化了运算的复杂度,还节省了存储空间.之所以能这样简化是因为图像中距离越近的点关系越大(序列处理也同理:离得越近关系越大).
3) 池化
池化是使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出,和卷积差不多,它也是层到层之间的运算,经过池化处理,层中节点可能不变,也可能变小(常用它降采样,以减少计算量).
最大池化就是将相邻的N个点作为输入,将其中最大的值输出到下一层.除了最大池化,池化算法还有:取平均值,加权平均等等.
池化具有平移不变性:若对图片进行少量平移,经过池化函数后的大多数输出并不会发生改变,比如最大池化,移动一像素后,区域中最大的值可能仍在该区域内.
同理,在一个稀疏的矩阵中(不一定是图像),假设对n点做最大池化,那么只要其中有一点非0,则池化结果非0.如下图所示,只要手写数据5符合其中一个判断标准,则将其识别为数字5.
池化还经常用于处理不同尺寸的图片:比如有800x600和200*150两张图,想对它们做同一处理,可通过设定池化的输出为100x75来实现保留特征的缩放,以至扩展到任意大小的图片.
基本上卷积网络都用使用池化操作.
4) DropOut
再举个例子,现在我们来识别老鼠,一般老鼠都有两只耳朵,两只眼睛,一个鼻子和一个嘴,它们有各自的形状且都是从上到下排列的.如果严格按照这个规则,那么"一只耳"就不会被识别成老鼠.
为提高鲁棒性,使用了DropOut方法.它随机地去掉神经网络中的一些点,用剩余的点训练,假设有一些训练集中的老鼠,被去掉的正是耳朵部分,那么"一只耳"最终也可能由于其它特征都对而被识别.
除了提高鲁棒性,DropOut还有其它一些优点,比如在卷积神经网络中,由于共享参数,我们训练一小部分的子网络(由DropOut剪出),参数共享会使得剩余的子网络也能有同样好的参数设定。保证学习效果的同时,也大大减少了计算量.
DropOut就如同在层与层之间故意加入了一些噪声,虽然避免了过拟合,但它是一个有损的算法,小的网络使用它可能会丢失一些有用的信息.一般在较大型的网络中使用DropOut.
5) 总结
卷积,池化,DropOut都是设计者根据数据的性质,采取的对全连接网络的优化和简化,虽然它们都是有损的,但是对计算和存储的优化也非常明显,使我们有机会将神经网络扩展到成百上千层.
3. 代码
1) 说明
代码的主要功能是根据Mnist库的图像数据训练手写数字识别.核心代码在后半部分,它使用了卷积层,池化层,Dropout层,Flatten层,和全连接层.
2) 代码
(为了让大家复制粘贴就能运行,还是附上了全部代码)
# -*- coding: utf-8 -*-
from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
batch_size = 128 # 批尺寸
num_classes = 10 # 0-9十个数字对应10个分类
epochs = 12 # 训练12次
# input image dimensions
img_rows, img_cols = 28, 28 # 训练图片大小28x28
# the data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()
if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')
# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
# 下面model相关的是关键部分
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3), #加卷积层,核大小3x3,输出维度32
activation='relu', #激活函数为relu
input_shape=input_shape)) #传入数据
model.add(Conv2D(64, (3, 3), activation='relu')) #又加一个卷积层
model.add(MaxPooling2D(pool_size=(2, 2))) # 以2x2为一块池化
model.add(Dropout(0.25)) # 随机断开25%的连接
model.add(Flatten()) # 扁平化,例如将28x28x1变为784的格式
model.add(Dense(128, activation='relu')) # 加入全连接层
model.add(Dropout(0.5)) # 再加一层Dropout
model.add(Dense(num_classes, activation='softmax')) # 加入到输出层的全连接
model.compile(loss=keras.losses.categorical_crossentropy, # 设损失函数
optimizer=keras.optimizers.Adadelta(), # 设学习率
metrics=['accuracy'])
model.fit(x_train, y_train,
batch_size=batch_size, # 设批大小
epochs=epochs, # 设学习次数
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0) # 用测试集评测
print('Test loss:', score[0])
print('Test accuracy:', score[1])
4. CNN的历史
看CNN的发展,从1998年LeCun的经典之作LeNet, 到将ImageNet图像识别率提高了10个百分点的AlexNet, VGG(加入更多卷积层), GoogleNet(使用了Inception一种网中网的结构), 再到RssNet(使用残差网络),ImageNet的Top-5错误率已经降到3.57%,低于人眼识别的错误率5.1%,并且仍在不断进步.这些不断提高的成绩以及在更多领域的应用让神经变得越来越热门.
从图中可见,从AlexNet的3层全连接神经网络,到ResNet的152层神经网络,全连接层越来越少,卷积层越来越多.除了算法的进步,人的知识也越来越多越来越细化地溶入了神经网络.