• [功能调试] MindSpore论坛报错活动第四期---在静态图模式下使用try语法报错问题
    1 系统环境硬件环境(Ascend/GPU/CPU): CPUMindSpore版本: 1.9.0执行模式(动态图/静态图): 静态图Python版本: 3.7操作系统平台:linux2 报错信息2.1 报错信息/root/miniconda3/envs/bin/python /mnt/d/06_project/trouble-shooter/tests/proposer/master/test_compiler_get_context.pyTraceback (most recent call last):File "/mnt/d/06_project/trouble-shooter/troubleshooter/proposer/proposal_action.py", line 43, in proposal_wrapperreturn func(*args, **kw)File "/mnt/d/06_project/trouble-shooter/tests/proposer/master/test_compiler_get_context.py", line 93, in test2out = net(input1, input2)File "/root/miniconda3/envs/lib/python3.7/site-packages/mindspore/nn/cell.py", line 615, in __call__out = self.compile_and_run(*args)File "/root/miniconda3/envs/lib/python3.7/site-packages/mindspore/nn/cell.py", line 934, in compile_and_runself.compile(*inputs)File "/root/miniconda3/envs/lib/python3.7/site-packages/mindspore/nn/cell.py", line 908, in compilejit_config_dict=self._jit_config_dict)File "/root/miniconda3/envs/lib/python3.7/site-packages/mindspore/common/api.py", line 1363, in compileresult = self._graph_executor.compile(obj, args_list, phase, self._use_vm_mode())RuntimeError: Unsupported statement 'Try'.More details please refer to syntax support at https://www.mindspore.cn----------------------------------------------------- The Traceback of Net Construct Code:----------------------------------------------------# In file /root/miniconda3/envs/lib/python3.7/site-packages/mindspore/context.py:462try:----------------------------------------------------- C++ Call Stack: (For framework developers)----------------------------------------------------mindspore/ccsrc/pipeline/jit/parse/parse.cc:774 ParseStatement2.2 脚本代码"""This is a python code template"""import mindspore.context as contextimport mindspore.nn as nnfrom mindspore import Tensorfrom mindspore.nn import Cellfrom mindspore import opsfrom mindspore import dtype as mstypeimport mindsporecontext.set_context(mode=mindspore.GRAPH_MODE, pynative_synchronize=False)class Net1(nn.Cell):def __init__(self):super().__init__()self.add = ops.Add()self.sub = ops.Sub()self.mul = ops.Mul()self.div = ops.Div()def func(x, y):context.get_context("device_target")return self.div(x, y)def construct(self, x, y):a = self.sub(x, 1)b = self.add(a, y)c = self.mul(b, self.func(a, b))return cdef test2():input1 = Tensor(3, mstype.float32)input2 = Tensor(2, mstype.float32)net = Net1()out = net(input1, input2)print(out)if __name__ == '__main__':test2()3 根因分析******此处由用户补充详细的定位过程******根因可能原因: 静态图模式下,网络的construct函数、@ms_function修饰的函数、@ms.jit修饰的函数中出现如下问题:1)直接或者间接使用了try语法,此语法静态图模式不支持;2)直接或间接的调用了MindSpore的get_context接口(get_context接口会使用try语法),此接口不能在construct等函数中调用;补充说明:此处是间接使用了try语法,mindspore静态图目前不支持try语法,可以参考官网静态图语法支持的链接:cid:link_0 里面并没有try语句而且为什么间接使用了try语法,是因为直接调用get_context接口,具体可以通过代码查看,get_context接口调用了函数_context(),而_context()里面调用了try语法get_context() 是在def func()被调用,def func()是在def construct()被调用因此而报错处理建议:1)将try语法在函数中移除;2)函数中调用的get_context()接口,迁移到函数外调用;补充说明:此处由于是间接调用try, 而且try是在mindspore代码里面的不能修改mindspore代码删除try, 因此就把get_context()迁移到函数外使用即可4 解决方案******此处由用户填写******最终方案:2)函数中调用的get_context()接口,迁移到函数外调用;代码:"""This is a python code template"""import mindspore.context as contextimport mindspore.nn as nnfrom mindspore import Tensorfrom mindspore.nn import Cellfrom mindspore import opsfrom mindspore import dtype as mstypeimport mindsporecontext.set_context(mode=mindspore.GRAPH_MODE, pynative_synchronize=False)class Net1(nn.Cell): def __init__(self): super().__init__() self.add = ops.Add() self.sub = ops.Sub() self.mul = ops.Mul() self.div = ops.Div() def func(x, y): #context.get_context("device_target") return self.div(x, y) def construct(self, x, y): a = self.sub(x, 1) b = self.add(a, y) c = self.mul(b, self.func(a, b)) return cdef test2(): context.get_context("device_target") input1 = Tensor(3, mstype.float32) input2 = Tensor(2, mstype.float32) net = Net1() out = net(input1, input2) print(out)if __name__ == '__main__': test2()最终调试结果:
  • [基础知识] Mindspore众智项目 RBPN模型迁移经验分享
    前言我在2022.3-2022.10期间完成了Mindspore 昇腾众智的RBPN模型迁移项目。总的来说,我在该项目中收获良多,迁移过程中也踩过很多坑,在此写下这篇文章分享一下自己的开发经验和心得,避免刚接触模型迁移项目的同学们在某些地方踩同样的坑。项目目标完成RBPN模型从Pytorch到Mindspore框架的模型迁移任务使迁移后模型的精度和性能在Mindspore上的表现超过或者等同于Pytorch保证模型能够顺利导出并完成310推理并且精度性能达标流程以下是我在模型RBPN模型迁移过程中的具体流程:Pytorch源码。首先要做的就是跑通模型迁移项目的源码,有作者权重文件的建议直接测试一下精度是否与论文有偏差,没有权重文件的话建议有条件的同学完整的训练一遍再测试精度。(在后期交付中,模型精度和性能都需要比对Pytorch和Mindspore,跑一遍源码没有坏处)。MindSpore代码。源码没问题后,就可以着手开始迁移了。Mindspore-GPU。建议首先完成在GPU上实现mindspore代码,好处是它相对于torch来说运算是很相似的,不会出现跨度很大的问题,如果直接在ascend910芯片上实现mindspore代码,不一定能第一时间发现问题。Mindspore-ascend910。完成GPU上的mindspore代码实现后,很容易的就可以改成在ascend910上实现的代码。迁移过程中问题。比如Pytorch的算子没有对应的MindSpore算子或者算子的用法不一样等等(比如,Pytorch中三个以上的Tensor可以直接cat在一起,而MindSpore中的cat方法只能两两cat),这时候就需要迁移人员自己想办法解决问题。Pynative。 在Pynative模式下跑通代码,能在一个Epoch完成后保存ckpt文件即可。Graph。在Graph模式下跑通代码,提交的精度性能都是在Graph模式下训练的。训练。在完成Graph模式下跑通代码后,就要开始训练了。第一次训练尽量保持和torch源码一模一样的参数,优化器和初始化方法。单卡训练。采用单卡训练的时候可以直接运行.py脚本,只要选好卡就可以(命令如:python main.py --device_id=0)。多卡训练。采用多卡训练直接运行 .py脚本是不可行的,需要写好 .sh脚本(命令如:bash run_distribute_train.py --a a --b b ..... --f f)云上训练。当然的,如果需要庞大的训练资源,一般都是到云上去进行训练的(我用的是OpenI 启智的云服务器,具体用法可以参考官网说明)。910推理。在完成训练后,需要在ascend910机器上完成推理,达标精度。310推理。需要在310机器上完成模型导出和310推理,达标精度。以下是310推理的各种脚本代码。export.py 。完成模型的导出,导出的模型是一个 .mindir为后缀的文件。preprocess.py 。 数据预处理过程,model_zoo官方库中的大多模型都是采用将数据变为二进制流文件作为310推理的数据预处理的输出,该脚本执行后,会生成 .bin为后缀的二进制流文件。main.cc 。310推理核心代码,导入预处理的.bin文件和 .mindir的模型文件得到模型的输出结果,也是二进制流文件以 .bin为后缀。postprocess.py。完成推理,用模型得到的结果 .bin文件进行评估得到最后的评估结果。run_eval.sh。 310推理脚本,将上述脚本连在一起直接输出最终评估结果。提交。在910和310都满足精度性能后,就可以在gitee上提交pr, 上传自己的代码,通过pr 并且经过验证后就OK啦。开发心得与踩过的坑权重初始化。 找到适合模型的权重初始化很重要,RBPN模型一开始我采用的是HeNormal进行初始化的,这和torch源码一样,在GPU上的表现都是一开始很大的loss值,然后很快收敛到小于1的值。但是,在某些参数情况下ascend910上loss就有可能一直在4位数的值上波动,很难下降。后来改成normal初始化后就能正常收敛了。网络中的Tensor。 进入网络中的数据建议全都统一成Tensor。例如torch代码中的RBPN模型的一个输入是一个列表装入了6个Tensor, 同等情况下在Mindspore静态图中,就会在性能表现上大幅度降低,比原来慢好几倍,(我的理解是Mindspore静态图在生成图的时候由于每个step输入的列表的内存地址是随机的,所以要重构静态图,不能当成已构图来直接使用,所以变慢)总之,应尽量保证网络中都是用Tensor,而不适用列表,字典等结构。训练的写法。 主要是两种不同的写法如下:**model.train()**。这种方法对训练过程是封装好的。不用类似于pytorch那样自己写迭代过程,只要给定参数就可以直接训练。绝大部分官方库的模型都是采用这种方法实现训练的,简单高效。trainonestep。这种方法类似于pytorch。如果模型是多输入的可以考虑这种写法(官方库中基于Gan的模型基本都是用trainonestep的方法训练的)。loss_scale。这个参数是用来放大loss的,在计算过程中放大loss,算完后再缩小回去。当精度不太理想的时候,调整loss_scale可能会有意想不到的收获。如果使用trainonestep写法用到了loss_scale时,需要再反向的时候使用hyper_map的函数主动将loss缩小回去这点需要注意。num_workers。如果发现训练过程比pytorch的代码速度慢很多,请注意num_workers的设置。num_workers是另外运行python进程来帮助程序获取数据集的,当获取数据的速度慢于数据通过网络的速度的时候,就可以考虑增大num_workers的值了。为了提高性能可以测试一下更大的num_workers。梯度截断。 如果发现代码的loss一直维持在一个很高的区间波动,可以尝试使用梯度截断。多卡存储ckpt。mindspore里面的多卡逻辑可以理解成为每张卡创建一个文件夹,该文件夹包含所有运行需要的脚本。比如8卡就有8个文件夹,它们的相互独立,区别在于数据的并行。自然的如果按照单卡逻辑生成ckpt,那么每个文件夹下都会生成属于自己卡的ckpt文件。建议加入一个判断逻辑,只有0卡可以生成ckpt权重文件。debug。 无论是在IDE上还是直接在Linux服务器上,debug都很重要,debug是为了更好的理解模型中数据的细节部分,以便于更好的定位问题。如果遇到问题,最好debug和pytorch源码进行一步一步的比对,一般都能定位到问题。提issue。 如果遇到了自己解决不了的问题,可以上gitee/mindspore上面去提issue,打上相应的标签很快就会有工程师来帮助你解决问题。我的一个大坑。 我在启智云上训练的时候经常会训练到一半就自动停止,一开始我以为是启智的问题,后来在线下910机器上完整训练的时候发生了同样的问题,这时候才发现原来是发生了内存泄露。脚本运行时发生内存泄露会问题,随着运行时间不断增加,程序会不断变多,程序所占能存不断变大,直到占满内存资源,然后g。(issue:cid:link_0)调试过程:先进行控制变量的问题筛选,之后发现num_workers>1 时,才会发生内存泄露问题,num_workers=1的时候不会发生。针对num_workers=2进行debug寻找问题,发现在运行到迭代器(create_dict_iterator())后,系统会新建立 进程数=num_workers数的python进程。原因分析:我的代码使用的是trainonestep写法进行的训练,用了迭代器(create_dict_iterator()),迭代器会在num_workers>=2时创建新的进程从而帮助获取数据。但是我将迭代器的声明写到了for epoch循环里类似于下述这样:for epoch in range(args.start_iter, args.nEpochs + 1): e_loss = 0 t0 = time.time() for iteration, batch in enumerate(trainds.create_dict_iterator(), 1):在官方的model库里面有的模型就是如上述写法(我现在感觉这样的写法是有问题的),这表明每次在新的epoch开始时都会创建新的迭代器,之前的迭代器又不会释放掉,从而发生内存泄露,而DBPN可能没发生内存泄露的原因是,它没有使用多线程去抓取数据也就是num_workers=1。解决方案:将迭代器的生成写在for epoch循环的外面。结言我最想说的其实是,虽然我在项目迁移过程中遇到了很多问题,但最后都很好的解决了。这期间离不开朋友、工程师、项目相关人员的帮助。当遇到短期内解决不了的问题时,一定要积极的寻求帮助,有耐心的去一步步定位问题,解决问题。最后,RBPN顺利交付,希望以后也能多多参与众智项目。
  • [安装] “输入以下命令安装MindSpore 1.5.0版本”报错
    “输入以下命令安装MindSpore 1.5.0版本”报错
  • 🔈昇思MindSpore论坛迁移通知🔈
    ↵↵尊敬的昇思MindSpore用户:       感谢大家和MindSpore论坛的一路相伴,昇思MindSpore论坛将于2023年01月15日前迁移到新地址,届时此处论坛中的所以帖子将会被锁定,也不能发新帖子或评论,仅支持查看内容。欢迎大家到我们的新论坛中开启新的篇章。昇思MindSpore新论坛地址:cid:link_0解锁新论坛:在新论坛中发帖需要大家先注册账号,建议大家新账号的昵称与老账号一致哦!欢迎大家多多提问和分享技术干货,感谢大家支持和谅解。
  • [热门活动] MindSpore论坛报错活动第四期
    活动详情请移步到:MindSpore论坛报错活动第四期期待您的参与!!!
  • 万元奖金池,硬核赛道,第四届MindCon超级挑战赛等你来战!
    活动介绍MindCon极客周是昇思MindSpore每半年举办一次的开发者狂欢盛会。第四届MindCon极客周以“超级挑战赛”为主题,强势来袭!实战挑战:多领域多级别应用场景挑战任务,探索AI开源趣味世界。活动激励:万元挑战赛礼品与昇思官方证书,惊喜满满。大咖分享:各领域大咖分享前沿技术,感受前沿AI 技术。体验开源:体验特别学习小组(SIG)运作,与志同道合的开发者共同交流。活动详情任务一:超级知识卷挑战10道随机人工智能领域题(含选择题和填空题),让知识卷起来!任务令:每天3次挑战机会,每次100分钟。分数:选择题5分,填空题15分,最高80分。任务二:超级马拉松挑战下方指定赛题(四选一),疯狂累积分!西安旅游主题分类挑战外卖评论分类挑战美食图片分类挑战口罩检测识别挑战任务令:按赛题具体指定要求挑战,赛题为个人赛,不可组队。分数:1、成功完成一个赛题(符合赛题提交要求,并能跑通)且提交Ckpt文件加200分。2、每个赛题精度排名前两名者,加200分。任务三:超级掌门人分享昇思知识,夺最强掌门人!任务令:参加任一MindSpore特别兴趣小组(SIG)分享,不局限于本次参赛内容,可分享基于MindSpore做的任一项目开发分享,或对MindSpore认识等。联系小助手预约分享时间,微信:mindspore0328分数:完成一次分享加120分,最高120分。活动奖项一等奖(1名,总分至少480分):10000元京东卡+荣誉证书第二名(2名,总分至少400分):5000元京东卡+荣誉证书第三名(3名,总分至少300分):3000元京东卡+荣誉证书参与奖(数名,总分至少280分):MindSpore周边+荣誉证书活动时间
  • label.shape无法处于正确的值
    使用mindspore.nn.SoftmaxCrossEntropyWithLogits 交叉熵损失函数时,输入值label.shape的要求一直无法达到文档中shape(N, )这样的要求在运行自己的数据集时,label.shape一直处于以下的状态求解有什么方法可以降低label的维度
  • [技术干货] 2022年11月份昇腾问题求助合计
    1:Ascend 910使用pytorch报错:error code 507033cid:link_72:ATLAS原理图设计检视申请cid:link_8昇腾Atlas200DK的GPIO在python代码下如何作为输入端口?cid:link_9华为云AI服务器安装驱动固件cid:link_0基于Atlas310的云环境推理时出现报错cid:link_1安装CANN toolkit之后,执行npu-smi出错cid:link_2华为云ECS的AI加速型x86昇腾310镜像CentOS 7.6应该如何使用?cid:link_10ModelZoo里面的YOLOv5算法,训练出的模型支持CPU、Ascend310推理吗cid:link_3在运行Atlas500的样例时,上传文件到atlas中时出现问题,用的是mobaxtermcid:link_410.500小站如何挂载新的SSD固态硬盘NVMe M.2接口 TiPlus5000系列cid:link_511.昇腾设备相关信息查询 cid:link_11昇腾910B的训练服务器,装了mindspore1.9,搞点什么目标检测的模型实现起来比较快?cid:link_12请问 CANN 5.1.RC1.alpha005 版本的驱动下载链接在哪?昇腾官网下载列表里面没有这个版本的cid:link_6Atlaas200cid:link_13是否又视频时序进行多帧目标检测模型cid:link_14昇腾推理基础镜像的CentOS版本支持openEuler吗?cid:link_15MindStudio转换ONNX到OM模型时,Generating Model Graph没反应 https://bbs.huaweicloud.com/forum/thread-0245105173208313022-1-1.html
  • [执行问题] 向大佬请教一个在Mindspore中遇到的问题,ValueError: For 'MatMul', the input dimensions must be equal, but got 'x1_col':
    import os# os.environ['DEVICE_ID'] = '6'import numpy as npimport mindspore as msfrom mindspore import nnfrom mindspore import contextfrom mindspore import datasetfrom mindspore.train.callback import LossMonitorfrom mindspore.common.api import ms_functionfrom mindspore.ops import operations as Pfrom PIL import Image#当前实验选择算力为Ascend,如果在本地体验,参数device_target设置为"CPU”context.set_context(mode=context.GRAPH_MODE, device_target="Ascend")#要筛选的分辨率条件targetWidth=426targetHeight=640targetChannal=3#读取animal文件夹下所有文件的名字rootDir='animal'fileNameList=['cat','elephant','sheep']label_map = { 'cat': 0, 'elephant': 1, 'sheep': 2}X,Y=[],[]for fileName in fileNameList: fileDir=rootDir+'/'+fileName #print(fileDir) imgNameList=os.listdir(fileDir) #print(imgNameList) for imgName in imgNameList: imgDir=fileDir+'/'+imgName img=Image.open(imgDir) img=np.array(img) if(len(img.shape)==3): width,height,channal=img.shape if width==targetWidth and height==targetHeight and channal==targetChannal:#符合筛选条件的样本留下放到X,其标签放到Y X.append(img.flatten()) Y.append(label_map[fileName])#类别#print(X,Y)#划分训练集和测试集合sampleNum=len(X)train_idx = np.random.choice(sampleNum, int(sampleNum*0.8), replace=False)#取80%的样本作为训练集test_idx = np.array(list(set(range(sampleNum)) - set(train_idx)))#剩下的样本作为测试集X_train=[X[i].astype(np.float32) for i in range(len(X)) if i in train_idx]Y_train=[Y[i] for i in range(len(Y)) if i in train_idx]X_test=[X[i].astype(np.float32) for i in range(len(X)) if i in test_idx]Y_test=[Y[i] for i in range(len(Y)) if i in test_idx]XY_train = list(zip(X_train, Y_train))ds_train = dataset.GeneratorDataset(XY_train, ['x', 'y'])# ds_train.set_dataset_size(sampleNum)ds_train = ds_train.shuffle(buffer_size=sampleNum).batch(32, drop_remainder=True)XY_test = list(zip(X_test, Y_test))ds_test = dataset.GeneratorDataset(XY_test, ['x', 'y'])ds_test = ds_test.batch(30)#具体作用#print(XY_test)for e in X_train: print(e.shape)net = nn.Dense(targetWidth*targetHeight, 3)loss = nn.loss.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')opt = nn.optim.Momentum(net.trainable_params(), learning_rate=0.05, momentum=0.9)model = ms.train.Model(net, loss, opt, metrics={'acc', 'loss'})model.train(25, ds_train, callbacks=[LossMonitor(per_print_times=ds_train.get_dataset_size())], dataset_sink_mode=False)metrics = model.eval(ds_test)print(metrics)本小白正学习如何使用Mindspore,打算用逻辑回归对图片进行分类。输入到回归模型的每个样本数据都是426*640的图片经过flatten后产生的数组,该数组的shape为(817920,),X_train就是若干这样的数组组成的。构建完模型之后,开始训练时产生了如下的错误:[ERROR] ANALYZER(8534,ffffb5cca780,python):2022-11-30-10:59:18.593.719 [mindspore/ccsrc/pipeline/jit/static_analysis/async_eval_result.cc:66] HandleException] Exception happened, check the information as below.The function call stack (See file '/home/ma-user/work/rank_0/om/analyze_fail.dat' for more details):# 0 In file /home/ma-user/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/wrap/cell_wrapper.py(373) loss = self.network(*inputs) ^# 1 In file /home/ma-user/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/wrap/cell_wrapper.py(111) out = self._backbone(data) ^# 2 In file /home/ma-user/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/layer/basic.py(323) if len(x_shape) != 2:# 3 In file /home/ma-user/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/layer/basic.py(326) if self.has_bias:# 4 In file /home/ma-user/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/layer/basic.py(325) x = self.matmul(x, self.weight) ^---------------------------------------------------------------------------ValueError Traceback (most recent call last)/tmp/ipykernel_8534/2891349598.py in 1 model = ms.train.Model(net, loss, opt, metrics={'acc', 'loss'})----> 2 model.train(25, ds_train, callbacks=[LossMonitor(per_print_times=ds_train.get_dataset_size())], dataset_sink_mode=False) 3 metrics = model.eval(ds_test) 4 print(metrics)~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/train/model.py in train(self, epoch, train_dataset, callbacks, dataset_sink_mode, sink_size) 904 callbacks=callbacks, 905 dataset_sink_mode=dataset_sink_mode,--> 906 sink_size=sink_size) 907 908 def build(self, train_dataset=None, valid_dataset=None, sink_size=-1, epoch=1, jit_config=None):~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/train/model.py in wrapper(self, *args, **kwargs) 85 raise e 86 else:---> 87 func(self, *args, **kwargs) 88 return wrapper 89 ~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/train/model.py in _train(self, epoch, train_dataset, callbacks, dataset_sink_mode, sink_size) 540 self._check_reuse_dataset(train_dataset) 541 if not dataset_sink_mode:--> 542 self._train_process(epoch, train_dataset, list_callback, cb_params) 543 elif context.get_context("device_target") == "CPU": 544 logger.info("The CPU cannot support dataset sink mode currently."~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/train/model.py in _train_process(self, epoch, train_dataset, list_callback, cb_params) 792 cb_params.train_dataset_element = next_element 793 list_callback.step_begin(run_context)--> 794 outputs = self._train_network(*next_element) 795 cb_params.net_outputs = outputs 796 if self._loss_scale_manager and self._loss_scale_manager.get_drop_overflow_update():~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/cell.py in __call__(self, *args, **kwargs) 584 logger.warning(f"For 'Cell', it's not support hook function in graph mode. If you want to use hook " 585 f"function, please use context.set_context to set pynative mode.")--> 586 out = self.compile_and_run(*args) 587 return out 588 ~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/cell.py in compile_and_run(self, *inputs) 962 """ 963 self._auto_parallel_compile_and_run = True--> 964 self.compile(*inputs) 965 966 new_inputs = []~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/nn/cell.py in compile(self, *inputs) 935 """ 936 if self._dynamic_shape_inputs is None or self._dynamic_shape_inputs[0] is None:--> 937 _cell_graph_executor.compile(self, *inputs, phase=self.phase, auto_parallel_mode=self._auto_parallel_mode) 938 else: 939 self._check_compile_dynamic_shape(*inputs)~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/common/api.py in compile(self, obj, phase, do_convert, auto_parallel_mode, *args) 1004 enable_ge = context.get_context("enable_ge") 1005 self._graph_executor.set_weights_values(obj.parameters_dict())-> 1006 result = self._graph_executor.compile(obj, args_list, phase, self._use_vm_mode()) 1007 obj.compile_cache.add(phase) 1008 if not result:~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/ops/primitive.py in __check__(self, *args) 465 for track in tracks: 466 fn = getattr(self, 'check_' + track)--> 467 fn(*(x[track] for x in args)) 468 469 ~/anaconda3/envs/MindSpore/lib/python3.7/site-packages/mindspore/ops/operations/math_ops.py in check_shape(self, x1, x2) 1387 if np.all(np.array(x1) != -1) and np.all(np.array(x2) != -1): 1388 if x1_col != x2_row:-> 1389 raise ValueError(f"For '{cls_name}', the input dimensions must be equal, but got 'x1_col': {x1_col} " 1390 f"and 'x2_row': {x2_row}. And 'x' shape {x1}(transpose_a={self.transpose_a}), " 1391 f"'y' shape {x2}(transpose_b={self.transpose_b}).")ValueError: For 'MatMul', the input dimensions must be equal, but got 'x1_col': 817920 and 'x2_row': 272640. And 'x' shape [32, 817920](transpose_a=False), 'y' shape [3, 272640](transpose_b=True).请教各位有经验的大佬,我这个模型或者其他代码有什么问题,如何修改?
  • [其他] 浅谈DCGAN,推荐使用MindSporeDCGAN生成漫画头像数据集
    DCGAN(深度卷积对抗生成网络,Deep Convolutional Generative Adversarial Networks)是GAN的直接扩展。不同之处在于,DCGAN会分别在判别器和生成器中使用卷积和转置卷积层。论文:Unsupervised Representations Learning With Deep Convolutional Generative Adversarial Networks它最早由Radford等人在论文Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks中进行描述。判别器由分层的卷积层、BatchNorm层和LeakyReLU激活层组成。输入是3x64x64的图像,输出是该图像为真图像的概率。生成器则是由转置卷积层、BatchNorm层和ReLU激活层组成。输入是标准正态分布中提取出的隐向量z,输出是3x64x64的RGB图像。生成式对抗网络(Generative Adversarial Networks,GAN)是一种深度学习模型,是近年来复杂分布上无监督学习最具前景的方法之一。最初,GAN由Ian J. Goodfellow于2014年发明,并在论文Generative Adversarial Nets中首次进行了描述,GAN由两个不同的模型组成——生成器和判别器:生成器的任务是生成看起来像训练图像的“假”图像;判别器需要判断从生成器输出的图像是真实的训练图像还是虚假的图像。DCGAN的贡献:提出了一类基于卷积神经网络的GANs,称为DCGAN,它在多数情况下训练是稳定的。与其他非监督方法相比,DCGAN的discriminator提取到的图像特征更有效,更适合用于图像分类任务。通过训练,DCGAN能学到有意义的 filters。DCGAN的generator能够保持latentspace到image的“连续性”。动漫头像数据集华为云AI框架昇思MindSporeDCGAN生成漫画头像https://download.mindspore.cn/dataset/Faces/faces.zip
  • [论文解析] 以GENET为例,pytorch迁移到mindspore学习经验分享
    GENET简介GENet_Res50是一个基于GEBlock构建于ResNet50之上的卷积神经网络,可以将ImageNet图像分成1000个目标类,准确率达78.47%。在Resnet50的基础上,作者改进了原本的残差结构,设计了两个操作:gather和excite。gather从局部空间位置上提取特征,excite将gather提取到的特征还原回原来的尺度。这个过程类似于encoder-decoder模型,称为编码-解码模型。这个GEnet跟SEnet一样可以嵌入到任何卷积网络中,以很小的参数和计算复杂度为代价能够提升网络的性能,主要应用在目标检测中。论文cid:link_4环境要求昇腾处理器(Ascend)框架:MindSpore参考资源:MindSpore教程MindSpore Python API模型基础ResNet50从经验来看,当增加网络层数后,网络可以进行更加复杂的特征模式的提取,所以当模型更深时理论上可以取得更好的结果。但是更深的网络其性能一定会更好吗?实验发现深度网络出现了退化问题:网络深度增加时,网络准确度出现饱和,甚至出现下降。何凯明博士提出了残差学习来解决退化问题。对于一个堆积层结构,当输入为x时其学习到的特征记为 H(x) ,现在我们希望可以学习到残差F(x)=H(x)-x,这样其实原始的学习特征是F(x)+x。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。SENet对于CNN网络来说,其核心计算是卷积算子,其通过卷积核从输入特征图学习到新特征图。从本质上讲,卷积是对一个局部区域进行特征融合,这包括空间上(H和W维度)以及通道间(C维度)的特征融合。对于卷积操作,很大一部分工作是提高感受野,即空间上融合更多特征融合,或者是提取多尺度空间信息。SENet网络的创新点在于关注channel之间的关系,希望模型可以自动学习到不同channel特征的重要程度。为此,SENet提出了Squeeze-and-Excitation (SE)模块。Squeeze-and-Excitation:又称为特征重标定卷积,或者注意力机制。具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前用处不大的特征。Squeeze操作,先进行全局池化,具有全局的感受野,并且输出的维度和输入的特征通道数相匹配,它表征着在特征通道上响应的全局分布。Excitation操作,通过全连接层为每个特征通道生成权重,建立通道间的相关性,输出的权重看做是进过特征选择后的每个特征通道的重要性,然后通过乘法逐通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。SE模块很容易嵌入到其它网络中,在其它网络如ResNet和VGG中引入SE模块,都能提升网络的效果。SE模块主要为了提升模型对channel特征的敏感性,这个模块是轻量级的,而且可以应用在现有的网络结构中,只需要增加较少的计算量就可以带来性能的提升。GENet模型结构GENet其实是SENet的改进版,以便于更好地利用特征图空间上下文信息。Global-Avg-Pooling的方式已被SENet证明是有效的方式,且一系列Bag-of-Visual-words模型也表明:用汇集局部区域所得的局部描述子,来组建成新的表示,这种方法是有效的。 故基于SENet,GENet针对如何从特征图中提取出好的feature context,再用于特征图间重要程度的调控进行了研究。GENet定义了Gather算子和Excite算子,如图所示:gather operator: 从每个feature map中的每个空间邻域提取出context,即聚合了给定空间范围内的神经元响应。excite operator: 通过gather的值和输入值,来调整特征图它们的重要程度操作,产生一个与原始输入相同维度的新张量。代码算子映射代码里涉及的pytorch与mindspore算子的对应关系如下:pytorchmindspore功能torch.nnmindspore.nn构建神经网络的预定义构建块或计算单元torch.nn.Conv2d; torch.nn.BatchNorm2d ;torch.nn.ReLUmindspore.nn.Conv2dBnAct二维卷积层+归一化函数+ReLU激活函数torch.nn.Conv2dmindspore.nn.Conv2d二维卷积层torch.nn.BatchNorm2dmindspore.nn.BatchNorm2d归一化函数torch.nn.ModuleListmindspore.nn.CellList将Cell保存在List中torch.nn.Sequentialmindspore.nn.SequentialCellCell的List将按照它们在构造函数中传递的顺序添加到里面torch.nn.AdaptiveAvgPool2d(1,1)mindspore.ops.ReduceMean平均池化torch.nn.ReLUmindspore.nn.ReLUReLU激活函数torch.nn.Sigmoidnn.Sigmoidsigmoid激活函数torch.addmindspore.ops.Add将两个输入张量相加torch.mulmindspore.ops.Mul将两个输入张量相乘torch.nn.Flattenmindspore.nn.Flatten展平张量torch.nn.functional.one_hotmindspore.ops.OneHot把tensor转为one-hot向量形式nn.CrossEntropyLossnn.SoftmaxCrossEntropyWithLogits交叉熵损失torch.cuda.set_devicemindspore.context.set_context设置运行环境的contexttorch.nn.Module.load_state_dictmindspore.load_checkpoint mindspore.load_param_into_net加载模型权重torch.onnx.export;torch.jit.exportmindspore.export导出对应的模型文件:pytorch主要是jit和onnx文件;mindspore主要是导出AIR, MINDIR,ONNX文件torch.optim.SGDmindspore.nn.Momentum梯度下降优化器数据集使用的数据集:imagenet 2012数据集大小:144G,共1000个类、125万张彩色图像训练集:138G,共120万张图像测试集:6G,共5万张图像数据格式:RGB数据集处理主要分为四个步骤:定义函数create_dataset来创建数据集。定义需要进行的数据增强和处理操作,为之后进行map映射做准备。使用map映射函数,将数据操作应用到数据集。进行数据shuffle、batch操作。训练过程:初始化context# init context context.set_context(mode=context.GRAPH_MODE, device_target=target, save_graphs=False)是否分布式训练,若是,设置分布式训练的一些初始化的参数。if run_distribute: context.set_context(device_id=device_id, enable_auto_mixed_precision=True) context.set_auto_parallel_context(device_num=device_num, parallel_mode=ParallelMode.DATA_PARALLEL, gradients_mean=True) set_algo_parameters(elementwise_op_strategy_follow=True) context.set_auto_parallel_context(all_reduce_fusion_config=[85, 160]) init()创建数据集dataset = create_dataset(dataset_path=local_train_data_url, do_train=True, repeat_num=1, batch_size=config.batch_size, target=target, distribute=run_distribute) step_size = dataset.get_dataset_size()定义网络mlp = trans_char_to_bool(args_opt.mlp) extra = trans_char_to_bool(args_opt.extra) net = net(class_num=config.class_num, extra=extra, mlp=mlp)初始化权重如果有预训练权重,直接加载checkpoint;若没有,且是卷积操作,则用HeKaiMing均匀算法初始化数组,并从均匀分布中采集样本;若没有,且是全连接操作,则初始化用截断正态分布,是有界正态分布的。 if args_opt.pre_trained: param_dict = load_checkpoint(args_opt.pre_trained) load_param_into_net(net, param_dict) else: for _, cell in net.cells_and_names(): if isinstance(cell, nn.Conv2d): cell.weight.set_data(weight_init.initializer(weight_init.HeUniform(), cell.weight.shape, cell.weight.dtype)) if isinstance(cell, nn.Dense): cell.weight.set_data(weight_init.initializer(weight_init.TruncatedNormal(), cell.weight.shape, cell.weight.dtype)) lr = get_lr(config.lr_init, config.lr_end, config.epoch_size, step_size, config.decay_mode) lr = Tensor(lr)定义优化器采用的是Momentum优化器decayed_params = [] no_decayed_params = [] for param in net.trainable_params(): if 'beta' not in param.name and 'gamma' not in param.name and 'bias' not in param.name: decayed_params.append(param) else: no_decayed_params.append(param) group_params = [{'params': decayed_params, 'weight_decay': config.weight_decay}, {'params': no_decayed_params}, {'order_params': net.trainable_params()}] opt = Momentum(group_params, lr, config.momentum, loss_scale=config.loss_scale)定义loss和模型网络采用的是交叉熵损失函数。if target == "Ascend": if not config.use_label_smooth: config.label_smooth_factor = 0.0 loss = CrossEntropySmooth(sparse=True, reduction="mean", smooth_factor=config.label_smooth_factor, num_classes=config.class_num) loss_scale = FixedLossScaleManager(config.loss_scale, drop_overflow_update=False) model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, metrics={'acc'}, amp_level="O2", keep_batchnorm_fp32=False) else: raise ValueError("Unsupported device target.")定义callbackstime_cb = TimeMonitor(data_size=step_size) loss_cb = LossMonitor() rank_id = int(os.getenv("RANK_ID")) cb = [time_cb, loss_cb] if rank_id == 0: config_ck = CheckpointConfig(save_checkpoint_steps=config.save_checkpoint_epochs*step_size, keep_checkpoint_max=config.keep_checkpoint_max) ckpt_cb = ModelCheckpoint(prefix="GENet", directory=ckpt_save_dir, config=config_ck) cb += [ckpt_cb]进行训练dataset_sink_mode = target != "CPU" model.train(config.epoch_size, dataset, callbacks=cb, sink_size=dataset.get_dataset_size(), dataset_sink_mode=dataset_sink_mode) if device_id == 0 and args_opt.is_modelarts == "True": mox.file.copy_parallel(ckpt_save_dir, args_opt.train_url)遇到的问题从pytorch迁移到mindspore中遇到了很多问题,感谢论坛中的各位老师的帮助。 印象较深的主要有一下两点:问题1AdaptivePooling与AvgPooling昇腾上用mindspore.ops.AvgPool替换mindspore.ops.AdaptiveAvgPool2Dmindspore.ops.AdaptiveAvgPool2D只支持GPU,所以要用mindspore.ops.AvgPool替换AdaptivePooling与AvgPooling相互转换的时候卷积核和stride应该怎么设置mindspore.ops.AdaptiveAvgPool2D(output_size)mindspore.ops.AvgPool(kernel_size=1, strides=1, pad_mode="valid", data_format="NCHW")需要用output_size换算kernel_size和stridestride = floor(input_size/output_size) kernel_size = input_size − (output_size−1) * stride问题2在pynative模式下跑模型,会遇到The pointer[cnode] is null.的问题(跑到一半就报错)该问题主要是因为在自定义数据集里面使用了Tensor计算Tensor计算会调用底层的算子进行计算,但是数据处理是多线程并行处理因此会起多个线程进行计算,但是计算当前不支持多线程执行,因此报错;自定义数据集中的getitem中尽量不使用MindSpore的Tensor及相关操作,使用numpy
  • [MindX SDK] 二维码风格化迁移
    二维码风格化迁移1 案例概述1.1 概要描述在本系统中,目的是基于 MindX SDK 和 MindSpore 框架,在昇腾平台上,开发端到端二维码风格化迁移的参考设计,在保证扫码软件能正确扫码的条件下,对黑白二维码实现色彩化风格迁移的功能,达到功能要求。1.2 模型结构该论文通过模拟二维码采样网络层和虚拟读码器实现了风格化二维码图像的任务,模型的总体流程如下图所示。其中,模型结构如下:(1)VGG-16 网络:用于提取输入风格图像、输入内容图像和生成风格化二维码图像的深度特征。(2)模拟采样层:如下图所示,通过将卷积核权重设置为标准高斯分布,长宽与二维码分块标准一致,符合二维码采样过程中“越靠近分块中心的点越容易被采样”的原则,将二维码采样过程拟合为卷积操作。(3)虚拟读码器:如下图所示,用于比较风格化二维码与真实二维码读码结果,如果读码结果正确则继续美化风格和样式;如果不正确则考虑鲁棒性。(4)损失函数:模型使用了三个损失函数,分别用于约束图像风格、图像内容和读码精确度,分别对应公式 (1),公式 (2) 和公式 (3)。其中 $L_s$ 为输入风格图像;$L_c$ 为输入内容图像;$Q$ 为生成的二维码图像;$C, H, W$ 为对应图像的尺寸;$f$ 为 VGG-16 网络生成的深度特征图像;$G$ 为用于风格迁移任务的格拉姆矩阵变换;$L_{code}^{M_k}$ 为虚拟读码器对不同分块的读码结果,相同为 $0$。$$ L_{\text {style }}\left(I_s, Q\right)=\frac{1}{C_s H_s W_s}\left|G\left[f_s\left(I_s\right)\right]-G\left[f_s(Q)\right]\right|_2^2 \tag{1}\ $$$$ L_{\text {content }}\left(I_c, Q\right)=\frac{1}{C_c H_c W_c}\left|f_c\left(I_c\right)-f_c(Q)\right|_2^2 \tag{2} \ $$$$ L_{\text {code }}=\sum_{M_k \in Q} L_{\text {code }}^{M_k} \tag{3} $$1.3 模型介绍本项目主要用到了两个模型,分别是:用于图像特征提取的模型 VGG-16 和用于模拟二维码扫码的 SSLayer 模型。VGG-16 模型的相关文件可在此处下载:cid:link_1SSLayer 模型参数固定,内嵌于代码中,在初始化模型时指定所使用的公开数据集可以在此处下载:cid:link_01.4 实现流程实现流程如下:1、基础环境:MindSpore、Ascend 910、Ascend-CANN-toolkit、Ascend Driver 2、业务流程编排与配置 3、Python 训练流程代码开发 4、模型效果及性能对齐1.5 技术流程技术流程图如下:1.6 代码地址本项目的代码地址为:cid:link_22 软件方案介绍2.1 项目方案架构介绍本系统的主要流程为:加载预训练的 Vgg16 模型和固定参数的 SSLayer 模型,解码载入二维码图、内容图和风格图,通过数据变换将图像 Resize 到适当大小,并根据二维码图和内容图初始化风格化图像,并将其设为梯度优化更新的参数;利用 Vgg16 模型和 SSLayer 模型得到对应的图像特征,计算 style 损失、content 损失和 code 损失,通过梯度优化更新风格化图像;迭代 100 次后输出最终的风格化二维码图像。各模块功能描述如下表所示:序号插件功能描述1图像解码调用 PIL 的 Image.open,分别读取二维码图、内容图和风格图2图像缩放调用MindSpore的mindspore.dataset.vision.Resize将图像缩放至合适大小3图像数据Tensor化调用MindSpore的mindspore.dataset.vision.ToTensor将图像数据转为Tensor数据4初始化风格化二维码图像根据加载的二维码图和内容图初始化风格化二维码图像Tensor,并调用MindSpore的mindspore.Parameter将其转为可梯度更新的参数5二维码风格化使用预训练好的Vgg16模型,并加载固定参数的SSLayer模型,Fix所有的模型参数,通过调用mindspore.nn.MSELoss计算 style、content和code损失,迭化优化风格化二维码图像6图像编码调用PIL的Image.open,将最终迭代优化的风格化二维码图像编码输出2.2 代码目录结构与说明本项目名称为 ArtCoder,其工程目录如下:. ├── datasets | └── download_datasets.sh // 数据集下载脚本 ├── mindspore // mindspore 版 │ ├── artcoder.py │ ├── main.py │ ├── model │ │ └── download_model.sh // 预训练模型下载脚本 │ ├── output // 风格化二维码图片输出目录 │ ├── requirements.txt // Python 库依赖 │ ├── run.sh // 运行脚本 │ ├── scan.py │ ├── scan.sh // 风格化二维码扫码测试 │ ├── ss_layer.py │ ├── test.sh // 批量测试生成风格化二维码 │ ├── utils.py │ ├── var_init.py │ └── vgg.py ├── .gitignore ├── LICENSE └── README.md3 开发准备支持的硬件形态和操作系统版本硬件环境操作系统版本Linux-x86_64 + CPU / GPU / Ascend910Ubuntu LTS >= 18.04.1Linux-aarch64+ CPU / GPU / Ascend310Ubuntu LTS >= 18.04.1软件依赖软件名称版本opencv4.6.0.66opencv-contrib1.23.3python3.9.2pillow9.3.0mindspore1.8.14 准备正式运行项目前,需要先根据第 2 节的要求,配置好相应的运行环境;然后根据提供的脚本下载好数据集和预训练模型。4.1 数据集下载风格化二维码的开源数据集:cd datasets bash download_datasets.sh cd ..4.2 模型下载 MindSpore 框架下的开源 VGG-16 预训模型:cd mindspore/model/ bash download_model.sh cd ../../5 运行首先进入到对应的代码目录中:cd mindspore/根据不同的硬件平台修改 Artcoder.py 文件中对应的硬件指定代码:# context.set_context(mode=context.GRAPH_MODE, device_target='CPU') # CPU 平台 # context.set_context(mode=context.GRAPH_MODE, device_target='GPU') # GPU 平台 context.set_context(mode=context.GRAPH_MODE, device_target='Ascend') # Ascend 平台然后根据以下注释编缉 run.sh 脚本:#!/bin/bash epoch=100 # 迭代优化次数 style_img_path="../datasets/style/square.jpg" # 风格图像路径 content_img_path="../datasets/content/man.jpg" # 内容图像路径 code_img_path="../datasets/code/man.jpg" # 二维码图像路径 output_dir="./output/man/" # 输出目录 export OMP_NUM_THREADS=1 python -W ignore main.py --epoch ${epoch} --style_img_path ${style_img_path} --content_img_path ${content_img_path} --code_img_path ${code_img_path} --output_dir ${output_dir}修改好对应的图像路径后,运行 run.sh 脚本:bash run.sh待程序跑完后可在输出目录下找到最终输出的风格化图像。6 测试如果想要批量地进行测试,可以运行 test.sh 脚本,根据以下注释修改对应参数:mode='some' # 测试模式 epoch=100 style_dir="../datasets/style" # 风格图片目录 content_dir="../datasets/content" # 内容图片目录 code_dir="../datasets/code" # 二维码图片目录 output_dir="./output" # 输出目录 # ...由于风格图像比较多,故提供两种测试模式:all 和 some,分别指使用所有的风格图进行测试和使用部分风格图进行测试。如果使用 some,其所使用的部分风格图列表在 test.sh 也可自行修改,即对应下面代码中的 style_list,可按需修改成自定义选定的测试图像列表:# 部分风格图测试 style_list=('candy.jpg' 'hb.jpg' 'been.jpg' 'st.jpg' 'square.jpg' 'udnie.jpg') # 选定的测试图像列表 for style in ${style_list[@]}; do style_name=$(basename ${style} .jpg) style_img_path="${style_dir}/${style_name}.jpg" output_img_path="${output_dir}/${image_name}_${style_name}" python -W ignore main.py --epoch ${epoch} --style_img_path ${style_img_path} --content_img_path ${content_img_path} --code_img_path ${code_img_path} --output_dir ${output_img_path} done然后运行 scan.sh 脚本,根据以下注释修改对应参数:scan_dir="./output" # 扫码测试目录 python scan.py --scan_dir ${scan_dir}最后会打印出能正确识别出的风格二维码的准确度。二维码识别使用了 Opencv-Contrib 中带的微信二维码扫码器。7 效率以下测速均在单进程下进行,涉及 GPU 和 NPU 也都是只使用单张卡进行测速。本测试主要对比对象为 Pytorch 版的开源 ArtCoder。针对一张 $592 \times 592$ 的二维码图风格化迁移总流程的实际硬件平台测速,包含模型载入、风格化总流程(图像读取、图像 Resize、图像变换、图像风格化迭代优化以及图像保存等步骤),每张二维码图风格化迭代优化 $100$ 个 Epoch。风格化总流程测速包含到图像读取、图像变换等 CPU 操作,受 CPU 型号及服务器上其它 CPU 任务影响较大。为了更好地比较该模型分别基于 PyTorch 的 GPU 平台,和 MindSpore 的 NPU 平台的效率,分别对模型载入、风格化总流程以及风格化流程中的迭代优化进行测速(其中 Mindspore 框架的模型载入时间不稳定,多次测量变化较大):框架硬件模型载入(s)风格化总流程(s)迭代优化(s)PyTorch 1.8.1Intel(R) Xeon(R) Gold 6226R CPU @ 2.90GHz + NVIDIA GeForce RTX 2080 Ti GPU6.6011.5211.25PyTorch 1.8.1Intel(R) Xeon(R) Gold 6226R CPU @ 2.90GHz1.96662.31656.21PyTorch 1.8.1Intel(R) Xeon(R) Gold 6226R CPU @ 2.90GHz + NVIDIA Tesla V100-PCIE GPU4.889.198.79MindSpore 1.8.1Intel(R) Xeon(R) Gold 6226R CPU @ 2.90GHz + NVIDIA GeForce RTX 2080 Ti GPU15.2911.4410.03MindSpore 1.8.1Intel(R) Xeon(R) Gold 6226R CPU @ 2.90GHz80.7821.1520.76MindSpore 1.8.1Intel(R) Xeon(R) Gold 6240 CPU @ 2.60GHz + Ascend 910 NPU17.3023.1419.498 可视化二维码图内容图风格图风格二维码图9 参考链接MindSpore 框架下预训练的 VGG-16 模型: cid:link_1二维码风格化模型代码参考:cid:link_3参考论文:@inproceedings{su2021artcoder, title={ArtCoder: An End-to-End Method for Generating Scanning-Robust Stylized QR Codes}, author={Su, Hao and Niu, Jianwei and Liu, Xuefeng and Li, Qingfeng and Wan, Ji and Xu, Mingliang and Ren, Tao}, booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, pages={2277--2286}, year={2021} }
  • [其他干货] Mindspore项目DPRNN模型迁移经验分享
    今年4月份到10月份参加了华为众智项目新一期的开发,在模型迁移过程中学到了很多,也积累了一些迁移方面的经验,想通过这个帖子分享给大家。模型结构简介:在分享迁移经验之前首先简单介绍一下DPRNN模型,便于大家对这个模型有一个简单的了解。双路径循环神经网络(DPRNN)是一个时域单通道语音分离的高效长序列建模,它由三个处理阶段组成:编码器、分离阶段和解码器。首先,编码器模块用于将混合波形的短段转换为它们在中间特征空间中的对应表示。然后,该表示用于在每个时间步估计每个源的乘法函数(掩码)。最后利用解码器模块对屏蔽编码器特征进行变换,重构源波形。下图是论文中关于语音分离阶段流程的解释。看完上面的模型简介之后,相信大家已经对DPRNN模型有了一个大致的认识。接下来就是我在做此项目过程中具体的流程规划。模型迁移流程在正式进行模型迁移工作之前,首先要详细阅读论文并且熟悉论文对应的源码,然后就是阅读mindspore开发文档,熟悉mindspore整体的开发流程,查询torch与mindspore算子对应关系,这样可以有效减少模型迁移的时间。DPRNN模型的迁移流程如下:将torch源码迁移成mindspore版本代码,并在pynative动态图模式下跑通(pynative模式下,可以方便地设置断点,获取网络执行的中间结果).将mindspore版本代码在graph图模式下跑通通过调整模型超参,初始化等方法提升模型评估精度310推理代码编写训练,评估以及310推理的单卡和多卡运行脚本编写迁移难点模型的迁移主要分为三个阶段,分别为数据预处理,网络主干以及loss。下面是在这三个阶段中遇到的问题以及解决办法:数据预处理在这一阶段的开发中,我发现mindspore库中缺少一个重要的音频处理算子:torchaudio,通过查询库上代码和各版本的源码,了解到原始音频是应该先处理为一个二进制的json文件,再将这个json文件传入训练的网络之中。所以我重写了预处理文件,为了验证结果,将这部分代码处理后的json文件传入了torch代码中,发现效果基本一致,完美规避了torchaudio没有mindspore对应算子的问题。网络主干首先是一维卷积算子nn.Conv1d,由于初始化的方式不同,torch版本和mindspore版本的输入输出有很大的差异,经过尝试,发现使用HeUniform()初始化方法可以得到和torch版本相同的输出。然后是nn.GroupNorm归一化算子,通过代码报错发现mindspore版本的GroupNorm算子不能对三维向量进行运算,而对应的torch版本算子却可以正常运行,所以只能对向量先进行扩维操作,然后通过transpose函数进行向量转置,通过GroupNorm算子之后再进行转置以及降维操作,通过此方法可以得到和torch版本GroupNorm算子相同的输出。对于nn.LSTM算子,mindspore1.5版本环境下需要加上两个Tensor(h_0,c_0)的元组,数据类型为mindspore.float32或mindspore.float16,shape为(num_directions * num_layers, batch_size, hidden_size),如果不加这两个参数脚本运行会报错。但是mindspore1.6以及之后的版本都可以不加这两个参数。两版代码的区别我放在下面的代码区。mindspore1.5版本可运行的LSTM算子部分代码如下:def __init__(self, out_channels,hidden_channels, rnn_type='LSTM', norm='ln',dropout=0, bidirectional=False, num_spks=2): super(Dual_RNN_Block, self).__init__() # RNN model self.intra_rnn = nn.LSTM(out_channels, hidden_channels, 1, batch_first=True, dropout=dropout, bidirectional=bidirectional) self.h0 = Tensor(np.ones([2 * 1, 516, hidden_channels]).astype(np.float32)) self.c0 = Tensor(np.ones([2 * 1, 516, hidden_channels]).astype(np.float32)) self.inter_rnn = nn.LSTM(out_channels, hidden_channels, 1, batch_first=True, dropout=dropout, bidirectional=bidirectional) self.h1 = Tensor(np.ones([2 * 1, 500, hidden_channels]).astype(np.float32)) self.c1 = Tensor(np.ones([2 * 1, 500, hidden_channels]).astype(np.float32)) def construct(self, x): ''' x: [B, N, K, S] out: [Spks, B, N, K, S] ''' B, N, K, S = x.shape # intra RNN # [BS, K, N] intra_rnn = x.transpose((0, 3, 2, 1)).view(B*S, K, N) # [BS, K, H] intra_rnn, (hn, cn) = self.intra_rnn(intra_rnn, (self.h0, self.c0))mindspore1.6以及之后版本可运行的LSTM算子部分代码如下:def __init__(self, out_channels,hidden_channels, rnn_type='LSTM', norm='ln',dropout=0, bidirectional=False, num_spks=2): super(Dual_RNN_Block, self).__init__() # RNN model self.intra_rnn = nn.LSTM(out_channels, hidden_channels, 1, batch_first=True, dropout=dropout, bidirectional=bidirectional) self.inter_rnn = nn.LSTM(out_channels, hidden_channels, 1, batch_first=True, dropout=dropout, bidirectional=bidirectional) def construct(self, x): ''' x: [B, N, K, S] out: [Spks, B, N, K, S] ''' B, N, K, S = x.shape # intra RNN # [BS, K, N] intra_tran = x.transpose((0, 3, 2, 1)).view(B*S, K, N) # [BS, K, H] intra_rnn, _ = self.intra_rnn(intra_tran)loss使用SI-SNR标准来计算loss,这也是语音分离任务中普遍使用的loss函数。这一部分基本没遇到什么问题。模型迁移过程中为了随时debug调试,推荐大家使用pynative模式进行开发,但是最终的交付目标是graph模式运行成功,所以最后我们需要在graph模式下再将整个训练的过程走一遍。在这一部分中,由于语音分离模型的特殊性,传入的语音长度不尽相同,所以源码对语音进行了切割处理,但是在pynative模式下正常运行的代码到了graph模式下就会出现op compale failed的问题,最终我们发现在graph模式下计算动态长度会出现一些问题,所以进行了先处理、再编译的方式处理数据,成功解决了graph模式不能运行的问题。模型调优经验除去之前因为mindspore环境原因导致loss一直不下降(在-2~0之间来回震荡),模型调参提升精度是整个项目耗时最长的阶段。在mindspore版本代码graph图模式下跑通之后,loss下降趋势与torch源码loss下降趋势有很大差异,比如torch源码sisnr评估精度是12.68,但是刚迁移完成没经过任何调优的mindspore版本代码sisnr精度只能到5.11,这个时候就需要通过调整模型参数、优化器、初始化函数以及学习率等方法来提升模型的精度。调整优化器torch源码使用的是Adam优化器,但是最初的mindspore版本代码使用Adam优化器之后效果却不尽人意,在conv-tasnet网络使用SGD优化器取得显著效果之后,我也试着把Adam优化器改为SGD优化器,最后效果反而比使用Adam优化器版本代码的效果更差,经过各种查询,得出SGD优化器对全卷积网络模型精度提升很多,但是对DPRNN这种含有LSTM的网络来说,使用SGD优化器不是一个很好的选择。在尝试了各种优化器之后,我最终换回了Adam优化器,后来发现通过调整优化器中weight-decay参数对应值可以取得不错的效果。调整初始化函数源码的初始化策略在mindspore上并无对应,而mindspore默认的初始化函数效果又很差,通过不断的尝试与对比,发现Heuniform初始化函数效果最好。调整学习率torch源码使用的初始学习率为5e-4,通过使用ReduceLROnPlateau学习率策略动态调整学习率,当特定的度量指标,如训练损失、验证损失或准确率不再变化时,学习率就会降低为原来的1/2,但是mindspore1.9以下的版本却没有这个算子对应,所以只能通过加载模型文件继续训练的方法找到最合适的学习率,最后通过nn.piecewise_constant_lr函数将合适的学习率整合成动态学习率,代码如下:milestone = [15 * num_steps, 45 * num_steps, 75 * num_steps, 100 * num_steps] learning_rates = [args.lr, args.lr1, args.lr2, args.lr3] lr = nn.piecewise_constant_lr(milestone, learning_rates) optimizer = nn.Adam(net.trainable_params(), learning_rate=lr, weight_decay=args.l2)310推理编写310推理的时候报过out of memory错误,我使用的模型总共需要6层dprnn block,使用5层block时310推理可以正常运行。但当使用6层block的时候,可以export出mindir文件,但是310推理却报out of memory错误,在gitee上提了issue之后,工程师建议看做没做8bit量化,如果是全卷机网络可以把输入大小减少些。后续将输入通道大小减少一半,在未影响精度的前提下310推理可以正常运行,如果之后有同学在310推理的时候也遇到这个问题的话,可以尝试一下这个方法。除此之外的其他部分当时我参考了gitee上modelZoo库中模型中的310推理,几乎没做任何改动。只适当修改了执行脚本,没遇到什么太多的问题。总结总之,如果在模型迁移开发中遇到了问题,可以去gitee上提交issue,会有工程师给你提供解决问题的方法,虽然mindpsore还有一些不足之处,但它也在不断完善,不断进步,希望这个帖子可以对大家有一点点帮助,也祝愿Mindpsore平台越来越好。
  • [API使用] 使用ResNet报错提示GetEvaluatedValueForNameSpaceString
    在执行res = model.eval(test_data)时报错,错误提示是:File "D:\program\python39\lib\site-packages\mindspore\common\api.py", line 1131, in compileresult = self._graph_executor.compile(obj, args_list, phase, self._use_vm_mode())AttributeError: ConvNormActivation<(features): SequentialCell<(0): Conv2d(1): BatchNorm2d>> object has no attribute __bool__----------------------------------------------------- C++ Call Stack: (For framework developers)----------------------------------------------------mindspore\ccsrc\pipeline\jit\static_analysis\prim.cc:1455 GetEvaluatedValueForNameSpaceString测试代码是参考这篇文章的https://developer.huaweicloud.com/develop/aigallery/notebook/detail?id=9f2ffd90-6e2a-4df5-beba-2ad8acbbdbce该文章的代码能正常运行(训练和预测),文章里用是它自行定义的ResNet;改成使用:from mindvision.classification.models.resnet import resnet18 as resnetnet = resnet(num_classes=config.class_num, pretrained=False)后就会产生上述错误。
  • [算子使用] 经验分享|昇思MindSpore周旭林:首先学会像成功者那样自信
    本期项目经验分享来自周旭林同学,承担的项目是【昇思MindSpore CPU正向算子开发:SparseApplyProximalAdagrad】。01、关于昇思MindSpore项目介绍项目名称昇思MindSpore CPU正向算子开发:SparseApplyProximalAdagrad(注:项目内容在开放申请阶段经过了社区调整,后续实际开发的算子为EditDistance,其它描述不变)项目链接https://summer-ospp.ac.cn/#/org/prodetail/221cb0310项目描述算子开发流程:我的项目关于算子开发,交付目标是完成一个CPU正向算子。先来解释这些概念。算子是什么算子是什么?算子是深度学习框架的基本组件。一般AI模型由若干个计算模块(即网络中的block)组合而成,比如全连接层Dense、卷积层Conv、激活函数Relu就是计算模块,模块的实现是通过调用封装好的算子来完成的。算子怎么分类?由于计算模块的数据处理对象一般为Tensor,而不同的后端硬件平台对Tensor的存储、处理和计算方式有所不同(如何基于GPU和Ascend等AI加速器的硬件特点对算子实现做特殊处理以使其执行速度更快,这是另一个有趣的话题)。MindSpore对 CPU、GPU 和 Ascend 等硬件平台都提供支持,因此根据算子所支持的不同设备的底层逻辑,可分为 CPU 算子、GPU 算子和 Ascend 算子。因为AI模型需要进行反向传播,所以有时需要定义一个新算子来履行原先算子的反向实现功能,因此就有正向算子和反向算子之分。算子如何开发算子开发本质上是打通算子在MindSpore中被调用的各个环节,这些环节可分为Python前端和C++后端两个部分。算子通过Python前端的接口被调用,并在具体硬件平台的后端实际执行。算子如何被调用?MindSpore支持静态图模式,在该模式下AI模型的工作的会经过构图、图执行这些步骤:首先在前端构图环节,会将编写模型的 Python 语言中用到的算子接口API(比如mindspore.ops.Conv表示卷积模块)表达翻译为计算图(由计算模块共同组成的有向无环图);接下来在图执行环节,每个计算模块所对应算子输入的 type 和 shape 才会被推导(即构图完成后才能拿到,这样处理是为了能在不考虑Tensor具体信息的情况下先对计算图做编译优化);最后后端会根据图以及算子名称、输入输出个数和类型匹配具体平台的算子实现,并将执行结果返回到 Python 侧进行输出。那么,算子如何开发?遵照算子被调用的逻辑,主要需要开发 Python 算子原语、C++侧输出推导文件和基于硬件平台的底层实现文件,具体来说:1.Python 算子原语开发:算子原语就是算子的前端接口 API,在 MindSpore 中用 Python 实现,是组成网络模型的基本单元,可以被运行在不同硬件平台上的算子共用。算子原语主要包括算子的名称、属性、输入输出名称、输出 shape 推理方法、输出 data type 推理方法等信息。在存在 C++侧输出推导文件时,Python 算子原语的开发意义主要在于定义名称和属性;2.C++侧输出推导文件开发:C++侧输出推导文件本质上是算子原语的 C++形式。由于算子属性的初始化已在 Python 算子原语中完成,因此 C++算子原语的主要功能是对输入数据的规范性进行校验,并推导算子输出的 data type 和 shape。这里其实可以将C++侧输出推导文件理解为算子前端,单独抽取出来并写成C++文件的意义主要是为了保证一般算子在推导时的高效。目前只有当遇到动态shape等特殊情况时,为了方便处理才会直接在Python算子原语中推导;3.CPU 底层实现文件开发:利用框架提供的 C++ API,结合算子具体特性实现算子内部计算逻辑,并在保证结果和精度达标的基础上尽量保证性能达到最优。这里的C++ API 除了经典的STL库以外,还包括MindSpore提供的辅助类和方法。总的来说,算子这个组件在MindSpore框架中,分为了算子前端和算子后端:其中前端是算子接口 API,用于从抽象上指代某类算子并供用户调用;后端是对用户不可见的算子底层实现,随实际运行平台而不同。算子开发既要实现算子接口API(重点是实现其中的data type和shape的推导流程),也要完成后端底层实现。我当时修改并交付的代码包括以下文件,最终在Gitee中以PR的形式合入。 文件名功能含义修改描述edit_distance_cpu_kernel.ccEditDistance算子CPU底层实现的源文件实现算子的底层逻辑,包括具体算法实现和新增数据类型支持edit_distance_cpu_kernel.hEditDistance算子CPU底层实现的头文件定义算子的所用数据结构,实现对编辑距离的求解edit_distance.ccEditDistance算子C++侧推导文件从type和shape等角度验证算子输入的规范性array_ops.pyEditDistance算子Python前端接口更新算子基本信息描述算子如何验收算子开发除了交付代码以外,为了方便验证和合入代码,还需要提交开发者自测交付件。交付件需要涵盖算子设计文档、算子自测用例、测试报告与执行结果等。这些交付件需要给华为的测试负责人验收,一般他们关心以下方面:1.算子文档是否规范?交付件需要证明该算子的文档齐全且为最新(比如,若打通了CPU平台上该算子的支持,就要更新官网上的适用平台说明);2.代码交付是否完全合格?这里的合格除了接口规格要与友商保持一致以外,还需要保证结果在各种情况下都正确。为了证明这点,交付件中需要包括根据“算子自验报告”中的逐个细节(比如Tensor维度取从0维到7维的情况、输入数据类型不匹配的报错情况等)一一编写。算子自验报告需要面面俱到,当时的报告长得像是以下这样子。因此,交付件需要清楚地涵盖到以上的内容。在我的交付件中,一级目录的文件和文件夹描述如下。名称类型描述doctest自验执行结果截图算子的文档规范验证结果算子自验报告Excel表格自测数据的构造逻辑和覆盖内容算子自验测试用例由自测数据组成的代码库包括20个自测数据和必要的测试脚本EditDistance测试用例执行结果.docx截图算子通过自测数据的截图证明EditDistance接入设计文档.docWord文档算子开发文档其文件树结构如下所示。02、项目开发方案描述模型开发流程下面介绍我负责的项目内容和开发细节,内容组织遵循项目的开发思路。开发思路在了解了算子开发流程后,我遵循了以下流程进行开发前调研、开发中划分模块和开发后自测检验。1.算子分析:了解算子的作用、适用范围和特性,关注算子的属性、输入和输出参数的shape和dtype等规格;2.接口实现:基于算子的功能设计面向用户的Python API接口,并按照指定文档格式添加算子的基本信息。由于MindSpore中已有EditDistance算子的Python接口,因此本次开发不涉及该部分;3.输入规范性检验:即实现C++侧推导文件。需要考虑包括算子名称、属性、输出shape、dtype推理函数等信息,并对不合理的输入抛出异常;4.实现基于特定平台的底层逻辑:在本项目中为基于CPU平台实现算子内部逻辑,主要包括“序列生成”、“序列比较”和“编辑距离求解”三个模块。其中,“序列生成”模块负责从所输入的稀疏矩阵中提取出需要计算编辑距离的序列;“序列比较”模块负责对所提取出的序列进行枚举和比较;比较后需要输出的结果通过调用“编辑距离求解”模块得到。5.测试用例构建:测试用例包括UT测试用例和ST测试用例。前者需要附在Pull Request上面,在本项目中不涉及;后者无需跟开发代码一同上库,但是需要根据测试提供的模板编写用例,与标杆对比结果,并全量测试算子功能和异常场景。算子分析与示例演示我的项目开发的算子对象是EditDistance算子,需要对标TensorFlow已有实现来完成交付。该算子的功能是计算两个序列的编辑距离。序列将作为输入以稀疏矩阵的形式给出。提一下这里涉及到的两个概念:编辑距离是是衡量两个字符串(序列)差异程度的量化标准。具体来说,给定当前字符串s和期望字符串t,并定义如下操作均为合法操作:(1)在s插入一个新字母;(2)在s中删除一个原有字母;(3)替换s中的任意一个字母为其它字母。则s和t的编辑距离,就是将s变成t的最小合法操作数。稀疏矩阵是内部元素大部分都为无意义值的矩阵,它是相对于稠密矩阵而言的。在深度学习框架中,稠密矩阵一般用 Tensor 存储,因为 Tensor 是存储多维数组的理想数据结构;但是稀疏矩阵一般会采用特定的表示方法,即通过只存储在给定索引上有意义的元素值及其下标来表示一个矩阵。其中一种表示方法需要三个参数:indices、values 和 shape,其中:indices 表示稀疏矩阵中所有有意义值各自所在的位置。它是一个 shape 为 [N, R] 的 Tensor,其中N表示稀疏矩阵中有意义值的个数,R表示该稀疏矩阵的维度;values 表示稀疏矩阵中所有的有意义值。它是一个 shape 为[N]的 Tensor,用来给 indices 中的每个位置提供具体数值;shape:表示该稀疏矩阵的维度。它是一个包含R个数的元组或 shape 为[R]的 Tensor。现在,了解了算子作用后,我遇到的第一个挑战是这个算子的输入太多了,且序列之间比较的逻辑看起来不容易理解。贴一个该算子对参数和输入的具体规范感受下。normalize:bool类型参数,在EditDistance算子类初始化时传入。若为true,表示输出得到的编辑距离会被目标序列的长度normalize;hypothesis_indices :Tensor类型输入,要求shape为[N,R],其中N表示hypothesis中有意义的值的个数,R表示hypothesis的矩阵维度;hypothesis_values:Tensor类型输入,要求shape为[N];hypothesis_shape:Tensor类型输入,要求shape为[R];truth_indices:Tensor类型输入,要求shape为[M,R],其中M表示truth中有意义的值的个数,R表示truth的矩阵维度,且该维度应与hypothesis的矩阵维度保持一致;truth_values:Tensor类型输入,要求shape为[M];truth_shape :Tensor类型输入,要求shape为[R]。output:以Tensor形式返回的编辑距离,该Tensor的shape为R-1,为表示当前序列的稀疏矩阵和表示目标序列的稀疏矩阵在对应序列位置上的编辑距离。因此,这里让我们用一个具体的例子来详细分析一下该算子怎么用。这里以tensorflow做示范(mindspore的用法与其相同),给定以下包括truth和hypothesis两个稀疏矩阵的输入。让我们通过手工计算求解基于这些输入的编辑距离。hypothesis的稀疏矩阵维度为(2,1,1),它实际上表示的是一个维度为(2,1)的序列矩阵,且序列中的元素个数不超过1。即位置在(0,0)的序列内容为[1],位置在(1,0)的序列内容为[2],为方便指代,可简记为:(0,0) = [1](1,0) = [2]truth的稀疏矩阵维度为(2,2,2),它实际上表示的是一个维度为(2,2)的序列矩阵,且序列中的元素个数不超过2。可简记为:(0,0) = [](0,1) = [1](1,0) = [2, 3](1,1) = [1]由于truth的序列矩阵相关维度包括了hypothesis的序列矩阵相关维度,因此在计算编辑距离之前,会自动将hypothesis的序列矩阵维度转化为与truth的一致,并将额外增加的位置对应的序列内容置为空。随后,比较序列矩阵中的每一个序列,具体来说:对于(0,0)位置:hypothesis的序列为 [1],truth的序列为 [],因此编辑距离为1;由于truch的序列长度为0,因此normalize后的编辑距离为inf;对于(0,1)位置:hypothesis的序列为 [],truth的序列为 [1],因此编辑距离为1;由于truch的序列长度为1,因此 normalize后的编辑距离为1;对于(1,0)位置:hypothesis的序列为 [2],truth的序列为 [2,3],因此编辑距离为1;由于truch的序列长度为2,因此normalize后的编辑距离为0.5;对于(1,1)位置:hypothesis的序列为 [],truth的序列为 [1],因此编辑距离为1;由于truch的序列长度为1,因此normalize后的编辑距离为1。采用edit_distance算子所得的结果如下。可以看出实际输出结果与上述推导的结果一致。输入规范性检验在规范性检验里,我遇到了开发中的第二个挑战,即由于 EditDistance 算子的参数较多,对它们的规范性校验较为复杂,需要细心处理。这个过程既需要单独考虑每个参数的特定规格,又需要考虑不同参数之间的约束和联系(比如代表同一个稀疏矩阵的三个参数必须遵循稀疏矩阵的构造规范)。在Editdistance的开发中,依次通过如下检查步骤来完成算子输入的规范性检验,若其中一个不通过则抛出具体的报错异常:确认输入为六个参数;确认六个参数的 dtype 都为 Tensor;获取六个参数的 shape 并做检查。对于分别表示两个稀疏矩阵索引的两个参数,确认它们的秩为 2;对于其他四个参数,确认它们的秩为 1;确认表示稀疏矩阵索引和表示稀疏矩阵索引范围的两对参数,它们的维度数量一致;确认表示稀疏矩阵索引范围的两个参数的值保持一致或满足自动广播条件;确认六个参数的元素数据类型合法,其中表示稀疏矩阵索引的参数和表示稀疏矩阵索引范围的参数元素数据类型必须为 int64,表示稀疏矩阵值的一对参数元素数据类 型必须一致且在所支持的参数列表内。序列生成序列生成负责基于输入的稀疏矩阵得到需要计算编辑距离的序列。该部分开发的难点在于无法直接参照TensorFlow源码的算子实现逻辑,这是我在开发中的遇到的第三个挑战,也是本项目的难点所在。一般算子开发任务是对标友商中的已有实现的,这会给开发工作带来便利:因为已经有了现成的源码,只需要参考逻辑实现“本土化”移植到mindspore中就可以了。但是本次开发却行不通,这是因为在TensorFlow中,EditDistance算子的内部实现涉及将输入的三个Tensor转化为一个与其对应的稀疏矩阵,并使用稀疏矩阵的遍历方法来对矩阵进行处理,因此需要动用专门的SparseTensor数据结构完成稀疏矩阵的通用操作。但是MindSpore中没有此类数据结构(更具体地来说,是没有实现稀疏矩阵的顺序遍历方法),因此只好自行构造方案进行处理。本次开发采用的方案基于朴素的枚举思路,方案伪代码如下。其中,indices、values和shape三个输入参数共同表示一个稀疏矩阵,该稀疏矩阵可能存在部分有实际值的下标是落在输出矩阵的某一个特定下标output_index对应的位置里面的,算法的目标就是对于特定的output_index,将稀疏矩阵中所有落在该位置的有意义的值收集起来生成一个序列,以便后续进行比较。当然在实际开发中,还需要考虑代码书写的规范性。比如两个不同数据类型变量的计算需要调用工具函数做显性类型转换,并判断生成序列的所在下标是否超出输出矩阵范围等,这里就不做详细讨论了。序列比较在基于两个稀疏矩阵hypothesis和truth都生成了对应的序列后,需要通过序列比较模块,对位于同样输出下标的序列进行比较以计算编辑距离。这里遇到了第四个挑战:该模块尽管可以通过简单循环和嵌套判断直接实现,但是函数内环形复杂度就会过大导致无法通过门禁。这里的环形复杂度是一种程序逻辑复杂性的度量,程序中所包含的判断和循环语句越多,环形复杂度越大。为了在保证功能的前提下精简算法,首先需要分类讨论序列比较的八种不同情况如下。若采用直接的判断方法,则每种对应情况需要进行三次分支判断才能检索到。为了降低环形复杂度,修改思路是将部分判断“隐藏”在另一个函数里,通过两个函数合理分摊分支判断,这样就能避免某个函数的环形复杂度过大的情况。注意到当且仅当 truth 序列为空且 normalize 参数为 true 时,需要根据 hypothesis 序列是否为空来判断是返回 inf 还是 0,而除了这种情况以外的情况都可以直接调用编辑距离求解函数求解。通过这种方式就可以将对序列是否为空的判断转移到求解函数中(求解函数会在下一节探讨),从而降低当前函数的环形复杂度。这个部分的码风长如下这样子。// calculate distance if (normalize_ && truth_seq_size == 0) { output_addr[output_index] = (hypothesis_seq_size != 0 ? std::numeric_limits::infinity() : static_cast(0)); continue; } auto cmp = std::equal_to(); size_t dis = LevenshteinDistance(truth_seq, hypothesis_seq, cmp); output_addr[output_index] = (normalize_ ? SizeToFloat(dis) / SizeToFloat(truth_seq_size) : SizeToFloat(dis)); }编辑距离求解在完成序列生成和序列比较两个预处理步骤后,我们就把算子开发任务转换成了:给定两个已经从稀疏矩阵中单独抽离出来的纯粹的序列,求解它们的编辑距离。这是第五个挑战,难度等同于这道力扣题:力扣[1]。可以用动态规划解决,动态转移方程为如下。其中,dis(i, j)表示 s 的前缀子串 s[0~i]与 t 的前缀子串 t[0~j]的编辑距离,[s[i − 1] ≠ t[j − 1]] 表示若 s 的第 i 位字母与 t 的第 j 位字母不同,则该值为 1;否则为 0。最终 s 和 t 的编辑距离为dis(s. length, t. length)。下面是这道力扣题的求解代码。class Solution {public: int minDistance(string word1, string word2) { vector> dis(word1.size()+1, vector(word2.size()+1,0)); int len1 = word1.size(); int len2 = word2.size(); for (int i=0;i<=len2;i++) dis[0][i]=i; for (int i=0;i<=len1;i++) dis[i][0]=i; for (int i=1;i<=len1;i++) for (int j=1;j<=len2;j++) { dis[i][j]=min({dis[i-1][j]+1,//删除word1的一个字母 dis[i][j-1]+1,//删除word2的一个字母,等价于增加 word1的一个字母 dis[i-1][j-1]+(word1[i-1]!=word2[j-1])}); //修改word1的一个字母。若该字母其实无需修改,则不增加合法操作数 } return dis[len1][len2]; } };下面是本次项目开发关于编辑距离求解部分的函数。template inline size_t LevenshteinDistance(const std::vector &s, const std::vector &t, const Cmp &cmp) { const size_t s_size = s.size(); const size_t t_size = t.size(); if (s_size == 0 || t_size == 0) { return std::max(s_size, t_size); } std::vector> dis(s_size + 1, std::vector(t_size + 1, 0)); for (size_t i = 0; i <= t_size; i++) dis[0][i] = i; for (size_t i = 0; i <= s_size; i++) dis[i][0] = i; for (size_t i = 1; i <= s_size; i++) for (size_t j = 1; j <= t_size; j++) { dis[i][j] = std::min({dis[i - 1][j] + 1, dis[i][j - 1] + 1, dis[i - 1][j - 1] + (cmp(s[i - 1], t[j - 1]) ? 0 : 1)}); } return dis[s_size][t_size];}从上面两个代码片段可以看出:同样是封装成函数,实际开发场景与力扣模拟场景还是存在值得注意的区别。举例来说,需要用 C++ 的 template 机制实现以支持不同种类的数据类型,并且传入自定义比较类型 Cmp 实现 所传入的序列元素的对比;另外还要考虑到在 EditDistance 算子的使用场景中,大部分比较的两个序列中至少有一方的长度为 0,因此增加相应的特殊判断显得更有必要,同时也可以均摊上一节提到的环形复杂度。项目开发完成后,就需要一一编写针对性测试用例进行验证。若多次测试无误,就可以将代码和交付件转交给测试人员验收,同时预告项目开发基本完成了。最后放上Editdistance的MindSpore官网链接[2]。03、随访参与开源之夏ospp:请简单介绍一下自己和你的开源经历吧xlinsist:我是来自暨南大学19级智能科学与技术专业的周旭林,我最开始投身开源的动机是希望能以更有效的方式接触和揭秘AI“黑盒”背后的技术。在日常专业课的学习中,老师会给我们讲授神经网络和深度学习模型的概念和原理,并提供机会让我们基于TensorFlow搭建模型进行实践。由于我时常好奇所用到的这些库的内部实现方式,因此经常会通过查阅技术博客采撷对这些细节的解读。这个习惯为我能“随机搜索”到开源项目提供了基础。“梦开始的地方”是在今年初关注到的华为开源书籍《机器学习系统:设计与实现》。这本书讲述了 AI 模型以计算图的形式经过框架和编译器并最终得到部署的完整流程。由于彼时鲜有全面涉及相关内容的教材,我饶有兴趣地参与了这本书的内测阅读,提出了一些关于排版的建议和修订并最终在书籍检视活动中获了奖。这在给我带来成就感的同时也让我了解到了这样一种社区玩法,即通过积极参与自己感兴趣的方向中正在开展的活动,能够让你保持对前沿资讯的关注并投入到有实际价值的事情中。ospp:你是从什么渠道了解开源之夏的,为什么选择参与开源之夏?xlinsist:由于书籍检视活动让我意犹未尽,于是我开始有意识地在网上寻找其它机会,就是在这个过程中我认识了开源之夏。事实上,好的开源平台自然会吸引开发者关注、参与并留下产出。开源之夏就是这样一个优秀的平台,它的开发流程、导师制度和奖励机制能够很好地激发我们参与其中的热情,因此我关注到了它。在翻看了前两届学生的活动记录和结项成果后,我当即认为这就是一个我可以争取的机会,因为通过主动了解前辈们的开发心得和项目经验,我实际上是为自己确定了在项目开发中想成为的榜样,而榜样的树立可以为我带来无形的激励力量。参与开源社区ospp:介绍一下你在开源之夏活动的项目吧,你是怎么从这么多项目中选中它的呢?xlinsist:我选择的项目关于算子开发,交付目标是对标友商开发完成一个CPU算子并验证通过。这里的算子是深度学习框架的基本组件,深度学习模型中一个网络层的实现就是通过调用算子来完成的。挑中该项目主要出于两个考虑:首先,算子开发本质上是打通算子在MindSpore中被调用的各个环节,这些环节包括Python前端和C++后端两个部分,我认为理清算子开发流程对我接触MindSpore这个“黑盒”背后的技术会有很大帮助;再者,算子开发是一个通用的开发任务,它不要求开发者预先对框架的某一组件部分有较深的理解基础,但是需要有一定的源码理解和编程实现能力,可以参考友商框架的实现思路在MindSpore中完成逻辑开发。我相信当开发思路大致清楚后,这就不会是一个特别难上手的任务,因此我把它视为我在昇思MindSpore社区的“good-first-issue”,它是我上手MindSpore的一个很好的切入点。ospp:在开展项目的过程中,涉及陌生的知识领域你是如何应对的?xlinsist:开展项目对我来说是一个从0到1的过程。除了理清算子开发的完整流程,我还需要熟记目标算子的功能和参数,熟悉MindSpore的安装和使用,才可以开始编码开发。编码期间需要了解库函数的使用并遵循一定的编程规范,编码完成后也需要遵循验收标准提供交付件。这些过程看似繁琐,但都是项目开发的必经之路。因此我不但需要适应它,还需要刻意将这些细节培养成自己的开发习惯。在这样的意识指导下,我会用一种“终局思维”去应对陌生的知识领域,即为了推进这个项目,我首先要走出当下因知识盲区而面临的困惑;为了解答这个困惑,我首先要弄清楚某个具体知识点的含义。这样就将当下遇到的大困惑转换成小困惑,大困难拆解成小困难,最后通过研读源码和求助社区和导师不重复也不遗漏地解决一个个小困难,最终就能解决问题。ospp:项目进行中社区和导师给你带来了怎样的帮助?xlinsist:在项目开发前后我幸运地得到了社区前辈们的支持,他们为我在开源社区中快速成长带来了很多帮助,对此我由衷表示感谢。首先要感谢我的导师,他对我在开发过程中遇到的困惑和瓶颈都有问必答。每周我都会将当前工作进展汇总后发给他交流确认,在重要的开发阶段也会建立线上会议与他直接探讨。跟导师的及时交流让我在项目开展时逐渐建立起一种自信,即眼前项目最终一定能完成,因为我所遇到的流程性问题都能在他那寻求解答,而对于相对复杂的细节和程序性问题也能够找他磋商,这对项目进展起到了非常积极的推进作用。然后也要感谢社区的算子测试负责人,他帮助我仔细检查了自测交付件的规范性,并在处理转测遇到的突发情况时提供了关键的设备资源和材料。在开发代码刚开始转测时,曾出现测试环境与本地环境不一致导致无法验证自测结果的问题。为了提高沟通效率,我在导师的帮助下直接单方面联系到他,他为我提供了众智服务器以保证源码编译验证的高效性,并及时地将测试结果发与我交流。这些验收反馈对保证交付质量具有重要意义。最后还要感谢在社区和交流群中为我答疑解惑的前辈们。在最开始准备项目申请书的日子里,我曾参加华为SIG的内部交流会向前辈提出自己的疑问,也曾在交流群里向老师们寻求参考资料和学习建议,令我感激的是我每次都能得到他们热心且详细的答复,这对我在申请书的撰写和项目的学习上都带来了很大帮助。ospp:通过参与这次开源之夏的活动,你对开源社区有什么新的理解和看法?你会怎么跟在校学生介绍或推荐开源社区?xlinsist:开源本身就是一个很棒的想法。从宣传的角度来看,它为社区和外围开发者搭建起了交流的桥梁,这一方面可以提高社区的知名度,另一方面可以满足开源爱好者对关注前沿和参与其中的需求,是一个“双赢”的策略;从运作的角度来看,吸引外部的开发者可以为社区输送有潜力的人才,改善人员结构,并带动社区的活跃氛围。开源社区是一个宝贵的资源库。对于像我这样的在校学生来说,最初踏入开源社区仿佛打开新世界的大门,那里有大量深度和广度兼具的成功项目,它们会帮助你建立起你跟其它技术大佬的联系。在有这样丰富机会的环境下,你会自觉地去思考自己想要的是什么,而不是受限于自己在学校只接触过什么;你会开始跟随自己的兴趣去探索,而不是盲目跟随大家都在讨论的规划。你不需要顾虑自己以前没参与过类似项目,因为社区中有很多适合你的“good-first-issue”可以作为切入点;你不需要担心自己的水平有限,因为社区里总会有热心高手为你答疑解惑。你要做的是“Fake it till you make it”,即先从“假装”自己就是以后也想成为的大佬中获得自信,像他们那样子参与其中,迈出第一步,直到真的能把这个事情办成。在校学生参与开源项目有许多好处。首先,它是项目导向的,即需要在规定时间内完成项目开发,但是整体节奏可以自行安排,这就为那些在学校尚有学习任务但是想体验“兼职实习”的学生提供了机会。其次,由于项目的ddl和任务指标较为明确,这反而有利于推动自己完成一些看似不可能的挑战。再者,这样的一次项目经历会成为大学阶段的一个代表性成就,是一个极佳的“面向简历学习”范例,即所学的内容和所做的事情刚好能成为自己简历上有价值的一笔,后续去面试时,就能有东西可以说。尽管对方不一定会关心你的项目细节,但是从你的申请书撰写、项目开发推进过程、项目难点解决方案中,也可以了解你的思考历程和行动方式,这会成为一个亮点。收获与寄语ospp:这次的开源之夏你顺利完成并结项,有什么珍贵的经验可以分享给大家呢?xlinsist:首先,在开发前再怎么强调调研的重要性都不为过。开源之夏会根据大家的项目申请书决定中选学生,因此我会建议参与开源之夏的学弟学妹们提早决定和准备自己的项目,做足充分的调研并将自己对项目的理解尽可能详细地呈现在申请书上。然后,在调研的过程中会涉猎到很多技术博客,有些博客的观点和细节可能让人“眼花缭乱”,这时切记不要迷信和盲从,而是回归自己的项目内容并诉诸实践。因为当你决定承担起这个项目时,你就是这个项目的“专家”,你应该敢于让自己成为这方面的“权威”,并通过理性输出自己的见解来打动他人。在正式开发时,注意要跟导师主动沟通,做好及时反馈。这是因为一旦项目开始,没有人会清楚你目前的项目进展和细节,一般也不会直接打扰和过问,直到你能将自己的进展反馈出来,并主动讲述自己的情况。我在开始开发项目时,就跟导师约好我会于每周五晚上用几句话概括本周的工作和下周的安排,并将自己目前遇到的问题或觉得会有挑战性的地方备注在后面。这样的反馈沟通既是对导师的尊重,也表明自己正在把握着对项目的掌控感。在任何时候,若需要资源和信息都可以跟社区多多求助。我在前面也提到过社区甚至为我提供了众智服务器验证本地环境,这意味着我不再需要在自己的轻薄本上编译一次就要挂一个晚上等待了:) 这极大提高了我的生产效率。另外在最后的转测阶段,为了合力定位问题,社区还提供了原有测试仓的部分关键用例供我进行本地检验,这就给任务的交付质量多带来了一层保障。ospp:最后,有什么话想对准备参与开源之夏的学弟学妹们说的?xlinsist:我曾经在项目申请书的结尾这样写道:“希望如有机会,我也能够成长为后续开源之夏活动的导师,以实际行动点亮开源计划中的一盏指明灯,为后辈们指引光明的道途。”我始终相信开源社区是一个注重传承的社区,既能承载过来人的经验,也能包容后浪们的智慧。我会继续以实际行动投入到开源社区的建设中,尽我所能给大家解答困惑。也希望大家在开展项目时,能够主动参与到开源社区中来,积极参与到大家的讨论中来。当我们的力量汇聚在了一起时,我们的光明就是黑夜中璀璨的星空。[1]https://leetcode.cn/problems/edit-distance/[2]https://www.mindspore.cn/docs/zh-CN/master/api_python/ops/mindspore.ops.EditDistance.html