【mindspore】mindspore实现手写数字识别-创新互联

mindspore实现手写数字识别

具体流程参考教程:MindSpore快速入门 MindSpore 接口文档
注:本文章记录的是我在开发过程中的学习笔记,仅供参考学习,欢迎讨论,但不作为开发教程使用。

我们拥有10余年网页设计和网站建设经验,从网站策划到网站制作,我们的网页设计师为您提供的解决方案。为企业提供成都网站建设、网站建设、微信开发、重庆小程序开发成都手机网站制作H5开发、等业务。无论您有什么样的网站设计或者设计方案要求,我们都将富于创造性的提供专业设计服务并满足您的需求。数据的流水线处理
def datapipe(dataset, batch_size):
    '''
    数据处理流水线
    '''
    image_transform = [
        vision.Rescale(1.0 / 255.0, 0), # 缩放 output = image * rescale + shift.
        vision.Normalize(mean=(0.1307,), std=(0.3081,)),    # 根据平均值和标准偏差对输入图像进行归一化
        vision.HWC2CHW()    # 转换为NCHW格式
    ]
    label_transform = transforms.TypeCast(mindspore.int32)  # 转为mindspore的int32格式

    dataset = dataset.map(image_transform, 'image')     # 对各个图像按照流水线处理
    dataset = dataset.map(label_transform, 'label')     # 对各个标签转换为int32
    dataset = dataset.batch(batch_size)
    return dataset

这段代码中对输入图片进行了缩放、归一化和格式转换三个操作,按照流水线运行。

流水线操作

数据流水线处理的介绍:【AI设计模式】03-数据处理-流水线(Pipeline)模式
总结而言,海量数据下,流水线模式可以实现高效的数据处理,当然也会占用更多的CPU和内存资源。

map操作

mindspore下dataset的map操作:第一个参数是处理函数列表,第二个参数是需要处理的列。
map函数会将数据集中第二个参数的指定的列作为输入,调用第一个参数的处理函数执行处理,如果有多个处理函数,上一个函数的输出作为下一个函数的输入。

NCHW和NHWC格式的优缺点

NCHW
缺点:必须等所有通道输入准备好才能得到最终输出结果,需要占用较大的临时空间。
优点:是 Nvidia cuDNN 默认格式,使用 GPU 加速时用 NCHW 格式速度会更快。(这个是什么原因呢?没找到资料_(:з」∠)_)
NHWC
缺点:GPU 加速较NCHW更慢
优点:访存局部性更好(每三个输入像素即可得到一个输出像素)
参考文章:【深度学习框架输入格式】NCHW还是NHWC?
为什么pytorch中transforms.ToTorch要把(H,W,C)的矩阵转为(C,H,W)?

模型
class Network(nn.Cell):
    '''
    Network model
    '''
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.dense_relu_sequential = nn.SequentialCell(
            nn.Dense(28*28, 512),
            nn.ReLU(),
            nn.Dense(512, 512),
            nn.ReLU(),
            nn.Dense(512, 10)
        )

    def construct(self, x):
        x = self.flatten(x)
        logits = self.dense_relu_sequential(x)
        return logits
基类

mindspore的模型基类是mindspore.nn.Cell
pytorch的模型基类是torch.nn.Module

全连接层

mindspore的全连接层是mindspore.nn.Dense
pytorch的全连接层是torch.nn.Linear

模型连接

mindspore的顺序容器是mindspore.nn.SequentialCell
pytorch的顺序容器是torch.nn.Sequential

前向传播

mindspore的前向传播函数(要执行的计算逻辑)基类函数为construct(self, xxx)
pytorch的前向传播函数基类函数为forward(self, xxx)

损失函数和优化策略
my_loss_fn = nn.CrossEntropyLoss()
my_optimizer = nn.SGD(model.trainable_params(), 1e-2)

交叉熵:把来自一个分布q的消息使用另一个分布p的最佳代码传达方式计算得到的平均消息长度,即为交叉熵。针对交叉熵,这个文章讲的较好:损失函数:交叉熵详解

mindspore的交叉熵函数和pytorch类似:
前者是mindspore.nn.CrossEntropyLoss(),后者是torch.nn.CrossEntropyLoss()

训练
def train(model_train, dataset, loss_fn, optimizer):
    '''
    训练函数
    '''
    # Define forward function
    def forward_fn(data, label):
        logits = model_train(data)
        loss = loss_fn(logits, label)
        return loss, logits

    # Get gradient function
    grad_fn = ops.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)

    # Define function of one-step training
    def train_step(data, label):
        (loss, _), grads = grad_fn(data, label)
        loss = ops.depend(loss, optimizer(grads))
        return loss

    size = dataset.get_dataset_size()
    model_train.set_train()
    for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):
        loss = train_step(data, label)

        if batch % 100 == 0:
            loss, current = loss.asnumpy(), batch
            print(f"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]")
value_and_grad

官网对value_and_grad函数的介绍如下:mindspore.ops.value_and_grad
按照官网的描述,这个函数的作用是:生成求导函数,用于计算给定函数的正向计算结果和梯度。
我们需要给这个函数传入模型的正向传输函数和待求导的参数
其中模型的正向传输函数需要封装一下,返回loss的计算, 用于后续优化器的梯度计算;
待求导的参数可以写为model.trainable_params(),也可以由优化器提供(optimizer.parameters),因为优化器初始化时已经传入需要求导的参数。
总之,这个接口返回的是一个函数,函数的作用是把正向传播、反向传播的整个流程走一遍,最后的输出为正向传输函数的返回值+待求导参数的梯度值

depend算子

在训练时使用到了depend算子,官网对Depend函数的介绍如下:mindspore.ops.Depend

# Define function of one-step training
    def train_step(data, label):
        (loss, _), grads = grad_fn(data, label)
        loss = ops.depend(loss, optimizer(grads))
        return loss

经询问分析,使用depend算子的原因是,在静态图模式下,函数执行的先后顺序可能会被优化,这就可能存在loss在grad_fn(value_and_grad)之前就被返回使用的情况,导致返回的loss不正确。
因此通过使用depend算子,来保证loss的返回动作在optimizer之后执行,而optimizer的输入依赖grad_fn,因此optimizer一定在grad_fn之后执行,这就保证了depend返回的loss确实是经过grad_fn计算的最新结果。
当然,mindspore也是支持动态图模式的,只需加一行代码:

# 设置为动态图模式
mindspore.set_context(mode=mindspore.PYNATIVE_MODE)
# 设置为静态图模式
# mindspore.set_context(mode=mindspore.GRAPH_MODE)

model = Network()
print(model)

然后训练函数就可以这么写:

def train_step(data, label):
    (loss, _), grads = grad_fn(data, label)
    optimizer(grads)
    return loss

但是实测,动态图模式下,训练速度相比静态图慢了很多。
关于mindspore动态图和静态图模式的介绍,可看这个官方文档:动静态图

训练尺寸

各个文章在介绍梯度下降法时,通常介绍的是批量梯度下降法,但是训练模型时用的最多的是小批量梯度下降法。这里先讲下批量梯度下降、随机梯度下降和小批量梯度下降的区别。

批量梯度下降

批量梯度下降法的流程是:假设有1000个数据,经过正向计算,得到1000个计算结果,误差函数的计算公式依赖这1000个计算结果;再对误差函数进行反向传播求导,得到模型里参数的梯度值;同样地,对误差函数求导得梯度,也依赖这1000个计算结果;最后基于学习率更新参数,然后进入下一轮训练。
因此,标准的批量梯度下降,需要每次计算出1000个数据的正向传播结果,才可以得到参数梯度值,然后下一轮训练,重新计算1000个计算结果…这就存在大量的运算量,使得训练容易变得非常耗时。
梯度下降

随机梯度下降

随机梯度下降法的流程是,假设有1000个数据,我们随机取1个数据,经过正向计算,得到1个计算结果,误差函数的计算公式就只依赖这1个计算结果;然后反向传播求导,得到基于1个计算结果的梯度值,最后基于学习率更新参数,然后进入下一轮训练。下一轮训练时,随机取另1个数据,重复上述操作…
这种方法下,极大地降低了计算量,而且理论上,只要数据量够大,数据足够随机,最后也总会下降到所需极值点,毕竟计算数据量小了很多,算得更快了,下降速度也会快很多。但是每次只依赖1个数据,就使得梯度的下降方向在整体方向上不稳定,容易到处飘,最后的结果可能不会是全局最优。
随机梯度下降

小批量梯度下降

小批量梯度下降法的流程是:假设有1000个数据,我们随机取100个数据,经过正向计算,得到100个计算结果,误差函数的计算公式依赖这100个计算结果;然后反向传播求导,得到基于100个计算结果的梯度值,最后基于学习率更新参数,然后进入下一轮训练。下一轮训练时,随机取另100个数据,重复上述操作…
可以看出,小批量梯度下降 结合了 批量梯度下降 和 随机梯度下降 的优缺点,使得计算即不那么耗时,又保证参数更新路径和结果相对稳定。

实例

mindspore的这个例子用的是小批量梯度下降,train_step每次输入64个数据,然后前向传播、计算梯度、更新参数,再进入下一个epoch,随机取新的64个数据,重复训练…

size = dataset.get_dataset_size()
model_train.set_train()
for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):
    loss = train_step(data, label)
    if batch % 100 == 0:
        loss, current = loss.asnumpy(), batch
        print(f"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]")

在将数据集进行datapipe后,返回的train_dataset和test_dataset都是以batch_size=64个为一组进行输出的,此处dataset.get_dataset_size()返回的size是有多少组数据。测试集返回的size为938,表示一共有938组,每组64个图片数据。实际上MNIST只有60000个测试集图片,因此最后一组只有32个图片。

运行结果
Epoch 1
-------------------------------
loss: 2.303684  [  0/938]
loss: 2.291476  [100/938]
loss: 2.273411  [200/938]
loss: 2.212310  [300/938]
loss: 1.969760  [400/938]
loss: 1.600426  [500/938]
loss: 1.004380  [600/938]
loss: 0.735266  [700/938]
loss: 0.672223  [800/938]
loss: 0.578563  [900/938]
Test: 
 Accuracy: 85.3%, Avg loss: 0.528851

Epoch 2
-------------------------------
loss: 0.384008  [  0/938]
loss: 0.453575  [100/938]
loss: 0.277697  [200/938]
loss: 0.317674  [300/938]
loss: 0.294471  [400/938]
loss: 0.519272  [500/938]
loss: 0.253794  [600/938]
loss: 0.389252  [700/938]
loss: 0.383196  [800/938]
loss: 0.334877  [900/938]
Test: 
 Accuracy: 90.2%, Avg loss: 0.334850

此处跑了两轮训练,可以看出,第一轮的938组数据的训练过程中,参数快速调整至合理范围(loss从2.3降低到0.5),但是第二轮的938组数据的训练过程中,loss出现了上下波动(0.3->0.4->0.2->0.3…),即模型参数向当前数据组的梯度下降的方向走了一小步后,新的数据组算出的loss反而比之前还提高了。
这主要是因为当前数据组的梯度下降方向 无法代表 替他数据组/所有数据的梯度下降方向,当然也可能是学习率(步长)太大导致跨过了最低点,这个就具体问题具体分析了。

总结

mindspore和pytorch在接口命名上存在区别,但是实际使用过程中,开发思路还是一致的。因此最关键的还是要熟悉深度学习的思路和流程,至于思路和代码实现的映射,这就唯手熟尔。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前题目:【mindspore】mindspore实现手写数字识别-创新互联
链接分享:http://myzitong.com/article/gdsho.html