LeNet由卷积神经网络的祖师爷LeCun提出,用来解决手写数字识别的视觉任务。从那时起,卷积神经网络的架构基本就定下来了:卷积层、池化层、全连接层。如今各大深度学习框架中所使用的LeNet都是简化改进过的LeNet-5(-5表示具有5个层),和原始的LeNet有些许不同,比如把激活函数改为了现在很常用的ReLu。
LeNet的结构:
LeNet-5包含7层(不包括输入层)每一层都包含可训练参数(权重)。当时的输入图片是32*32尺寸的图片
1、C1层(卷积层):6@28x28
C1层使用了6个卷积核,每个卷积核大小为5x5,这样就得到了6个feature map(特征图)
(1)特征图
每个卷积核(5x5)与原始图像(32x32)卷积得到的特征图的尺寸为(32-5+1)x(32-5+1)即28x28
(2)参数个数
由于参数(权值)共享的原因,对于同个卷积核每个神经元均使用相同的参数,因此,参数个数为(5×5+1)×6= 156,其中5×5为卷积核参数,1为偏置参数
(3)连接数
卷积后的大小为28x28,因此每个特征图有28x28个神经元,每个卷积核参数为5×5+1,因此连接数为(5×5+1)×6×28×28=122304
2、S2(池化层):6@14x14
池化层的池化单元大小为2x2
(1)特征图
由于池化的strides为2,每个池化单元之间没有重叠,故6个特征图经过池化以后尺寸变为(28-2)/2 + 1 = 14,即为14x14(也可以这样理解,每个池化单元为2x2,每个池化单元之间没有重叠,相当于每两行两列重新算出一个特征值来,即相当于图像减半)
(2)参数个数
S2层每个特征图都共享相同的w和b两个参数,即共有6x2=12个参数
(3)连接数
下采样之后的图像大小为14×14,因此S2层的每个特征图有14×14个神经元,每个池化单元连接数为2×2+1(1为偏置量),因此,该层的连接数为(2×2+1)×14×14×6 = 5880
3、C3(卷积层):16@10x10
C3层有16个卷积核,、每个卷积核的大小为5x5
(1)特征图
每个卷积核大小为5x5,输入大小为14x14,故特征图大小为(14-5+1)x(14-5+1)即10x10
(2)参数个数
注意:C3与S2并不是全连接而是部分连接,有些是C3连接到S2三层、有些四层、甚至达到6层,通过这种方式提取更多特征,连接的规则如下表所示:
例如第一列表示C3层的第0个特征图(feature map)只跟S2层的第0、1和2这三个feature map相连接,计算过程为:用3个卷积模板分别与S2层的3个feature maps进行卷积,然后将卷积的结果相加求和,再加上一个偏置,再取sigmoid得出卷积后对应的feature map了。其它列也是类似(有些是3个卷积模板,有些是4个,有些是6个)。因此,C3层的参数数目为(5×5×3+1)×6 +(5×5×4+1)×9 +5×5×6+1 = 1516
(3)连接数
卷积后的特征图大小为10x10,参数个数为1516,故连接数为1516x10x10 = 151600
4、S4(池化层):16@5x5
(1)特征图大小
与S2分析类似,由于池化单元为2x2,strides=2,故通过池化后图像尺寸减半,即5x5
(2)参数个数
每个特征图都共享w和b两个参数,即参数个数为16x2 = 32
(3)连接数
连接数为(2×2+1)×5×5×16 = 2000
5、C5(卷积层):120
(1)特征图大小
每个卷积核为5x5,卷积核数目为120,因此有120个特征图,每个特征图的大小为(5-5+1)x(5-5+1)即1x1。这样该层恰好变成了全连接,这只是巧合,如果输入比32x32大,则该层就不是全连接层了。
(2)参数个数
和之前计算类似,参数个数为120x(5x5x16+1)=48120
(3)连接数
连接数为48120×1×1=48120
6、F6(全连接层):84
(1)特征图大小
F6层有84个单元,之所以选这个数字的原因是来自于输出层的设计,对应于一个7×12的比特图,如下图所示,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。
该层有84个特征图,特征图大小与C5一样都是1×1,与C5层全连接。
(2)参数个数
由于是全连接,参数数量为(120+1)×84=10164。跟经典神经网络一样,F6层计算输入向量和权重向量之间的点积,再加上一个偏置,然后将其传递给sigmoid函数得出结果。
(3)连接数
由于是全连接,连接数与参数数量一样,也是10164。
7、OUTPUT(输出层):10
Output层也是全连接层,共有10个节点,分别代表数字0到9。
(1)特征图大小
该层采用径向基函数(RBF)的网络连接方式,假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:
上式中的Wij的值由i的比特图编码确定,i从0到9,j取值从0到7×12-1。RBF输出的值越接近于0,表示当前网络输入的识别结果与字符i越接近。
(2)参数个数
由于是全连接,参数个数为84×10=840
(3)连接数
由于是全连接,连接数与参数个数一样,也是840
这里使用TensorFlow构建LeNet网络实现手写数字识别,利用mnist数据集实现
# LeNet-5 简单实现手写数字识别
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)
# 权重参数初始化
def weight_variable(shape):
initial = tf.truncated_normal(shape,stddev=0.1)
return tf.Variable(initial)
# 偏置参数初始化
def bias_variable(shape):
initial = tf.constant(0.1,shape=shape)
return tf.Variable(initial)
# 定义卷积层
"""
conv2d(x,W,strides=[1,1,1,1],padding='SAME')参数含义与上述类似
x:input
W:filter,滤波器大小
strides:步长,1*1,表示filter窗口每次水平移动1格,每次垂直移动1格
padding:填充方式,补零('SAME')
"""
def conv2d(x,W):
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')
# 定义池化层
'''
max_pool(x,ksize,strides,padding)参数含义
x:input
ksize:filter,滤波器大小2*2
strides:步长,2*2,表示filter窗口每次水平移动2格,每次垂直移动2格
padding:填充方式,补零
'''
def max_pool_2x2(x):
return tf.nn.max_pool2d(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
def compute_accuracy(test_xs, test_ys):
# 使用全局变量prediction
global prediction
# 获得预测值y_pre
y_pre = sess.run(prediction, feed_dict = { x: test_xs, keep_prob: 1})
# 判断预测值y和真实值y_中最大数的索引是否一致,y_pre的值为1-10概率, 返回值为bool序列
correct_prediction = tf.equal(tf.argmax(y_pre, 1), tf.argmax(test_ys, 1))
# 定义准确率的计算
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) #tf.cast将bool转换为float32
# 计算准确率
result = sess.run(accuracy)
return result
# 定义输入输出数据
x = tf.placeholder(tf.float32,[None,784])# 图片为28*28=784
y = tf.placeholder(tf.float32,[None,10])# 输出标签为0-9
keep_prob = tf.placeholder(tf.float32) # dropout的比例
x_image = tf.reshape(x,[-1,28,28,1]) # 对输入数据重新排列,形成28*28*1
# 卷积层1
W_conv1 = weight_variable([5,5,1,32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
# 卷积层2
W_conv2 = weight_variable([5,5,32,64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1,W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
# 全连接层1
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flatten = tf.reshape(h_pool2,[-1,7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flatten,W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)
# 全连接层2
W_fc2 = weight_variable([1024,10])
b_fc2 = bias_variable([10])
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2) + b_fc2)
# 计算loss
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(prediction), reduction_indices=[1]))
# 神经网络训练
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy) #0.0001
# 定义Session
sess = tf.Session()
init = tf.global_variables_initializer()
# 执行初始化
sess.run(init)
# 进行训练迭代
for i in range(1000):
# 取出mnist数据集中的100个数据
batch_xs, batch_ys = mnist.train.next_batch(50) #100
# 执行训练过程并传入真实数据
sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 0.5})
if i % 100 == 0:
# 判断预测值y和真实值y_中最大数的索引是否一致,y_pre的值为1-10概率, 返回值为bool序列
correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(batch_ys, 1))
# 定义准确率的计算
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) #tf.cast将bool转换为float32
result = sess.run(accuracy,feed_dict={x:batch_xs,y:batch_ys,keep_prob:1})
print(result)