1 - 引言
之前我们介绍了一下卷积神经网络的基本结构——卷积层和池化层。通过这两个结构我们可以任意的构建各种各样的卷积神经网络模型,不同结构的网络模型也有不同的效果。但是怎样的神经网络模型具有比较好的效果呢?
下图展示了CNN的发展历程。
经过人们不断的尝试,诞生了许多有有着里程碑式意义的CNN模型。因此我们接下来会学习这些非常经典的卷积神经网络
- LeNet -5
- AlexNet
- VGG
- Inception
- ResNet
2 - LeNet-5模型
LeNet-5模型是Yann LeCun教授于1998年在论文Gradient-Based Learning Applied to Document Recognition中提出的,它是第一个成功应用与数字识别问题的卷积神经网络。在MNIST数据集上,LeNet-5模型可以达到大约99.2%的正确率,LeNet-5模型如下图所示:
下面我们来详细介绍一个LeNet-5模型每一层的结构
2.1 第一层:卷积层
数据维数详细说明:
这一层的输入就是原始的图像像素,LeNet-5模型输入层为32X32X1的图像(只能识别灰度图像而不能识别彩色图像)。第一个卷积层的过滤器尺寸为5X5,深度为6(深度既是通道值),不使用padding,步长为1,因为没有使用padding,这一层的输出尺寸为32-5+1=28,深度为6。这一个卷积层总共有5x5x1x6+6=156个参数,其中6个为偏置项参数。本层卷积层总共有28x28x6x(5x5+1)=122304个连接
总结:
输入图片:
卷积核大小:
卷积核种类:6
输出featuremap大小:
神经元数量:
可训练参数:
连接数:
2.2 第二层:池化层
这一层的输入为第一层的输出,是一个28x28x6的节点矩阵。本层采用的过滤器大小为2x2,步长为2,所以输出矩阵为14x14x6。
输入:
采样区域:
采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid
采样种类:6
输出featureMap大小:
神经元数量:
连接数:
S2中每个特征图的大小是C1中特征图大小的1/4。
详细说明:第一次卷积之后紧接着就是池化运算,使用$ 2*2$核 进行池化,于是得到了S2,6个 的 特征图(28/2=14)。S2这个pooling层是对C1中的 区域内的像素求和乘以一个权值系数再加上一个偏置,然后将这个结果再做一次映射。同时有5x14x14x6=5880个连接。
2.3 第三层:卷积层
本层的输入矩阵大小为14x14x6,使用的过滤器大小为5x5,深度为16。本层不使用padding,步长为1,所以输出节点为10x10x16。所以有5x5x6x16+16 = 2416个参数。10x10x16x(25+1)=41600个连接。
输入:14x14x6
卷积核大小:
卷积核种类:16
输出featureMap大小:
2.4 第四层:池化层
本层输入矩阵大小为10x10x16,采用的过滤器大小为2x2,步长为2,本层输出矩阵大小为5x5x16
2.5 第五层:卷积层
输入:5x5x16
卷积核大小:5*5
卷积核种类:120
输出featureMap大小:1*1(5-5+1)
可训练参数/连接:120*(1655+1)=48120
虽然LeNet-5模型的论文中将这一层成为卷积层,但是因为过滤器的大小就是5x5,所以和全连接层没有区别。
2.6 F6层-全连接层
本层的输入节点个数为120个,输出节点个数为84个,总共参数为120x84+84 = 10164个。
2.7 Output层-全连接层
本层输入节点为84个,输出节点个数为10个,总共参数为84x10+10 = 850个。
3 - TensorFlow实现LeNet-5模型
** mnist_train_LeNet_5.py文件:**
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
#加载mnist_inference.py中定义的常量和前向传播的函数。
import mnist_inference_LeNet_5
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最开始的学习率
LEARNING_RATE_DECAY = 0.99 # 在指数衰减学习率的过程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 30000 # 训练轮数,注意,训练一个Batch就是一个step
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均模型的衰减率,最后我会讲解滑动平均模型
#模型保存的路径和中文名
MODEL_SAVE_PATH = "/path/to/model/"
MODEL_NAME = "model.ckpt"
def train(mnist):
# 定义输入输出placeholder。
x = tf.placeholder(tf.float32,[
BATCH_SIZE, #第一维表示一个batch中样例的个数
mnist_inference_LeNet_5.IMAGE_SIZE, #第二维和第三维表示图片的尺寸
mnist_inference_LeNet_5.IMAGE_SIZE,
mnist_inference_LeNet_5.NUM_CHANNELS], #第四维表示图片的深度,对于RBG格式的图片,深度为5
name='x-input')
y_ = tf.placeholder(tf.float32, [None, mnist_inference_LeNet_5.OUTPUT_NODE], name='y-input')
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
# 直接使用mnist_inference.py中定义的前向传播过程
y = mnist_inference_LeNet_5.inference(x,train, regularizer)
global_step = tf.Variable(0, trainable=False)
# 定义损失函数、学习率、滑动平均操作以及训练过程
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AVERAGE_DECAY, global_step
)
variable_averages_op = variable_averages.apply(
tf.trainable_variables()
)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
logits=y, labels=tf.argmax(y_, 1)
)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
mnist.train.num_examples / BATCH_SIZE,
LEARNING_RATE_DECAY
)
train_step = tf.train.GradientDescentOptimizer(learning_rate)\
.minimize(loss, global_step=global_step)
with tf.control_dependencies([train_step, variable_averages_op]):
train_op = tf.no_op(name='train')
# 初始化TensorFlow持久化类
saver = tf.train.Saver()
with tf.Session() as sess:
tf.global_variables_initializer().run()
# 在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独
# 立的程序来完成。
for i in range(TRAINING_STEPS):
xs, ys = mnist.train.next_batch(BATCH_SIZE)
xs = np.reshape(xs,(
BATCH_SIZE,
mnist_inference_LeNet_5.IMAGE_SIZE,
mnist_inference_LeNet_5.IMAGE_SIZE,
mnist_inference_LeNet_5.NUM_CHANNELS))
_, loss_value, step = sess.run([train_op, loss, global_step],
feed_dict={x: xs, y_: ys})
# 每1000轮保存一次模型
if i % 1000 == 0:
# 输出当前的训练情况。这里只输出了模型在当前训练batch上的损失
# 函数大小。通过损失函数的大小可以大概了解训练的情况。在验证数
# 据集上正确率的信息会有一个单独的程序来生成
print("After %d training step(s), loss on training "
"batch is %g." % (step, loss_value))
# 保存当前的模型。注意这里给出了global_step参数,这样可以让每个
# 被保存的模型的文件名末尾加上训练的轮数,比如“model.ckpt-1000”,
# 表示训练1000轮之后得到的模型。
saver.save(
sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME),
global_step=global_step
)
# 主程序入口
def main(argv=None):
# 声明处理MNIST数据集的类,这个类在初始化时会自动下载数据。
mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
train(mnist)
# TensorFlow提供的一个主程序入口,tf.app.run会调用上面定义的main函数
if __name__ == "__main__":
tf.app.run()
mnist_inference_LeNet_5.py文件:
# -*- coding: utf-8 -*-
import tensorflow as tf
# 定义神经网络结构相关的参数
INPUT_NODE = 784
OUTPUT_NODE = 10
IMAGE_SIZE = 20
NUM_CHANNELS = 1
NUM_LABELS = 10
#第一层卷积层的尺寸和深度
CONV1_DEEP = 32
CONV1_SIZE = 5
#第二层卷积层的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5
#全连接层的节点个数
FC_SIZE = 512
#定义卷积神经网络的前向传播过程。这里添加了一个新的参数train,用于区分训练过程和测试
#过程,在这个过程中将用到dropout方法(只在训练时使用)
def inference(input_tensor,train,regularizer):
#通过不同的命名空间隔离不同层的变量,这可以让每一层中的变量名只需要
#考虑在当前层的作用。而不需要担心重名的问题。
with tf.variable_scope('layer1-conv1'):
conv1_weights = tf.get_variable(
"weight",[CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv1_biases = tf.get_variable(
"bias",[CONV1_DEEP],initializer=tf.constant_initializer(0.0))
#使用边长为5,深度为32的过滤器,过滤器移动的步长为1,且使用全零填充
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,这一层的输入时上一层的输出,也就是28x28x32
#的矩阵,输出为14x14x32的矩阵
with tf.name_scope('layer2-pool1'):
pool1 = tf.nn.max_pool(
relu1,ksize=[1,2,2,1],strides=[1,2,1,1],padding='SAME')
#声明第三层卷积层的变量并实现前向传播过程,这一层的输入为14x14x32的矩阵
#输出为14x14x64的矩阵
with tf.variable_scope('layer3-conv2'):
conv2_weights = tf.get_variable(
"weight",[CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP],
initializer=tf.truncated_normal_initializer(stddev=0.1))
conv2_biases = tf.get_variable(
"bias",[CONV2_DEEP],initializer=tf.constant_initializer(0.0))
#使用边长为5,深度为64的过滤器,过滤器移动的步长为1,且使用全零填充
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))
#实现第四层池化层的前向传播过程,这一层和第二层的结构是一样的,这一层的输入为
#14x14x64的矩阵,输出为7x7x64的矩阵。
with tf.name_scope('layer4-pool2'):
pool2 = tf.nn.max_pool(
relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
#将第四层池化层的输出转化为第五层全连接的输入格式。第四层的输入为7x7x64的矩阵,
#然而第五层全连接层需要的输入格式为向量,所以在这里需要将这个7x7x64的矩阵拉直成
#一个向量,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的向量。这里我们引入了dropout的概念。
#dropout会随机的将部分节点输出改为0,避免过拟合的问题,这种优化方法一般只在
#全连接层而不是卷积层使用
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的向量,并且输出通过sotfmax之后就得到了最后的分类结果
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
After 1 training step(s), loss on training batch is 3.2112.
After 1001 training step(s), loss on training batch is 0.231712.
After 2001 training step(s), loss on training batch is 0.182711.
·
·
·
After 27001 training step(s), loss on training batch is 0.0336458.
After 28001 training step(s), loss on training batch is 0.036755.
After 29001 training step(s), loss on training batch is 0.0390648.