实现一个图片分类应用

    本例子会实现一个简单的图片分类的功能,整体流程如下:

    • 处理需要的数据集,这里使用了MNIST数据集。

    • 定义一个网络,这里我们使用LeNet网络。

    • 定义损失函数和优化器。

    • 加载数据集并进行训练,训练完成后,查看结果及保存模型文件。

    • 加载保存的模型,进行推理。

    • 验证模型,加载测试数据集和训练后的模型,验证结果精度。

    这是简单、基础的应用流程,其他高级、复杂的应用可以基于这个基本流程进行扩展。

    准备环节

    在动手进行实践之前,确保,你已经正确安装了MindSpore。如果没有,可以通过将MindSpore安装在你的电脑当中。

    同时希望你拥有Python编码基础和概率、矩阵等基础数学知识。

    那么接下来,就开始MindSpore的体验之旅吧。

    我们示例中用到的数据集是由10类28*28的灰度图片组成,训练数据集包含60000张图片,测试数据集包含10000张图片。

    将数据集下载并解压到本地路径下,这里将数据集解压分别存放到工作区的./MNIST_Data/train./MNIST_Data/test路径下。

    目录结构如下:

    导入Python库&模块

    在使用前,需要导入需要的Python库。

    目前使用到os库,为方便理解,其他需要的库,我们在具体使用到时再说明。

    1. Copyimport os

    详细的MindSpore的模块说明,可以在MindSpore API页面中搜索查询。

    配置运行信息

    在正式编写代码前,需要了解MindSpore运行所需要的硬件、后端等基本信息。

    导入context模块,配置运行需要的信息。

    1. Copyimport argparse
    2. from mindspore import context
    3.  
    4. if __name__ == "__main__":
    5. parser = argparse.ArgumentParser(description='MindSpore LeNet Example')
    6. parser.add_argument('--device_target', type=str, default="Ascend", choices=['Ascend', 'GPU', 'CPU'],
    7. help='device where the code will be implemented (default: Ascend)')
    8. args = parser.parse_args()
    9. context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target,
    10. enable_mem_reuse=False)
    11. ...

    在样例中我们配置样例运行使用图模式。根据实际情况配置硬件信息,譬如代码运行在Ascend AI处理器上,则—device_target选择Ascend,代码运行在CPU、GPU同理。详细参数说明,请参见context.set_context()接口说明。

    数据集对于训练非常重要,好的数据集可以有效提高训练精度和效率。在加载数据集前,我们通常会对数据集进行一些处理。

    我们定义一个函数create_dataset()来创建数据集。在这个函数中,我们定义好需要进行的数据增强和处理操作:

    • 定义数据集。

    • 定义进行数据增强和处理所需要的一些参数。

    • 根据参数,生成对应的数据增强操作。

    • 使用map()映射函数,将数据操作应用到数据集。

    • 对生成的数据集进行处理。

    1. Copyimport mindspore.dataset as ds
    2. import mindspore.dataset.transforms.c_transforms as C
    3. import mindspore.dataset.transforms.vision.c_transforms as CV
    4. from mindspore.dataset.transforms.vision import Inter
    5. from mindspore.common import dtype as mstype
    6.  
    7. def create_dataset(data_path, batch_size=32, repeat_size=1,
    8. num_parallel_workers=1):
    9. """ create dataset for train or test
    10. Args:
    11. data_path: Data path
    12. batch_size: The number of data records in each group
    13. repeat_size: The number of replicated data records
    14. num_parallel_workers: The number of parallel workers
    15. """
    16. # define dataset
    17. mnist_ds = ds.MnistDataset(data_path)
    18. # define operation parameters
    19. resize_height, resize_width = 32, 32
    20. rescale = 1.0 / 255.0
    21. shift = 0.0
    22. rescale_nml = 1 / 0.3081
    23. shift_nml = -1 * 0.1307 / 0.3081
    24.  
    25. # define map operations
    26. resize_op = CV.Resize((resize_height, resize_width), interpolation=Inter.LINEAR) # resize images to (32, 32)
    27. rescale_nml_op = CV.Rescale(rescale_nml, shift_nml) # normalize images
    28. rescale_op = CV.Rescale(rescale, shift) # rescale images
    29. hwc2chw_op = CV.HWC2CHW() # change shape from (height, width, channel) to (channel, height, width) to fit network.
    30. type_cast_op = C.TypeCast(mstype.int32) # change data type of label to int32 to fit network
    31.  
    32. # apply map operations on images
    33. mnist_ds = mnist_ds.map(input_columns="label", operations=type_cast_op, num_parallel_workers=num_parallel_workers)
    34. mnist_ds = mnist_ds.map(input_columns="image", operations=resize_op, num_parallel_workers=num_parallel_workers)
    35. mnist_ds = mnist_ds.map(input_columns="image", operations=rescale_op, num_parallel_workers=num_parallel_workers)
    36. mnist_ds = mnist_ds.map(input_columns="image", operations=rescale_nml_op, num_parallel_workers=num_parallel_workers)
    37. mnist_ds = mnist_ds.map(input_columns="image", operations=hwc2chw_op, num_parallel_workers=num_parallel_workers)
    38.  
    39. # apply DatasetOps
    40. buffer_size = 10000
    41. mnist_ds = mnist_ds.shuffle(buffer_size=buffer_size) # 10000 as in LeNet train script
    42. mnist_ds = mnist_ds.batch(batch_size, drop_remainder=True)
    43. mnist_ds = mnist_ds.repeat(repeat_size)
    44.  
    45. return mnist_ds

    其中,batch_size:每组包含的数据个数,现设置每组包含32个数据。repeat_size:数据集复制的数量。

    先进行shuffle、batch操作,再进行repeat操作,这样能保证1个epoch内数据不重复。

    定义网络

    我们选择相对简单的LeNet网络。LeNet网络不包括输入层的情况下,共有7层:2个卷积层、2个下采样层(池化层)、3个全连接层。每层都包含不同数量的训练参数,如下图所示:

    我们需要对全连接层以及卷积层进行初始化。

    TruncatedNormal:参数初始化方法,MindSpore支持TruncatedNormalNormalUniform等多种参数初始化方法,具体可以参考MindSpore API的mindspore.common.initializer模块说明。

    初始化示例代码如下:

    1. Copyimport mindspore.nn as nn
    2. from mindspore.common.initializer import TruncatedNormal
    3.  
    4. def weight_variable():
    5. """
    6. weight initial
    7. """
    8. return TruncatedNormal(0.02)
    9.  
    10. def conv(in_channels, out_channels, kernel_size, stride=1, padding=0):
    11. """
    12. conv layer weight initial
    13. """
    14. weight = weight_variable()
    15. return nn.Conv2d(in_channels, out_channels,
    16. kernel_size=kernel_size, stride=stride, padding=padding,
    17. weight_init=weight, has_bias=False, pad_mode="valid")
    18.  
    19. def fc_with_initialize(input_channels, out_channels):
    20. """
    21. fc layer weight initial
    22. """
    23. weight = weight_variable()
    24. bias = weight_variable()
    25. return nn.Dense(input_channels, out_channels, weight, bias)

    使用MindSpore定义神经网络需要继承。Cell是所有神经网络(Conv2d等)的基类。

    神经网络的各层需要预先在init()方法中定义,然后通过定义construct()方法来完成神经网络的前向构造。按照LeNet的网络结构,定义网络各层如下:

    基本概念

    在进行定义之前,先简单介绍损失函数及优化器的概念。

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

    定义了损失函数后,可以得到损失函数关于权重的梯度。梯度用于指示优化器优化权重的方向,以提高模型性能。

    定义损失函数

    MindSpore支持的损失函数有SoftmaxCrossEntropyWithLogitsL1LossMSELoss等。这里使用SoftmaxCrossEntropyWithLogits损失函数。

    1. Copyfrom mindspore.nn.loss import SoftmaxCrossEntropyWithLogits

    main函数中调用定义好的损失函数:

    1. Copyif __name__ == "__main__":
    2. #define the loss function
    3. net_loss = SoftmaxCrossEntropyWithLogits(is_grad=False, sparse=True, reduction='mean')
    4. ...

    MindSpore支持的优化器有AdamAdamWeightDecayMomentum等。

    这里使用流行的Momentum优化器。

    1. Copyif __name__ == "__main__":
    2. ...
    3. #learning rate setting
    4. lr = 0.01
    5. momentum = 0.9
    6. #create the network
    7. network = LeNet5()
    8. #define the optimizer
    9. net_opt = nn.Momentum(network.trainable_params(), lr, momentum)
    10. ...

    训练网络

    配置模型保存

    MindSpore提供了callback机制,可以在训练过程中执行自定义逻辑,这里使用框架提供的ModelCheckpointLossMonitor为例。ModelCheckpoint可以保存网络模型和参数,以便进行后续的微调(fune-tune)操作,LossMonitor可以监控训练过程中值的变化。

    1. Copyfrom mindspore.train.callback import ModelCheckpoint, CheckpointConfig
    2.  
    3. if __name__ == "__main__":
    4. ...
    5. # set parameters of check point
    6. config_ck = CheckpointConfig(save_checkpoint_steps=1875, keep_checkpoint_max=10)
    7. # apply parameters of check point
    8. ckpoint_cb = ModelCheckpoint(prefix="checkpoint_lenet", config=config_ck)
    9. ...

    配置训练网络

    通过MindSpore提供的model.train接口可以方便地进行网络的训练。这里把epoch_size设置为1,对数据集进行1个迭代的训练。

    其中,在train_net方法中,我们加载了之前下载的训练数据集,mnist_path是MNIST数据集路径。

    使用以下命令运行脚本:

    1. Copypython lenet.py --device_target=CPU

    其中,lenet.py:为你根据教程编写的脚本文件。—device_target CPU:指定运行硬件平台,参数为CPUGPU或者Ascend,根据你的实际运行硬件平台来指定。

    训练过程中会打印loss值,类似下图。loss值会波动,但总体来说loss值会逐步减小,精度逐步提高。每个人运行的loss值有一定随机性,不一定完全相同。训练过程中loss打印示例如下:

    1. Copy...
    2. epoch: 1 step: 262, loss is 1.9212162
    3. epoch: 1 step: 263, loss is 1.8498616
    4. epoch: 1 step: 264, loss is 1.7990671
    5. epoch: 1 step: 265, loss is 1.9492403
    6. epoch: 1 step: 266, loss is 2.0305142
    7. epoch: 1 step: 267, loss is 2.0657792
    8. epoch: 1 step: 268, loss is 1.9582214
    9. epoch: 1 step: 269, loss is 0.9459006
    10. epoch: 1 step: 270, loss is 0.8167224
    11. epoch: 1 step: 271, loss is 0.7432692
    12. ...

    训练完后,即保存的模型文件,示例如下:

    1. Copycheckpoint_lenet-1_1875.ckpt

    其中,checkpointlenet-1_1875.ckpt:指保存的模型参数文件。名称具体含义checkpoint{网络名称}-{第几个epoch}_{第几个step}.ckpt。

    验证模型

    在得到模型文件后,通过模型运行测试数据集得到的结果,验证模型的泛化能力。

    • 使用model.eval()接口读入测试数据集。

    • 使用保存后的模型参数进行推理。

    1. Copyfrom mindspore.train.serialization import load_checkpoint, load_param_into_net
    2.  
    3. ...
    4. def test_net(args,network,model,mnist_path):
    5. """define the evaluation method"""
    6. print("============== Starting Testing ==============")
    7. #load the saved model for evaluation
    8. param_dict = load_checkpoint("checkpoint_lenet-1_1875.ckpt")
    9. #load parameter to the network
    10. load_param_into_net(network, param_dict)
    11. #load testing dataset
    12. ds_eval = create_dataset(os.path.join(mnist_path, "test"))
    13. acc = model.eval(ds_eval, dataset_sink_mode=False)
    14. print("============== Accuracy:{} ==============".format(acc))
    15.  
    16. if __name__ == "__main__":
    17. ...
    18. test_net(args, network, model, mnist_path)

    其中,load_checkpoint():通过该接口加载CheckPoint模型参数文件,返回一个参数字典。checkpoint_lenet-1_1875.ckpt:之前保存的CheckPoint模型文件名称。load_param_into_net:通过该接口把参数加载到网络中。

    使用运行命令,运行你的代码脚本。

    其中,lenet.py:为你根据教程编写的脚本文件。—device_target CPU:指定运行硬件平台,参数为CPU、或者Ascend,根据你的实际运行硬件平台来指定。

    运行结果示例如下:

    1. Copy...
    2. ============== Accuracy:{'Accuracy': 0.9742588141025641} ==============