上一篇介绍了如何将tensorflow转为caffe模型,这一篇将介绍从caffe模型转为nnie框架的wk模型,以及一些模型转换精度下降后的定位方法。
开始之前,首先需要准备RuyiStudio软件,上一篇中有RuyiStudio的链接,参考《HiSVP 开发指南》中的第五章节(RuyiStudio 工具使用指南)进行安装,主要有很多的依赖项,配置依赖项的过程中可以查看ruyi_env_setup中的readme.txt与error.log,按照开发指南的操作很顺畅的安装完毕。
caffe转wk
在进行这一步之前一定要确保转出来的caffe模型与pb模型的推理结果完全一致,否则后续的结果误差定位无法开展。
1. 模型转换的前期准备
参考开发指南的3.5.2节,根据配置文件准备需要的文件。
1.caffe模型
2.模型训练时使用的测试数据集中的20~50张用于数据量化。
3.mean.txt,其中每一行浮点数表示对应的通道均值。
准备好上面三个文件就可以开始模型转换啦。
2.创建新NNIE项目
打开RuyiStudio.exe,创建一个新的nnie_project。
写入工程名称,所用芯片,注意选择红框中的参数,由于不同芯片对应的转换工具不同,这里一定要选择自己需要的芯片类型!
创建完成后,左侧工程目录会出现创建好的项目。
将第一步准备好的文件拷贝到该工程中,具体的目录结构如下。
3.填写转换配置文件
双击打开test.cfg,参考开发指南的3.5.2节来配置文件。
参数设置:
marked_prototxt:自动生成,不要对其进行修改。
log_level:选择Function level打印出每一层的量化输出,方便定位精度问题。
batch_num:一般情况下填写 1。
image_type: 一般情况下选择 U8。
RGB_order: 模型推理时图片输入为RGB或者BGR,根据自己的模型训练时的输入进行选择。
image_list:可以通过选择create,选择好需要的图片之后RuyiStudio会自动生成 image_list.txt,不需要自己创建txt。
norm_type: 查看开发指南里的定义,根据实际图像预处理选择减均值与乘方差的操作。该模型每个通道对应一个均值,mean.txt 分别写三个通道的均值,注意按照 RGB_ORDER设定的通道数填写的对应通道的mean,模型转换时会将图像预处理的相关操作加在wk模型里,先减均值后乘以scale。
其他的参数参考开发指南填写,填写完成后进行保存。
4.模型转换
转换配置文件填写好后,运行转换操作。
转换成功的日志如下:
成功转换后会生成:
- test_inst.wk
- 由于选择的log_level为Function level,所以会生成mapper_quant文件夹,里面会有针对image_list.txt中的最后一张图片的每一层的量化输出结果,这个对之后定位精度问题大有用处。
到此为止模型转换的所有步骤就结束了,上板验证模型转换结果是否正确。
5.wk模型校验
直接在板子上校验生成的wk模型(为什么不使用RuyiStudio自带的仿真程序,因为之前尝试过,仿真跑出来的结果与板子上跑出来的结果不一致,仿真结果相似度99%,而板子只有 70%)。
针对Hi3516CV500的SDK,通过简单的修改sample样例代码去验证。
重新拷贝一份svp文件夹
cp -r /smp/a7_linux/mpp/sample/svp /smp/a7_linux/mpp/sample/test_svp
之后需要修改样例代码 test_svp/nnie/sample/sample_nnie.c中的 void SAMPLE_SVP_NNIE_Cnn(void) 函数的头和尾,img_3_112_112.bgr是需要提前准备好的测试数据并且为BGR planar类型:
void SAMPLE_SVP_NNIE_Cnn(void)
{
HI_CHAR *pcSrcFile = "./data/img_3_112_112.bgr"; //测试数据 BGR PLANAR image
HI_CHAR *pcModelName = "./data/test_inst.wk"; //转换好的模型
HI_U32 u32PicNum = 1;
···
···
/*NNIE process(process the 0-th segment)*/
stProcSegIdx.u32SegIdx = 0;
s32Ret = SAMPLE_SVP_NNIE_Forward(&s_stCnnNnieParam,&stInputDataIdx,&stProcSegIdx,HI_TRUE);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_Forward failed!\n");
/* 删除之后的 Software process 与 Print result
s_stCnnNnieParam.astSegData[0].astDst 为模型的output,最多支持16个output
根据模型output个数,将所有的output都打印出来, 假如模型有5个输出 */
for(int i = 0; i < 5; i++) {
int output_width = s_stCnnNnieParam.astSegData[0].astDst[i].unShape.stWhc.u32Width;
int output_height = s_stCnnNnieParam.astSegData[0].astDst[i].unShape.stWhc.u32Height;
int output_channel = s_stCnnNnieParam.astSegData[0].astDst[i].unShape.stWhc.u32Chn;
int output_stride = s_stCnnNnieParam.astSegData[0].astDst[i].unShape.stWhc.u32Chn;
int output_size = output_width * output_height * output_channel;
printf("output_width %d, output_height %d, output_channel %d, output_size %d\n", output_width, output_height, output_channel, output_size);
HI_S32* output = (HI_S32* )((HI_U8* )s_stCnnNnieParam.astSegData[0].astDst[i].u64VirAddr);
HI_FLOAT outputScore = 0.0f;
printf("output %d: \n", i);
for (int j = 0; j < output_size; ++j) {
outputScore = *(output + j) / 4096.f;
printf("%f ", outputScore);
}
printf("\n");
}
// SAMPLE_SVP_NNIE_PrintReportResult 函数可以输出结果报表文件,在定位精度误差的时候可能会用到
// s32Ret = SAMPLE_SVP_NNIE_PrintReportResult(&s_stCnnNnieParam);
// SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret, CNN_FAIL_1, SAMPLE_SVP_ERR_LEVEL_ERROR,"Error,SAMPLE_SVP_NNIE_PrintReportResult failed!");
CNN_FAIL_1:
...
...
对比上述的输出与caffe模型输出误差是否相差不大(多尝试几张测试图片),如果相差不大,则模型转换成功,可以进行后续的工程落地,若误差较大,则需要定位误差来源!
wk模型转换定位输出误差
定位输出误差基本从几个方面考虑,输入是否一致,nnie算子支持形式,量化造成的结果。下面从这几方面入手,来分析误差造成的原因。
这里只提供分析误差的方法与可以通过努力而规避的误差问题,但实际中有一些问题是本身模型转换的问题(量化或算子支持的问题),没有办法解决,只能更换模型了。
1.使用vector compare
RuyiStudio支持caffe输出每一层结果,点击工具栏上的caffe output标识。
caffe inference 是根据RuyiStudio\Resources\pythonScript\CNN_convert_bin_and_print_featuremap.py脚本运行,脚本默认使用BGR图片,
如果模型输入为RGB,需要在脚本中进行修改:
填写配置参数需要注意Image File 一定要选择 image_list.txt中的最后一张图,因为mapper quant中输出的是最后一张图的量化结果。
最终输出结果log:
查看自己设置的output dir中出现每一层的输出结果:
将caffe output 与mapper quant对比,点击对比按钮。
填写相应的参数信息,Left Folder和Right Folder分别选择caffe output文件夹和mapper_quant文件夹,等待加载完成后点击compare即可开始对比。
双击CosinSimilarity这一列的数字可以查看每一层的具体输出,勾选Convert To Float 将量化结果转换为浮点数。
这一步主要关注输入data是否一致,如果层数太多,可以直接点击文件进行文件的更换,更快的对比自己怀疑拥有误差的层数。如果输入一致,data这一层的余弦相似度应为99%以上,若data相似度很低,一般情况下是RGB,BGR的问题。
若data输入一致,则可以直接对比output层是否一致,如果output层相似度也比较高,则模型转换就没有问题。若output层相似度低,可以从第一层开始观察在哪一层相似度急剧下降,查看相似度低的这一层是否设置的参数与NNIE支持算子的参数设置相冲突。
2.量化造成的误差?
定位是否是由于量化造成的误差,可以在模型转换参数配置时将 compile_mode 的模式修改为 High-precision,生成int16的模型,重新上板跑结果,对比是否与caffe的output一致,一般回归模型在低精度模型下的结果会很飘,在高精度下的结果与caffe基本一致。
3.裁剪caffe模型,定位误差
有时通过上面的操作,也不能找到误差原因。可以采用裁剪caffe模型的方法(直接在prototxt中删除操作,caffemodel不用修改),然后逐渐增加层数去逐步转模型,对比caffe与mapper quant结果是否一致。
将转出来的小模型上板子跑,调用 SAMPLE_SVP_NNIE_PrintReportResult 函数输出结果报表,将输出结果与caffe结果使用vector compare工具进行对比,根据prototxt中算子的前后判断对应的输出参数层名称。
自己在使用这个方法定位出模型中Reshape算子问题,算子的参数如下,加上Reshape操作之后,caffe与量化的data相似度只有58%(很疑惑为什么模型很后面的Reshape层会影响第一层的输入data的量化结果),但是删掉Reshape层后,量化的data以及上一层的output结果与caffe的相似度为99%。
layer {
name: "reshape_6/Reshape"
type: "Reshape"
bottom: "dense_9/Relu:0"
top: "reshape_6/Reshape:0"
reshape_param {
shape {
dim: 1
dim: 3
dim: 3
}
}
}
定位出Reshape算子的问题,尝试将Reshape参数改为4维参数,最后结果就很正常了,可能nnie只能支持4维运算。
layer {
name: "reshape_6/Reshape"
type: "Reshape"
bottom: "dense_9/Relu:0"
top: "reshape_6/Reshape:0"
reshape_param {
shape {
dim: 1
dim: 3
dim: 3
dim: 1
}
}
}
可能遇到的问题
这一小节总结一下最近转模型遇到的问题,供大家参考~
1. caffe与mapper output中的输入data与output的相似度都很高,为什么最后上板之后结果很差!!
在转模型时,vector compare的输入data与output相似度都很高,但是上板结果很差,通过查询开发指南与资料,发现是由于输入问题导致最后误差较大,这里说明一下RGB_ORDER与输入图像通道之间的关系。
RGB_ORDER | 给进去的图片通道 | 最终NNIE喂给模型的图片通道 |
---|---|---|
BGR | BGR | BGR |
BGR | RGB | RGB |
RGB | BGR | RGB |
RGB | RGB | BGR |
从上面的表格可知,
- 若RGB_ORDER设置为BGR,最终NNIE喂给模型的图片通道排序与你给进去的通道排序一致。
- 若RGB_ORDER设置为RGB,则最终NNIE喂给模型的图片通道排序与你给进去的通道排序相反。
- 若RGB_ORDER设置为RGB,在转wk模型时,已经将通道转换加在模型里了。
输入很重要!!输入很重要!!输入很重要!!有问题先排查输入问题!!
2. 为什么caffe与mapper quant对比时在某一层相似度为99%,后面几层相似度很低,之后又出现一层相似度为99%?为什么有时caffe中出现的层mapper quant中没有出现?
还记得在设置模型转换配置文件的时候,有一个 marked_prototxt 中的参数是自动生成的吗?该文件中进行了一些算子融合的操作,RuyiStudio最终使用marked_prototxt进行模型转换,而不是我们提供的caffe prototxt, 所以最终的mapper quant层对应的输出并不是caffe中对应层的输出,举个例子。
caffe与mapper quant 对比的第一层conv层相似度只有31%。
查看marked_prototxt与caffe prototxt,发现marked_prototxt中将 CONV+BN+RELU 进行了融合,故mapper quant 的 conv1 输出对应的应该是caffe模型的 relu1 层的输出。
将caffe中的conv1 层文件更改为 relu1 层的输出文件,对比结果如下,可看出该层相似度达到 99%以上,模型转换在该层没有问题。
在mapper quant中有一些层没有出现也是因为算子融合的问题,在使用vector compare时关注层的对齐即可。
3. 开发指南上面有一些NNIE支持的算子,caffe 1.0并不支持,使用RuyiStudio软件的caffe output 会提示算子不支持,这种情况下怎么使用vector compare来定位问题呢?
应该有同学已经知道怎么做了哈,就是在你自己本地的caffe环境中添加这些层后重新编译,将CNN_convert_bin_and_print_featuremap.py脚本拷贝到本地,运行后会生成每一层的feature map文件xxx_caffe.linear.float,将文件拷贝到项目目录中与mapper quant文件夹对比。运行命令如下,每一个参数的意义在脚本中都可以看到,这里不再赘述。
python CNN_convert_bin_and_print_featuremap.py -m ./facelandmark_mbv2/mbv2_new.prototxt -w ./facelandmark_mbv2/mbv2_new.caffemodel -i ./000030.jpg -n 2 -s 1.0 -o ./caffe_output/ -c 0
需要提醒的是:
- 处理的图片需要是image_list.txt中的最后一张图片。
- 脚本不能处理后缀为txt的mean文件,所以将通道均值直接写在脚本中,使用 transformer.set_mean(‘data’,np.array([103.0, 116.0, 123.5]))替换脚本中的均值读取部分。
- 用于量化的测试图片先resize成模型输入的大小,然后再进行模型转换与本地caffe推理,这样可以避免输入data不一致但是找不到原因的情况。
4. 模型转换配置文件中data scale只能填写一个数,但是实际模型推理的时候是针对每个通道都进行data scale,也就是需要填写3个数值,这种情况怎么办?
可以从两个方面来解决:
- 尝试在转换模型过程中,使用三个通道的 scale 均值作为 模型转换的 data scale,看最终转换的模型误差是否可以接受。
- 在模型开始加一个mul算子,将三个通道的 scale 参数写在mul算子当中,添加算子不同模型有不同的方式,这里不详细展开讲解,关于onnx模型添加算子可以参考此系列的上一篇博客。
5. 模型共有3个输出,caffe与mapper quant相似度很高,但是上板后最终结果很奇怪?
sample样例代码中的 s_stCnnNnieParam.astSegData[0].astDst 为模型的output,最多支持16个output,根据转模型的经验,有时由于修改了caffe模型,导致输出index与最终输出不匹配。
解决方法是尝试打印16个output的width,height,channel,记录与实际输出shape一致的output index,打印对应index的output,看是否与caffe结果相似。
注意输出的每个channel数据是16位对齐的,故当输出shape类似:output_width 1, output_height 1, output_channel 36, output_size 36, 读取数据需要16位对齐:
for (int i = 0; i < output_size; ++i) {
outputScore = *(output + i * 4) / 4096.f;
printf("%f ", outputScore);
}
printf("\n");
6. 一般情况下模型转为量化模型后不是应该变小吗?为什么我转了之后模型反而变大了!!
我转的模型也出现了这种情况,查询资料后发现是因为 conv 中有group参数,在mapper编译的时候会进行拆分,拆分出来的层和指令比较占空间,convolution参数如下:
layer {
name: "conv3"
type: "Convolution"
bottom: "relu2:0"
top: "conv3:0"
convolution_param {
num_output: 24
bias_term: true
group: 24
pad_h: 1
pad_w: 1
kernel_h: 3
kernel_w: 3
stride_h: 1
stride_w: 1
dilation: 1
}
}
当出现这种情况之后,若该层 Convolution 的 input channel,output channel 和 参数中的group相等,这时使用 DepthwiseConv 替换卷积操作,如上面的卷积操作可以变为:
layer {
name: "conv3"
type: "DepthwiseConv"
bottom: "relu2:0"
top: "conv3:0"
convolution_param {
num_output: 24
bias_term: true
pad_h: 1
pad_w: 1
kernel_h: 3
kernel_w: 3
stride_h: 1
stride_w: 1
dilation: 1
}
}
使用DepthwiseConv替换Convolution完成后,发现转模型后确实体积变小了,而且耗时也变短了。根据查询资料发现,若想提高硬件利用率,对于每层的CHW,最好channel是4对齐,width是16对齐。
总结
根据经验,一般情况下,如果不是算子不支持,70%的问题都出现在输入问题上,无论如何,一定要确保data的相似度99%以上,但是有时相似度为99%,但是数据的绝对误差很大,这种情况下要从图像的大小思考一下。上板验证要确保输入的通道情况,保证输入之后再考虑是否是剩下层的问题。另外,裁剪caffe模型定位误差这个方法也很好用。
这篇文章主要介绍了如何使用RuyiStudio将caffe转为wk模型,一些误差定位的方法,以及模型转换过程中可能有疑问的情况。希望这篇文章可以在模型转换过程帮助到大家。