LeNet-5模型是第一个成功应用于数字识别问题中的卷积神经网络,在MNIST 数据集上, LeNet-5 模型可以达到大约99.2% 的正确率。LeNet-5 模型总共有7 层
第一层:卷积层:
这一层的输入就是原始的图像像素, LeNet-5 模型接受的输入层大小为32 * 32 1 。第一个卷积层过滤器的尺寸为5 × 5 ,深度为6 ,不使用全0 填充,步长为1 。因为没有使用全0填充,所以这一层的输出的尺寸为32-5+1=28 , 深度为6 。这一个卷积层总共有5 × 5 × 1 × 6+6=156 个参数,其中6 个为偏置项参数。因为下一层的节点矩阵有2828*6=4704个节点,每个节点和5 × 5=25 个当前层节点相连,所以本层卷积层总共有4704 × (25 + 1) = 122304 个连接。
第二层:池化层:
这一层的输入为第一层的输出, 是一个28×28 × 6 的节点矩阵。本层采用的过滤器大小为2 × 2 ,长和宽的步长均为2 ,所以本层的输出矩阵大小为14 × 14 × 6 。
第三层:卷积层
本层的输入矩阵大小为14 × 14 × 6 ,使用的过滤器大小为55 ,深度为16 。本层不使用全0 填充, 步长为1。本层的输出矩阵大小为10 × 10 × 16 。按照标准的卷积层,本层应该有55616+16=2416 个参数,101016*(25+1) =41600 个连接。
第四层:池化层:
本层的输入矩阵大小为101016,采用的过滤器大小为22,步长为2,本层的输出矩阵大小为55*16
第五层:全连接层:
本层的输入矩阵大小为5516 ,在LeNet-5 模型的论文中将这一层称为卷积层,但是因为过滤器的大小就是5 × 5 ,所以和全连接层没有区别,在之后的TensorFlow 程序实现中,也会将这一层看成全连接层。如果将5516 矩阵中的节点拉成一个向量,那么这一层和全连接层输入就一样了。本层的输出节点个数为120 ,总共有5x5x16x120+120=48120 个参数。
第六层:全连接层:
本层的输入节点为120个,输出节点个数为84个,总共参数为120*84+84=10164个
第七层:全连接层:
本层的输入节点个数为84 个,输出节点个数为10 个,总共参数为84 × 10+10=850 个。上面介绍了LeNet-5 模型每一层结构和设置,下面给出一个TensorFlow 的程序来实现一个类似LeNet-5 模型的卷积神经网络来解决MNIST 数字识别问题。通过TensorFlow 训练卷积神经网络的过程和训练全连接神经网络是完全一样的。损失函数的计算、反向传播过程的实现都可以给出的mnist_train.py 程序。唯一的区别在卷积神经网络的输入层为一个三维矩阵,所以需要调整一下输入数据的格式:
#调整输入数据的placeholder的格式,输入为一个四维矩阵
x=tf.placeholder(tf.float32,[BATCH_SIZE,minist_inference.IMAGE_SIZE,
minist_inference.IMAGE_SIZE,mnist_inference.NUM_CHANNELS],name='x-input')
#第四维表示图片的深度,对于RGB格式的图片,深度为3
#类似输入一个训练数据合适调整为四维矩阵,并将这个调整后的数据传入see.run过程
reshaped_xs=np.reshape(VATCH_SIZE,minist_inference.IMAGE_SIZE,
minist_inference.IMAGE_SIZE,mnist_inference.NUM_CHANNELS)
下面给出修改程序
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 inferrnce(input_tensorflow,train,regularizer):
#声明第一层卷积层的变量并实现前向传播过程。通过使用不同的命名空间来隔离不同层的变量,
#这可以让每一层的变量命名只需要考虑当前层的的作用,而不需要考虑重名的问题,和标准
# LeNet-5 模型不大一样,这里定义的卷积层输入为28*28*1的原始MNIST图片像素,因为卷积
#层中使用全0填充,所以输出为28*28*32的矩阵。
with tf.variable_scope('later1-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
#使用全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
#输出为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_initeralizer(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,2,2,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'
)
#将第四层池化层输出转化为第五层全连接层的输入格式。第同层的输出为7x7x64 的矩阵,
#然而第五层全连接层需要的输入格式为向量,所以在这里面要将这个7x7x64 的矩阵拉直成一
#个向盐。pool2.get_shape 函数可以得到第四层输出矩阵的维度而不需要手工计算。注意
#因为每一层神经网络的输入输出都为一个batch 的矩阵,所以这里得到的维度也包含了一个
#batch数据的个数
pool_shape=pool2.get_shape().as_list()
#计算将矩阵拉直成向量之后的长度,这个长度就是矩阵长宽及深度的乘积。注意在这里
# pool_shape[O]为一个batch 中数据的个数。
nodes=pool_shape[1]*pool_shape[2]*pool_shape[3]
#通过tf.reshape函数将第四层的输出变成一个batch的向量
reshapeed=tf.reshape(pool2,[poop_shape[0],nodes])
#声明第五层全连接层的变量并实现前向传播过程。这一层的输入是拉直之后的一组向盘,
#向盘长度为3136,输出是一组长度为512 的向量。这一层和之前介绍的基本一致,唯一的
#区别就是引入了dropout 的概念。dropout 在训练时会随机将部分节点的输出改为0
#dropout 可以避免过拟合问题,从而使得模型在测试数据上的效果更好。
#dropout一般只在全连接层而不是卷积层或者池化层使用。
with tf.variable_scope('layer5-fc1'):
fc1_weight=tf.get_variable(
'weight',[nodes,FC_SIZE],initializer=tf.constant_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