本文是使用python语言基于TensorFlow实现手写数字识别,代码已注释
1. LeNet-5介绍
LeNet-5出自论文Gradient-Based Learning Applied to Document Recognition,是一种用于手写体字符识别的非常高效的卷积神经网络。
在讲解LeNet-5之前,让我们先看下CNN。卷积神经网络能够很好的利用图像的结构信息。LeNet-5是一个较简单的卷积神经网络。下图显示了其结构:输入的二维图像,先经过两次卷积层到池化层,再经过全连接层,最后使用softmax分类作为输出层。下面我们主要介绍卷积层和池化层。
1.卷积层 卷积层是卷积神经网络的核心基石。在图像识别里我们提到的卷积是二维卷积,即离散二维滤波器(也称作卷积核)与二维图像做卷积操作,简单的讲是二维滤波器滑动到二维图像上所有位置,并在每个位置上与该像素点及其领域像素点做内积。卷积操作被广泛应用与图像处理领域,不同卷积核可以提取不同的特征,例如边沿、线性、角等特征。在深层卷积神经网络中,通过卷积操作可以提取出图像低级到复杂的特征。
2.池化层 池化是非线性下采样的一种形式,主要作用是通过减少网络的参数来减小计算量,并且能够在一定程度上控制过拟合。通常在卷积层的后面会加上一个池化层。池化包括最大池化、平均池化等。其中最大池化是用不重叠的矩形框将输入层分成不同的区域,对于每个矩形框的数取最大值作为输出层。
LeNet5 这个网络虽然很小,但是它包含了深度学习的基本模块:卷积层,池化层,全链接层。是其他深度学习模型的基础 LeNet-5共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个FeatureMap通过一种卷积滤波器提取输入的一种特征,然后每个FeatureMap有多个神经元。
输入层
输入层是统一的32×32大小的,如果不是32×32的需要转换成该大小,在本文中,原大小是28×28的,通过下面两行代码将其转换成32×32
x_train
= np
.pad
(x_train
, ((0,0), (2,2), (2,2)), 'constant', constant_values
=0)
x_test
= np
.pad
(x_test
, ((0,0), (2,2), (2,2)), 'constant', constant_values
=0)
注意:其中输入层通常是不作为网络的层次结构
C1 第一卷积层:
输入图片大小:32×32卷积核大小:5×5填充方式:valid激活函数:relu卷积核个数:6输出大小:28×28 (这里28=(32-5+1))神经元数量:28×28×6可训练参数:6×(5×5+1)=156(每个滤波器5×5个参数再加上1个偏置参数,一共6个滤波器)连接数:6×(5×5+1)×28×28=122304
tf
.keras
.layers
.Conv2D
(filters
=6, kernel_size
=(5,5), padding
='valid', activation
=tf
.nn
.relu
, input_shape
=(32,32,1)),
S2 第一池化层:
输入:28×28采样区域:2×2采样个数:6填充方式:same输出大小:14×14 (这里14=28/2)神经元数量:6×14×14可训练参数:1×6+6连接数:6×(2×2+1)×14×14采用的是平均池化方式
tf
.keras
.layers
.AveragePooling2D
(pool_size
=(2,2), strides
=(2,2), padding
='same'),
C3 第二卷积层:
卷积核大小:5×5卷积核个数:16填充方式:valid激活函数:relu输出大小:10×10(这里10=(14-5+1))可训练参数:6×(3×5×5+1)+9×(4×5×5+1)+1×(6×5×5+1)=1516 前6个特征映射与S2层连续3个特征映射相连,后面接着的6个映射与S2层的连续的4个特征映射相连,然后的3个特征映射与S2层不连续的4个特征映射相连,最后一个映射与S2层的所有特征映射相连。连接数:10×10×1516
tf
.keras
.layers
.Conv2D
(filters
=16, kernel_size
=(5,5), padding
='valid', activation
=tf
.nn
.relu
),
S4 第二池化层
与S2同理
输入:10×10采样区域:2×2填充方式:same采样个数:16输出大小:5×5(这里5=10/2)神经元数量:5×5×16训练参数:16+16=32连接数:16×(2×2+1)×5×5=2000
tf
.keras
.layers
.AveragePooling2D
(pool_size
=(2,2), strides
=(2,2), padding
='same'),
C5 卷积层:也可称为全连接层
在本文中直接使用全连接层,作用是一样的
输入:与S4全连接C5层是一个卷积层。由于S4层的16个图的大小为5x5,与卷积核的大小相同,所以卷积后形成的图的大小为1x1。这里形成120个卷积结果。每个都与上一层的16个图相连。所以共有(5x5x16+1)x120 = 48120个参数,同样有48120个连接。
tf
.keras
.layers
.Dense
(units
=120, activation
=tf
.nn
.relu
),
F6 全连接层
输入:S5的120维向量可训练参数:84*(120+1)=10164
tf
.keras
.layers
.Dense
(units
=84, activation
=tf
.nn
.relu
),
输出层 全连接层
输出层也是全连接层,共有10个节点,分别代表数字0到9,且如果节点i的值为0,则网络识别的结果是数字i。
tf
.keras
.layers
.Dense
(units
=10, activation
=tf
.nn
.softmax
)
2.代码实现
import datetime
import matplotlib
.pyplot
as plt
import numpy
as np
import tensorflow
as tf
from sklearn
.metrics
import precision_score
def iamge_transform(x_train
,x_test
):
x_train
= np
.pad
(x_train
, ((0,0), (2,2), (2,2)), 'constant', constant_values
=0)
x_test
= np
.pad
(x_test
, ((0,0), (2,2), (2,2)), 'constant', constant_values
=0)
x_train
= x_train
.astype
('float32')
x_test
= x_test
.astype
('float32')
x_train
/= 255
x_test
/= 255
x_train
= x_train
.reshape
(x_train
.shape
[0], 32, 32, 1)
x_test
= x_test
.reshape
(x_test
.shape
[0], 32, 32, 1)
return x_train
, x_test
def train_model(x_train
, y_train
,batch_size
,num_epochs
):
'''
构建模型,训练模型
返回训练好的模型
参数
——————————————
x_trian:训练集
y_trian:训练集标签
batch_size:批大小
num_epochs:训练次数
filters:卷积核个数
kernel_size:卷积核大小
padding:填充方式
activation:激活函数
input_shape:输入数据格式
pool_size:池化大小
strides:步长
units:输出的维数
'''
model
= tf
.keras
.models
.Sequential
([
tf
.keras
.layers
.Conv2D
(filters
=6, kernel_size
=(5,5), padding
='valid', activation
=tf
.nn
.relu
, input_shape
=(32,32,1)),
tf
.keras
.layers
.AveragePooling2D
(pool_size
=(2,2), strides
=(2,2), padding
='same'),
tf
.keras
.layers
.Conv2D
(filters
=16, kernel_size
=(5,5), padding
='valid', activation
=tf
.nn
.relu
),
tf
.keras
.layers
.AveragePooling2D
(pool_size
=(2,2), strides
=(2,2), padding
='same'),
tf
.keras
.layers
.Flatten
(),
tf
.keras
.layers
.Dense
(units
=120, activation
=tf
.nn
.relu
),
tf
.keras
.layers
.Dense
(units
=84, activation
=tf
.nn
.relu
),
tf
.keras
.layers
.Dense
(units
=10, activation
=tf
.nn
.softmax
)
])
adam_optimizer
= tf
.keras
.optimizers
.Adam
(learning_rate
)
model
.compile(optimizer
=adam_optimizer
,
loss
=tf
.keras
.losses
.sparse_categorical_crossentropy
,
metrics
=['accuracy'])
start_time
= datetime
.datetime
.now
()
model
.fit
(x
=x_train
, y
=y_train
, batch_size
=batch_size
, epochs
=num_epochs
)
end_time
= datetime
.datetime
.now
()
time_cost
= end_time
- start_time
print('时间花费:', time_cost
)
return model
def save_model(model
,filepath
):
'''
保存模型
model:模型
filepath:保存路径
'''
model
.save
(filepath
)
def load_models(filepath
):
'''
加载模型
filepath:模型所在的路径
返回 加载的模型
'''
model
= tf
.keras
.models
.load_model
(filepath
)
return model
if __name__
== "__main__":
filepath
= 'C:\\Users\\HKZ\\Desktop\\我的\\手写数字识别\\lenet_model.h5'
num_epochs
= 5
batch_size
= 64
learning_rate
= 0.001
mnist
= tf
.keras
.datasets
.mnist
(x_train
,y_train
),(x_test
,y_test
) = mnist
.load_data
()
x_train
, x_test
= iamge_transform
(x_train
, x_test
)
model
= load_models
(filepath
)
print(model
.summary
())
image_index
= 100
pred
= model
.predict
(x_test
[image_index
].reshape
(1,32,32,1))
print('预测结果',pred
.argmax
())
plt
.imshow
(x_test
[image_index
].reshape
(32,32), cmap
='Greys')
plt
.show
()
模型展示:
通过训练,模型准确率达到99.26%
说在最后,我这里是将训练模型和测试模型都写在同一个代码中,对着注释来就好