卷积神经网络(CNN)
- 1 CNN的组成
- 2 卷积层
- 2.1 卷积的计算
- 2.2 多通道卷积
- 2.3 多卷积核卷积
- 2.4 特征图大小
- 2.5 卷积层 api 实现
- 3 池化层
- 3.1 最大池化
- 3.2 平均池化
- 4 全连接层
- 5 CNN的构建
- 5.1 数据加载
- 5.2 数据处理
- 5.3 模型搭建
- 5.4 模型编译
- 5.5 模型训练
- 5.6 模型评估
1 CNN的组成
CNN网络受人类视觉神经系统的启发,人类的视觉原理:从原始信号摄入开始(瞳孔摄入像素 Pixels),接着做初步处理(大脑皮层某些细胞发现边缘和方向),然后抽象(大脑判定,眼前的物体的形状,是圆形的),然后进一步抽象(大脑进一步判定该物体是只人脸)。下面是人脑进行人脸识别的一个示例:
CNN网络主要有三部分构成:
- 卷积层
- 提取图像中的局部特征
- 池化层
- 大幅降低参数量级(降维)
- 全连接层
- 类似人工神经网络的部分,用来输出想要的结果
整个CNN网络结构如下图所示:
2 卷积层
卷积层是卷积神经网络中的核心模块,卷积层的目的是提取输入特征图的特征,如下图所示,卷积核可以提取图像中的边缘信息。
2.1 卷积的计算
卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。
Output 左上角点的计算方法:
同理可以计算其他各点,得到最终的卷积结果:
最后一点的计算方法是:
在上述卷积过程中,特征图比原始图减小了很多,我们可以在原图像的周围进行 padding,来保证在卷积过程中特征图大小不变。
按照步长为1来移动卷积核,计算特征图如下所示:
如果我们把stride增大,比如设为2,也是可以提取特征图的,如下图所示:
2.2 多通道卷积
实际中的图像都是多个通道组成的,我们怎么计算卷积呢?
计算方法如下:当输入有多个通道(channel)时(例如图片可以有 RGB 三个通道),卷积核需要拥有相同的channel数,每个卷积核 channel 与输入层的对应 channel 进行卷积,将每个 channel 的卷积结果按位相加得到最终的 Feature Map
输入层的通道数等于每个卷积核的通道数
2.3 多卷积核卷积
如果有多个卷积核时怎么计算呢?当有多个卷积核时,每个卷积核学习到不同的特征,对应产生包含多个 channel 的 Feature Map,例如下图有两个 filter,所以 output 有两个 channel。
卷积核的个数等于输出的通道数
2.4 特征图大小
输出特征图的大小与以下参数息息相关:
- size:卷积核/过滤器大小,一般会选择为奇数,比如有1 * 1, 3 * 3, 5 * 5
- padding:零填充的方式
- stride:步长
计算方法如下图所示:
例如,输入特征图为5x5,卷积核为3x3,外加padding 为1,则其输出尺寸为:
如下图所示:
2.5 卷积层 api 实现
tf.keras.layers.Conv2D(
filters, kernel_size, strides=(1, 1), padding='valid',
activation=None
)
主要参数说明如下:
3 池化层
池化层降低了后续网络层的输入维度,缩减模型大小,提高计算速度,并提高了Feature Map 的鲁棒性,防止过拟合,它主要对卷积层学习到的特征图进行下采样(subsampling)处理,主要由两种。
3.1 最大池化
Max Pooling,取窗口内的最大值作为输出,这种方式使用较广泛。
池化层最大池化的 api 实现如下:
tf.keras.layers.MaxPool2D(
pool_size=(2, 2), strides=None, padding='valid'
)
参数:
- pool_size:池化窗口的大小
- strides:窗口移动的步长,默认为1
- padding:是否进行填充,默认是不进行填充的
3.2 平均池化
Avg Pooling,取窗口内的所有值的均值作为输出
池化层平均池化的 api 实现如下:
tf.keras.layers.AveragePooling2D(
pool_size=(2, 2), strides=None, padding='valid'
)
4 全连接层
全连接层位于CNN网络的末端,经过卷积层的特征提取与池化层的降维后,将特征图转换成 一维向量 送入到全连接层中进行分类或回归的操作。
在 tf.keras 中全连接层使用 tf.keras.dense 实现
5 CNN的构建
我们构建卷积神经网络在mnist数据集上进行处理,如下图所示:LeNet-5 是一个较简单的卷积神经网络, 输入的二维图像,先经过两次卷积层,池化层,再经过全连接层,最后使用softmax分类作为输出层。
5.1 数据加载
导入工具包:
import tensorflow as tf
# 数据集
from tensorflow.keras.datasets import mnist
加载数据集:
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
5.2 数据处理
卷积神经网络的输入要求是:N H W C ,分别是图片数量,图片高度,图片宽度和图片的通道,因为是灰度图,通道为1
# 数据处理:n,h,w,c
# 训练集数据
train_images = tf.reshape(train_images, (train_images.shape[0],train_images.shape[1],train_images.shape[2], 1))
print(train_images.shape) # (60000, 28, 28, 1)
# 测试集数据
test_images = tf.reshape(test_images, (test_images.shape[0],test_images.shape[1],test_images.shape[2], 1))
5.3 模型搭建
Lenet-5模型输入的二维图像,先经过两次卷积层、池化层,再经过全连接层,最后使用 softmax 分类作为输出层,模型构建如下:
# 模型构建
net = tf.keras.models.Sequential([
# 卷积层:6个5*5的卷积核,激活是sigmoid
tf.keras.layers.Conv2D(filters=6,kernel_size=5,activation='sigmoid',input_shape= (28,28,1)),
# 最大池化
tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
# 卷积层:16个5*5的卷积核,激活是sigmoid
tf.keras.layers.Conv2D(filters=16,kernel_size=5,activation='sigmoid'),
# 最大池化
tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
# 维度调整为1维数据
tf.keras.layers.Flatten(),
# 全卷积层,激活sigmoid
tf.keras.layers.Dense(120,activation='sigmoid'),
# 全卷积层,激活sigmoid
tf.keras.layers.Dense(84,activation='sigmoid'),
# 全卷积层,激活softmax
tf.keras.layers.Dense(10,activation='softmax')
])
我们通过 net.summary()
查看网络结构:
Model: "sequential_11"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_4 (Conv2D) (None, 24, 24, 6) 156
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 12, 12, 6) 0
_________________________________________________________________
conv2d_5 (Conv2D) (None, 8, 8, 16) 2416
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 4, 4, 16) 0
_________________________________________________________________
flatten_2 (Flatten) (None, 256) 0
_________________________________________________________________
dense_25 (Dense) (None, 120) 30840
_________________________________________________________________
dense_26 (Dense) (None, 84) 10164
dense_27 (Dense) (None, 10) 850
=================================================================
Total params: 44,426
Trainable params: 44,426
Non-trainable params: 0
______________________________________________________________
关于参数量计算:
- conv1中的卷积核为5x5x1,卷积核个数为6,每个卷积核有一个bias,所以参数量为:5x5x1x6+6=156
- conv2中的卷积核为5x5x6,卷积核个数为16,每个卷积核有一个bias,所以参数量为:5x5x6x16+16 = 2416
绘制模型结构图:
tf.keras.utils.plot_model(net)
5.4 模型编译
设置优化器和损失函数:
# 优化器
optimizer = tf.keras.optimizers.SGD(learning_rate=0.9)
# 模型编译:损失函数,优化器和评价指标
net.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
5.5 模型训练
# 模型训练
net.fit(train_images, train_labels, epochs=5, validation_split=0.1)# 省略verbose参数,默认verbose=1
训练流程:
Epoch 1/5
1688/1688 [==============================] - 10s 6ms/step - loss: 0.8255 - accuracy: 0.6990 - val_loss: 0.1458 - val_accuracy: 0.9543
Epoch 2/5
1688/1688 [==============================] - 10s 6ms/step - loss: 0.1268 - accuracy: 0.9606 - val_loss: 0.0878 - val_accuracy: 0.9717
Epoch 3/5
1688/1688 [==============================] - 10s 6ms/step - loss: 0.1054 - accuracy: 0.9664 - val_loss: 0.1025 - val_accuracy: 0.9688
Epoch 4/5
1688/1688 [==============================] - 11s 6ms/step - loss: 0.0810 - accuracy: 0.9742 - val_loss: 0.0656 - val_accuracy: 0.9807
Epoch 5/5
1688/1688 [==============================] - 11s 6ms/step - loss: 0.0732 - accuracy: 0.9765 - val_loss: 0.0702 - val_accuracy: 0.9807
5.6 模型评估
# 模型评估
score = net.evaluate(test_images, test_labels, verbose=1)
print('Test accuracy:', score[1])
输出为:
313/313 [==============================] - 1s 2ms/step - loss: 0.0689 - accuracy: 0.9780
Test accuracy: 0.9779999852180481
与使用全连接网络相比,准确度提高了很多。
verbose是日志显示,有三个参数可选择,分别为0,1和2
- 当verbose=0时,简单说就是不输出日志信息 ,进度条、loss、acc这些都不输出
- 当verbose=1(默认)时,带进度条的输出日志信息
- 当verbose=2时,为每个epoch输出一行记录,和1的区别就是没有进度条
- 训练和评估时都默认取值为1,但在评估时参数只有0和1