参考博文
Hanson:海思NNIE之Mobilefacenet量化部署
刘山:海思芯片基本教程系列
知乎专栏:AI移动端优化
海思NNIE全称Neural Network Inference Engine,神经网络推理引擎,是海思媒体SoC中专门针对神经网络特别是深度学习卷积神经网络进行加速处理的硬件单元,支持当前大部分公开网络。
目前NNIE只支持Caffe框架。使用其他框架的网络模型需要先转化为Caffe框架下的模型。
某些特定网络层不受NNIE支持,需要进行修改,或分段到CPU上执行。
NNIE通过将caffe模型量化为其专用的wk格式文件在板上进行读取运行。
在进行量化部署之前,需要完成的一些准备工作如下:
在这一步中,会在windows环境中安装好以下工具
参考官方手册《HiSVP开发指南》
直接使用官方SDK包中的脚本安装可能会出现一系列问题,包括Path设置不正确,Python相关库下载失败等等,因此推荐在运行一次脚本之后,对照生成的setup_error.log文件逐一解决问题。
一些常见的问题可以参考博文https://blog.csdn.net/u011728480/article/details/91125581中的解决方法。
path设置的问题
若使用提供的脚本一键安装,path的自动设置可能会出现问题,建议根据手册中的手动安装指南自行设置环境变量。
python一键安装的问题
python一键安装会报很多错误,如下图所示,主要的原因是脚本中设置的许多包的下载地址已经失效了,解决办法是自行下载对应的包并解压在python35路径中,具体的路参考上文博客。
完成安装后,在Ruyi Studio中导入sample_simulator工程后,查看Properties->C/C++ Build->Environment,其中的环境变量应当被正常设置,其中的MYSY_HOME变量可能未自动设置,需要自己手动设置为对应路径
尝试编译并调试sample_simulator工程,若没有出现问题则安装成功。
tips:
1、Ruyi Studio中无法读取头文件,大片错误提示
检查环境变量设置,右键工程,Index->Freshen All Files
海思NNIE目前只支持Caffe模型,这里提供一个已完成Caffe转换的模型。
github
从LFW下载一些人脸数据集用于量化、测试
LFW人脸数据集
量化的一般流程如下:
依次点击File->New->NNIE Project,设置项目名称,检查芯片选择为Hi3559AV100,Project type选择Empty Project,ToolChains选择MinGW GCC,点击Finish。
创建完成后的工程目录如图,其中
includes包含了一些外部引用文件
mapper中会保存导入prototxt后生成的对应网络
sim_out为项目设置文件,若电脑已安装CUDA ,可以通过勾选nnie_sim.ini中的Enable CUDA选项来加速量化过程
MobileFace.cfg为量化参数配置文件
在根目录下创建一个data文件夹,放入下载的mobilefacenet的prototxt和caffemodel文件。在data下创建images文件夹,放入量化用的人脸图片。放入的人脸图片应进行预处理,裁剪背景,使图片仅包含正脸,并resize到112x112大小(量化的resize的插值方法跟python不同,可能会导致数据输入出现微小误差,也可以不做。)
根据官方文档介绍,量化用的图片应选择典型场景图片,一般从网络模型的测试场景中选择20-50张作为参考图片进行量化。且选择的图片要尽量覆盖模型的各个场景。不要选择偏僻场景,过度曝光、纯黑、纯白的图片,选择识别率高,色彩均匀的典型场景图片。
在创建项目时,会自动生成一个与项目名称相同的cfg文件,也可以自己通过在项目右键添加新的cfg文件。双击cfg文件后会显示其图形界面配置,可以通过在下方选项卡切换到文本配置模式,配置参数如下。
可以通过官方手册《HiSVP开发指南》p102~p106的配置文件说明查看每个配置参数的具体介绍。
需要特别注意的几点:
运行完成后,项目目录下会出现几个新文件和新文件夹
其中mapper_quant中保存了所有的输出信息,MobileFace_fun.wk是生成的仿真wk文件。
注意:mapper_quant中保存的输出信息是选择的image_list文件的最后一张图片的输出
Ruyi Studio提供了Vector Comparision工具,能够对比输出向量的相似度、绝对误差等信息,可用于验证模型量化后的输出精度误差大小。
同时海思提供了一个中间层输出脚本,其路径如下。该工具可以通过读取cfg文件输出中间层结果。
HiSVP_PC_V1.1.2.0/tools/nnie/windows/RuyiStudio-2.0.31/Resources/pythonScript/CNN_convert_bin_and_print_featuremap.py
将该脚本复制到工程的data目录下,同时复制一份MobileFace.cfg文件到data目录下,脚本运行时需要读取cfg文件。
此处的cfg文件有两处需要修改的地方。
删除image_list选项,增加image_file选项,路径为量化时使用的image_list文件的最后一张图片的路径。
- [image_list] ./data/images/imageList.txt
+ [image_file] ./images/9.jpg
修改mean_file路径为
- [mean_file] ./data/pixel_mean.txt
+ [mean_file] ./pixel_mean.txt
CNN_convert_bin_and_print_featuremap.py脚本中也有几处需要修改的地方**
1、第438行,此处用于从mean.txt文件中读取mean参数并设置,但是在运行时正则表达式没有正常工作,无法获取均值。
transformer.set_mean(data_layer,np.array(list(map(float,re.findall(r'[-+]?\d*\.\d+|\d+',meanfile)))))
同时,在该脚本中,先进行scale操作,后进行mean操作,因此mean不再是之前设置的127.5,而应当是1,因此改为如下。
transformer.set_mean(data_layer,np.array([1.0, 1.0, 1.0]))
2、因为我们在cfg文件中设置了输入给网络的通道顺序为RGB,而脚本中默认输入的顺序是BGR,因此,在脚本中我们需要进行一次RGB通道顺序变换。在413行添加
img = cv2.imdecode(np.fromfile(img_filename, dtype=np.uint8), -1)
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
inputs = img
脚本的运行指令
python CNN_convert_bin_and_print_featuremap.py -i MobileFace.cfg -m mobilefacenet.prototxt -w mobilefacenet.caffemodel -c 0
建议在首次运行时开启调试模式,通过单步调试查看图像数据预处理过程中的图像数据变化,确认预处理工作正确。
运行结束后会在data文件夹下生成一个output文件夹,其中存储了中间层输出结果B
着重观察第一行数据输入是否完全一致,若出现微小误差,则可能是图像进行resize操作或scale等操作出现的误差,若误差较大,则可能是RGB通道顺序错误,或者图片不一致等等。
观察最后输出的结果,本文测试的输出结果相似度在0.987左右,判断是否符合精度需求。可以通过点击具体条目来查看向量的详细信息。
可以通过修改prototxt文件,删除一些层只留下前面的层,通过这种方法来逐层查看数据输出的误差。
这里输入数据在经过conv层和batchnorm后就完全对不上,但是最后的输出结果又误差极小,目前不知道是什么原因导致的,怀疑可能是NNIE的层处理与caffe有所不同,这种误差可以通过merge_batchnorm操作消除。
经过仿真的向量对比验证后就可以生成inst的板上运行WK了,在cfg的is_simulation选项中选择Chip/Inst,同时将log_level设置为No Print,加快生成速度。
这样过程根据PC机的性能不同所需要的时间在10分钟到1小时不等,需要耐心等待。
最后会在根目录下生成一个MobileFace_inst.wk文件。
通过修改海思提供的例程可以迅速在板上读取并跑通模型,并保存结果。
SVP NNIE例程路径如下
Hi3559AV100R001C02SPC020/Packages/Hi3559AV100R001C02SPC020/01.software/board/Hi3559AV100_SDK_V2.0.2.0/mpp/sample/svp/multi-core/nnie
在sample/sample_nnie.c文件中添加自己的CNN函数,复制函数SAMPLE_SVP_NNIE_Cnn(void),并进行一些细微的修改,代码贴在最后。在sample_nnie_main.c中添加调用该函数即可。
运行后会在运行目录下生成结果输出的hex文件,将该文件拷贝到pc上使用Vector Comparision工具进行对比,即可验证真实的运行误差。
实际运行的相似度一般保持在0.98左右。
void SAMPLE_SVP_NNIE_Cnn(void)
{
HI_CHAR *pcSrcFile = "./data/nnie_image/rgb_planar/10.bgr";
HI_CHAR *pcModelName = "./data/nnie_model/face/mobilefacenet_inst.wk";
HI_U32 u32PicNum = 1;
HI_S32 s32Ret = HI_SUCCESS;
SAMPLE_SVP_NNIE_CFG_S stNnieCfg = {0};
SAMPLE_SVP_NNIE_INPUT_DATA_INDEX_S stInputDataIdx = {0};
SAMPLE_SVP_NNIE_PROCESS_SEG_INDEX_S stProcSegIdx = {0};
/*Set configuration parameter*/
stNnieCfg.pszPic= pcSrcFile;
stNnieCfg.u32MaxInputNum = u32PicNum; //max input image num in each batch
stNnieCfg.u32MaxRoiNum = 0;
stNnieCfg.aenNnieCoreId[0] = SVP_NNIE_ID_0;//set NNIE core
s_stCnnSoftwareParam.u32TopN = 5;
/*Sys init*/
SAMPLE_COMM_SVP_CheckSysInit();
/*CNN Load model*/
SAMPLE_SVP_TRACE_INFO("Cnn Load model!\n");
s32Ret = SAMPLE_COMM_SVP_NNIE_LoadModel(pcModelName,&s_stCnnModel);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_COMM_SVP_NNIE_LoadModel failed!\n");
/*CNN parameter initialization*/
/*Cnn software parameters are set in SAMPLE_SVP_NNIE_Cnn_SoftwareParaInit,
if user has changed net struct, please make sure the parameter settings in
SAMPLE_SVP_NNIE_Cnn_SoftwareParaInit function are correct*/
SAMPLE_SVP_TRACE_INFO("Cnn parameter initialization!\n");
s_stCnnNnieParam.pstModel = &s_stCnnModel.stModel;
s32Ret = SAMPLE_SVP_NNIE_Cnn_ParamInit(&stNnieCfg,&s_stCnnNnieParam,&s_stCnnSoftwareParam);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_Cnn_ParamInit failed!\n");
/*record tskBuf*/
s32Ret = HI_MPI_SVP_NNIE_AddTskBuf(&(s_stCnnNnieParam.astForwardCtrl[0].stTskBuf));
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,HI_MPI_SVP_NNIE_AddTskBuf failed!\n");
/*Fill src data*/
SAMPLE_SVP_TRACE_INFO("Cnn start!\n");
stInputDataIdx.u32SegIdx = 0;
stInputDataIdx.u32NodeIdx = 0;
s32Ret = SAMPLE_SVP_NNIE_FillSrcData(&stNnieCfg,&s_stCnnNnieParam,&stInputDataIdx);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_FillSrcData failed!\n");
/*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*/
/*if user has changed net struct, please make sure SAMPLE_SVP_NNIE_Cnn_GetTopN
function's input datas are correct*/
s32Ret = SAMPLE_SVP_NNIE_Cnn_GetTopN(&s_stCnnNnieParam,&s_stCnnSoftwareParam);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_CnnGetTopN failed!\n");
/*Print result*/
SAMPLE_SVP_TRACE_INFO("Cnn result:\n");
s32Ret = SAMPLE_SVP_NNIE_Cnn_PrintResult(&(s_stCnnSoftwareParam.stGetTopN),
s_stCnnSoftwareParam.u32TopN);
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_1,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,SAMPLE_SVP_NNIE_Cnn_PrintResult failed!\n");
/*Print results*/
{
printf("features:\n{\n");
printf("stride: %d\n",s_stCnnNnieParam.astSegData[0].astDst[0].u32Stride);
printf("blob type :%d\n",s_stCnnNnieParam.astSegData[0].astDst[0].enType);
printf("{\n\tc :%d", s_stCnnNnieParam.astSegData[0].astDst[0].unShape.stWhc.u32Chn);
printf("\n\th :%d", s_stCnnNnieParam.astSegData[0].astDst[0].unShape.stWhc.u32Height);
printf("\n\tw :%d \n}\n", s_stCnnNnieParam.astSegData[0].astDst[0].unShape.stWhc.u32Width);
HI_S32* ps32Score = (HI_S32* )((HI_U8* )s_stCnnNnieParam.astSegData[0].astDst[0].u64VirAddr);
printf("blobs fc1:\n[");
for(HI_U32 i = 0; i < 128; i++)
{
printf("%f ,",*(ps32Score + i) / 4096.f);
}
printf("]\n}\n");
}
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:
/*Remove TskBuf*/
s32Ret = HI_MPI_SVP_NNIE_RemoveTskBuf(&(s_stCnnNnieParam.astForwardCtrl[0].stTskBuf));
SAMPLE_SVP_CHECK_EXPR_GOTO(HI_SUCCESS != s32Ret,CNN_FAIL_0,SAMPLE_SVP_ERR_LEVEL_ERROR,
"Error,HI_MPI_SVP_NNIE_RemoveTskBuf failed!\n");
CNN_FAIL_0:
SAMPLE_SVP_NNIE_Cnn_Deinit(&s_stCnnNnieParam,&s_stCnnSoftwareParam,&s_stCnnModel);
SAMPLE_COMM_SVP_CheckSysExit();
}
参考博文:
Hanson:海思NNIE之RetinaFace量化部署
人脸检测:RetinaFace(开源简化版)详细解读
Retinaface是Insight Face在2019年提出的人脸检测模型,作者提供了三种基础网络支持,基于ResNet的ResNet50和ResNet152版本能提供更好的精度,以及基于mobilenet(0.25)的轻量版本mnet,检测速度更快。
本文使用的是基于mnet网络的模型,使用模型来自
Charrin/RetinaFace-Cpp
Retinaface采用了图像金字塔设计,使用了stride32、stride16、stride8三个尺度,每个尺度在网络输出三个结果,分别是cls:置信度,bbox:候选框信息,landmark:特征点信息,一共9组输出。
前文说过海思对某些特殊层不提供支持,而retinaface中的crop层是NNIE不支持的层。对于不支持的层海思提供的解决方法是将模型分段,将不支持的层移植到CPU上实现。但是在深度学习方便,海思的NNIE硬件算力和CPU算力存在差距,将层分割移植也可能出现许多BUG,因此首先了解该层的功能,再去思考如何解决。
这里参考了Hanson优化处理过之后的prototxt文件,具体的操作步骤参考知乎文章Hanson:海思NNIE之RetinaFace量化部署查看。
从中下载处理好的prototxt和model文件,当然也可以自己修改prototxt文件,结果没有区别。
nniefacelib
需要特别注意的一点:norm_type不需要选择,不需要进行减均值和缩放操作,只需要将指定顺序的图片信息输入给模型即可,模型内部自己会进行相关的预处理操作。若进行了设置最后会检测不到人脸。
量化所使用的图片应尽量选择人脸清晰,明确的图片。
RetinaFace最后会输出9组结果,其中cls的相似度应当很高,而bbox和landmark因为本身数值较小,相对偏移较大,但是转换到实际图片位置后只有像素级的误差,可以忽略不计。
仿真验证没有问题之后,生成inst WK文件在板上运行。
板上运行代码参考nniefacelib中的代码
下载后的代码放在官方例程的svp/multi-core目录下,如下图所示
因为是在Hi3559上运行,代码中的一些地方需要变动。
首先需要从官方例程中复制并替换nnie_face_api.c中的三个函数,包括
static HI_S32 SAMPLE_SVP_NNIE_Forward(SAMPLE_SVP_NNIE_PARAM_S *pstNnieParam,
SAMPLE_SVP_NNIE_INPUT_DATA_INDEX_S* pstInputDataIdx,
SAMPLE_SVP_NNIE_PROCESS_SEG_INDEX_S* pstProcSegIdx,HI_BOOL bInstant);
static HI_S32 SAMPLE_SVP_NNIE_FillSrcData(SAMPLE_SVP_NNIE_CFG_S* pstNnieCfg,
SAMPLE_SVP_NNIE_PARAM_S *pstNnieParam, SAMPLE_SVP_NNIE_INPUT_DATA_INDEX_S* pstInputDataIdx)
static HI_S32 SAMPLE_SVP_NNIE_PrintReportResult(SAMPLE_SVP_NNIE_PARAM_S *pstNnieParam)
其中Makefile.Debug也需要修改
# Hisilicon Hi35xx sample Makefile
include $(PWD)/../Makefile.param
CFLAGS += -I$(PWD)/vision
CFLAGS += -I$(PWD)/sample_nnie_software
CFLAGS += -O3
#CFLAGS += -DSAMPLE_SVP_NNIE_PERF_STAT
SMP_SRCS := $(wildcard *.c)
SMP_SRCS += $(wildcard ./vision/*.c)
SMP_SRCS += $(wildcard ./sample_nnie_software/*.c)
SMP_SRCS += $(wildcard $(PWD)/../common/*.c)
TARGET := nnie_face_test
TARGET_PATH := $(PWD)
# compile linux or HuaweiLite
include $(PWD)/../../$(ARM_ARCH)_$(OSTYPE).mak
通过编译后即可在板上运行,运行结果可以保存后拷贝至PC进行对比分析。
成功运行结果如下