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个连接
总结:
输入图片: 32 ∗ 32 ∗ 1 32*32*1 32∗32∗1
卷积核大小: 5 ∗ 5 5*5 5∗5
卷积核种类:6
输出featuremap大小: 28 ∗ 28 ( 32 − 5 + 1 ) = 28 28*28 (32-5+1)=28 28∗28(32−5+1)=28
神经元数量: 28 ∗ 28 ∗ 6 28*28*6 28∗28∗6
可训练参数: ( 5 ∗ 5 + 1 ) ∗ 6 ( 每 个 滤 波 器 5 ∗ 5 = 25 个 u n i t 参 数 和 一 个 b i a s 参 数 , 一 共 6 个 滤 波 器 ) (5*5+1) * 6(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器) (5∗5+1)∗6(每个滤波器5∗5=25个unit参数和一个bias参数,一共6个滤波器)
连接数: ( 5 ∗ 5 + 1 ) ∗ 6 ∗ 28 ∗ 28 = 122304 (5*5+1)*6*28*28=122304 (5∗5+1)∗6∗28∗28=122304
2.2 第二层:池化层
这一层的输入为第一层的输出,是一个28x28x6的节点矩阵。本层采用的过滤器大小为2x2,步长为2,所以输出矩阵为14x14x6。
输入: 28 ∗ 28 ∗ 6 28*28*6 28∗28∗6
采样区域: 2 ∗ 2 2*2 2∗2
采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid
采样种类:6
输出featureMap大小: 14 ∗ 14 ( 28 / 2 ) 14*14(28/2) 14∗14(28/2)
神经元数量: 14 ∗ 14 ∗ 6 14*14*6 14∗14∗6
连接数: ( 2 ∗ 2 + 1 ) ∗ 6 ∗ 14 ∗ 14 (2*2+1)*6*14*14 (2∗2+1)∗6∗14∗14
S2中每个特征图的大小是C1中特征图大小的1/4。
详细说明:第一次卷积之后紧接着就是池化运算,使用$ 2*2$核 进行池化,于是得到了S2,6个 14 ∗ 14 14*14 14∗14的 特征图(28/2=14)。S2这个pooling层是对C1中的 2 ∗ 2 2*2 2∗2区域内的像素求和乘以一个权值系数再加上一个偏置,然后将这个结果再做一次映射。同时有5x14x14x6=5880个连接。
2.3 第三层:卷积层
本层的输入矩阵大小为14x14x6,使用的过滤器大小为5x5,深度为16。本层不使用padding,步长为1,所以输出节点为10x10x16。所以有5x5x6x16+16 = 2416个参数。10x10x16x(25+1)=41600个连接。
输入:14x14x6
卷积核大小: 5 ∗ 5 5*5 5∗5
卷积核种类:16
输出featureMap大小: 10 ∗ 10 ( 14 − 5 + 1 ) 10*10 (14-5+1) 10∗10(14−5+1)
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 = 28
NUM_CHANNELS = 1
NUM_LABELS = 10
# 第一个卷积层的尺寸和深度
CONV1_DEEP = 32
CONV1_SIZE = 5
# 第二个卷积层的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5
# 全连接层的节点个数
FC_SIZE = 512
# 定义卷积神经网络的前向传播过程。这里添加了一个新的参数train,用于区别训练过程和测试过程。在这个程序中将用到dropout方法
# dropout可以进一步提升模型可靠性并防止过拟合(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))
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))
with tf.name_scope('layer2-pool1'):
pool1 = tf.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
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))
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))
with tf.name_scope('layer4-pool2'):
pool2 = tf.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
pool2_shape = pool2.get_shape().as_list()
nodes = pool2_shape[1] * pool2_shape[2] * pool2_shape[3]
reshaped = tf.reshape(pool2, [pool2_shape[0], nodes])
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.0))
fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_weights) + fc1_biases)
if train:
fc1 = tf.nn.dropout(fc1, 0.5)
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.0))
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.