本节将介绍如何使用tensorRT C++ API 进行网络模型创建。
1 使用C++ API 进行 tensorRT 模型创建
还是通过 tensorRT官方给的一个例程来学习。
还是mnist手写体识别的例子。上一节主要是用 tensorRT提供的NvCaffeParser来将 Caffe中的model 转换成tensorRT中特有的模型结构。NvCaffeParser是tensorRT封装好的一个用以解析Caffe模型的工具 (较顶层的API),同样的还有 NvUffPaser是用于解析TensorFlow的工具。
除了以上两个封装好的工具之外,还可以使用tensorRT提供的C++ API(底层的API)来直接在tensorRT中创建模型。这时 tensorRT 相当于是一个独立的深度学习框架了,这个框架和其他框架(Caffe, TensorFlow,MXNet等)一样都具备搭建网络模型的能力(只有前向计算没有反向传播)。
不同之处在于:
- 这个框架不能用于训练,模型的权值参数要人为给定;
- 可以针对设定网络模型(自己使用API创建网络模型)或给定模型(使用NvCaffeParser或NvUffPaser导入其他深度学习框架训练好的模型)做一系列优化,以加快推理速度(inference)
使用C++ API函数部署网络主要分为四个步骤:
- 创建网络;
- 为网络添加输入;
- 添加各种各样的层;
- 设定网络输出;
以上,第1,2,4步骤在使用 NvCaffeParser 时也是有的。只有第3步是本节所讲的方法中特有的,其实对于NvCaffeParser 工具来说,他只是把 第 3步封装起来了而已。
如下,对比一下 NvCaffeParser 的使用方法,下面的代码中只列出了关键部分的代码。完整代码请看上一节。
|
|
上述四个步骤对应部分已在注释标出。可见 NvCaffeParser 工具中最主要的是 parse 函数,这个函数接受网络模型文件(deploy.prototxt)、权值文件(net.caffemodel)为参数,这两个文件是caffe的模型定义文件和训练参数文件。parse 函数会解析这两个文件并对应生成 tensorRT的模型结构。
对于NvCaffeParser 工具来说,是需要三个文件的,分别是:
- 网络模型文件(比如,caffe的deploy.prototxt)
- 训练好的权值文件(比如,caffe的net.caffemodel)
- 标签文件(这个主要是将模型产生的数字标号分类,与真实的名称对应起来)
以下分步骤说明四个步骤:
1.1 创建网络
先创建一个tensorRT的network,这个network 现在只是个空架子,比较简单:
|
|
1.2 为网络添加输入
所有的网络都需要明确输入是哪个blob,因为这是数据传送的入口。
|
|
INPUT_BLOB_NAME 是为输入 blob起的名字;
dt是指数据类型,有kFLOAT(float 32), kHALF(float 16), kINT8(int 8)等类型;
12345678//位于 NvInfer.h 文件enum class DataType : int{kFLOAT = 0, //!< FP32 format.kHALF = 1, //!< FP16 format.kINT8 = 2, //!< INT8 format.kINT32 = 3 //!< INT32 format. 这个是TensorRT新增的};DimsCHW{ 1, INPUT_H, INPUT_W} 是指,batch为1(省略),channel 为1,输入height 和width分别为 INPUT_H, INPUT_W的blob;
1.3 添加各种各样的层
- 以下示例是添加一个 scale layer
|
|
主要就是 addScale 函数,后面接受的参数是这一层需要设置的参数。
scale 层的作用是对每个输入数据进行幂运算
f(x)= (shift + scale * x) ^ power
层类型:Power
可选参数:
power: 默认为1
scale: 默认为1
shift: 默认为0
就是一种激活层。
Weights 类的定义如下:
|
|
以上是不包含训练参数的层,还有 Relu层,Pooling层等。
包含训练参数的层,比如卷积层,全连接层,要先加载权值文件。
- 以下示例是添加一个卷积层
|
|
第6行添加卷积层:
|
|
*scale_1->getOutput(0) :获取上一层 scale层的输出
20:卷积核个数,或者输出feature map 层数
DimsHW{5, 5}:卷积核大小
weightMap["conv1filter"], weightMap["conv1bias"]:权值系数矩阵
上面的 mnistapi.wts 文件,是用于存放网络中各个层间的权值系数的,该文件位于 /usr/src/tensorrt/data
文件夹中。
可以用notepad打开看一下,如下:
可见每一行都是一层的一些参数,比如 conv1bias 是指第一个卷积层的偏置系数,后面的0 指的是 kFLOAT 类型,也就是 float 32;后面的20是系数的个数,因为输出是20,所以偏置是20个;下面一行是 卷积核的系数,因为是20个 5×5的卷积核,所以有 20×5×5=500个参数。其它层依次类推。
这个文件是例程中直接给的,感觉像是 用caffe等工具训练后,将weights系数从caffemodel 中提取出来的。直接读取caffemodel应该也是可以的,稍微改一下接口:解析caffemodel文件然后将层名和权值参数键值对存到一个map中,网上大概找了一下,比如 这个 ,解析后的caffemodel如下所示:
conv1 最下面有一个 blobs结构,这个是weights系数;每一个包含参数的层(卷积,全连接等;激活层,池化层没有参数)都有一个 blobs结构。只需将这些参数提取出来,保存到一个map中。
除此之外也可以添加很多其他的层,比如反卷积层,池化层,全连接层等,具体参考 英伟达官方API 。
添加层的过程就相当于 NvCaffeParser 工具中 parse 函数解析 deploy.prototxt 文件的过程。
1.4 设定网络输出
网络必须知道哪一个blob是输出的。
如下代码,在网络的最后添加了一个softmax层,并将这个层命名为 OUTPUT_BLOB_NAME,之后指定为输出层。
|
|
那直接使用底层API有什么好处呢?看下表
Feature | C++ | Python | NvCaffeParser | NvUffParser |
---|---|---|---|---|
CNNs | yes | yes | yes | yes |
RNNs | yes | yes | no | no |
INT8 Calibration | yes | yes | NA | NA |
Asymmetric Padding | yes | yes | no | no |
上表列出了 tensorRT 的不同特点与 API 对应的情况。可以看到对于 RNN,int8校准(float 32 转为 int8),不对称 padding 来说,NvCaffeParser是不支持的,只有 C++ API 和 Python API,才是支持的。
所以说如果是针对很复杂的网络结构使用tensorRT,还是直接使用底层的 C++ API,和Python API 较好。底层C++ API还可以解析像 darknet 这样的网络模型,因为它需要的就只是一个层名和权值参数对应的map文件。
2 官方例程
例程位于 /usr/src/tensorrt/samples/sampleMNISTAPI
2.1 build phase
|
|
APIToModel函数:
|
|
createMNISTEngine函数如下:
|
|
可见里面包含了很多 add* 函数,都是用于添加各种各样的层的。可参考英伟达官方API 。
2.2 deploy phase
deploy阶段基本与之前的无异。
|
|
doInference函数如下:
|
|
参考资料
- caffe中的一些激活函数:http://www.cnblogs.com/denny402/p/5072507.html
- caffemodel 解析:http://www.cnblogs.com/zjutzz/p/6185452.html
- caffemodel 解析:http://www.cnblogs.com/zzq1989/p/4439429.html
- tensorRT C++ API:https://docs.nvidia.com/deeplearning/sdk/tensorrt-api/c_api/index.html
- tensorRT python API:https://docs.nvidia.com/deeplearning/sdk/tensorrt-api/python_api/index.html
- tensorRT 开发者指南:https://docs.nvidia.com/deeplearning/sdk/tensorrt-developer-guide/index.html
- NVIDIA Deep Learning SDK:https://docs.nvidia.com/deeplearning/sdk/index.html