LeNet-5
LeNet-5模型是Yann LeCun教授1998年在论文Gradient-Based Learning Applied to Document Recognition中提出的,它是第一个成功应用于数字识别问题的卷积神经网络。在MNIST数据集上,LeNet-5模型可以达到大约99.2%的正确率。LeNet-5模型总共有7层,下图展示了LeNet-5模型的架构。
第一层,卷积层
这一层的输入就是原始的图像像素,LeNet-5模型接受的输入层大小为32321。第一个卷积层过滤器的尺寸为55,深度为6,不使用全0填充,步长为1。因为没有使用全0填充,所以这一层的输出的尺寸为32-5+1=28,深度为6。这个卷积层总共有5516+6=156个参数,其中6个为偏置参数。因为下一层的节点矩阵有28286=4704个节点,每个节点和55个当前层节点相连,所以卷积层总共有4704(25+1)= 122304个连接。
第二层,池化层
这一层的输入为第一层的输出,是一个28286的节点矩阵。本层采用的过滤器大小为22,长和宽的步长均为2,所以本层的输出矩阵大小为1414*6。
第三层,卷积层
本层的输入矩阵大小为14146,使用的过滤器大小为55,深度为16。本层不使用全0填充,步长为1。本层的输出矩阵大小为101016.按照标准的卷积层本层应该有55616+16=2416个参数,101016*(25+1)=41600个连接。
第四层,池化层
本层的输入矩阵大小为101016,采用的过滤器大小为22,步长为2。本层的输出矩阵大小为55*16。
第五层,全连接层
本层的输入矩阵大小为5516,在LeNet-5模型论文中将这一层称为卷积层,但是因为过滤器的大小就是55,所以和全连接层没有区别。本层的输出节点个数为120,总共有5516120+120=48120个参数。
第六层,全连接层
本层的输入节点个数为120个,输出节点个数为84个,总共参数为120*84+84=10164个。
第七层,全连接层
本层的输入节点个数为84个,输出节点个数为10个,总共参数为84*10+10=850个。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : LaoChen_ZeroonE
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
#输出维度
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
BATCH_SIZE = 100 #一个训练batch中训练数据个数
LEARNING_RATE_BASE = 0.01 #基础学习率
LEARNING_RATE_DECAY = 0.99 #学习衰减率
REGULARIZATION_RATE = 0.0001 #描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 6000 #训练轮数
MOVING_AVERAGE_DECAY = 0.99 #滑动平均衰减率
def inference(input_tensor, train, regularizer):
#和标准LeNet-5不一样,这里使用了全0填充,所以输出为28*28*32的矩阵
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,且使用全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
#输入为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的矩阵,输出为14*14*64的矩阵
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,使用全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然而第五层全连接需要的格式为向量,所以将矩阵转化为向格式,
#pool2.get_shape()函数得到矩阵的维度,计算将矩阵拉直成向量之后的长度,pool_shape[0]为一个batch中数据个数
pool_shape = pool2.get_shape().as_list()
nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
#通过tf.reshape函数将矩阵变成一个batch的向量
reshaped = tf.reshape(pool2, [pool_shape[0], nodes])
#第五层全连接变量和向前传播过程,输入是拉直后的向量长度为3136,输出是一组长度为512的向量
#dropout在训练时会随机将部分节点的输出改为0,可以避免过拟合
#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的向量
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
def train(mnist):
# 定义输出为4维矩阵
x = tf.placeholder(tf.float32, [
BATCH_SIZE, #第一维表示一个batch中样例个数
IMAGE_SIZE, #第二、三维表示图片的尺寸
IMAGE_SIZE,
NUM_CHANNELS], #第四维表示图片的深度,对于RGB格式深度为3
name='x-input')
y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE) #L2正则化
#定义卷积神经网络向前传播的过程,参数train,用与区分训练和测试过程,在这个过程中用到dropout方法,dropout可以
#提升模型可靠性并防止过拟合,dropout只在训练时用
y = inference(x, False, regularizer)
# 定义训练轮数及相关的滑动平均类 ,
# 定义储存训练轮输的变量,这个变量不需要计算滑动平均值,所以这里指定这个变量为不可训练变量trainable=False
global_step = tf.Variable(0, trainable=False)
# 给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类
# 定训练轮数变量可以加快训练早期变量的更新速度 .
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
# 在所有代表神经网络参数的变量上使用滑动平均,tf.trainable_variables()返回图上集合GraphKeys.TRAINABLE_VARIABLES中的元素,
# 就是没有指定 trainable=False 的参数。
variables_averages_op = variable_averages.apply(tf.trainable_variables())
# 计算交叉熵及其平均值
# sparse_softmax_cross_entropy_with_logits第一个参数是神经网络不包括softmax层的向前传播结果,第二个参数是正确答案。
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,# 学习率衰减速率
staircase=True)
# 优化损失函数
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
# 反向传播更新参数和更新每一个参数的滑动平均值,还有tf.group
with tf.control_dependencies([train_step, variables_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)
reshaped_xs = np.reshape(xs, (
BATCH_SIZE,
IMAGE_SIZE,
IMAGE_SIZE,
NUM_CHANNELS))
_, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: reshaped_xs, y_: ys})
if i % 1000 == 0:
print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
def main(argv=None):
mnist = input_data.read_data_sets("D:/datasets/MNIST_data", one_hot=True)
#数据集会自动被分成3个子集,train、validation和test。以下代码会显示数据集的大小。
print("Training data size: ", mnist.train.num_examples) #Training data size: 55000
print("Validating data size: ", mnist.validation.num_examples) #Validating data size: 5000
print("Testing data size: ", mnist.test.num_examples) #Testing data size: 10000
train(mnist)
if __name__=='__main__':
main()