CNN笔记 - LeNet5结构详解

学习卷积神经网络(CNN)是学习TensorFlow绕不开的一道坎
CNN的学习计划是:【算法结构详解 + Tensorflow代码实现】

  1. LeNet5
  2. AlexNet
  3. VGG
  4. GoogleNet
  5. ResNet

====== 概念铺垫 ======
CNN有几个重要的点:局部感知、参数共享、池化。

  • 局部感知
    每个神经元其实没有必要对全局图像进行感知,只需要对局部进行感知,然后在更高层将局部的信息综合起来就得到了全局的信息。

  • 参数共享
    参数共享是减少参数的有效办法。
    具体做法是,在局部连接中隐藏层的每一个神经元连接的是一个 10×10 的局部图像,因此有10×10 个权值参数,将这 10×10 个权值参数共享给剩下的神经元。

  • 单核单通道卷积
    单个卷积核示意: 步长 Stride = 1
    5×5 Image ——> 3×3 kernel ——> (5-3+1)×(5-3+1) convolved feature map

  • 多核单通道卷积
    一个卷积核提取得到的特征是不充分的。假设有k个卷积核,那么可训练的参数的个数就变为了k×10×10。注意没有包含偏置参数。每个卷积核得到一个Feature Map。卷积的过程为特征提取的过程,多核卷积中,隐层的节点数量为: k×(1000-100+1)×(1000-100+1) ,对于下图的手写数字灰度图,做单通道卷及操作:

  • 多核多通道卷积
    当图像为RGB或ARGB(A代表透明度)时,可以在多通道进行卷积操作,或者对于堆叠卷积层来说, pooling 层之后可以继续接下一个 卷积层,对 pooling 层多个 Feature Map 的操作即为多通道卷积,下图为 两个卷积核在ARGB四通道上进行卷积操作,在生成 对应的 Feature Map 时, 这个卷积核对应4个卷积模板(这一个卷积核对应的四个模板都不一样),分别用4种不同的颜色表示,Feature Map 对应的位置的值是由四核卷积模板分别作用在4个通道的对应位置处的卷积结果相加然后取激活函数得到的,所以在四通道得到2通道的过程中,参数数目为 4×2×2×2个,其中4表示4个通道,第一个2表示生成2个卷积核,最后的2×2表示卷积核大小。见下图:

  • 池化 pooling
    池化过程示意:


正文:

LeNet5主要用于手写数字的自动识别
output输出类别共10个,为数字0,1,2,…,9

LeNet的8层结构:
输入层-C1卷积层-S2池化层-C3卷积层-S4池化层-C5卷积层-F6全连接层-输出层

LeNet的8层全过程图:

下图为对应每层的参数个数parameters和连接点个数connections计算流程图,每个feature map下有该层的计算公式:

== 输入层 ==

  • 1张32×32的图片

== C1卷积层 ==

  • 单通道,6个卷积核,得到6个feature maps
  • kernal size: 5×5
  • feature map size: (32-5+1)×(32-5+1)= 28×28
  • parameters: 6×(5×5+1)      5×5为卷积模板参数,1为偏置参数
  • connections: 6×(5×5+1) ×28×28

== S2池化层 ==

  • 6个池化核,得到6个feature maps
  • kernal size:2×2
  • feature map size: (28/2)×(28/2)= 14×14
  • parameters: 6×(1+1)
  • parameters计算过程:2×2 单元里的值相加然后再乘以训练参数w,再加上一个偏置参数b(每一个feature map共享相同w和b)(这个地方和之前自己想的不太一样)
  • connections: 6×(2×2+1) ×14×14

下图为卷积操作与池化的示意图:

== C3卷积层 ==

  • 多通道:14个通道,16个卷积核,得到16个feature maps
  • kernal size:5×5
  • feature map size: (14-5+1)×(14-5+1)= 10×10
  • parameters: 6×(3×5×5+1) + 6×(4×5×5+1) + 3×(4×5×5+1) + 1×(6×5×5+1)
  • 注意此处C3并不是与S2全连接而是部分连接,见下图
  • connections: 6×(3×5×5+1) + 6×(4×5×5+1) + 3×(4×5×5+1) + 1×(6×5×5+1) ×10×10

为什么采用部分连接? Dropout的由来
首先部分连接,可计算的参数就会比较少
其次更重要的是它能打破对称性,这样就能得到输入的不同特征集合
以第0个feature map描述计算过程:
用1个卷积核(对应3个卷积模板,但仍称为一个卷积核,可以认为是三维卷积核)分别与S2层的3个feature maps进行卷积,然后将卷积的结果相加,再加上一个偏置,再搞个激活函数就可以得出对应的feature map了

== S4池化层 ==

  • 16个池化核,得到16个feature maps
  • kernal size:2×2
  • feature map size: (10/2)×(10/2)= 5×5
  • parameters: 16×(1+1)
  • connections: 16×(2×2+1) ×5×5

== C5卷积层 ==

  • 120个卷积核,得到120个feature maps
  • kernal size:5×5
  • 每个feature map的大小都与上一层S4的所有feature maps进行连接,这样一个卷积核就有16个卷积模板
  • feature map size: (5-5+1)×(5-5+1)= 1×1     这样刚好变成了全连接,但是我们不把它写成F5,因为这只是巧合
  • parameters: 120×(16×5×5+1)
  • connections: 120×(16×5×5+1) ×1×1

== F6全连接层 ==

  • Full Connection
  • parameters: 84×(120×1×1+1)
  • connections: 84×(120×1×1+1) ×1×1

== 输出层 ==

  • 得到分类结果,例:结果为数字 5

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import os
print(os.getcwd()) #显示当前路径
os.chdir("D:/test/") #在引号内填入你想放代码和数据的路径

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST_data/",one_hot = True) #读取数据
sess = tf.InteractiveSession()

def weight_variable(shape): # 权值初始化设置
initial = tf.truncated_normal(shape,stddev=0.1)
return tf.Variable(initial)

def bias_variable(shape): # 偏置bias初始化设置
initial = tf.constant(0.1,shape = shape)
return tf.Variable(initial)

def conv2d(x,W):
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')
# 卷积strides=[首位默认为1,平行步长=1,竖直步长=1,尾位默认为1]

def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
# ksize=[1,2,2,1] 池化核size: 2×2
# 池化strides=[首位默认为1,平行步长=2,竖直步长=2,尾位默认为1]

# placeholder:等待输入数据,x为占位符,接受型号为float32的数据,输入格式为矩阵[None,784]
x = tf.placeholder(tf.float32,[None,784]) #784 = 28×28 只对输入矩阵的列数有要求

y_ = tf.placeholder(tf.float32,[None,10])

x_image = tf.reshape(x,[-1,28,28,1]) # [batch=-1, height=28, width=28, in_channels=1]


# Conv1 Layer 卷积层
W_conv1 = weight_variable([5,5,1,32]) # [5×5卷积核, in_channels=1, out_channels=32=卷积核个数]
b_conv1 = bias_variable([32]) # [32=卷积核个数=bias个数]
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1) # 激活函数:relu
h_pool1 = max_pool_2x2(h_conv1) # 池化方式:max pool 取最大值

# Conv2 Layer
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) # 激活函数:relu
h_pool2 = max_pool_2x2(h_conv2)

W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2,[-1,7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1) + b_fc1)

keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)

W_fc2 = weight_variable([1024,10])
b_fc2 = bias_variable([10])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2) + b_fc2)

# 损失函数loss function: 交叉熵cross_entropy
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv),reduction_indices=[1]))

train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) # 优化方法:AdamOptimizer| 学习速率:(1e-4)| 交叉熵:最小化

correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1)) # tf.equal返回布尔值 | tf.argmax(y_,1):数字1代表最大值
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

tf.global_variables_initializer().run()
for i in range(20000):
batch = mnist.train.next_batch(50) # 喂入训练集的数据
if i % 1000 == 0: # 批量梯度下降,把1000改成1:随机梯度下降
train_accuracy = accuracy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}) # train accuracy: accuracy.eval
print("step %d, training accuracy %g"%(i,train_accuracy))
train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}) # test accuracy: accuracy.eval

print("test accuracy %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))


参考:

别人的笔记看的再多也不如亲自看一遍原著论文呀~❤

would you buy me a coffee☕~
0%