PyTorch迁移MindSpore学习记录(下)
小网络迁移到MindSpore
书接上一会:https://www.hiascend.com/forum/thread-0244128586212080089-1-1.html
训练迁移
有了数据集和网络,接下来就是训练配置了。由MindSpore的官网资料,发现有很多种训练的模式,什么数据下沉、图下沉、循环下沉,看起来比较复杂。本文章只略微探索一下正常用法和数据下沉,以后有机会再讲别的。
正常迭代训练
参考了官网模型训练章节,我发现其实写一个for循环训练,跟PyTorch也差不大多,我就直接按我(半)改好的代码做一个展示
怎么说呢,无论哪一种训练的写法,都是一个习惯问题吧。像PyTorch的话,写多了就会习惯拿到一条数据先zero_grad,然后计算一下网络结果,最后再loss.backward() + optimizer.step()。而MindSpore也是类似吧,拿到数据,计算loss,更新优化器,反复循环。
参考资料
填坑
运行上述代码会提示 TypeError: For 'CrossEntropyLoss', the 'labels' should be Int32, but got 'mindspore.int64',发现交叉熵的输入label要求是int32的,但是给了int64的。这里需要修正一下grad_fn(item[0]["image"], item[0]["label"])为grad_fn(item[0]["image"], item[0]["label"].astype(ms.int32)),修复完之后就可以看到训练结果
数据下沉训练
根据官网的介绍,MindSpore有一种数据下沉的训练方法,可以帮助加速训练,但是需要用到一个高阶封装接口:mindspore.Model 。这个接口调试起来不大容易,还是老样子先上(半)改好的代码
ms.Model 就是MindSpore提供的高阶封装接口,用于简化训练代码,可以看到Model接口接收了损失函数、优化器,内部自动进行匹配计算。另外为了启动数据下沉模式,代码里调用了Model.train接口并把参数dataset_sink_mode置成了True,这样就可以开启有训练加速效果的数据下沉模式。
但其实这个代码是跑不通的,因为还有2个坑要解决一下!
1.运行报错 NotImplementedError: The python type <class 'numpy.object_'> cannot be converted to MindSpore type.
一开始我在找<class 'numpy.object_'>这个类型是从哪里来的,观察了一下调用栈
看到有调用 dataset.output_types() 获取类型的地方,我自己也尝试了一下
原数据看起来是一个list包含了一个dictionary,然后dictionary包含了2个字段分别是image和label,应该是这种类型在MindSpore中认为是Object对象,从而不能传入到网络里训练。经过了一些资料查找,我在这里官网文档也找到了印证:数据处理Python对象。
而且在这篇文档里也描述了数据下沉模式下,字典类型的数据是不能发送到网络里的,官网的原话是:“在数据下沉模式下,由于数据下沉通道当前无法支持字典类型的数据,字典类型的数据发送到下沉通道会造成错误”。同时官网也给出了解决方案:“因此可以考虑关闭数据下沉模式(dataset_sink_mode=False),或在最后一个数据处理节点将字典类型的数据展开为列表或元组类型的数据”。
有了官网的指导,我再进行了一些调试,以下是对代码的调整:
关闭数据下沉模式:这个不是我想要的,我就是需要启动数据下沉模式训练,忽略。
将字典类型的数据展开为列表或元组类型的数据:其实就是把dict展开为tuple,按照官网给出的例子,我给dataloader增加一个map操作
按照官网操作完后,dataloaer返回的数据就从dict("image", "label"),转变为tuple(image, label),代码就跑下去了(神奇)。
我再继续深究了一下,应该是tuple的第一个元素image被送到的了网络里,用于construct的计算,然后第二个元素label被送到了损失计算的函数里,用于计算损失,就刚好符合了ms.Model的输入格式。总体来说,ms.Model还是一个比较高阶的封装接口,用起来还是需要了解一下原理和资料,这些需要慢慢在官网上查才行。
2.运行报错 TypeError: For 'CrossEntropyLoss', the 'labels' should be Int32, but got 'mindspore.int64',提示label的类型仍然是int64,需要调整为int32。
这个时候没有grad_fn可以修改了,我的想法是直接修改了数据集__getitem__返回的数据类型,修改如下
原代码
新代码
经过这两步的坑,这个代码终于也是跑起来了。
=================================================================
本次把迁移PyTorch网络到MindSpore的学习基本完成了,是非常开心的~ 不过这个网络有点简单,MindSpore还有很多特性可以进一步学习和了解,下次再尝试搭建复杂一点的任务
=================================================================
参考资料
高阶封装Model: https://www.mindspore.cn/tutorials/zh-CN/r2.1/advanced/model.html
python dict使用指导 https://www.mindspore.cn/tutorials/zh-CN/r2.1/advanced/dataset/python_objects.html
下沉模式介绍: https://www.mindspore.cn/tutorials/experts/zh-CN/r2.1/optimize/execution_opt.html
填坑
附录:完整代码
正常迭代训练
import numpy as np
from PIL import Image
import mindspore as ms
import mindspore.nn as nn
import mindspore.dataset as ds
import mindspore.dataset.vision as vision
from mindspore import context
from mindspore import ops as F
ms.set_context(device_target="Ascend")
# No need inherit
class ImageDataset:
def __init__(self, file_paths, labels):
self.file_paths = file_paths
self.labels = np.array(labels).astype(np.int32)
def __len__(self):
return len(self.file_paths)
def __getitem__(self, idx):
img = Image.open(self.file_paths[idx]).convert('RGB')
img = vision.Resize((32, 32))(img)
img = vision.ToTensor()(img)
label = self.labels[idx]
return {"image": img, "label": label}
# nn.Module -> nn.Cell
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
# conv param mapping
self.conv1 = nn.Conv2d(3, 32, 3, 1, has_bias=True, pad_mode='valid')
self.conv2 = nn.Conv2d(32, 64, 3, 1, has_bias=True, pad_mode='valid')
# Linear to Dense
self.fc1 = nn.Dense(12544, 128)
self.fc2 = nn.Dense(128, 10)
# forward to construct
def construct(self, x):
#x = x["image"]
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
# cast to fp16
x = F.cast(x, ms.float16)
x = F.max_pool2d(x, 2)
# cast back to fp32
x = F.cast(x, ms.float32)
x = F.flatten(x)
x = F.relu(self.fc1(x))
x = self.fc2(x)
# dim to axis
output = F.log_softmax(x, axis=1)
return output
dataset = ImageDataset(["./images/class1/n10148035_108.JPEG",
"./images/class2/n11879895_10055.JPEG",
"./images/class3/n13052670_1088.JPEG",
"./images/class3/n13052670_1088.JPEG"],
[0, 1, 2, 3])
dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2)
model = Net()
# train
loss_fn = nn.CrossEntropyLoss()
optimizer = nn.SGD(model.trainable_params(), 1e-2)
def forward_fn(data, label):
logits = model(data)
loss = loss_fn(logits, label)
return loss, logits
# Get gradient function
grad_fn = ms.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)
epochs = 3
for epoch in range(1, epochs+1):
model.set_train()
for batch, item in enumerate(dataloader):
(loss, _), grads = grad_fn(item[0]["image"], item[0]["label"].astype(ms.int32))
optimizer(grads)
loss, current = loss.asnumpy(), batch
print(f"loss: {loss:>7f} [{current+1:>3d}/{len(dataloader):>3d}]") 数据下沉训练
import numpy as np
from PIL import Image
import mindspore as ms
import mindspore.nn as nn
import mindspore.dataset as ds
import mindspore.dataset.vision as vision
from mindspore import context
from mindspore import ops as F
ms.set_context(device_target="Ascend")
# No need inherit
class ImageDataset:
def __init__(self, file_paths, labels):
self.file_paths = file_paths
self.labels = np.array(labels).astype(np.int32)
def __len__(self):
return len(self.file_paths)
def __getitem__(self, idx):
img = Image.open(self.file_paths[idx]).convert('RGB')
img = vision.Resize((32, 32))(img)
img = vision.ToTensor()(img)
label = np.array(self.labels[idx], dtype=np.int32)
return {"image": img, "label": label}
# nn.Module -> nn.Cell
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
# conv param mapping
self.conv1 = nn.Conv2d(3, 32, 3, 1, has_bias=True, pad_mode='valid')
self.conv2 = nn.Conv2d(32, 64, 3, 1, has_bias=True, pad_mode='valid')
# Linear to Dense
self.fc1 = nn.Dense(12544, 128)
self.fc2 = nn.Dense(128, 10)
# forward to construct
def construct(self, x):
#x = x["image"]
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
# cast to fp16
x = F.cast(x, ms.float16)
x = F.max_pool2d(x, 2)
# cast back to fp32
x = F.cast(x, ms.float32)
x = F.flatten(x)
x = F.relu(self.fc1(x))
x = self.fc2(x)
# dim to axis
output = F.log_softmax(x, axis=1)
return output
dataset = ImageDataset(["./images/class1/n10148035_108.JPEG",
"./images/class2/n11879895_10055.JPEG",
"./images/class3/n13052670_1088.JPEG",
"./images/class3/n13052670_1088.JPEG"],
[0, 1, 2, 3])
dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2)
model = Net()
# train
loss_fn = nn.CrossEntropyLoss()
optimizer = nn.SGD(model.trainable_params(), 1e-2)
loss_callback = ms.LossMonitor()
trainer = ms.Model(model, loss_fn=loss_fn, optimizer=optimizer, metrics={'accuracy'})
def dict_to_tuple(d):
return tuple([i for i in d.values()])
# flatten the dict object bedfore it passed into network
dataloader = dataloader.map(dict_to_tuple, input_columns=['data'], output_columns=['my_data', 'my_data2'])
epochs = 3
trainer.train(epochs, dataloader, callbacks=[loss_callback], dataset_sink_mode=True)
PyTorch迁移MindSpore学习记录(下)
小网络迁移到MindSpore
书接上一会:https://www.hiascend.com/forum/thread-0244128586212080089-1-1.html
训练迁移
有了数据集和网络,接下来就是训练配置了。由MindSpore的官网资料,发现有很多种训练的模式,什么数据下沉、图下沉、循环下沉,看起来比较复杂。本文章只略微探索一下正常用法和数据下沉,以后有机会再讲别的。
正常迭代训练
参考了官网模型训练章节,我发现其实写一个for循环训练,跟PyTorch也差不大多,我就直接按我(半)改好的代码做一个展示
import mindspore as ms import mindspore.nn as nn # 定义了数据集加载(后面会有统一代码) dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2) # 定义了网络结构(后面会有统一代码) model = Net() # 开始定义优化器和训练部分 loss_fn = nn.CrossEntropyLoss() optimizer = nn.SGD(model.trainable_params(), 1e-2) def forward_fn(data, label): logits = model(data) loss = loss_fn(logits, label) return loss, logits # Get gradient function grad_fn = ms.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True) epochs = 3 for epoch in range(1, epochs+1): model.set_train() for batch, item in enumerate(dataloader): (loss, _), grads = grad_fn(item[0]["image"], item[0]["label"]) optimizer(grads) loss, current = loss.asnumpy(), batch print(f"loss: {loss:>7f} [{current+1:>3d}/{len(dataloader):>3d}]")怎么说呢,无论哪一种训练的写法,都是一个习惯问题吧。像PyTorch的话,写多了就会习惯拿到一条数据先
zero_grad,然后计算一下网络结果,最后再loss.backward() + optimizer.step()。而MindSpore也是类似吧,拿到数据,计算loss,更新优化器,反复循环。运行上述代码会提示
TypeError: For 'CrossEntropyLoss', the 'labels' should be Int32, but got 'mindspore.int64',发现交叉熵的输入label要求是int32的,但是给了int64的。这里需要修正一下grad_fn(item[0]["image"], item[0]["label"])为grad_fn(item[0]["image"], item[0]["label"].astype(ms.int32)),修复完之后就可以看到训练结果数据下沉训练
根据官网的介绍,MindSpore有一种数据下沉的训练方法,可以帮助加速训练,但是需要用到一个高阶封装接口:
mindspore.Model。这个接口调试起来不大容易,还是老样子先上(半)改好的代码import mindspore as ms import mindspore.nn as nn # 定义了数据集加载(后面会有统一代码) dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2) # 定义了网络结构(后面会有统一代码) model = Net() # 开始定义优化器和训练部分 loss_fn = nn.CrossEntropyLoss() optimizer = nn.SGD(model.trainable_params(), 1e-2) loss_callback = ms.LossMonitor() trainer = ms.Model(model, loss_fn=loss_fn, optimizer=optimizer, metrics={'accuracy'}) epochs = 3 trainer.train(epochs, dataloader, callbacks=[loss_callback], dataset_sink_mode=True)ms.Model就是MindSpore提供的高阶封装接口,用于简化训练代码,可以看到Model接口接收了损失函数、优化器,内部自动进行匹配计算。另外为了启动数据下沉模式,代码里调用了Model.train接口并把参数dataset_sink_mode置成了True,这样就可以开启有训练加速效果的数据下沉模式。但其实这个代码是跑不通的,因为还有2个坑要解决一下!
1.运行报错
NotImplementedError: The python type <class 'numpy.object_'> cannot be converted to MindSpore type.一开始我在找
<class 'numpy.object_'>这个类型是从哪里来的,观察了一下调用栈File "mindspore/train/_utils.py", line 56, in _get_types_and_shapes dataset_types = _convert_type(dataset.output_types()) File "mindspore/train/_utils.py", line 49, in _convert_type ms_type = pytype_to_dtype(np_type) File "mindspore/common/dtype.py", line 206, in pytype_to_dtype raise NotImplementedError(f"The python type {obj} cannot be converted to MindSpore type.") NotImplementedError: The python type <class 'numpy.object_'> cannot be converted to MindSpore type.看到有调用
dataset.output_types()获取类型的地方,我自己也尝试了一下for data in dataloader: print(data) # 输出 # [{'image': Tensor(shape=[2, 3, 32, 32]...), 'label': Tensor(shape=[2], dtype=Int32, value= [2, 1])}] print(dataloader.output_shapes()) # 输出 [dtype('O')]原数据看起来是一个list包含了一个dictionary,然后dictionary包含了2个字段分别是image和label,应该是这种类型在MindSpore中认为是Object对象,从而不能传入到网络里训练。经过了一些资料查找,我在这里官网文档也找到了印证:数据处理Python对象。
而且在这篇文档里也描述了数据下沉模式下,字典类型的数据是不能发送到网络里的,官网的原话是:“在数据下沉模式下,由于数据下沉通道当前无法支持字典类型的数据,字典类型的数据发送到下沉通道会造成错误”。同时官网也给出了解决方案:“因此可以考虑关闭数据下沉模式(dataset_sink_mode=False),或在最后一个数据处理节点将字典类型的数据展开为列表或元组类型的数据”。
有了官网的指导,我再进行了一些调试,以下是对代码的调整:
关闭数据下沉模式:这个不是我想要的,我就是需要启动数据下沉模式训练,忽略。
将字典类型的数据展开为列表或元组类型的数据:其实就是把dict展开为tuple,按照官网给出的例子,我给dataloader增加一个map操作
dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2) # 硬抄官网资料 def dict_to_tuple(d): return tuple([i for i in d.values()]) # flatten the dict object bedfore it passed into network dataloader = dataloader.map(dict_to_tuple, input_columns=['data'], output_columns=['my_data', 'my_data2'])按照官网操作完后,dataloaer返回的数据就从
dict("image", "label"),转变为tuple(image, label),代码就跑下去了(神奇)。我再继续深究了一下,应该是tuple的第一个元素image被送到的了网络里,用于construct的计算,然后第二个元素label被送到了损失计算的函数里,用于计算损失,就刚好符合了
ms.Model的输入格式。总体来说,ms.Model还是一个比较高阶的封装接口,用起来还是需要了解一下原理和资料,这些需要慢慢在官网上查才行。2.运行报错
TypeError: For 'CrossEntropyLoss', the 'labels' should be Int32, but got 'mindspore.int64',提示label的类型仍然是int64,需要调整为int32。这个时候没有grad_fn可以修改了,我的想法是直接修改了数据集
__getitem__返回的数据类型,修改如下原代码
def __getitem__(self, idx): ... label = self.labels[idx]新代码
def __getitem__(self, idx): ... # 直接使用numpy修改数值类型,这样就不用担心后面交叉熵拿到的是int64类型的数据了 label = np.array(self.labels[idx], dtype=np.int32)经过这两步的坑,这个代码终于也是跑起来了。
=================================================================
本次把迁移PyTorch网络到MindSpore的学习基本完成了,是非常开心的~ 不过这个网络有点简单,MindSpore还有很多特性可以进一步学习和了解,下次再尝试搭建复杂一点的任务
=================================================================
高阶封装Model: https://www.mindspore.cn/tutorials/zh-CN/r2.1/advanced/model.html
python dict使用指导 https://www.mindspore.cn/tutorials/zh-CN/r2.1/advanced/dataset/python_objects.html
下沉模式介绍: https://www.mindspore.cn/tutorials/experts/zh-CN/r2.1/optimize/execution_opt.html
数据下沉模式时,网络不能接受dictionary作为输入,因此需要拆开数据对象为tuple,如上所述。
数据下沉模式里调整不了label的类型,需要直接在数据集的定义里顺带调整一下label的类型,如上所述。
附录:完整代码
正常迭代训练
import numpy as np from PIL import Image import mindspore as ms import mindspore.nn as nn import mindspore.dataset as ds import mindspore.dataset.vision as vision from mindspore import context from mindspore import ops as F ms.set_context(device_target="Ascend") # No need inherit class ImageDataset: def __init__(self, file_paths, labels): self.file_paths = file_paths self.labels = np.array(labels).astype(np.int32) def __len__(self): return len(self.file_paths) def __getitem__(self, idx): img = Image.open(self.file_paths[idx]).convert('RGB') img = vision.Resize((32, 32))(img) img = vision.ToTensor()(img) label = self.labels[idx] return {"image": img, "label": label} # nn.Module -> nn.Cell class Net(nn.Cell): def __init__(self): super(Net, self).__init__() # conv param mapping self.conv1 = nn.Conv2d(3, 32, 3, 1, has_bias=True, pad_mode='valid') self.conv2 = nn.Conv2d(32, 64, 3, 1, has_bias=True, pad_mode='valid') # Linear to Dense self.fc1 = nn.Dense(12544, 128) self.fc2 = nn.Dense(128, 10) # forward to construct def construct(self, x): #x = x["image"] x = F.relu(self.conv1(x)) x = F.relu(self.conv2(x)) # cast to fp16 x = F.cast(x, ms.float16) x = F.max_pool2d(x, 2) # cast back to fp32 x = F.cast(x, ms.float32) x = F.flatten(x) x = F.relu(self.fc1(x)) x = self.fc2(x) # dim to axis output = F.log_softmax(x, axis=1) return output dataset = ImageDataset(["./images/class1/n10148035_108.JPEG", "./images/class2/n11879895_10055.JPEG", "./images/class3/n13052670_1088.JPEG", "./images/class3/n13052670_1088.JPEG"], [0, 1, 2, 3]) dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2) model = Net() # train loss_fn = nn.CrossEntropyLoss() optimizer = nn.SGD(model.trainable_params(), 1e-2) def forward_fn(data, label): logits = model(data) loss = loss_fn(logits, label) return loss, logits # Get gradient function grad_fn = ms.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True) epochs = 3 for epoch in range(1, epochs+1): model.set_train() for batch, item in enumerate(dataloader): (loss, _), grads = grad_fn(item[0]["image"], item[0]["label"].astype(ms.int32)) optimizer(grads) loss, current = loss.asnumpy(), batch print(f"loss: {loss:>7f} [{current+1:>3d}/{len(dataloader):>3d}]")数据下沉训练
import numpy as np from PIL import Image import mindspore as ms import mindspore.nn as nn import mindspore.dataset as ds import mindspore.dataset.vision as vision from mindspore import context from mindspore import ops as F ms.set_context(device_target="Ascend") # No need inherit class ImageDataset: def __init__(self, file_paths, labels): self.file_paths = file_paths self.labels = np.array(labels).astype(np.int32) def __len__(self): return len(self.file_paths) def __getitem__(self, idx): img = Image.open(self.file_paths[idx]).convert('RGB') img = vision.Resize((32, 32))(img) img = vision.ToTensor()(img) label = np.array(self.labels[idx], dtype=np.int32) return {"image": img, "label": label} # nn.Module -> nn.Cell class Net(nn.Cell): def __init__(self): super(Net, self).__init__() # conv param mapping self.conv1 = nn.Conv2d(3, 32, 3, 1, has_bias=True, pad_mode='valid') self.conv2 = nn.Conv2d(32, 64, 3, 1, has_bias=True, pad_mode='valid') # Linear to Dense self.fc1 = nn.Dense(12544, 128) self.fc2 = nn.Dense(128, 10) # forward to construct def construct(self, x): #x = x["image"] x = F.relu(self.conv1(x)) x = F.relu(self.conv2(x)) # cast to fp16 x = F.cast(x, ms.float16) x = F.max_pool2d(x, 2) # cast back to fp32 x = F.cast(x, ms.float32) x = F.flatten(x) x = F.relu(self.fc1(x)) x = self.fc2(x) # dim to axis output = F.log_softmax(x, axis=1) return output dataset = ImageDataset(["./images/class1/n10148035_108.JPEG", "./images/class2/n11879895_10055.JPEG", "./images/class3/n13052670_1088.JPEG", "./images/class3/n13052670_1088.JPEG"], [0, 1, 2, 3]) dataloader = ds.GeneratorDataset(dataset, ["data"]).batch(2) model = Net() # train loss_fn = nn.CrossEntropyLoss() optimizer = nn.SGD(model.trainable_params(), 1e-2) loss_callback = ms.LossMonitor() trainer = ms.Model(model, loss_fn=loss_fn, optimizer=optimizer, metrics={'accuracy'}) def dict_to_tuple(d): return tuple([i for i in d.values()]) # flatten the dict object bedfore it passed into network dataloader = dataloader.map(dict_to_tuple, input_columns=['data'], output_columns=['my_data', 'my_data2']) epochs = 3 trainer.train(epochs, dataloader, callbacks=[loss_callback], dataset_sink_mode=True)