CNN 与图像识别

上一篇教程 (使用Keras搭建第一个深度学习网络) 中,我们简单介绍了 Keras 框架,并且搭建了一个多层感知器模型来识别手写数字,达到了91%左右的正确率。不过在上一篇文末也说,如果我们改成使用卷积神经网络,那么识别手写数字的正确率得到很大的提升。不过什么是卷积神经网络,它为什么能够在这样的图像识别问题中取得如此大的突破呢?

1. 什么是卷积神经网络?

卷积神经网络 (Convolutional Neural Networks, CNN) 的灵感来源于“滤波器”在图像处理中的应用。它依靠一个卷积核在图像上滑动来 获取图像的特征 ,卷积核会对它范围内的像素进行一定的运算,并将它保存在一个新的像素中。而与之前的多层感知器不同,卷积神经网络可以保留高维空间的结构,因此常用于图像领域。并且能够保持更少的参数,大大减少了网络参数的数量。

不同于多层感知器模型,一个卷积神经网络中有许多功能不同的网络层。接下来,我将会逐一介绍每一层的原理和作用,以及我们应该如何在 Keras 中使用它。

1.1 Conv2D - 卷积层

卷积层是卷积神经网络中最基本的单位。每一个卷积层都包含一个卷积核 (由许多个滤波器组成) ,它会在图像上滑动,从而获得不同的特征。下图就展示了 一个 3x3 的滤波器在一张图像上的卷积过程:

CNN Motion

可以看到, 3x3 的滤波器会将原图像 (下方) 中每一个 3x3 的区域进行运算,并将结果赋值给新的图像 (上方) 。这样我们就实现了对图像的一次处理。而一个卷积层包含了很多不同的滤波器,从而将原图像处理为许多种不同的新图像:

Convolutional

而在 Keras 中, keras.layers.Conv2D 就代表了一个卷积层,而其中包含了以下的参数:

1.2 MaxPooling2D - 最大池化层

通过卷积层,我们就可以将原图像中的不同特征提取出来。但是卷积层会将图像的深度变得越来越大,这样无疑会让整个网络变得越来越大。所以为了保证网络的运行速度,除了卷积操作之外,我们还需要使用池化层 (Pooling) 来将图像压缩。而池化的操作有两种,分别为 平均池化 (AveragePooling) 和 最大池化 (MaxPooling) 。而通过实践证明: 最大池化的效果一般比平均池化的效果好 ,所以在这篇教程中,我们就只介绍最大池化层。

池化操作是如何进行的呢?下图就直观的展现了一个 2x2 最大池化的过程。我们将原图像划分成若干个 2x2 的小区域,然后取每一块区域中的最大值,然后将其组合生成一个新的图像。这样,通过 2x2 的池化,我们就将原图像的大小缩小为了原来的 1/4 。

Max Pooling Example

了解完了池化的原理之后,接下来让我们看一下如何在 Keras 中使用池化层。

Keras 中的最大池化层为 keras.layers.MaxPooling2D ,一般我们只使用一个参数:

2. Dropout 层与过拟合

在计算机视觉这一块领域中,深度学习网络非常容易出现过拟合 (Overfitting) 的问题。但是过拟合这是一个术语,我们应该如何理解它呢?

深度学习网络之所以能够根据输入进行分类,是因为它找到了输入与输出之间的关系。而如果我们的样本量较小,那么就容易出现模型 “记忆” 了样本库中图片的情况。此时,这个模型在训练集中的表现会非常好,但是在验证集中,模型的准确度甚至会下降,因为它的适应性更弱。这同时也说明了设置验证集的重要性。

那么,我们如何能够防止模型进行“记忆”,从而防止其进行过拟合呢?这个时候我们就需要使用正则化 (Regularization) 的方法。而 Dropout 便是一种非常有效的正则化方法。

Dropout 的原理是,在每一次训练时, Dropout 层会将两层之间的连接进行随机丢弃,丢弃的比例就由 rate 控制。下图就表示了一个 Dropout 的过程:

Dropout Example

通过 Dropout ,我们能够防止网络中某些参数出现权重过大的情况,并且能够有效地防止过拟合。

而在 Keras 中,使用 Keras.layers.Dropout 就可以在两层之间添加 Dropout 层了。与上方的原理相似,一般来说你只需要设定一个参数:

3. 一个常见的卷积神经网络

了解了卷积神经网络的各个层,并且学习了 Dropout 层之后,我们应该如何将这一些层组合起来,组建一个卷积神经网络呢?

首先,一般卷积层的深度 (由 filters 参数控制) 应该要越来越大,并且在每一个卷积层后加上池化层。卷积之后,我们将其展平,并使用全连接层来将特征进行分类。和上一篇教程一样,最终我们将用一层 10 个元素的全连接层来输出概率。因此,下图就是一个可行的模型:

CNN Example

4. 编写代码,并运行模型

根据上面设计的模型,我们就可以编写代码了。不过要注意的是,对 X_train 的预处理方法有一定的不同,我们将其 reshape 为了 (60000, 28, 28, 1) 的大小。这是因为卷积神经网络传入的是图像,而最后一个代表了通道数。因为我们的数据都是灰度图,所以我们将通道数设为 1 ,这样才能够顺利传入网络。因此,我们得到了如下的代码:

from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Flatten, MaxPooling2D, Dense, Conv2D, Dropout
from keras.datasets import mnist


(X_train, y_train), (X_test, y_test) = mnist.load_data()

X_train = X_train.astype('float32').reshape(-1, 28, 28, 1) / 127.5 - 1

y_train = to_categorical(y_train)

model = Sequential([
    Conv2D(input_shape=(28, 28, 1),
           filters=4,
           kernel_size=3,
           padding='same',
           activation='relu'),
    MaxPooling2D(2),
    Conv2D(filters=8,
           kernel_size=3,
           padding='same',
           activation='relu'),
    MaxPooling2D(2),
    Conv2D(filters=16,
           kernel_size=3,
           padding='same',
           activation='relu'),
    # (7, 7, 16)
    Flatten(),
    Dropout(0.25),
    Dense(units=784,
          activation='relu'),
    Dropout(0.25),
    Dense(units=100,
          activation='relu'),    
    Dense(units=10,
          activation='softmax')
])

# 输出模型结构
print(model.summary())

# 编译模型
model.compile(loss='mse',
              optimizer='adam',
              metrics=['acc'])

# 训练模型
model.fit(x=X_train,
          y=y_train,
          validation_split=0.2,
          epochs=10,
          verbose=2)

运行这一个程序,我们可以看到,模型在训练集和验证集中的准确率都达到了 98.5% 以上。这样的结果相较于前面的多层感知器模型已经有了非常大的提升。

Using TensorFlow backend.
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 28, 28, 4)         40
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 4)         0
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 14, 14, 8)         296
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 7, 7, 8)           0
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 7, 7, 16)          1168
_________________________________________________________________
flatten_1 (Flatten)          (None, 784)               0
_________________________________________________________________
dropout_1 (Dropout)          (None, 784)               0
_________________________________________________________________
dense_1 (Dense)              (None, 784)               615440
_________________________________________________________________
dropout_2 (Dropout)          (None, 784)               0
_________________________________________________________________
dense_2 (Dense)              (None, 100)               78500
_________________________________________________________________
dense_3 (Dense)              (None, 10)                1010
=================================================================
Total params: 696,454
Trainable params: 696,454
Non-trainable params: 0
_________________________________________________________________
None
Train on 48000 samples, validate on 12000 samples

Epoch 1/10
 - 12s - loss: 0.0106 - acc: 0.9280 - val_loss: 0.0056 - val_acc: 0.9628
Epoch 2/10
 - 8s - loss: 0.0051 - acc: 0.9674 - val_loss: 0.0037 - val_acc: 0.9756
Epoch 3/10
 - 9s - loss: 0.0041 - acc: 0.9739 - val_loss: 0.0031 - val_acc: 0.9808
Epoch 4/10
 - 8s - loss: 0.0034 - acc: 0.9780 - val_loss: 0.0029 - val_acc: 0.9812
Epoch 5/10
 - 8s - loss: 0.0032 - acc: 0.9801 - val_loss: 0.0024 - val_acc: 0.9848
Epoch 6/10
 - 8s - loss: 0.0028 - acc: 0.9824 - val_loss: 0.0024 - val_acc: 0.9840
Epoch 7/10
 - 9s - loss: 0.0028 - acc: 0.9827 - val_loss: 0.0026 - val_acc: 0.9843
Epoch 8/10
 - 8s - loss: 0.0026 - acc: 0.9837 - val_loss: 0.0028 - val_acc: 0.9831
Epoch 9/10
 - 8s - loss: 0.0025 - acc: 0.9845 - val_loss: 0.0025 - val_acc: 0.9854
Epoch 10/10
 - 8s - loss: 0.0024 - acc: 0.9854 - val_loss: 0.0022 - val_acc: 0.9865

结语

在本篇教程中,我们介绍了卷积神经网络、过拟合与Dropout,并且将其运用于识别手写数字的问题中,并且取得了很大的突破。当然,这个模型也并不是最优的。通过不断调整模型的结构与参数,你也许可以得到一个准确率更高的模型。

关于卷积神经网络更深层次的原理,大家也可以再多了解一些。 Keras 隐藏了许多网络中的技术细节,而深入地了解它们有利于你理解其中的原理。