基于TensorFlow的手写数字识别

本文是使用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%
在这里插入图片描述

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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiao黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值