PyTorch迁移MindSpore学习记录(上)
研一的时候学习深度学习,一开始学的是PyTorch,确实挺好用的,也基于PyTorch实现了很多算法。
研一暑假突发兴趣学习一下国产框架MindSpore,看到一些资料说已经迭代到2.0了,API也比较类似PyTorch,特来学习一下。
由于我比较熟悉PyTorch,我计划把一个简单的PyTorch网络迁移到MindSpore,顺带学习下API是怎么使用的。
PyTorch小网络
由于不知道迁移起来麻不麻烦,我先搭了一个类LeNet的网络,层数比较少,对照起来也方便。
在数据加载方面,我用DataLoader读取了本地的4张图片(取自ImageNet),本地的目录结构如下:
总体上,就是加载了4张图片作为数据集,然后图片统一resize成(32,32)传到网络里,然后网络进行2次卷积和2次全连接,最后计算一下softmax,再把结果与label计算一下交叉熵损失,最后bp一下。
from PIL import Image
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
from torchvision import transforms
class ImageDataset(Dataset):
def __init__(self, file_paths, labels):
super(ImageDataset, self).__init__()
self.file_paths = file_paths
self.labels = labels
def __len__(self):
return len(self.file_paths)
def __getitem__(self, idx):
# [h, w, c]
img = Image.open(self.file_paths[idx]).convert('RGB')
# [c, h, w]
img = transforms.ToTensor()(img)
# [c, 32, 32]
img = transforms.Resize((32, 32))(img)
label = self.labels[idx]
return {"image": img, "label": label}
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, 1)
self.conv2 = nn.Conv2d(32, 64, 3, 1)
self.fc1 = nn.Linear(12544, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2)
x = torch.flatten(x, 1)
x = F.relu(self.fc1(x))
x = self.fc2(x)
output = F.log_softmax(x, dim=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, 2])
dataloader = DataLoader(dataset, batch_size=2)
model = Net()
optimizer = optim.Adadelta(model.parameters(), lr=0.001)
scheduler = StepLR(optimizer, step_size=1, gamma=0.7)
epochs = 3
for epoch in range(1, epochs+1):
model.train()
for batch_idx, item in enumerate(dataloader):
optimizer.zero_grad()
output = model(item["image"])
loss = F.nll_loss(output, item["label"])
loss.backward()
optimizer.step()
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, (batch_idx+1) , len(dataloader),
100. * (batch_idx+1) / len(dataloader), loss.item())) 运行结果如下
一共4张图片,batch size为2,。因此训练3个epoch,每个epoch训练2个batch的数据,loss收不收敛这个就忽略吧,毕竟就几张图片,也没训几轮。那么接下来开始迁移MindSpore。
小网络迁移到MindSpore
数据集迁移
查了一些MindSpore官网的资料,PyTorch的DataLoader跟MindSpore的GeneratorDataset用法还是很像的,也是定义一个dataset类,传入到DataLoader里(MindSpore是传到GeneratorDataset里),而dataset的定义不能说毫不相同,只能说一模一样。
我就大概照着写了写就实现了:
关于怎么验证数据对不对,我也大概查了查资料,就类似PyTorch那样迭代DataLoader对象就好了。
第一步接入数据集就做完了,整体感觉API还是挺类似的,体验也还行。
参考资料
填坑
当然在迁移数据集的时候,还有遇到了一个坑:PyTorch的图像数据处理torchvision,要求输入图像是(c, h, w)格式的,因此在__getitem__里,torch的写法会先调用transforms.ToTensor()(img)把图像转为(c, h, w)格式,再传入到transforms.Resize((32, 32))中,这样维度才对的上,计算才是正确的。而MindSpore的vision数据处理,是要求输入图像是(h, w, c)格式的,因此写法上需要先调用vision.Resize((32, 32))(img)把图像尺寸调整完,再调用vision.ToTensor()(img)把格式转为(c, h, w)格式传到网络里。这里踩坑踩了一会,但是其实数据可以直接for循环迭代调试,所以整体难度不高。
迭代数据集的时候,也就是从for d in dataloader拿到的输出d,其实是个tuple,他不是__getitem__返回的字典本身,这里一开始会有点不习惯,后面习惯了还好,其实d就是tuple(dict,)这样的数据,把值取出来就好了。
网络迁移
网络迁移,整体上API相似度还是蛮高的,我感觉对应关系和修改量就是
网络结构上差不大多,我是这么定义的
网络就迁移完了,API相似度和结构都大差不多。测试下网络输出:
参考资料
填坑
改完网络之后随便输入了不同维度的数据到网络中,由于fc的参数不匹配,其实会导致计算错误,这个时候PyTorch的报错信息会直观一些,比如他会提示 RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x12544 and 12534x128),直接就告诉说2个相乘的矩阵是什么维度。而MindSpore的日志是这样的 ValueError: For 'MatMul' the input dimensions must be equal, but got 'x1_col': 12544 and 'x2_row': 12534.,感觉不是很直观相乘的2个矩阵是什么维度。
max_pool2d在Ascend上只支持float16,所以网络修改在max_pool2d之前只能手动转为float16,在计算完在转回float32
=================================================================
迁移PyTorch网络到MindSpore的的进度完成了一半,数据集和网络都完成了迁移,今天就先梳理一下学习到的知识点,下次再把训练跑起来
=================================================================
PyTorch迁移MindSpore学习记录(上)
研一的时候学习深度学习,一开始学的是PyTorch,确实挺好用的,也基于PyTorch实现了很多算法。
研一暑假突发兴趣学习一下国产框架MindSpore,看到一些资料说已经迭代到2.0了,API也比较类似PyTorch,特来学习一下。
由于我比较熟悉PyTorch,我计划把一个简单的PyTorch网络迁移到MindSpore,顺带学习下API是怎么使用的。
PyTorch小网络
由于不知道迁移起来麻不麻烦,我先搭了一个类LeNet的网络,层数比较少,对照起来也方便。
在数据加载方面,我用DataLoader读取了本地的4张图片(取自ImageNet),本地的目录结构如下:
root@root:mindspore# tree images/ images/ ├── class1 │ └── n10148035_108.JPEG ├── class2 │ └── n11879895_10055.JPEG └── class3 └── n13052670_1088.JPEG总体上,就是加载了4张图片作为数据集,然后图片统一resize成(32,32)传到网络里,然后网络进行2次卷积和2次全连接,最后计算一下softmax,再把结果与label计算一下交叉熵损失,最后bp一下。
from PIL import Image import numpy as np import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.optim.lr_scheduler import StepLR from torch.utils.data import DataLoader from torch.utils.data import Dataset from torchvision import transforms class ImageDataset(Dataset): def __init__(self, file_paths, labels): super(ImageDataset, self).__init__() self.file_paths = file_paths self.labels = labels def __len__(self): return len(self.file_paths) def __getitem__(self, idx): # [h, w, c] img = Image.open(self.file_paths[idx]).convert('RGB') # [c, h, w] img = transforms.ToTensor()(img) # [c, 32, 32] img = transforms.Resize((32, 32))(img) label = self.labels[idx] return {"image": img, "label": label} class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 32, 3, 1) self.conv2 = nn.Conv2d(32, 64, 3, 1) self.fc1 = nn.Linear(12544, 128) self.fc2 = nn.Linear(128, 10) def forward(self, x): x = F.relu(self.conv1(x)) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2) x = torch.flatten(x, 1) x = F.relu(self.fc1(x)) x = self.fc2(x) output = F.log_softmax(x, dim=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, 2]) dataloader = DataLoader(dataset, batch_size=2) model = Net() optimizer = optim.Adadelta(model.parameters(), lr=0.001) scheduler = StepLR(optimizer, step_size=1, gamma=0.7) epochs = 3 for epoch in range(1, epochs+1): model.train() for batch_idx, item in enumerate(dataloader): optimizer.zero_grad() output = model(item["image"]) loss = F.nll_loss(output, item["label"]) loss.backward() optimizer.step() print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, (batch_idx+1) , len(dataloader), 100. * (batch_idx+1) / len(dataloader), loss.item()))运行结果如下
一共4张图片,batch size为2,。因此训练3个epoch,每个epoch训练2个batch的数据,loss收不收敛这个就忽略吧,毕竟就几张图片,也没训几轮。那么接下来开始迁移MindSpore。
小网络迁移到MindSpore
数据集迁移
查了一些MindSpore官网的资料,PyTorch的DataLoader跟MindSpore的GeneratorDataset用法还是很像的,也是定义一个dataset类,传入到DataLoader里(MindSpore是传到GeneratorDataset里),而dataset的定义不能说毫不相同,只能说一模一样。
我就大概照着写了写就实现了:
import numpy as np from PIL import Image import mindspore.dataset as ds import mindspore.dataset.vision as vision class ImageDataset: def __init__(self, file_paths, labels): self.file_paths = file_paths self.labels = labels 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} 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)关于怎么验证数据对不对,我也大概查了查资料,就类似PyTorch那样迭代DataLoader对象就好了。
for d in dataloader: data = d[0]["image"] label = d[0]["label"] print(data.shape, label)第一步接入数据集就做完了,整体感觉API还是挺类似的,体验也还行。
GeneratorDataset用法: https://www.mindspore.cn/docs/zh-CN/r2.1/api_python/dataset/mindspore.dataset.GeneratorDataset.html#mindspore.dataset.GeneratorDataset
API映射对比: https://www.mindspore.cn/docs/zh-CN/r2.1/note/api_mapping/pytorch_diff/DataLoader.html
数据迭代: https://www.mindspore.cn/docs/zh-CN/r2.1/api_python/samples/dataset/dataset_gallery.html
当然在迁移数据集的时候,还有遇到了一个坑:PyTorch的图像数据处理torchvision,要求输入图像是(c, h, w)格式的,因此在
__getitem__里,torch的写法会先调用transforms.ToTensor()(img)把图像转为(c, h, w)格式,再传入到transforms.Resize((32, 32))中,这样维度才对的上,计算才是正确的。而MindSpore的vision数据处理,是要求输入图像是(h, w, c)格式的,因此写法上需要先调用vision.Resize((32, 32))(img)把图像尺寸调整完,再调用vision.ToTensor()(img)把格式转为(c, h, w)格式传到网络里。这里踩坑踩了一会,但是其实数据可以直接for循环迭代调试,所以整体难度不高。迭代数据集的时候,也就是从
for d in dataloader拿到的输出d,其实是个tuple,他不是__getitem__返回的字典本身,这里一开始会有点不习惯,后面习惯了还好,其实d就是tuple(dict,)这样的数据,把值取出来就好了。网络迁移
网络迁移,整体上API相似度还是蛮高的,我感觉对应关系和修改量就是
网络结构上差不大多,我是这么定义的
import mindspore as ms import mindspore.nn as nn from mindspore import ops as F # 由于我有Ascend环境,所以手动设置了一下硬件后端 ms.set_context(device_target="Ascend") # nn.Module -> nn.Cell class Net(nn.Cell): def __init__(self): super(Net, self).__init__() # conv param mapping # 参考cid:link_4 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 # 参考cid:link_5 self.fc1 = nn.Dense(12544, 128) self.fc2 = nn.Dense(128, 10) # forward 改成 construct def construct(self, x): 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) # log_softmax的参数从dim 改成 axis output = F.log_softmax(x, axis=1) return output网络就迁移完了,API相似度和结构都大差不多。测试下网络输出:
conv默认值行为不同 https://www.mindspore.cn/docs/zh-CN/r2.1/note/api_mapping/pytorch_diff/Conv2d.html
fc定义 https://www.mindspore.cn/docs/zh-CN/r2.1/note/api_mapping/pytorch_diff/Dense.html
max_pool介绍 https://www.mindspore.cn/docs/zh-CN/r2.1/api_python/ops/mindspore.ops.max_pool2d.html?highlight=max_pool2d#mindspore.ops.max_pool2d
ops函数搜索页面 https://www.mindspore.cn/docs/zh-CN/r2.1/api_python/mindspore.ops.html
改完网络之后随便输入了不同维度的数据到网络中,由于fc的参数不匹配,其实会导致计算错误,这个时候PyTorch的报错信息会直观一些,比如他会提示
RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x12544 and 12534x128),直接就告诉说2个相乘的矩阵是什么维度。而MindSpore的日志是这样的ValueError: For 'MatMul' the input dimensions must be equal, but got 'x1_col': 12544 and 'x2_row': 12534.,感觉不是很直观相乘的2个矩阵是什么维度。max_pool2d在Ascend上只支持float16,所以网络修改在max_pool2d之前只能手动转为float16,在计算完在转回float32
=================================================================
迁移PyTorch网络到MindSpore的的进度完成了一半,数据集和网络都完成了迁移,今天就先梳理一下学习到的知识点,下次再把训练跑起来
=================================================================