基于TensorFlow的手写数字识别

tech2025-07-11  1

本文是使用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%

说在最后,我这里是将训练模型和测试模型都写在同一个代码中,对着注释来就好

最新回复(0)