1. LeNet-5 基础介绍
通过详解卷积神经网络CNN一文,我们对卷积神经网络的有了很多认识,接下来我们将通过几个经典的卷积神经网络,加深对卷积神经网络的理解和认识。
1998 1998 1998年, Y a n n L e C u n Yann \ LeCun Yann LeCun等人在论文 < < G r a d i e n t B a s e d L e a r n i n g A p p l i e d t o D o c u m e n t R e c o g n i t i o n > > <<GradientBased \ Learning \ Applied \ to \ Document \ Recognition>> <<GradientBased Learning Applied to Document Recognition>>中提出并详细介绍了LeNet-5神经网络结构,并用于手写数字识别问题,能达到很高的识别率。LeNET-5包含卷积层,池化层,全连接层,成为了卷积神经网络的经典结构,被誉为是卷积神经网络的"Hello Word"。
该网络现在来看十分简单,十分适合入门,其结构如下
该网络共包含 7 7 7层(不含输入层),我们根据以下公式计算卷积或者池化之后的特征图大小
O w = { f l o o r ( W − 1 S w ) + 1 , p a d d i n g = " S A M E " f l o o r ( W − F w S w ) + 1 , p a d d i n g = " V A L I D " O h = { f l o o r ( H − 1 S h ) + 1 , p a d d i n g = " S A M E " f l o o r ( H − F h S h ) + 1 , p a d d i n g = " V A L I D " \begin{aligned}O_w &= \begin{cases} floor(\frac{W-1}{S_w})+1 ,padding="SAME"\\ \\ floor\bigg(\frac{W-F_w}{S_w} \bigg)+1 ,padding="VALID"\end{cases}\\\\ O_h &= \begin{cases}floor(\frac{H-1}{S_h})+1,padding="SAME"\\\\ floor\bigg(\frac{H-F_h}{S_h} \bigg)+1,padding="VALID" \end{cases}\end{aligned} OwOh=⎩⎪⎪⎨⎪⎪⎧floor(SwW−1)+1,padding="SAME"floor(SwW−Fw)+1,padding="VALID"=⎩⎪⎪⎨⎪⎪⎧floor(ShH−1)+1,padding="SAME"floor(ShH−Fh)+1,padding="VALID"
-
C 1 C1 C1 层
该层是一个卷积层,使用 6 6 6个 5 ∗ 5 5*5 5∗5的卷积核,步长为 1 1 1。
每个卷积核有 5 ∗ 5 + 1 ( B i a s ) 5*5+1(Bias) 5∗5+1(Bias)个参数,共有 ( 5 ∗ 5 + 1 ) ∗ 6 = 156 (5*5+1)*6=156 (5∗5+1)∗6=156个参数。
-
S 2 S2 S2层
该层是一个Pooling层,Pooling大小为 2 ∗ 2 2*2 2∗2,步长为 2 2 2.
该层没有要学习的参数
-
C 3 C3 C3层
该层是一个卷积层,使用 16 16 16个 5 ∗ 5 ∗ 6 5*5*6 5∗5∗6的卷积核,步长为 1 1 1。
共有 ( 5 ∗ 5 ∗ 6 + 1 ) ∗ 16 = 2416 (5*5*6+1)*16=2416 (5∗5∗6+1)∗16=2416个参数。
-
S 4 S4 S4层
该层是一个Pooling层,Pooling大小为 2 ∗ 2 2*2 2∗2,步长为 2 2 2.
该层没有要学习的参数
-
C 5 C5 C5层
论文描述的是一个卷积层,使用 120 120 120个 5 ∗ 5 ∗ 16 5*5*16 5∗5∗16的卷积核,步长为 1 1 1.
共有 ( 5 ∗ 5 ∗ 16 + 1 ) ∗ 120 = 48120 (5*5*16+1)*120=48120 (5∗5∗16+1)∗120=48120个参数
其实这层也可以看做是一个全连接层的一个隐藏层,神经元个数为 120 120 120个,输入的数据来自 S 4 S4 S4层特征数据拉直。
-
F 6 F6 F6层
该层是一个全连接层。
共有 120 ∗ 84 + 84 = 10164 120*84+84=10164 120∗84+84=10164个参数。
-
O U T P U T OUTPUT OUTPUT层
该层是一个全连接层。
共有 10 ∗ 84 + 10 = 850 10*84+10=850 10∗84+10=850个参数。
我们用以下表格更加直观的看下每一层都做了什么
L a y e r L a y e r T y p e I n p u t S i z e K e r n e l S i z e o r P o l l i n g S i z e F i l t e r s C o u n t S t r i d e s O u t p u t S i z e P a r a m s C o u n t C 1 卷 积 层 32 ∗ 32 ∗ 1 5 ∗ 5 6 1 28 ∗ 28 ∗ 6 156 S 2 池 化 层 28 ∗ 28 ∗ 6 2 ∗ 2 − 2 14 ∗ 14 ∗ 6 0 C 3 卷 积 层 14 ∗ 14 ∗ 6 5 ∗ 5 ∗ 6 16 1 10 ∗ 10 ∗ 16 2416 S 4 池 化 层 10 ∗ 10 ∗ 16 2 ∗ 2 − 2 5 ∗ 5 ∗ 16 0 C 5 卷 积 层 5 ∗ 5 ∗ 16 5 ∗ 5 ∗ 16 120 1 1 ∗ 1 ∗ 120 48120 F 6 全 连 接 层 120 − − − 84 10164 O U T P U T 输 出 层 84 − − − 10 850 \begin{array}{|c|c|c|c|c|c|c|c|} Layer &Layer \ Type &Input\ Size & Kernel\ Size \ or \ Polling\ Size & Filters\ Count & Strides & Output\ Size &Params \ Count \\ \hline C1 & 卷积层 & 32*32*1 & 5*5 & 6 & 1 & 28*28*6 & 156 \\ \hline \\S2 & 池化层 & 28*28*6 & 2*2 & - & 2 & 14*14*6 & 0 \\ \hline \\ C3 & 卷积层 & 14*14*6 & 5*5*6 & 16&1&10*10*16&2416 \\ \hline \\S4&池化层&10*10*16& 2*2 &-&2&5*5*16&0\\ \hline \\ C5&卷积层&5*5*16&5*5*16 &120&1&1*1*120&48120 \\ \hline \\ F6&全连接层& 120&-&-&-&84&10164 \\ \hline \\ OUTPUT &输出层&84&-&-&-&10&850\end{array} LayerC1S2C3S4C5F6OUTPUTLayer Type卷积层池化层卷积层池化层卷积层全连接层输出层Input Size32∗32∗128∗28∗614∗14∗610∗10∗165∗5∗1612084Kernel Size or Polling Size5∗52∗25∗5∗62∗25∗5∗16−−Filters Count6−16−120−−Strides12121−−Output Size28∗28∗614∗14∗610∗10∗165∗5∗161∗1∗1208410Params Count1560241604812010164850
这里重点介绍其网络结构,部分细节可能和论文不一致,需要原论文的可联系我。
2. 利用TesorFlow + LeNet-5识别mnist手写数字
#!/usr/bin/python3
# @Time : 2021/2/23 9:56
# @Author :
# @File : Lenet5
# @Software: PyCharm
# @Description : Python==3.7.3、tensorflow==2.4.1
import tensorflow as tf
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D,BatchNormalization,Activation,MaxPool2D,Dropout,Flatten,Dense,AveragePooling2D
from tensorflow.keras import Model
import os
# 设置超过多长省略显示,这里设置np.inf表示无限长
np.set_printoptions(threshold=np.inf)
# 准备数据集合
# cifar10 = tf.keras.datasets.cifar10
cifar10 = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train,x_test = x_train/255.0,x_test/255.0
# 给数据增加一个维度,使数据和网络结构匹配
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)
# 自定义网络模型
class Letnet5Model(Model,):
# 定义网络结构
def __init__(self):
super(Letnet5Model,self).__init__()
# 定义6个 5*5的卷积核
self.c1 = Conv2D(filters=6,kernel_size=(5,5),activation='sigmoid',padding='same')
# 平均池化,池化大小 2*2,步长2
self.s2 = AveragePooling2D(pool_size=(2,2),strides=2)
# 定义16个5*5的卷积核
self.c3 = Conv2D(filters=16,kernel_size=(5,5),activation='sigmoid')
self.s4 = AveragePooling2D(pool_size=(2,2),strides=2)
# 将卷积得到的特征数据拉直
self.f = Flatten()
# 定个一个120个神经元的全连接隐藏层
self.c5 = Dense(units=120, activation='sigmoid')
# 定个一个80个神经元的全连接隐藏层
self.f6 = Dense(units=84,activation='sigmoid')
# 定个一个10个神经元的全连接隐藏层
self.o7 = Dense(units=10,activation='softmax')
# 调用网络结构,实现前向传播
def call(self, inputs, training=None, mask=None):
inputs = self.c1(inputs)
inputs = self.s2(inputs)
inputs = self.c3(inputs)
inputs = self.s4(inputs)
inputs = self.f(inputs)
inputs = self.c5(inputs)
inputs = self.f6(inputs)
y = self.o7(inputs)
return y
def acc_loss_analyse(robot,):
# 绘制训练集和验证集的acc和loss曲线
acc = robot.history['sparse_categorical_accuracy']
val_acc = robot.history['val_sparse_categorical_accuracy']
loss = robot.history['loss']
val_loss = robot.history['val_loss']
plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title("Training and Validation Accuracy")
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()
def main():
model = Letnet5Model()
# 配置训练项,设定optimizer(优化器),loss(损失函数),metrics(网络评判标准)
model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy'])
breakpoint_sava_path = './checkpoint/LeNet5.ckpt'
if os.path.exists(breakpoint_sava_path+'.index'):
print('-------------load the model-----------------')
# 读取模型
model.load_weights(breakpoint_sava_path)
# 保存模型
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=breakpoint_sava_path, save_weights_only=True,save_best_only=True)
# 开始训练
robot = model.fit(x_train,y_train,batch_size=32,epochs=5,validation_data=(x_test,y_test),validation_freq=1,callbacks=[cp_callback])
# 查看网络结构基本信息
model.summary()
acc_loss_analyse(robot, )
if __name__ == '__main__':
main()
运行代码,可以看到 T e n s o r F l o w TensorFlow TensorFlow打印的每层参数个数和之前的分析保持一致
训练了两百轮,最好验证集准确率为
99.07
%
99.07\%
99.07%