实现一个图片分类应用

Linux Windows Ascend GPU CPU 全流程 初级 中级 高级

实现一个图片分类应用 - 图1 实现一个图片分类应用 - 图2 实现一个图片分类应用 - 图3

概述

下面我们通过一个实际样例,带领大家体验MindSpore基础的功能,对于一般的用户而言,完成整个样例实践会持续20~30分钟。

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

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

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

  3. 定义损失函数和优化器。

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

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

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

你可以在这里找到完整可运行的样例代码:https://gitee.com/mindspore/docs/blob/r1.0/tutorials/tutorial_code/lenet/lenet.py

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

准备环节

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

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

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

下载数据集

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

MNIST数据集下载页面:http://yann.lecun.com/exdb/mnist/。页面提供4个数据集下载链接,其中前2个文件是训练数据需要,后2个文件是测试结果需要。

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

目录结构如下:

  1. └─MNIST_Data
  2. ├─test
  3. t10k-images.idx3-ubyte
  4. t10k-labels.idx1-ubyte
  5. └─train
  6. train-images.idx3-ubyte
  7. train-labels.idx1-ubyte

为了方便样例使用,我们在样例脚本中添加了自动下载数据集的功能。

导入Python库&模块

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

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

  1. import os

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

配置运行信息

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

可以通过context.set_context来配置运行需要的信息,譬如运行模式、后端信息、硬件等信息。

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

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

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

数据处理

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

定义数据集及数据操作

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

  1. 定义数据集。

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

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

  4. 使用map映射函数,将数据操作应用到数据集。

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

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

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

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

MindSpore支持进行多种数据处理和增强的操作,各种操作往往组合使用,具体可以参考数据处理和与数据增强章节。

定义网络

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

LeNet-5

更多的LeNet网络的介绍不在此赘述,希望详细了解LeNet网络,可以查询http://yann.lecun.com/exdb/lenet/

我们对全连接层以及卷积层采用Normal进行参数初始化。

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

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

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

  1. import mindspore.nn as nn
  2. from mindspore.common.initializer import Normal
  3. class LeNet5(nn.Cell):
  4. """
  5. Lenet network structure
  6. """
  7. #define the operator required
  8. def __init__(self, num_class=10, num_channel=1):
  9. super(LeNet5, self).__init__()
  10. self.conv1 = nn.Conv2d(num_channel, 6, 5, pad_mode='valid')
  11. self.conv2 = nn.Conv2d(6, 16, 5, pad_mode='valid')
  12. self.fc1 = nn.Dense(16 * 5 * 5, 120, weight_init=Normal(0.02))
  13. self.fc2 = nn.Dense(120, 84, weight_init=Normal(0.02))
  14. self.fc3 = nn.Dense(84, num_class, weight_init=Normal(0.02))
  15. self.relu = nn.ReLU()
  16. self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2)
  17. self.flatten = nn.Flatten()
  18. #use the preceding operators to construct networks
  19. def construct(self, x):
  20. x = self.max_pool2d(self.relu(self.conv1(x)))
  21. x = self.max_pool2d(self.relu(self.conv2(x)))
  22. x = self.flatten(x)
  23. x = self.relu(self.fc1(x))
  24. x = self.relu(self.fc2(x))
  25. x = self.fc3(x)
  26. return x

定义损失函数及优化器

基本概念

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

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

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

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

定义损失函数

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

  1. from mindspore.nn.loss import SoftmaxCrossEntropyWithLogits

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

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

定义优化器

MindSpore支持的优化器有AdamAdamWeightDecayMomentum等。

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

  1. if __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机制,可以在训练过程中执行自定义逻辑,这里使用框架提供的ModelCheckpoint为例。 ModelCheckpoint可以保存网络模型和参数,以便进行后续的fine-tuning(微调)操作。

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

配置训练网络

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

  1. from mindspore.nn.metrics import Accuracy
  2. from mindspore.train.callback import LossMonitor
  3. from mindspore.train import Model
  4. ...
  5. def train_net(args, model, epoch_size, mnist_path, repeat_size, ckpoint_cb, sink_mode):
  6. """define the training method"""
  7. print("============== Starting Training ==============")
  8. #load training dataset
  9. ds_train = create_dataset(os.path.join(mnist_path, "train"), 32, repeat_size)
  10. model.train(epoch_size, ds_train, callbacks=[ckpoint_cb, LossMonitor()], dataset_sink_mode=sink_mode)
  11. ...
  12. if __name__ == "__main__":
  13. ...
  14. epoch_size = 1
  15. mnist_path = "./MNIST_Data"
  16. repeat_size = 1
  17. model = Model(network, net_loss, net_opt, metrics={"Accuracy": Accuracy()})
  18. train_net(args, model, epoch_size, mnist_path, repeat_size, ckpoint_cb, dataset_sink_mode)
  19. ...

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

运行并查看结果

使用以下命令运行脚本:

  1. python lenet.py --device_target=CPU

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

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

  1. epoch: 1 step: 1, loss is 2.3025916
  2. epoch: 1 step: 2, loss is 2.302577
  3. epoch: 1 step: 3, loss is 2.3023994
  4. epoch: 1 step: 4, loss is 2.303059
  5. epoch: 1 step: 5, loss is 2.3025753
  6. epoch: 1 step: 6, loss is 2.3027692
  7. epoch: 1 step: 7, loss is 2.3026521
  8. epoch: 1 step: 8, loss is 2.3014607
  9. ...
  10. epoch: 1 step: 1871, loss is 0.048939988
  11. epoch: 1 step: 1872, loss is 0.028885357
  12. epoch: 1 step: 1873, loss is 0.09475248
  13. epoch: 1 step: 1874, loss is 0.046067055
  14. epoch: 1 step: 1875, loss is 0.12366105

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

  1. checkpoint_lenet-1_1875.ckpt

其中,
checkpoint_lenet-1_1875.ckpt:指保存的模型参数文件。名称具体含义checkpoint_网络名称-第几个epoch_第几个step.ckpt。

验证模型

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

  1. 使用model.eval接口读入测试数据集。

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

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

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

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

  1. python lenet.py --device_target=CPU

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

运行结果示例如下:

  1. ...
  2. ============== Starting Testing ==============
  3. ============== Accuracy:{'Accuracy': 0.9663477564102564} ==============

可以在打印信息中看出模型精度数据,示例中精度数据达到96.6%,模型质量良好。随着网络迭代次数epoch_size增加,模型精度会进一步提高。