MINST手写识别文档

(一) 准备工作

MindSpore安装,数据准备

 

 

(二) 实验步骤代码及结果

  1. 1.      配置运行信息

from mindspore import context
context.set_context(mode=context.GRAPH_MODE, device_target="CPU")

 

l  导入context模块,调用context的set_context方法,参数mode表示运行模式。

MindSpore支持动态图和静态图两种模式,GRAPH_MODE表示静态图模式,PYNATIVE_MODE表示动态图模式,对于此项目手写数字识别问题,选择采用静态图模式。参数device_target表示硬件信息,有三个选项(CPU,GPU,Ascend),在电脑windows环境下,选择硬件平台使用CPU。

  1. 数据集的详细信息概览

import matplotlib.pyplot as plt
import matplotlib
import numpy as np
import mindspore.dataset as ds

train_data_path = "./MNIST_Data/train"
test_data_path = "./MNIST_Data/test"
mnist_ds = ds.MnistDataset(train_data_path)
#生成的图像有两列[img, label],列图像张量是uint8类型,
#因为像素点的取值范围是(0, 255),需要注意的是这两列数据的数据类型是Tensor

print('The type of mnist_ds:', type(mnist_ds))
print("Number of pictures contained in the mnist_ds", mnist_ds.get_dataset_size())
#int, number of batches.
#print(mnist_ds.get_batch_size())    #Return the size of batch.

dic_ds = mnist_ds.create_dict_iterator()    #数据集上创建迭代器,为字典数据类型,输出的为Tensor类型
item = next(dic_ds)
img = item["image"].asnumpy()   #asnumpyTensor中的方法,功能是将张量转化为numpy数组,因为matplotlib.pyplot中不
#能接受Tensor数据类型的参数

label = item["label"].asnumpy()

print("The item of mnist_ds:", item.keys())
print("Tensor of image in item:", img.shape)
print("The label of item:", label)

plt.imshow(np.squeeze(img))    #squeezeshape中为1的维度去掉,plt.imshow()函数负责对图像进行处理,并显示其格式
plt.title("number:%s"% item["label"].asnumpy())
plt.show()  #show显示图像

 

 

 

l  MindSpore提供的内置数据集处理方法默认输出一般都是在框架中通用的Tensor,但是对于非框架优化包含的python库,包括matplotlib,它们就无法处理接受Tensor,这是我们就要采用Tensor类中定义的asnumpy方法将张量转化为numpy数组。

l  得出结论:训练集中有60000张图片,以image和lable两个标签分别储存,并且每一张图片的大小为28 x 28,通道数为1说明里面是黑白图片。图中显示的图片是6,它的标签值也为6。多次验证,后得到数字8与数字1.

加载及处理数据集

定义一个create_dataset函数

a)       将原始的MNIST数据集加载进来, mnist_ds = ds.MnistDataset(data_path)

b)      设置一些数据增强和处理所需要的参数(parameters)

c)       使用2中设置的参数得到一些实例化的数据增强和处理方法

d)      将3中的方法映射到(使用)在对应数据集的相应部分(image,label)

e)       返回处理好的数据集(不同的神经网络可以接受不同的输入数据格式)

 

import mindspore.dataset.vision.c_transforms as CV
import mindspore.dataset.transforms.c_transforms as C
from mindspore.dataset.vision import Inter
from mindspore import dtype as mstype
import mindspore.dataset as ds

导入的模块说明:
#mindspore.dataset.vision.c_transformsMindSpore提供的通过数据增强操作对图像进行预处理,从而提高模型的广泛性。基于C++OpenCV实现,具有较高的性能。在增强MNIST数据集中,所使用到该模块中的方法
 
a)   CV.Resize(size, interpolation=Inter.LINEAR):
功能:使用给定的插值模式将输入图像调整为给定大小。
参数:size:调整后输出图像的大小;interpolation:图像的插值模式(5个常量可供选择),一般情况下使用默认的Inter.LINEAR 
 
b)   CV.Rescale(rescale, shift):
功能:使用给定的重缩放和移位重缩放输入图像。重新缩放输入图像命令:输出=图像*重新缩放+移位(output = image * rescale + shift),可以用来消除图片在不同位置给模型带来的影响。
参数:rescale:缩放因子;shift:偏移因子
 
c)   CV.HWC2CHW():
功能:将输入图像从形状(H,W,C)转换为形状(C,H,W)。输入图像应为3通道图像。
 
#mindspore.dataset.transforms.c_transforms:基于C++OpenCV实现用于支持常见的图形增强功能的模块。
 
a)   C.TypeCast(data_type):
功能:一个转化为给定MindSpore数据类型(data_type)的张量操作。
参数:data_type:MindSpore中dtype模块中所指定的数据类型。
 
#mindspore.dataset.MnistDatasetMindSpore中专门提供的加载处理MNIST数据集为MindSpore数据类型的类,使用了map方法,进行分析理解。
ds.map(operations, input_columns=None, output_columns=None, column_order=None, num_parallel_workers=None, python_multiprocessing=False, cache=None, callbacks=None):
 
功能:将具体的操作和方法应用到数据集上。
参数:
operations:用于数据集的操作列表,操作将按照他们在列表中的顺序进行应用。 input_columns:将作为输入传递给第一个操作的列的名称列表。num_parallel_workers:用于并行处理数据集的线程数,默认值为无。
 
参数含义以及定义原因理解说明
 

def create_dataset(data_path, batch_size=32, repeat_size=1,
                   num_parallel_workers=1):
    mnist_ds = ds.MnistDataset(data_path)

    # 定义数据增强和处理所需的一些参数
   
resize_height, resize_width = 32, 32
#我们要将MNIST数据中的28 x 28大小的图片转化为32 x 32的大小,这是因为后面我们要采用的LeNet-5的卷积神经网络训练模型,该模型要求输入图像的尺寸统一归一化为32 x 32,具体的关于该网络的细节,我会在构建网络时详细介绍。
    rescale = 1.0 / 255.0
    shift = 0.0
    rescale_nml = 1 / 0.3081
    shift_nml = -1 * 0.1307 / 0.3081

#这里将数据集中图像进行缩放,选择除以255是因为像素点的取值范围是(0,255)。该操作可以使得每个像素的数值大小在(0,1)范围中,可以提升训练效率。进行完该操作之后,有对图像进行了依次缩放,缩放的值选择直接使用官方代,没有进行多次训练实验。

    # 根据上面所定义的参数生成对应的数据增强方法,即实例化对象
   
resize_op = CV.Resize((resize_height, resize_width), interpolation=Inter.LINEAR)
    rescale_nml_op = CV.Rescale(rescale_nml, shift_nml)
    rescale_op = CV.Rescale(rescale, shift)
    hwc2chw_op = CV.HWC2CHW()
 
#使用它对图片张量进行转化的原因是原图片张量形状是(28 x 28 x 1)(高 x 宽 x通道),在python中用高维数组解释是28个 28 x 1的矩阵,这不是很符合计算习惯,所以我们将它转化为1 x 28 x 28(通道 x 高 x 宽),相对于是1个 28 x 28的矩阵,方便进行数据训练。

    type_cast_op = C.TypeCast(mstype.int32)

    # 将数据增强处理方法映射到(使用)在对应数据集的相应部分(imagelabel
   
mnist_ds = mnist_ds.map(operations=type_cast_op, input_columns="label", num_parallel_workers=num_parallel_workers)
    mnist_ds = mnist_ds.map(operations=resize_op, input_columns="image", num_parallel_workers=num_parallel_workers)
    mnist_ds = mnist_ds.map(operations=rescale_op, input_columns="image", num_parallel_workers=num_parallel_workers)
    mnist_ds = mnist_ds.map(operations=rescale_nml_op, input_columns="image", num_parallel_workers=num_parallel_workers)
    mnist_ds = mnist_ds.map(operations=hwc2chw_op, input_columns="image", num_parallel_workers=num_parallel_workers)

    # 处理生成的数据集
   
buffer_size = 10000
    mnist_ds = mnist_ds.shuffle(buffer_size=buffer_size)
#已经将MNIST数据集处理成网络和框架所能接受的数据,下面对处理好的数据集进行一个增强操作。
shuffle用于将数据集随机打乱,减少人为因素干扰训练,降低 MNIST数据集本身的规律。 

    mnist_ds = mnist_ds.batch(batch_size, drop_remainder=True)
#将整个数据集按照batch_size的大小分为若干批次,每一次训练的时候都是按一个批次的数据进行训练, drop_remainder:确定是否删除数据行数小于批大小的最后一个块,这里设置为True就是只保留数据集个数整除batch_size后得到的批次,多余得则从minst_ds中舍弃。

    mnist_ds = mnist_ds.repeat(repeat_size)

    return mnist_ds
train_data_path = "./MNIST_Data/train"
test_data_path = "./MNIST_Data/test"
ms_dataset = create_dataset(train_data_path)

print('Number of each group',ms_dataset.get_batch_size())
print('Number of groups in the dataset:', ms_dataset.get_dataset_size())
  1. 展示处理过后的数据集

将32张图片作为一个批次,将训练集总共分为了60000/32 = 1875个批次。

拿出一个批次的数据将其形状等属性显示,进行可视化。

每一次使用一个这样的大型矩阵去训练。这样可以避免过高维度数组造成的内存过大,训练时间过长的问题。

 

  1. 卷积神经网络LeNet-5

l  了解图像基础知识:

图片是通过像素来定义的,即每个像素点的颜色不同,其对应的颜色值不同,例如黑白图片的颜色值为0到255,手写体字符,白色的地方为0,黑色为1,像素其实就是图片的最小组成,黑白只是0-255,彩色其实就是RGB即三层基色合成的,就可以通过三层下图数值代表即可。

卷积层是卷积神经网络的核心基石。在图像识别里是二维卷积,即离散二维滤波器(卷积核)与二维图像做卷积操作,简单的讲是二维滤波器滑动到二维图像上所有位置,并在每个位置上与该像素点及其领域像素点做内积。卷积操作被广泛应用与图像处理领域,不同卷积核可以提取不同的特征,例如边沿、线性、角等特征。

在深层卷积神经网络中,通过卷积操作可以提取出图像低级到复杂的特征。

 

通过卷积核就可以提取图片的特征和压缩图片了,它的输入是一个黑白二维图片,先经过两层卷积层提取特征,然后到池化层,再经过全连接层连接,最后使用softmax分类作为输出层(因为它的输出有10个标签)

池化是非线性下采样的一种形式,主要作用是通过减少网络的参数来减小计算量,并且能够在一定程度上控制过拟合。通常在卷积层的后面会加上一个池化层。池化包括最大池化、平均池化等。其中最大池化是用不重叠的矩形框将输入层分成不同的区域,对于每个矩形框的数取最大值作为输出层

 

 

 

1)      输入层

我们的输入数据(手写数字图片)就是首先传入输入层,LeNet-5网络要求输入层的图片尺寸必须为32 x 32。

已经通过create_dataset()函数将图片处理为 32 x 32

2)      卷积层1(C1层)

 

输入:32 x 32 x 1

卷积核大小: 5 x 5(过滤器)

卷积核数量:6

输出形状:28 x 28 x 6

可训练参数:(5 x 5 +1)x 6 =156(5 x 5过滤器参数加一个偏差b)

 

说明:对输入图像做卷积运算,一个过滤器与图片进行卷积运算得到28 x 28(32 - 5 +1)的矩阵,使用6个过滤器自然得到28 x 28 x 6的高阶数组。可以理解为提取了6个不同的图片特征。对于32 x 32中的每一个像素点,它都和6个过滤器以及他们的偏差有连接,我们通过156个参数就完成了全部的连接,这主要是通过权值共享实现的。(这是卷积层的主要优势之一,可以使要训练的参数减少)。

3)      池化层1(S2层)

输入:28 x 28 x 6

采样区域:2 x 2

采用方式:这里采用最大池化。4个输入比较大小,取最大值。

采样种类:6

输出形状:14 x 14 x 6(这里的步长 s = 2)

说明:第一次卷积运算提取特征之后,进行池化运算,将图片的大小进行缩减。2 x 2的采样器通常高和宽缩小一半。

 

4)      卷积层2(C3层)

输入:14 x 14 x 6

卷积核大小 :5 x 5

卷积核数量 : 16

输出形状:10 x 10 x 16

 

5)      池化层2(S4层)

输入:10 x 10 x 16

采样区域:2 x 2

采样方式:最大池化

输出 形状:5 x 5 x 16

 

6)      C5层

输入:5 x 5 x 16

功能:展平张量

输出形状:1 x 1 x 120

 

7)      F6层(全连接层)

输入:C5

计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过Relu函数输出。

8)      Output层-全连接层

对应的是10个标签(0到9的数字)。需采用softmax分类得到。

 

  1. 定义网络

MindSpore中所有网络的定义都需要继承自mindspore.nn.cell,并重写父类的init和construct方法,其中init方法中完成一些我们所需算子,网络的定义,construct方法则是写网络的执行逻辑。而且在nn模块中有很多定义好的各网路层可以使用,包括卷积层(nn.Conv2d()),全连接层(nn.Flatten())等。

  1. 反向传播

已经定义好了网络的前向传播过程,为了改变我们的可训练参数,即让我们所使用的参数能够预测出更加精确的值。需要定义损失函数即优化器,在MindSpore框架中是封装好了损失函数和优化器的,这使得我们的编程可以更快更加高效。

概念:

损失函数:又叫目标函数,用于衡量预测值与实际值差异的程度。深度学习通过不停地迭代来缩小损失函数的值。定义一个好的损失函数,可以有效提高模型的性能。常见的有二分类的损失函数L,以及softmax损失函数等。

 

优化器:用于最小化损失函数,从而在训练过程中改进模型。

此实验使用Momentum算法——为了让寻找最优解的曲线能够不那么振荡、波动,希望让他能够更加的平滑,在水平方向的速度更快,运用了动量的定义。

基于梯度的移动指数加权平均

lr = 0.01
#learingrate,学习率,可以使梯度下降的幅度变小,从而可以更好的训练参数
momentum = 0.9
network = LeNet5()
vt+1=vtu+gradients
pt+1=pt−(grad
lr+vt+1ulr)
pt+1=pt−lr
vt+1
其中gradlrpvu分别表示梯度、学习率、参数、力矩和动量。
net_opt = nn.Momentum(network.trainable_params(), lr, momentum)

 

  1. 自定义一个回调类

自定义一个数据收集的回调类StepLossAccInfo该类继承自Callback类。主要用于收集两类信息,step与loss值之间地关系。step与对应模型精度之间联系。

我们将该类作为回调函数,会在后面的高级api——model.train模型训练函数中调用

# 回调
class StepLossAccInfo(Callback):
    def __init__(self, model, eval_dataset, steps_loss, steps_eval):
        self.model = model  # 计算图模型Model
       
self.eval_dataset = eval_dataset  # 测试数据集
       
self.steps_loss = steps_loss
        # 收集steploss值之间的关系,数据格式{"step": [], "loss_value": []},会在后面定义
       
self.steps_eval = steps_eval
        # 收集step对应模型精度值accuracy的信息,数据格式为{"step": [], "acc": []},会在后面定义

   
def step_end(self, run_context):
        cb_params = run_context.original_args()
        # cur_epoch_numCallbackParam中的定义,获得当前处于第几个epoch,一个epoch意味着训练集
       
# 中每一个样本都训练了一次
       
cur_epoch = cb_params.cur_epoch_num

        # 同理,cur_step_numCallbackParam中的定义,获得当前执行到多少step
       
cur_step = (cur_epoch - 1) * 1875 + cb_params.cur_step_num
        self.steps_loss["loss_value"].append(str(cb_params.net_outputs))
        self.steps_loss["step"].append(str(cur_step))
        if cur_step % 125 == 0:
            # 调用model.eval返回测试数据集下模型的损失值和度量值,dic对象
           
acc = self.model.eval(self.eval_dataset, dataset_sink_mode=False)
            self.steps_eval["step"].append(cur_step)
            self.steps_eval["acc"].append(acc["Accuracy"])

 

  1. 训练网络
epoch_size = 1  #每个epoch需要遍历完成图片的batch,这里是只要遍历一次
eval_dataset = create_dataset(test_data_path)
model_path = "./models/ckpt/mindspore_quick_start/"
#调用Model高级API,将LeNet-5网络与损失函数和优化器连接到一起,具有训练和推理功能的对象。
#metrics 参数是指训练和测试期,模型要评估的一组度量,这里设置的是"Accuracy"准确度
model = Model(network, net_loss, net_opt, metrics={"Accuracy": Accuracy()} )

#保存训练好的模型参数的路径
config_ck = CheckpointConfig(save_checkpoint_steps=375, keep_checkpoint_max=16)
ckpoint_cb = ModelCheckpoint(prefix="checkpoint_lenet", directory=model_path, config=config_ck)

#回调类中提到的我们要声明的数据格式
steps_loss = {"step": [], "loss_value": []}
steps_eval = {"step": [], "acc": []}
#使用model等对象实例化StepLossAccInfo,得到具体的对象
step_loss_acc_info = StepLossAccInfo(model , eval_dataset, steps_loss, steps_eval)

#调用Model类的train方法进行训练,LossMonitor(125)每隔125step打印训练过程中的loss,dataset_sink_mode为设置数据下沉模式,但该模式不支持CPU,所以这里我们只能设置为False
model.train(epoch_size, ms_dataset, callbacks=[ckpoint_cb, LossMonitor(125), step_loss_acc_info], dataset_sink_mode=False)

 

  1. 提取出一个批次的图片,使用已训练好的上面的模型来预测一下每一张图片的标签,并将其可视化,得到识别的结果
from mindspore import load_checkpoint, load_param_into_net

def test_net(network, model):
    """Define the evaluation method."""
   
print("开始验证")
    # load the saved model for evaluation
   
param_dict = load_checkpoint("./models/ckpt/mindspore_quick_start/checkpoint_lenet-1_1875.ckpt")
    # load parameter to the network
   
load_param_into_net(network, param_dict)
    # load testing dataset

   
acc = model.eval(eval_dataset, dataset_sink_mode=False)
    print("准确率:{}".format(acc))

test_net(network, model)


ds_test = eval_dataset.create_dict_iterator()
data = next(ds_test)
images = data["image"].asnumpy()
labels = data["label"].asnumpy()

output = model.predict(Tensor(data['image']))
#利用加载好的模型的predict进行预测,注意返回的是对应的(09)的概率
pred = np.argmax(output.asnumpy(), axis=1)
err_num = []
index = 1
for i in range(len(labels)):
    plt.subplot(4, 8, i+1)
    color = 'blue' if pred[i] == labels[i] else 'red'
   
plt.title("pre:{}".format(pred[i]), color=color)
    plt.imshow(np.squeeze(images[i]))
    plt.axis("off")
    if color == 'red':
        index = 0
        print("{},{}列 错误识别成 {}, 正确值应为 {}".format(int(i/8)+1, i%8+1, pred[i], labels[i]), '\n')
if index:
    print("已成功识别组内全部数字!")
print(pred, "(预测值)")
print(labels, "(准确值)")
plt.show()

 

(三)          实验结果与模型性能分析

在进行了多次训练预测后,截取部分具有代表性的实验结果,有一些字迹的确较难识别。

 

l  准确率:指的是分类正确的样本数量占样本总数的比例,此模型所得准确率在97%左右。

 

epoch值进行改变,分别epoch=1 ,lr = 0.01;epoch=5,lr=0.01

随着迭代次数的增多,误识别率逐渐减小。

 

l  卷积层 1、2 不同特征图数时的误识别率与识别时间

改变特征图数,发现得出:卷积层 1、2 的特征图个数对手写数字识别正确率有直接影响。随着特征图个数增多,误识别率会逐渐下降,识别时间则相应增加。特征图个数增加到一定值后,误识别率不会再继续降低,甚至有时会变高。

 

l  样本个数 batch

控制一切参数不变的情况下,改变每次训练样本个数 batch 的大小,所得误识别率和时间如表 1 所示。随着样本个数的增多,误识别率增大,识别时间减少,综合考虑

posted @ 2022-03-11 15:33  Clematis  阅读(162)  评论(0)    收藏  举报