6.4 经典卷积网络模型
6.4.4 LeNet-5模型
LeNet-5模型是Yann LeCun教授于1998年在论文Gradient-based learning applied to document recognition中提出的,它是第一个成功应用于数字识别问题的卷积神经网络。在MNIST数据集上,LeNet-5模型可以达到大约99.2%的正确率。
LeNet-5模型的架构
- 第一层、卷积层
这一层的输入就是原始的图像像素,LeNet-5模型接收的输入层大小为32×32×1。第一个卷积层的卷积核尺寸为5×5×1,有六个,不使用全0填充,步长为1。因为没有使用全0填充,所以这一层的输出的尺寸为32-5+1=28,深度为6。这一个卷积层总共有5×5×1×6+6=156个参数,其中6个偏置项参数。因为下一层节点矩阵有28×28×6=4704个节点,每个节点和5×5=25个当前层节点相连,所以本层卷积层总共有4704×(25+1)=122304个连接。 - 第二层、池化层
这一层的输入为第一层的输出,是一个28×28×6的节点矩阵。本层采用的过滤器大小为2×2,长和宽的步长均为2,所以本层的输出矩阵大小为14×14×6。 - 第三层、卷积层
本层的输入矩阵大小为14×14×6,使用的卷积核尺寸为5×5×6,个数为16。本层不使用全0填充,步长为1。本层的输出矩阵大小为10×10×16。按照标准的卷积层,本层应该有5×5×6×16+16=2416个参数,10×10×16×(25+1)=41600个连接。 - 第四层、池化层
本层的输入矩阵大小为10×10×16,采用的过滤器大小为2×2,步长为2。本层输出矩阵尺寸为5×5×16。 - 第五层、全连接层
本层的输入矩阵大小为5×5×16,在LeNet-5模型的论文中将这一层成为卷积层,但是因为卷积核的大小就是5×5,所以和全连接层没有区别,在之后的TensorFlow程序实现中也会将这一层看成全连接层。本层的输出节点个数为120,也就是说有120个卷积核,总共有5×5×16×120+120=48120个参数。 - 第六层、全连接层
本层的输入节点个数为120个,输出节点个数为84个,总共参数为120×84+84=10164个。 - 第七层、全连接层
本层的输入节点个数为84个,输出节点个数为10个,总共参数为84×10+10=850个。
下面给出一个TensorFlow的程序来实现一个类似LeNet-5模型的卷积神经网络来解决MNIST数字识别问题。
源代码
# 调整输入数据格式,输入一个四维矩阵
x = tf.placeholder(tf.float32, [
mnist_train.BATCH_SIZE,
mnist_inference.IMAGE_SIZE,
mnist_inference.IMAGE_SIZE,
mnist_inference.NUM_CHANNELS],
name='x-input')
# 类似地将输入的训练数据格式调整为一个四维矩阵,并将这个调整后的数据传入sess.run过程
reshaped_xs = np.reshape(xs, (mnist_train.BATCH_SIZE,
mnist_inference.IMAGE_SIZE,
mnist_inference.IMAGE_SIZE,
mnist_inference.NUM_CHANNELS))
然后实现类似LeNet-5的前向传播过程
# mnist_inference.py
# -*- coding: utf-8 -*-
# @Time : 2019/3/15 20:58
# @Author : Chord
import tensorflow as tf;
# 定义神经网络结构相关参数
INPUT_NODE = 784
OUTPUT_NODE = 10
# 定义输入图像尺寸和通道数
IMAGE_SIZE = 28
NUM_CHANNELS = 1
NUM_LABELS = 10
# 第一层卷积层的尺寸和卷积核数量
CONV1_NUM = 32
CONV1_SIZE = 5
# 第二层卷积层的尺寸和卷积核数量
CONV2_NUM = 64
CONV2_SIZE = 5
# 全连接层节点个数
FC_SIZE = 512
# 定义卷积神经网络的前向传播过程。这里添加了一个新的参数train,用于区分训练过程和测试
# 过程。在这个程序中将用到dropout方法dropout可以进一步提升模型可靠性并防止过拟合,
# dropout过程只在训练时使用
def inference(input_tensor, train, regularizer):
# 声明第一层卷积层的变量并实现前向传播过程。这个过程通过使用不同命名空间来隔离不
# 同层的变量,这可以让每一层中的变量命名只需要考虑在当前层的作用,而不需要担心重
# 名的问题。和标准LeNet-5模型不大一样,这里定义的卷积层输入为28×28×1的原始MNIST
# 图片像素。因为卷积层中使用了全0填充,所以输出为28×28×32的矩阵
with tf.variable_scope('layer1-conv1'):
conv1_weights = tf.get_variable(
"weight", [CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_NUM],
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
conv1_biases = tf.get_variable(
"bias", [CONV1_NUM], initializer=tf.constant_initializer(0.0)
)
# 使用32个5×5的卷积核,步长为1,且使用全0填充
conv1 = tf.nn.conv2d(
input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding='SAME'
)
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases))
# 实现第二层池化层的前向传播过程。这里选用最大池化层,池化层过滤器的边长为2,使用全0
# 填充且步长为2.这一层的输入时上一层的输出,也就是28×28×32的矩阵。输出为14×14×32的矩阵
with tf.name_scope('layer2-pool1'):
pool1 = tf.nn.max_pool(
relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME'
)
# 声明第三层卷积层的变量并实现前向传播过程,输入为14×14×32,输出为7×7×64
with tf.variable_scope('layer3-conv2'):
conv2_weights = tf.get_variable(
"weight", [CONV2_SIZE, CONV2_SIZE, CONV1_NUM, CONV2_NUM],
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
conv2_biases = tf.get_variable(
"bias", [CONV2_NUM], initializer=tf.constant_initializer(0.0)
)
# 64个5×5的卷积核,步长为1,全0填充
conv2 = tf.nn.conv2d(
pool1, conv2_weights, strides=[1, 1, 1, 1], padding='SAME'
)
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_biases))
# 实现第四层池化层的前向传播过程。这一层和第二层的结构是一样的。输入14×14×64,输出7×7×64
with tf.name_scope('layer4-pool2'):
pool2 = tf.nn.max_pool(
relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME'
)
# 将第四层池化层的输出转化为第五层全连接层的输入格式。第四层的输出为7×7×64的矩阵,然而第五层
# 全连接层需要的输入格式为向量,所以在这里需要将这个7×7×64的矩阵拉直成一个向量。pool2.get_shape
# 函数可以得到第四层输出矩阵的维度为不需要手工计算。注意因为每一层的神经网络的输入输出都为一个batch
# 的矩阵,所以这里得到的维度也包含了一个batch中数据的个数
pool_shape = pool2.get_shape().as_list()
# 计算将矩阵拉直成响亮之后的长度,这个长度就是矩阵长宽与深度的乘积
# 注意这个pool_shape[0]为一个batch中数据的个数
nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
# 通过tf.reshape函数将第四层的输出变成一个batch的向量
reshaped = tf.reshape(pool2, [pool_shape[0], nodes])
# 声明第五层全连接层的变量并实现前向传播过程。这一层的输入时拉直后的一组向量,向量长度为3136,
# 输出是一组长度为512的向量。这一层引入了fropout的概念。fropout在训练时会随机将部分节点的输出改为0。
# dropout可以避免过拟合问题,从而使得模型在测试数据上的效果更好。
# dropout一般只在全连接层而不是卷积层或池化层使用
with tf.variable_scope('layer5-fc1'):
fc1_weights = tf.get_variable(
"weight", [nodes, FC_SIZE],
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
# 只有全连接层的权重需要加入正则化
if (regularizer!=None):
tf.add_to_collection('losses', regularizer(fc1_weights))
fc1_biases = tf.get_variable(
'bias', [FC_SIZE], initializer=tf.constant_initializer(0.1)
)
fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_weights) + fc1_biases)
if (train):
fc1 = tf.nn.dropout(fc1, 0.5)
# 声明第六层全连接层的变量并实现前向传播过程。这一层输入为一组长度为512的向量,输出为一组长度为10的向量。
# 这一层通过Softmax之后就得到了最后的分类结果
with tf.variable_scope('layer6-fc2'):
fc2_weights = tf.get_variable(
"weight", [FC_SIZE, NUM_LABELS],
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
if (regularizer!=None):
tf.add_to_collection('losses', regularizer(fc2_weights))
fc2_biases = tf.get_variable(
'bias', [NUM_LABELS], initializer=tf.constant_initializer(0.1)
)
logit = tf.matmul(fc1, fc2_weights) + fc2_biases
# 返回第六层的输出
return logit;
# 初始学习率为0.8
After 1 training step(s), loss on training batch is 8.06673.
After 1001 training step(s), loss on training batch is 904.366.
After 2001 training step(s), loss on training batch is 774.32.
After 3001 training step(s), loss on training batch is 664.866.
After 4001 training step(s), loss on training batch is 572.533.
感觉就是学习率太大给整歪了,试图减小学习率,然后发现损失降低了很多,而且一点点减小
# 降低学习率为0.1
After 1 training step(s), loss on training batch is 7.05461.
After 1001 training step(s), loss on training batch is 0.734978.
After 2001 training step(s), loss on training batch is 0.671189.
After 3001 training step(s), loss on training batch is 0.621566.
After 4001 training step(s), loss on training batch is 0.589219.
类似的修改了mnist_eval的输入部分,就可以测算正确率了
# 学习率0.8
After 4001 training step(s), validation accuracy = 0.06
# 学习率0.1
After 4001 training step(s), validation accuracy = 0.99
输入层→(卷积层+→池化层?)+→全连接层+
- “卷积层+”表示一层或多层卷积层,一般最多连续使用三层卷积层。
- “池化层?”表示没有或者一层池化层,池化层虽然可以减少参数防止过拟合问题,但是在部分论文中也发现可以直接通过调整卷积层步长来实现同样的效果。
- “(卷积层+→池化层?)+”表示括号内作为组合,可以连续出现多次。
- “全连接层+”表示多层全连接层。
如LeNet-5模型为如下结构,输入层→卷积层→池化层→卷积层→池化层→全连接层→全连接层→输出层。
下表展示了VGGNet论文Very Deep Convolutional Networks for Large-Scale Image Recognition中作者尝试过的不同卷积神经网络架构。
convX-Y表示卷积的边长为X,个数为Y。
VGGNet的卷积核边长一般是3或者1。
一般卷积核的大小不会超过5。
在卷积核的数量上,大部分卷积神经网络都采用逐层递增的方式。
池化层的配置相对简单,一般都是采用最大池化层。
池化层的过滤器边长一般为2或者3,步长也一般为2或者3。