关于深度学习:毕设r16自定义算子CPU-SampleDistortedBoundingBox算子移植

24次阅读

共计 6207 个字符,预计需要花费 16 分钟才能阅读完成。

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。

正文完
 0