前言
Mindspore已经推出2.x版本,框架可用性有了很大提高,生态逐渐完善。依赖于华为提供的软硬件一体解决方案,Mindspore配合Ascend系列npu有非常好的训练和推理表现,尤其在边缘计算上有着极大的优势。
同为热门的深度学习框架,Mindspore和Pytorch有很多相似之处,也有各自的特性,具体的api对比参考以下链接:
本文将聚焦于数据处理方面,完成从Pytorch到Mindspore的迁移工作。
基础数据操作对比
不管是Pytorch还是Mindspore,都支持直接导入一部分数据集,我们以MNIST手写数字数据集为例,从Pytorch迁移至Mindspore。
以下是Pytorch示例代码:
# 导入依赖
import torchvision.datasets as datasets
import torchvision.transforms as transforms
import torch.utils.data.DataLoader as DataLoader
# 设置数据处理方式
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(
mean=[0.5, ],
std=[0.5, ]
)
])
# 训练集导入
data_train = datasets.MNIST(root='./data/', transform=transform, train=True, download=True)
# 测试集导入
data_test = datasets.MNIST(root='./data/', transform=transform, train=False, download=True)
上面的代码主要包含两部分:
- 定义数据处理方式(transformer)
- 导入数据集
对应的Mindspore代码会有一些改动,先给出代码:
# 导入依赖
from download import download
from mindspore.dataset import transforms, vision
from mindspore.dataset import MnistDataset
# 下载数据集
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/" \
"notebook/datasets/MNIST_Data.zip"
path = download(url, "./data/", kind="zip", replace=True)
# 设置数据处理方式
transform = transforms.Compose([
vision.Normalize(
mean=(0.5,),
std=(0.5,)
),
])
# 训练集导入
train_dataset = MnistDataset('./data/MNIST_Data/train')
# 测试集导入
test_dataset = MinistDataset('./data/MNIST_Data/test')
# 数据处理
train_dataset = train_dataset.map(composed, 'image')
test_dataset = train_dataset.map(composed, 'image')
从上面的代码不难看出,对数据集需要做的事情还是一样的,但是在api和流程上有些许区别:
- Mindspore只支持本地指定路径下的数据集导入,并不能像Pytorch一样可以自动下载,所以我们需要提前准备好数据集(或如上写出数据集下载的代码部分)
- 数据处理部分差别比较明显。首先,因为框架的机制原因,Mindspore没有ToTensor()函数,我们也不必担心数据类型不是Tensor,在数据集导入部分框架已经帮我们做了这部分的操作;其次,Mindspore并不支持在导入数据集的时候一并进行数据操作(transform),需要使用map()方法进行数据操作
- 除此之外,参考torchvision.datasets.MNIST和mindspore.dataset.MnistDataset两个类的文档不难发现,Mindspore提供了更多的操作参数:
class torchvision.datasets.MNIST(root: str, train: bool = True, transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, download: bool = False)
class mindspore.dataset.MnistDataset(dataset_dir, usage=None, num_samples=None, num_parallel_workers=None, shuffle=None, sampler=None, num_shards=None, shard_id=None, cache=None)
- 诸如shuffle等参数,Pytorch需要在DataLoader中进行操作,而Mindspore在Dataset中就可以进行相应的操作 。其他参数请根据自己的项目需要参考api对照表进行
自定义数据集对比
除了框架本身会给我们提供的数据集之外,特殊情况下我们也需要自定义数据来训练,下面我们来对比自定义数据集的异同。
首先,我们需要使用构造自定义数据集类或自定义数据集生成函数的方式来生成数据集,Pytorch和Mindspore中都类似的方式供我们使用)。
下面我们以输入自定义图像为例子:
在Pytorch中,我们使用torch.utils.data.Dataset作为基类,基本使用代码如下:
import os
from PIL import Image
import numpy as np
from torch.utils.data import Dataset
class MyDataset(Dataset):
def __init__(self, imgdir, imgpath, train=True,
transform=None, target_transform=None):
self.root = os.path.expanduser(imgdir) # 设定数据集根目录
self.transform = transform # 图像转换函数
self.target_transform = target_transform # 目标转换函数
self.train = train # 设定训练集或者测试集
# 加载numpy数组
if self.train:
self.train_data = []
self.train_labels = []
with open(imgpath, "r") as imgpath:
for line in imgpath:
line = line.split(' ')
image = Image.open(line[0])
image = np.array(image)
self.train_data.append(image)
self.train_labels.append(int(line[1]))
imgpath.close()
self.train_data = np.array(self.train_data)
self.train_data = self.train_data.reshape((1000, 3, 32, 32)) # 转换形状
self.train_data = self.train_data.transpose((0, 2, 3, 1)) # 转换通道顺序为HWC
else:
self.test_data = []
self.test_labels = []
with open(imgpath, "r") as imgpath:
for line in imgpath:
line = line.split(' ')
image = Image.open(line[0])
image = np.array(image)
self.test_data.append(image)
self.test_labels.append(int(line[1]))
imgpath.close()
self.test_data = np.array(self.test_data)
self.test_data = self.test_data.reshape((200, 3, 32, 32))
self.test_data = self.test_data.transpose((0, 2, 3, 1))
def __getitem__(self, index):
if self.train:
img, target = self.train_data[index], self.train_labels[index]
else:
img, target = self.test_data[index], self.test_labels[index]
img = Image.fromarray(img) # 将numpy数组转换为PIL图像
if self.transform is not None:
img = self.transform(img)
if self.target_transform is not None:
target = self.target_transform(target)
return img, target
def __len__(self):
if self.train:
return len(self.train_data)
else:
return len(self.test_data)
从上面代码可以看出,我们自定义的数据集类需要实现Dataset基类的两个方法:__getitem__()
和__len__()
,分别提供了获取数据项和数据集规模的方法,在__init__()
函数中应该设置相应的数据集处理、转换(比如转换成np数组),确保框架能够处理数据集。
在使用方面,下面是示例代码:
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
trainset = MyDataset(imgdir='./Train', imgpath='./Train.txt', transform=transform_train)
trainloader = DataLoader(trainset, batch_size=8, shuffle=True, num_workers=2)
testset = MyDataset(imgdir='./Test', imgpath='./Test.txt', train=False, transform=transform_test)
testloader = DataLoader(testset, batch_size=8, shuffle=False, num_workers=2)
可见在数据处理的定义上与上面的情况无异,但是在这里,我们使用DataLoader对数据进行引入和变换处理。如果使用Mindspore,应该如何编写呢?
以下为自定义数据集类的代码示例:
import os
import numpy as np
from PIL import Image
class MyDataset:
def __init__(self, imgdir, imgpath, train=True):
self.root = os.path.expanduser(imgdir)
self.train = train
if self.train:
self.train_data = []
self.train_labels = []
with open(imgpath, "r") as imgpath:
for line in imgpath:
line = line.split(' ')
image = Image.open(line[0])
image = np.array(image)
self.train_data.append(image)
self.train_labels.append(int(line[1]))
imgpath.close()
self.train_data = np.array(self.train_data)
self.train_data = self.train_data.reshape((1000, 3, 32, 32))
self.train_data = self.train_data.transpose((0, 2, 3, 1))
else:
self.test_data = []
self.test_labels = []
with open(imgpath, "r") as imgpath:
for line in imgpath:
line = line.split(' ')
image = Image.open(line[0])
image = np.array(image)
self.test_data.append(image)
self.test_labels.append(int(line[1]))
imgpath.close()
self.test_data = np.array(self.test_data)
self.test_data = self.test_data.reshape((200, 3, 32, 32))
self.test_data = self.test_data.transpose((0, 2, 3, 1))
def __getitem__(self, index):
if self.train:
img, target = self.train_data[index], self.train_labels[index]
else:
img, target = self.test_data[index], self.test_labels[index]
return img, target
def __len__(self):
if self.train:
return len(self.train_data)
else:
return len(self.test_data)
从上面代码中可以看出,Mindspore没有相应的Dataset基类以供用户继承,而是要求用户自行编写一个包含__getitem__()
、__len__()
和__init__()
三个方法的类,其中大多数代码逻辑和PyTorch代码无异,但是在其中不能包含数据处理的相关操作。
以下是使用代码:
transform_train = transforms.Compose([
vision.RandomCrop(32, padding=4),
vision.RandomHorizontalFlip(),
vision.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
transform_test = transforms.Compose([
vision.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
dataset_train_it = MyDataset(imgdir='./Train', imgpath='./Train.txt', train=True)
dataset_test_it = MyDataset(imgdir='./Test', imgpath='./Test.txt', train=False)
train_data = ds.GeneratorDataset(dataset_train_it, ["image", "target"])
test_data = ds.GeneratorDataset(dataset_test_it, ["image", "target"])
train_data = train_data.map(operations=transform_train, input_columns=["image"])
test_data = test_data.map(operations=transform_test, input_columns=["image"])
由此可见,数据操作部分应当在GeneratorDataset操作过后再进行;同时也不难发现,在PyTorch中与之对应的是Dataloader。
PS: 虽然自定义数据集很多时候是必要的,但是如果使用常见开源数据集,建议使用Mindspore官方的数据集接口,因为这些接口底层通过C++实现,会带来更好的性能。
写在最后
随着Mindspore的持续发展,其生态已经有了很大进步。从本文分析的数据操作部分来讲,从其他深度学习框架迁移至Mindspore的成本并不高,只需少量代码就可以完成迁移。希望各位开发者能够多多使用Mindspore完成自己的创意,为Mindspore的生态添砖加瓦。