是Yann LeCun 教授于1998 年在论文Gradient-based learning applied to document recognition中提出的,它是第一个成功应用于数字识别问题的卷积神经网络。
在下面的篇幅中将详细介绍LeNet-5 模型每一层的结构。
这一层的输入就是原始的图像像素,LeNet-5 模型接受的输入层大小为32×32×1。第一个卷积层过滤器的尺寸为5×5,深度为6,不使用全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,深度为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 程序实现中也会将这一层看成全连接层。如果将5×5×16 矩阵中的节点拉成一个向量,那么这一层和全连接层输入就一样了。本层的输出节点个数为120 , 总共有5×5×16×120+120=48120 个参数。
本层的输入节点个数为120 个,输出节点个数为84 个,总共参数为120×84+84=10164个。
本层的输入节点个数为84 个,输出节点个数为10 个,总共参数为84×10+10=850 个。
# 调整输入数据placeholder 的格式,输入为一个四维矩阵。
x = tf.placeholder(tf.float32, [
BATCH_SIZE, # 第一维表示一个batch 中样例的个数。
mnist_inference.IMAGE_SIZE, # 第二维和第三维表示图片的尺寸。
mnist_inference.IMAGE_SIZE,
mnist_inference.NUM_CHANNELS], # 第四维表示图片的深度,对于RBG 格
# 式的图片,深度为3。
name='x-input')
# 类似地将输入的训练数据格式调整为一个四维矩阵,并将这个调整后的数据传入sess.run 过程。
reshaped_xs = np.reshape(xs, (BATCH_SIZE,
mnist_inference.IMAGE_SIZE,
mnist_inference.IMAGE_SIZE,
mnist_inference.NUM_CHANNELS))
# -*- coding: utf-8 -*-
import tensorflow as tf
# 配置神经网络的参数。
INPUT_NODE = 784 # 28x28
OUTPUT_NODE = 10 # 10分类
IMAGE_SIZE = 28 # 图片像素大小
NUM_CHANNELS = 1 # 灰度图,深度为1
NUM_LABELS = 10 # 标记数量,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):
# 声明第一层卷积层的变量并实现前向传播过程。
# 通过使用不同的命名空间来隔离不同层的变量,这可以让每一层中的变量命名只需要
# 考虑在当前层的作用,而不需要担心重名的问题。和标准LeNet-5 模型不大一样,这里
# 定义的卷积层输入为28×28×1 的原始MNIST 图片像素。因为卷积层中使用了全0 填充,
# 所以输出为28×28×32 的矩阵 (CONV1_DEEP = 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,
# 使用全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_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 的矩阵,
# 然而第五层全连接层需要的输入格式为向量,所以在这里需要将这个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 的向量。这一层
# 引入了dropout 的概念。dropout 在训练时会随机将部分节点的
# 输出改为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
# 输出结果
$ python test.py
Extracting /tmp/data/train-images-idx3-ubyte.gz
Extracting /tmp/data/train-labels-idx1-ubyte.gz
Extracting /tmp/data/t10k-images-idx3-ubyte.gz
Extracting /tmp/data/t10k-labels-idx1-ubyte.gz
After 1 training step(s), loss on training batch is 6.45373.
After 1001 training step(s), loss on training batch is 0.824825.
After 2001 training step(s), loss on training batch is 0.646993.
After 3001 training step(s), loss on training batch is 0.759975.
After 4001 training step(s), loss on training batch is 0.68468.
After 5001 training step(s), loss on training batch is 0.630368.
如何设计卷积神经网络的架构呢?以下正则表达式公式总结了一些经典的用于图片分类问题的卷积神经网络架构:
输入层→(卷积层+→池化层?)+→全连接层+
在以上公式中,“卷积层+”表示一层或者多层卷积层,大部分卷积神经网络中一般最多连续使用三层卷积层。
“池化层?”表示没有或者一层池化层。池化层虽然可以起到减少参数防止过拟合问题,但是在部分论文中也发现可以直接通过调整卷积层步长来完成。所以有些卷积神经网络中没有池化层。在多轮卷积层和池化层之后,卷积神经网络在输出之前一般会经过1~2 个全连接层。
比如LeNet-5 模型就可以表示为以下结构。
输入层→卷积层→池化层→卷积层→池化层→全连接层→全连接层→输出层
声明
本博客是个人学习时的一些笔记摘录和感想,不保证是为原创,内容汇集了网上相关资料和书记内容,在这之中也必有疏漏未加标注者,如有侵权请与博主联系。