【毕设】(r1.6)自定义算子(CPU) & SampleDistortedBoundingBox算子移植

1 项目概述本项目开题时的目的是将TensorFlow框架下的SampleDistortedBoundingBox算子移植到MindSpore框架下,开题时MindSpore版本是r1.5向r1.6的过渡期,正式开发时MindSpore发行版本为r1.6,因此,本文主要讲述MindSpore r1.6版本下的自定义算子(CPU)的设计思路以及对SampleDistortedBoundingBox算子功能的分析。  如果希望向MindSpore合入代码请参考网址:MindSpore算子众智2 SampleDistortedBoundingBox(后简称SDDB)算子2.1 SDDB算子功能简述在图像识别或目标定位中,除了标注外,通常还会提供边界框注释,训练这种系统的一种常用技术是在保留图像内容的同时随机扭曲图像,即数据增强。本文中的SampleDistortedboundingBox算子在给出图像大小、边界框等一些约束后输出对象(边界框)的随机扭曲定位,后文将新生成的输出对象称为裁剪框。
【毕设】(r1.6)自定义算子(CPU) & SampleDistortedBoundingBox算子移植_第1张图片
图2-1 SDDB算子演示效果   图2-1中,蓝色框(偏左侧框)为原始的输入框,红色框(偏右侧框)为新的在约束条件下新生成的约束框,即裁剪框。2.2 TensorFlow框架下的SDDB算子分析本文移植的SDDB算子参考的TensorFlow版本为(v2.6.0)。源代码gitee镜像,各个输入参数的功能及要求如下:“image_size”参数类型是TensorFlow里面的Tensor,“image_size”中的数据为int类型,一维且长度为3的张量,3个整型数据含义依次为图片高度、图片宽度、图片通道数。“bounding_boxes”算子参数是3维张量,其中的数据类型为float32,三个维度依次表示batch(循环次数),N(单次循环输入边框数),边框坐标。在源代码阅读中,batch并未参与算子的C++实现,N的意义在测试中及阅读源码中发现,其表示输入的边框可以为多个,对于后续的约束,在输入的边框中边框与边框之间形成的不同约束为或的关系,即新生成的裁剪框满足其中一个输入边框参与的约束即可。“seed”参数为整型,其目的为提供一个随机种子,为输入中的几个约束提供随机依据,在TensorFlow1中,seed参数有两个:seed1,seed2,在第二版本后仅使用一个参数seed,默认值为0。“min_object_covered”参数是float32类型浮点数,默认值为0.1,允许的输入值范围为[0,1],其表示输出的裁剪框与输入的边框的交集面积占输入的边框面积的最小值,输入边框之间约束关系为或。“aspect_ratio_range”参数为长度为2的列表,其中数据类型为float32类型浮点数,其表示裁剪框的宽纵比范围。默认值为[0.75,1.33]。“area_range”参数为长度为2的列表,其中数据类型为float32类型浮点数,其表示裁剪框面积占图片面积的范围,默认值为[0.05,1],取值范围为[0,1]。“max_attempts”参数为int型,默认值为100,由于裁剪框是在约束条件下依次随机生成,生成的裁剪框可能不满足最后一个约束,故需要重新生成,该值为生成裁剪框的最大尝试次数,尝试次数超过该值将返回原图大小的边框作为裁剪框。“use_image_if_no_bounding_boxes”参数为bool型,默认值为false,如果为true,则参数“bounding_boxes”为空,将原图边框作为输入框参与算子运算。算子输出为元组,包含三个参数:“begin”,“size”,“bboxes”,其中“begin”、“size”可以作为tensorflow.slice()的输入,用于将裁剪框中的图像裁剪出来,bboxes参数可以作为tensorflow.image.draw_bounding_boxe()的输入,用于在原图中将裁剪框绘制出来。“begin”参数为一维张量,含三个值,分别代表裁剪框左顶点纵坐标,左顶点横坐标,以及维度(取值0)。“size”参数为一维张量,含有三个值,分别代表裁剪框高度,裁剪框宽度,裁剪通道数(取值-1时表示所有通道,在本算子中取值为定值-1)。“bboxes”参数为一维张量,含有四个值,依次表示左上顶点纵坐标、左上顶点横坐标、右下顶点纵坐标,右下顶点横坐标,均归一化至[0,1]区间。算子的流程图大致如图2-2
【毕设】(r1.6)自定义算子(CPU) & SampleDistortedBoundingBox算子移植_第2张图片
图2-2 SDDB算子流程图以下给出当前找到的Tesnsorflow(v2.6.0)框架下涉及SampleDistortedBoundingBox算子实现的相关文件(还有一些定义接口的文件没给出):“\TensorFlow\Python\ops\image_ops_impl.py”。该文件定义了SampleDistorted BoundingBox算子调用的直接接口,并存在于源代码目录中。“\TensorFlow\Python\ops\gen_image_ops.py”。该文件包含了文件(1)中算子接口调用的下一级函数,该函数对参数进行简单的判断,并录入默认值,该文件在编译时生成,不存在于源代码目录中。“\TensorFlow\core\kernels\image\sample_distorted_bounding_box_op.cc”。该文件定义了SampleDistortedBoundingBox算子的实现类以及相关工具类和工具函数,该文件存在于源代码中。“\TensorFlow\core\ops\image_ops.cc”该文件包含了SampleDistortedBounding Box算子的注册函数,该文件存在于源代码中。本设计中关于SampleDistortedBoundingBox算子的实现研究主要在于文件3。3 MindSpore自定义算子本部分主要参考的网址为:自定义算子(CPU),更为详细的版本在irrational #。注意,这是自定义CPU算子的流程,用于自己使用,希望代码合入的话绕道:MindSpore算子众智环境搭建可以与算子设计并行进行,算子设计建议先搭建一个独立的C++算子运行环境。3.1 环境搭建本设计使用的是windows系统的环境,运行build.bat脚本进行编译。环境配置参考:这里。各种需要的软件安装方法可分别在网络上寻找,部分软件安装步骤过时,但可以自行判断,以配置相应环境为目的即可。其中,Python环境可以使用anaconda或minconda安装的环境,但要使编译需要的Python环境以及pip3环境为默认环境(及在cmd中使用的python命令和pip命令来自目标环境,可通过配置系统环境变量设置)编译时注意设置gitee镜像源,github访问不稳定会导致编译失败(有时科学上网也不能解决)。可以通过添加一个名为FROM_GITEE值为1的系统环境变量设置gitee镜像源(这里我找不到论坛的原帖了)。  编译用的电脑性能越强越好,我18年¥4900然后加装到16G内存的游戏本第一次编译跑起来需要近三个小时,后面的编译就只会重新build修改的那些文件涉及的文件,运行时间会减到半个小时(链接过程不吃CPU,但耗时间)。  PS:内存一定要够,我租的2核4G的服务器报内存不够。3.2 自定义算子设计自定义算子的流程以及涉及的文件在官网中简洁明了,Python侧作为前端定义对外接口(注册算子原语),C++侧的头文件定义对内接口(注册算子信息)C++侧的.cc/.cpp文件则是算子实现,这一部分则主要重载两个函数InitKernel(初始化算子)和Launch(算子逻辑执行)。  正是基于MindSpore自定义算子前后端分离的特性,我们在设计算子时,可以围绕函数InitKernel和Launch搭建独立的C++工程来完成和调试算子以回避编译所需要的大量时间。  在搭建项目时,用VScode就行了,因为编译命令在cmd下达就可以了。3.3 自定义算子实现自定义算子(CPU)在众智里是有API参考的(我在代码写完了后才发现。。。通过看其它源代码推测我的项目需要使用哪些函数,再去看源代码推测函数功能)。3.3.1 注册算子原语算子原语的基类有三个:Python中算子原语的基类Primitive;定义了检查算子输入参数的函数,但是使用了C++源码中注册的推理方法的Python中原语的基类PrimitiveWithCheck;在python中定义了跟踪推理的函数的原语基类PrimitiveWithInfer。由于SampleDistortedBoundingBox算子是多输入多输出的,所以选择PrimitiveWithInfer作为SampleDistortedBoundingBox算子的基类。  算子属性由构造函数__init__()定义,属性参数通过该函数的参数列表传值,函数参数列表即可完成默认值的设置。该函数需要完成属性参数值的类型、形状以及数值的检查,同时需要将参数设置为算子的属性参数,并声明算子输入输出的参数名。  算子输入输出的参数名的声明函数为self.init_prim_io_names(inputs,outputs),其中inputs和outputs都是一维列表类型,存储相应的参数名。算子的输入、输出参数应当在函数__infer__()中实现,由于SampleDistorted BoundingBox算子有多个输出,所以由函数infer_shape()、infer_dtype()、infer_value()组合替代了函数__infer__(),前两个函数分别返回输出参数的形状和数据类型,而由于该框架的输出没有list和tuple类型,故输出参数均以Tensor的形式返回。  算子原语注册中使用的检查函数均来自类validator,我使用到的部分函数如下:check_value_type(arg_name, arg_value, valid_types, prim_name=None),该函数用于检查参数的类型。arg_name是字符串类型的参数,表示参数名,其用于检查到错误时报错语句中,arg_value即需要检查的参数,valid_types是一个列表,表示arg_value允许的数据类型。prm_name用于指向算子原语对象。check(arg_name, arg_value, value_name, value, rel=Rel.EQ, prim_name=None, excp_cls=ValueError),该函数用于值与值的比较,arg_name表示参数名,用于报错语句,arg_value表示需要检查的值,arg_value表示被比较的值名,value表示被比较的值,rel表示比较状态,必须是类Rel里的值:EQ表示“==”,NE表示“!=”,LT表示“=”,IN表示“∈”。check_tensor_dtype_valid(arg_name, arg_type, valid_dtypes, prim_name),该函数用于检查Tensor的数据类型。与1用法相同。需要注意的是,在本框架下,算子的输出参数没有Tuple和List,因此在多输出时,均用Tensor输出数据。3.3.2 算子实现自定义CPU算子需要在头文件里完成算子类的定义,继承类CPUKernel,除了增加C++独立实现的变量以及函数外,需要重写函数InitKernel()和函数Launch()。  函数InitKernel(const CNodePtr &kernel_node)功能主要是初始化算子,读取Python侧算子的属性Args参数,CNodePtr表示算子节点,是Python侧与C++侧完成数据交互的关键类。数据的交互依赖于类AnfRuntimeAlgorithm里的函数, GetCNodeName()用于获取算子名称,函数GetInputTensorNum()用于获取输入参数数量,函数GetOutput TensorNum()用于获取输出参数数目,函数GetPrevNodeOutputInferShape()用于根据索引值获取参数的形状。函数GetNodeAttr(),根据参数名获取参数值,这里需要特别注意T所表示的类型需要与参数实际类型保持一致,如果参数涉及到维度,则用vector承载,此外,还应当注意Python侧传入C++侧后参数类型的变化。  函数Launch(const std::vector &inputs, const std::vector< kernel::AddressPtr> &,const std::vector &outputs),函数第三个参数本应当是workspace,这里因为没用到省略,由于输入参数是通过inputs读取,所以不同于C++独立实现部分,本函数承载了输入参数检查的任务。inputs->addr表示第i个输入的地址,这里则是通过指针传递参数,需要注意指针的维度。输出参数同理。  不同于TensorFlow框架下的算子只在C++侧对参数进行检查,MindSpore框架下自定义算子在Python侧和C++侧都对参数进行了检查,以保证程序的安全运行,也得益于MindSpore框架完整的API接口,即使没有说明文档,我在调试中未遇到较大问题。3.3.3 注册算子信息注册算子信息的代码写头文件中,算子的信息注册的实现通过宏定义MS_REG_CPU_KERNEL_(COUNT, OPNAME, ATTR, OPCLASS),COUNT可以省略,OPNAME是算子在Python侧的名称,OPCLASS是算子在C++侧的名称,ATTR表示输入参数和输出参数类型的注册信息。当算子参数的类型组合不同时,需要的注册代码也不同,因此一个算子会有多个注册语句。当组合较多时,可以编写代码借助for循环生成注册语句以提高代码编写效率,本设计的算子注册语句由代码生成,这个方案在参数类型存在调整时只用对源代码的部分地方进行少量的修改便能完成原注册代码的多行修改,大大提高了笔者的工作效率。  在完成算子信息注册后,为保证程序运行的安全,笔者对程序在不同部分所使用的数据类型进行了记录,见表3-1,表3-2,表3-3。  对于数据类型来说,保证程序运行安全的主要有两条:第一,用于承接参数的数据类型字节长度需要大于或等于接收的参数的数据类型或字节长度;第二,计算时,生成的数据不会发生数据溢出,因此,本算子涉及的计算尽量避免了整型数据的乘法运算,涉及乘法的两个数据之一会在0到1之间。
图片
表3-1 输入参数数据类型
【毕设】(r1.6)自定义算子(CPU) & SampleDistortedBoundingBox算子移植_第3张图片
表3-2 中间参数数据类型
图片
表3-3 输出参数数据类型4 源代码本项目算子实现的代码在附件(超字数了),放出来为需要自定义CPU算子的同学参考,本项目没有反向算子,代码中的SDDB算子在流程上与原算子不同,数学论证公式稍多,本文就没打出来,从自己测试集的结果上看,相对原流程,该流程在该测试集的成功生成率提高了%2.6,在该测试集中通过判定出无效输入节省的时间成本为%16.7。

你可能感兴趣的:(深度学习机器学习人工智能)