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算子在给出图像大小、边界框等一些束缚后输入对象(边界框)的随机扭曲定位,后文将新生成的输入对象称为裁剪框。
图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
图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 输出参数数据类型
表3-2 两头参数数据类型
表3-3 输入参数数据类型4 源代码本我的项目算子实现的代码在附件(超字数了),放进去为须要自定义CPU算子的同学参考,本我的项目没有反向算子,代码中的SDDB算子在流程上与原算子不同,数学论证公式稍多,本文就没打进去,从本人测试集的后果上看,绝对原流程,该流程在该测试集的胜利生成率进步了%2.6,在该测试集中通过断定出有效输出节俭的工夫老本为%16.7。