![1780c3c6d0110c0eeae6e1e290f78c31.png](https://i-blog.csdnimg.cn/blog_migrate/f533462bbc823eb2e94fb0fc97da26ec.png)
本文将简要介绍经典卷积神经网络的基本原理,并以minst图片分类为例展示用Keras实现经典卷积神经网络的方法。
理解卷积神经网络的关键在于理解卷积运算和池化运算。卷积运算的作用主要是特征提取,池化运算的作用主要是特征降维。
让我们出发吧!
一,卷积运算
卷积运算是卷积核矩阵在输入矩阵上不断滑动,并在每一步将卷积核矩阵与输入矩阵对应位置元素相乘求和输出的运算。下面这幅图展示了卷积运算的基本过程。
![5828de9a355dda09ddf23b72cd732782.gif](https://i-blog.csdnimg.cn/blog_migrate/77879495ea886ffb75543a922ce64a95.gif)
以上是基本的卷积过程,卷积运算还有两个可选的重要参数,叫做步长stride和填充padding。
步长stride表示卷积核每次滑动的窗格数量,默认为1。
下面是步长为2的卷积过程。
![667553819da867b44b456432db353c5e.gif](https://i-blog.csdnimg.cn/blog_migrate/b6a04e266cdfc8e8e40b18c9736f1c87.gif)
padding有两种,即valid和same,默认为valid。valid即不对输入矩阵进行填充,通常经过valid卷积后输出矩阵尺寸比输入矩阵更小。而same填充会在输入矩阵四周填入适当的0,使得输出矩阵的尺寸和输入矩阵相同(这就是same的含义)。
假如当padding参数取为valid时,卷积过程如下。
![8dd590f6662552648ed030319d671309.gif](https://i-blog.csdnimg.cn/blog_migrate/e9647f21d3a7b6ed0181ab6ceebcda29.gif)
那么当padding参数取为same时,对应的卷积过程是这样的。
![85a72e458670bf48d19a7d854f3c8af1.gif](https://i-blog.csdnimg.cn/blog_migrate/e870be22b2aa6591cbe87089fe34769d.gif)
下面我们来看看Keras中的卷积函数K.conv2d。这个卷积运算的操作符除了实现了上述介绍的功能外,还考虑了对多张相同尺寸的图片同时进行卷积,并且考虑了图片可以有多个通道(例如彩色图像就有RGB共3个通道),而且可以有多个卷积核同时对这些图片进行卷积。
以下为函数说明:
K.conv2d(x, kernel, strides=(1, 1), padding='valid',
data_format=None, dilation_rate=(1, 1))
#x的shape为[batch, in_height, in_width, in_channels],
#分别是[图片张数, 图片高度, 图片宽度, 图像通道数(黑白为1,rgb为3)]。
#kernel的shape为[kernel_height, kernel_width, in_channels, out_channels]
#分别对应[卷积核高度, 卷积核宽度, 接受图像的通道数, 卷积后通道数]
#strides:步长,默认为(1,1)
#padding:填充方式,参数只有两种,‘valid’和‘same’
#data_format 一般为 'channels_last',即输入图像的格式为BHWC
#dilation_rate 与空洞卷积有关,默认为(1,1)即普通卷积
下面为代码范例。
import numpy as np
from keras import backend as K
image = K.constant(np.array([[10,10,10,0,0,0]]* 6))
kernel = K.constant(np.array([[1,0,-1]]*3))
print('image:')
print(K.eval(image))
print('kernel:')
print(K.eval(kernel))
# 将image和kernel通过reshape变成规范格式
image = K.reshape(image,[1,6,6,1])
kernel = K.reshape(kernel,[3,3,1,1])
result = K.conv2d(image,kernel,padding='valid',
data_format = 'channels_last')
print('result:')
print(K.eval(result).reshape(4,4))
![fcb62778abf555006c27ef5a6a18d9b3.png](https://i-blog.csdnimg.cn/blog_migrate/c2a2f417e260476d5e90e81607327221.jpeg)
二,池化运算
池化运算可以理解成为一种特殊的卷积运算,特殊之处在于两点,一是通常由池化运算构成的池化层是没有参数需要学习的,二是池化运算的stride通常大于1以便于更加有效地降维。
池化运算有两种,最大池化和平均池化。常用的是最大池化。
![b017c2812e981b0680352da84e9f573f.png](https://i-blog.csdnimg.cn/blog_migrate/5833c68187124698a30907495cc77aad.jpeg)
以下为Keras中池化函数K.pool2d的一个使用示范。
import numpy as np
from keras import backend as K
x = K.constant(np.array([[0,2,4,5],[3,7,6,5],[2,5,9,5],[3,3,6,4]]))
print('x:')
print(K.eval(x))
# 将x通过reshape变成Batch,Height,Width,Channel格式
x = K.reshape(x,[1,4,4,1])
x_maxpool = K.pool2d(x,pool_size = (2,2),strides = (2,2),
padding = 'valid',pool_mode = 'max')
x_avgpool = K.pool2d(x,pool_size = (2,2),strides = (2,2),
padding = 'valid',pool_mode = 'avg')
print('x_maxpool:')
print(K.eval(x_maxpool).reshape(2,2))
print('x_avgpool:')
print(K.eval(x_avgpool).reshape(2,2))
![b855c8e0a6497f3eb46e3278762e7899.png](https://i-blog.csdnimg.cn/blog_migrate/faa74b6522300492f2f7c1bb4a007512.jpeg)
三,经典CNN结构的一般模式
卷积神经网络的结构通常具有以下经典模式:卷积层和池化层交替排列,然后展开后连接几个全连接层,最后连接softmax分类器。
随着网络的深入,特征图大小将不断减少,但特征图数量(通道数量)将不断增加。
注:池化层不改变特征图数量,特征图数量取决于卷积层的卷积核数量。
![583c696999476a261bc30da261a36a17.png](https://i-blog.csdnimg.cn/blog_migrate/a77b5a1e4c0d1354c02c0e9afb4a8c23.jpeg)
四,Keras构建卷积神经网络
Keras一般用layers.Conv2D来构建2维卷积层,用layers.MaxPooling2D来构建2维池化层。
这两个类的主要参数说明如下。
from keras import layers
layers.Conv2D(filters, kernel_size, strides=(1, 1),padding='valid')
# filters: 卷积核数量
# kernel_size: 卷积核尺寸
# 其余参数一般不调整
layers.MaxPooling2D(pool_size=(2, 2),strides = None,padding = 'valid')
# pool_size: 池化窗口尺寸,一般就取默认值(2,2)
# 其余参数一般不调整
下面我们构造CNN的完整结构
from keras import models,layers
from keras import backend as K
K.clear_session()
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3) ))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3)))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
![3a1ee4c1c083a628eb328e3114db1edc.png](https://i-blog.csdnimg.cn/blog_migrate/c2b62419dcba0332a6382dc639938a2f.jpeg)
下面我们重点说一下各层参数的计算方式:
密集层的参数数量 = 输入的特征数 * 密集层神经元个数 + 密集层神经元个数(bias项导致)。
卷积层的参数数量 = 输入的特征图数量(通道数量) * 一个卷积核参数数量 * 卷积核数量 + 卷积核数量(bias项导致)。
池化层参数数量为0 。
所以 conv2d_1的参数数量为 1 * 3 * 3 * 32 + 32 = 320
conv2d_2的参数数量为 32 * 3 * 3 * 64 + 64 = 18496
……
dense_1的参数数量为 1152 * 64 +64 = 73792
dense_2的参数数量为 64 * 10 + 10 = 650
由此可见,卷积层的参数数量完全与图片尺寸无关,只和输入的特征图数量,卷积核大小,以及卷积核数量有关,而输入的特征图数量由上一个卷积层的卷积核数量或输入图片的通道数决定。
五,经典CNN对minst图片分类
以下为利用上述CNN模型结构对minst图片分类的完整代码。测试集准确率达到了98.89%。
# coding=utf-8
from __future__ import print_function
from __future__ import division
__author__ = 'Python_Ai_Road'
# ======================================================================
# 〇,设置gpu使用量控制
import os
import tensorflow as tf
from keras.backend.tensorflow_backend import set_session
os.environ["CUDA_VISIBLE_DEVICES"] = "0" #有多个GPU时可以指定只使用第几号GPU
config = tf.ConfigProto()
config.allow_soft_placement=True #允许动态放置张量和操作符
config.gpu_options.per_process_gpu_memory_fraction = 0.4 #最多使用40%GPU内存
config.gpu_options.allow_growth=True #初始化时不全部占满GPU显存, 按需分配
sess = tf.Session(config = config)
set_session(sess)
# ======================================================================
# 一,准备数据
from keras import datasets
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()
x_train = x_train.reshape((60000, 28, 28, 1))
x_train = x_train.astype('float32') / 255
x_test = x_test.reshape((10000, 28, 28, 1))
x_test = x_test.astype('float32') / 255
# ======================================================================
# 二,构建模型
from keras import models,layers
from keras import backend as K
K.clear_session()
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3) ))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3)))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
# ======================================================================
# 三,训练模型
model.compile(optimizer='rmsprop',
loss='sparse_categorical_crossentropy', # 注意此处loss形式针对未作Onehot的分类标签
metrics=['accuracy'])
history = model.fit(x_train, y_train, epochs=8,
batch_size=64,validation_data =(x_test,y_test))
# ======================================================================
# 四,评估模型
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'png'
dfhistory = pd.DataFrame(history.history)
dfhistory.index = range(1,len(dfhistory) + 1)
dfhistory.index.name = 'epoch'
dfhistory.to_csv('hitory_metrics',sep = 't')
acc = history.history['acc']
val_acc = history.history['val_acc']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
# ======================================================================
# 五,使用模型
from keras import metrics
# 计算top_2_accuracy
y_pred = model.predict(x_test)
top_2_accuracy = K.eval(metrics.sparse_top_k_categorical_accuracy(y_test,y_pred,k = 2))
print('top_2_accuracy = ',top_2_accuracy)
# ======================================================================
# 六,保存模型
model.save('minst_model.h5')
######
#####
####
###
##
#
![6158a802d43fe22647028632bda0dd68.png](https://i-blog.csdnimg.cn/blog_migrate/79f6d2f2d1583e6b091672d276210b41.jpeg)