本文是使用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
# 将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个连接。
# 第五层 全连接层 激活函数是relu
tf.keras.layers.Dense(units=120, activation=tf.nn.relu),
F6 全连接层
- 输入:S5的120维向量
- 可训练参数:84*(120+1)=10164
# 第六层 全连接层 激活函数是relu
tf.keras.layers.Dense(units=84, activation=tf.nn.relu),
输出层 全连接层
- 输出层也是全连接层,共有10个节点,分别代表数字0到9,且如果节点i的值为0,则网络识别的结果是数字i。
# 第七层 全连接层 激活函数是softmax
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):
# 将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)
# print(x_train.shape)
# 数据类型转换 -> 换成tf需要的
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
# 数据正则化 -> 转换成(0,1)之间
x_train /= 255
x_test /= 255
# 数据维度转换 四维 [n,h,w,c] n -> number, h -> 高度, w -> 宽度, c -> 通道
x_train = x_train.reshape(x_train.shape[0], 32, 32, 1)
x_test = x_test.reshape(x_test.shape[0], 32, 32, 1)
# print(x_test.shape)
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(),
# 第五层 全连接层 激活函数是relu
tf.keras.layers.Dense(units=120, activation=tf.nn.relu),
# 第六层 全连接层 激活函数是relu
tf.keras.layers.Dense(units=84, activation=tf.nn.relu),
# 第七层 全连接层 激活函数是softmax
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)
# print(model.summary()) # 打印模型结构
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是测试集标签
(x_train,y_train),(x_test,y_test) = mnist.load_data()
# 查看数据格式
# print(x_train.shape, y_train.shape)
# print(x_test.shape, y_test.shape)
# 随机显示一个图片并查看
# image_index = 123
# print(y_train[image_index])
# plt.imshow(x_train[image_index]) # 显示图像
# plt.show()
# 将训练集和测试集转换成需要的格式
x_train, x_test = iamge_transform(x_train, x_test)
# 构建并训练模型
# model = train_model(x_train, y_train,batch_size,num_epochs)
# 保存模型
# save_model(model,filepath)
# 加载模型 注意在加载模型之前 把上述两个语句注释掉
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()
#模型评估
# print(model.evaluate(x_test, y_test, verbose=2))
模型展示:
通过训练,模型准确率达到99.26%
说在最后,我这里是将训练模型和测试模型都写在同一个代码中,对着注释来就好