在开始之前,首先声明本篇文章参考官方实现一个图片分类应用,我基于官网的这篇文章加以自己的理解发表了这篇博客,希望大家能够更快更简单直观的体验MindSpore,如有不妥的地方欢迎大家指正。
【本文代码编译环境为MindSpore1.3.0 CPU版本】
准备环节
- 确保已安装MindSpore(可以根据自己的硬件情况安装,CPU,GPU,Ascend环境均可)
- 选择一个集成开发工具(Jupyter Notebook,Pycharm等),我选择的是Pycharm
- 查看是否安装python中的画图库 matplotlib,若未安装,在终端输入pip install matplotlib
数据集
下载数据集
手写数字识别初体验采用的是业界经典的MNIST数据集,它包含了60000张训练图片,10000张测试图片。
MindSpore中提供了很多经典数据集的下载命令,其中自然包括MNIST数据集。在Jupyter Notebook中执行如下命令下载MNIST数据集。
Copy!mkdir -p ./datasets/MNIST_Data/train ./datasets/MNIST_Data/test
!wget -NP ./datasets/MNIST_Data/train https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/mnist/train-labels-idx1-ubyte --no-check-certificate
!wget -NP ./datasets/MNIST_Data/train https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/mnist/train-images-idx3-ubyte --no-check-certificate
!wget -NP ./datasets/MNIST_Data/test https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/mnist/t10k-labels-idx1-ubyte --no-check-certificate
!wget -NP ./datasets/MNIST_Data/test https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/mnist/t10k-images-idx3-ubyte --no-check-certificate
!tree ./datasets/MNIST_Data
得到的文档目录结构如图所示:
./datasets/MNIST_Data
├── test
│ ├── t10k-images-idx3-ubyte
│ └── t10k-labels-idx1-ubyte
└── train
├── train-images-idx3-ubyte
└── train-labels-idx1-ubyte
2 directories, 4 files
./ 表示当前目录的意思,(当前项目中有一个datasets的文件夹,datasets文件夹下有一个MNIST_Data文件夹,然后依次类推)。
这里我没有采用命令下载的方法,因为我用的是pycharm。笔者可前往http://yann.lecun.com/exdb/mnist/下载MNIST数据集,并将它按照文档目录结构图的形式放置于我们的项目中。
配置运行信息
接着我们配置MindSpore的运行模式,硬件信息等。
from mindspore import context context.set_context(mode=context.GRAPH_MODE, device_target="CPU")
导入context模块,调用context的set_context方法,参数mode表示运行模式,MindSpore支持动态图和静态图两种模式,GRAPH_MODE表示静态图模式,PYNATIVE_MODE表示动态图模式,对于手写数字识别问题来说采用静态图模式即可。参数device_target表示硬件信息,有三个选项(CPU,GPU,Ascend),读者可根据自己的真实环境选择。
查看数据集的详细信息
既然我们已经将MNIST数据集下载到本地,那我们自然得去看一下它里面到底有什么东西,是否真的是我们熟知的手写阿拉伯数字。这里我们就要采用画图库了。
import matplotlib.pyplot as plt import matplotlib import numpy as np import mindspore.dataset as ds train_data_path = "./datasets/MNIST_Data/train" test_data_path = "./datasets/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() #asnumpy为Tensor中的方法,功能是将张量转化为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)) #squeeze将shape中为1的维度去掉,plt.imshow()函数负责对图像进行处理,并显示其格式 plt.title("number:%s"% item["label"].asnumpy()) plt.show() #show显示图像
代码部分的解释,我想注释已经写的很明白了,但我们需要注意几点。
- 我们尽量将自己拿到的数据集转化为MindSpore能够处理的数据类型,因为框架里面加了一些对于数据加速的方法。当然,这里的MNIST数据集不需要我们使用复杂的加载,因为对于MNIST这一类经典的数据集MindSpore都提供了专门的加载方法,将它加载成MindSpore能处理的数据。
- MindSpore提供的内置数据集处理方法默认输出一般都是在框架中通用的Tensor,但是对于非框架优化包含的python库,包括matplotlib,它们就无法处理接受Tensor,这是我们就要采用Tensor类中定义的asnumpy方法将张量转化为numpy数组
上述代码的运行截图:
上述结果验证了我们前面的说法,训练集中有60000张图片,以image和lable两个标签分别储存,并且每一张图片的大小为28 x 28,通道数为1说明里面是黑白图片。图中显示的图片是6(我觉得有点不像),它的标签值也为6。
加载及处理数据集
定义一个create_dataset函数。在这个函数中,总体分为一下几步。
- 将原始的MNIST数据集加载进来, mnist_ds = ds.MnistDataset(data_path)
- 设置一些数据增强和处理所需要的参数(parameters)
- 使用2中设置的参数得到一些实例化的数据增强和处理方法
- 将3中的方法映射到(使用)在对应数据集的相应部分(image,label)
- 返回处理好的数据集(不同的神经网络可以接受不同的输入数据格式)
create_dataset函数代码如下:
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 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 rescale = 1.0 / 255.0 shift = 0.0 rescale_nml = 1 / 0.3081 shift_nml = -1 * 0.1307 / 0.3081 # 根据上面所定义的参数生成对应的数据增强方法,即实例化对象 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() type_cast_op = C.TypeCast(mstype.int32) # 将数据增强处理方法映射到(使用)在对应数据集的相应部分(image,label) 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_ds = mnist_ds.batch(batch_size, drop_remainder=True) mnist_ds = mnist_ds.repeat(repeat_size) return mnist_ds
导入的模块:
- mindspore.dataset.vision.c_transforms:MindSpore提供的通过数据增强操作对图像进行预处理,从而提高模型的广泛性。它是基于基于C++的OpenCV实现,具有较高的性能。接着来看一下,在增强MNIST数据集中,我们所使用到该模块中的方法(算子)。
CV.Resize(size, interpolation=Inter.LINEAR):
功能:使用给定的插值模式将输入图像调整为给定大小。
参数:size:调整后输出图像的大小;interpolation:图像的插值模式(5个常量可供选择),一般情况下使用默认的Inter.LINEAR即可。
CV.Rescale(rescale, shift):
功能:使用给定的重缩放和移位重缩放输入图像。此运算符将使用以下命令重新缩放输入图像:输出=图像重新缩放+移位(output = image rescale + shift),可以用来消除图片在不同位置给模型带来的影响。
参数:rescale:缩放因子;shift:偏移因子
CV.HWC2CHW():
功能:将输入图像从形状(H,W,C)转换为形状(C,H,W)。输入图像应为3通道图像。
- mindspore.dataset.transforms.c_transforms:该模块也是一个用于支持常见的图形增强功能的模块,基于C++的OpenCV实现的。
C.TypeCast(data_type):
功能:一个转化为给定MindSpore数据类型(data_type)的张量操作。
参数:data_type:MindSpore中dtype模块中所指定的数据类型。
- mindspore.dataset.MnistDataset,MindSpore中专门提供的加载处理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:用于并行处理数据集的线程数,默认值为无。
参数含义
现在既然我们清楚了这些方法的功能以及他们中参数的含义,那么这里为什么要将参数定义成这样呢?
resize_height, resize_width = 32, 32
resize_op = CV.Resize((resize_height, resize_width), interpolation=Inter.LINEAR)
mnist_ds = mnist_ds.map(operations=resize_op, input_columns="image", num_parallel_workers=num_parallel_workers)
我们要将MNIST数据中的28 x 28大小的图片转化为32 x 32的大小,这是因为后面我们要采用的LeNet-5的卷积神经网络训练模型,该模型要求输入图像的尺寸统一归一化为32 x 32,具体的关于该网络的细节,我会在构建网络时详细介绍。
rescale = 1.0 / 255.0, shift = 0.0
rescale_op = CV.Rescale(rescale, shift)
mnist_ds = mnist_ds.map(operations=rescale_op, input_columns="image", num_parallel_workers=num_parallel_workers)
这里我们将数据集中图像进行缩放,选择除以255是因为像素点的取值范围是(0,255)。该操作可以使得每个像素的数值大小在(0,1)范围中,可以提升训练效率。进行完该操作之后,有对图像进行了依次缩放,不过那个缩放的值应该是总结出来的能够进一步提高训练效率的值,需要大量实验才能得到,这里我直接是使用了官方代码上的值。
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的矩阵,这不就是我们推导计算过程中常用的值吗。因此,它能够方便我们进行数据训练。
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)
将数据集重复repeat_size次,需要注意得是该操作一般使用在batch操作之后。
原理讲完之后,让我们看一下处理好的数据集是什么样子的吧。
数据集的展示
ms_dataset = create_dataset(train_data_path)
print('Number of each group',ms_dataset.)
print('Number of groups in the dataset:', ms_dataset.get_dataset_size())
可以看到,我们将32张图片作为一个批次,将训练集总共分为了60000/32 = 1875个批次。接着我们拿出一个批次的数据将其形状等属性显示出来并将它进行可视化。
data = next(ms_dataset.create_dict_iterator(output_numpy=True)) images = data["image"] labels = data["label"] print('Tensor of image:', images.shape) print('Labels:', labels) count = 1 for i in images: plt.subplot(4, 8, count) plt.imshow(np.squeeze(i)) plt.title('num:%s'%labels[count-1]) plt.xticks([]) count += 1 plt.axis("off") plt.show()
从Tensor of image:可以看出我们在处理数据的操作中执行Resize和HWC2CHW成功将图片转化为1 x 32 x 32的,同时可以知道所谓的分批次就是将batch_size张图片放在一起,用矩阵表示就是将batch_size个矩阵合成一个大型的矩阵,每一次使用一个这样的大型矩阵去训练,一个一个来。这样可以避免过高维度数组造成的内存过大,训练时间过长的问题,接着看一下图片显示。
本篇文章讲到主要是手写数字识别初体验的准备以及数据集的加载与处理环节,希望大家能有所收获。在下一篇文章中,我将给大家介绍卷积神经网络LeNet-5的一些基础知识,以及如何使用MindSpore来构建出一个LeNet-5网络。