内容简介:加入极市专业CV交流群,与同时提供每月大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流。关注
加入极市专业CV交流群,与 1 0000+来自港科大、北大、清华、中科院、CMU、腾讯、百度 等名校名企视觉开发者互动交流!
同时提供每月大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流。关注 极市平台 公众号 , 回复 加群, 立刻申请入群~
本文授权转自知乎作者 Monstarrrr,https://zhuanlan.zhihu.com/p/100459760。未经作者许可,不可二次转载。
2019年的最后一天,终于填了一个早就想了解的坑。就是关于pytorch如何自定义一个扩展,这里主要是说C++扩展。
首先为什么需要扩展?python调用C++的库也是可行的啊。刚开始我也在思考这个问题,觉得没有必要。但是后来深入了解了以后发现还是有必要的。举个栗子,调用始终是使用的是别人的东西,但是扩展则是通过他人的帮助来完成一个属于自己的东西。
pytorch的C++ extension和 python 的c/c++ extension其实原理差不多,本质上都是为了扩展各自的功能,当然也为了使程序运行更加有效率,差别在于pytorch的C++ extension实施步骤较python的c/c++ extension的要简化一些。
这里以实现神经网络自定义的layer为例:
先说一下基本的流程:
-
利用C++写好自定义层发功能,主要包括前向传播和方向传播,以及pybind11的内容。
-
写好setup.py脚本, 并利用python提供的setuptools来编译并加载C++代码。
-
编译安装,在python中调用C++扩展接口
pybind11是python的一个库,主要负责python与C++11之间的通信
下面就以一个最简单的z=2x+y来看看如何一步步完成这样一个简单运算的layer。
第一步:编写头文件,这里就叫做test.h
/*test.h*/ #include <torch/extension.h>#include <vector> // forward propagation torch::Tensor Test_forward_cpu(const torch::Tensor& inputA, const torch::Tensor& inputB); // backward propagation std::vector<torch::Tensor> Test_backward_cpu(const torch::Tensor& gradOutput);
这里包含一个重要的头文件<torch/extension.h>
这个头文件里面包含很多重要的模块。如用于python和C++11交互的pybind11,以及包含Tensor的一系列定义操作,因为pytorch的基本数据单元是Tensor。
头文件写完以后就要开始写源文件了test.cpp
/*test.cpp*/ #include "test.h" // part1:forward propagation torch::Tensor Test_forward_cpu(const torch::Tensor& x, const torch::Tensor& y) { AT_ASSERTM(x.sizes() == y.sizes()); torch::Tensor z = torch::zeros(x.sizes()); z = 2 * x + y; return z; } //part2:backward propagation std::vector<torch::Tensor> Test_backward_cpu(const torch::Tensor& gradOutput) { torch::Tensor gradOutputX = 2 * gradOutput * torch::ones(gradOutput.sizes()); torch::Tensor gradOutputY = gradOutput * torch::ones(gradOutput.sizes()); return {gradOutputX, gradOutputY}; } // part3:pybind11 (将python与C++11进行绑定, 注意这里的forward,backward名称就是后来在python中可以引用的方法名) PYBIND11_MODULE(TORCH_EXTENSION_NAME, m){ m.def("forward", &Test_forward_cpu, "Test forward"); m.def("backward", &Test_backward_cpu, "Test backward"); }
源文件cpp里面包含了三个部分,第一个部分是forward函数,第二个部分是backward函数,第三个部分是pytorch和C++交互的部分。
至此C++部分的工作就完成了。也就是我们的步骤一:利用C++写好自定义层发功能,主要包括前向传播和方向传播,以及pybind11的内容。
下面的工作就是pytorch如何识别和使用这个扩展程序了。
第二步:编写setup.py,这个文件的主要作用是用来编译C++文件以及建立链接关系。
现在的文件目录排布为:
setup.py中的内容为:
from setuptools import setup import os import glob from torch.utils.cpp_extension import BuildExtension, CppExtension # 头文件目录 include_dirs = os.path.dirname(os.path.abspath(__file__)) #源代码目录 source_file = glob.glob(os.path.join(working_dirs, 'src', '*.cpp')) setup( name='test_cpp', # 模块名称 ext_modules=[CppExtension('test_cpp', sources=source_file, include_dirs=[include_dirs])], cmdclass={ 'build_ext': BuildExtension } )
这一部分基本上算是一个固定的格式针对不同的问题需要修改的地方就是ext_modules参数,这里面根据实际的需要列表中可以存在多个CppExtension模块,也就是说可以同时编译多个C++文件。
例如像这样:
完成setup.py以后,需要在终端执行python setup.py install
NOTE:建议将扩展安装在个人虚拟环境中
这一步其实是包含了build+install执行的是先编译链接动态链接库,然后将构建好的文件以package的形式安装存放再 当前开发环境 的package的集中存放处,这样就相当于生成了一个完整的package了。和其他的如numpy,torch这些package没什么两样。
执行完这一步后就生成了这一堆东西:
这样,我们的第二步“写好setup.py脚本, 并利用python提供的setuptools来编译并加载C++代码。”也完成了。
NOTE:此时如果在python的控制台中输入import test_cpp会得到这样的错误:
undefined symbol: _ZTIN3c1021AutogradMetaInterfaceE
原因是因为它还没有封装起来,暂时还见不得人~。
下面是最后一步:封装调用这个扩展(extension),先在与setup.py相同的目录下新建一个test.py
内容为:
from torch.autograd import Function import torch import test_cpp class _TestFunction(Function): @staticmethod def forward(ctx, x, y): """ It must accept a context ctx as the first argument, followed by any number of arguments (tensors or other types). The context can be used to store tensors that can be then retrieved during the backward pass.""" return test_cpp.forward(x, y) @staticmethod def backward(ctx, gradOutput): gradX, gradY = test_cpp.backward(gradOutput) return gradX, gradY # 封装成一个模块(Module) class Test(torch.nn.Module): def __init__(self): super(Test, self).__init__() def forward(self, inputA, inputB): return _TestFunction.apply(inputA, inputB)
这是pytorch的autograd中的一个扩展函数的接口模板。基本pytorch中所有的层的前向传播和反向传播都是这样写的。
关于pytorch的方向传播的细节,有两个需要注意的点。其一,forward函数中有一个ctx变量,这是一定需要的。因为这里面会存一些对方向传播有用的变量(因为有些函数求导是需要用到前向计算过程中的一些计算结果)。backward中也有ctx参数,可以获取从forward函数中所保存的变量。第二个需要注意的点就是backward输出的都是关于变量的梯度,其数目要和forwad中输入的一致,这是一种强制性的要求,如果有些变量不需要求导,就直接返回None即可。
一切就绪以后,可以开始使用了,但是在这之前还需要确定你写的反向传播层的梯度否计算正确。pytorch提供了一个torch.autograd.gradcheck()函数来检查的所计算的梯度是否合理。这个检查的原理是通过比较梯度的数值计算和解析表达之间的误差来判断梯度计算是否正确:
梯度的数值计算法:
梯度的解析法就是我们通过求导公式计算得到的。如:
这一步检查无误以后就可以happy的使用这个模块了。也就是说完整的完成了一个pytorch的c++扩展。
总结一下:首先要写C++源码程序,需要使用一个torch的库<torch/extension.h>。这个库里面规定了如何利用这个库来写C++的extension,里面的基本数据格式为Tensor类型,不是一般的int/char/float类型。需要在C++源码中写一个forward函数和backward函数,在C++源码的最后使用PYBIND11来进行C++11和python的对话。之后便是编写setup.py文件,使用python提供的setuptools和torch自带的BuildExtension和CppExtension工具来进行编译的准备工作。然后再命令行中键入python setup.py install(根据需要使用build或者install,如你不想把你的package安装到系统路径中去,也就是site-package中,那么就用build命令,反之就用install命令),编译完成后还需要使用torch.autograd.Function来将这个扩展写成一个函数,方便在构建网络的时候调用。最后就在合适的地方使用Function.apply(*args)。这样一个定制化的模块就搞定了,也就是说完成了一个完整的pytorch的C++扩展(让用户丝毫感觉不到这个代码是在C++上扩展的~,但是作为一名算法菜狗,了解这些过程还是有必要的,这本身就是问题的一部分,包括如何写新网络中新的layer,毕竟看到再多,如果不自己动手始终差些感觉)
最后补充一点,关于求导,标量对标量的求导就不用多说了,这里主要是标量对向量/矩阵的求导。
这里使用到的标量对于矩阵的求导方法可以参见以下文章,写的非常好。
https://zhuanlan.zhihu.com/p/24709748
极市独家福利
40万奖金的AI移动应用大赛,参赛就有奖,入围还有额外奖励
添加极市小助手微信 (ID : cv-mart) ,备注: 研究方向-姓名-学校/公司-城市 (如:目标检测-小极-北大-深圳),即可申请加入 目标检测、目标跟踪、人脸、工业检测、医学影像、三维&SLAM、图像分割等极市技术交流群 ,更有 每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、 干货资讯汇总、行业技术交流 , 一起来让思想之光照的更远吧~
△长按添加极市小助手
△长按关注极市平台,获取 最新CV干货
觉得有用麻烦给个在看啦~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机程序设计艺术:第4卷 第4册(双语版)
Donald E.Knuth / 苏运霖 / 机械工业出版社 / 2007-4 / 42.00元
关于算法分析的这多卷论著已经长期被公认为经典计算机科学的定义性描述。迄今已出版的完整的三卷组成了程序设计理论和实践的惟一的珍贵源泉,无数读者都赞扬Knuth的著作对个人的深远影响。科学家们为他的分析的美丽和优雅所惊叹,而从事实践的程序员们已经成功地应用他的“菜谱式”的解到日常问题上,所有人都由于Knuth在书中所表现出的博学、清晰、精确和高度幽默而对他无比敬仰。 为开始后续各卷的写作并更......一起来看看 《计算机程序设计艺术:第4卷 第4册(双语版)》 这本书的介绍吧!