- 下载ncnn
- 下载ncnn:推荐使用git工具,不建议直接download zip,后续可能会出现.h与.cpp文件缺失问题。
- 编译ncnn
-
下载protobuf:
使用git工具下载,用cmake工具生成protobuf工程项目,分别在debug与release模式下编译libprotobuf与protoc子工程项目,确认执行该步骤后是否生成protoc.exe、libprotobuf.lib与libprotobufd.lib这三个文件; -
cmake编译ncnn工程项目的注意点:
cmake设置:选中Advanced选项,方便查看所有选项;
ncnn 顶层CMakeLists.txt文件:去掉 # add_subdirectory(examples) 注释,生成的squeezenet子工程方便代码参考; -
第一次Configure时会出现如图1所示的错误:
添加libprotobufd.lib、libprotobuf.lib、protoc.exe文件与src文件夹的路径,再次Configure后不再报错,点击Generate便可生成ncnn.sln文件;
图1.png -
debug或release模式下编译ncnn、squeezenet与mxnet2ncnn项目:
设置子项目工程的Properties/(C/C++)/All Options/Runtime Library选项与protobuf一致(MTD);删除Properties/(C/C++)/All Options/Additional Options的内容,如图2所示:
图2.png -
编译ncnn,出现C3005错误:
“collapse”: unexpected token encountered on OpenMP “parallel for”directive,点击该处,进入convolutiondepthwise.cpp文件,将175行的collapse注释便可编译成功,得到ncnn.lib文件,编译mxnet2ncnn可得到mxnet2ncnn.exe文件。
- 模型转换
-
在mxnet2ncnn.exe文件的当前目录新建两个文件夹,orig_model用来放mxnet框架的模型文件,converted_model用来放转换为支持ncnn框架的模型文件;
-
在mxnet2ncnn.exe文件的当前目录新建.bat脚本文件,复制下面的内容;
set MXNET_MODEL_DIR=orig_model
set NCNN_MODEL_DIR=converted_model
mxnet2ncnn.exe %MXNET_MODEL_DIR%/multitask2-symbol.json %MXNET_MODEL_DIR%/multitask2-0004.params %NCNN_MODEL_DIR%/multitask2.param %NCNN_MODEL_DIR%/multitask2.bin
pause
- 代码移植
-
mxnet对输入图片执行的操作顺序:
色彩转换:BGR2RGB;
图片尺寸调整为96×96;
数据类型转换为float32;
减均值:RGB的3个通道对应值为(123, 117, 104) -
以squeezenet.cpp为框架模板,将mxnet执行的操作按顺序移植到ncnn:
加载模型:添加转换后的模型路径;
色彩转换与尺寸调整;
数据类型转换;
减均值;
数据输入与输出(模型的第一层与最后一层);
- debug
-
“std::greater< std::pair<float, int> >());” 提示Greater不是std的成员:
在文件开头添加 #include <functional>; 这一句; -
cls_scores的尺寸调整:
设置squeezenet子工程项目为启动项,调试运行出现如图3所示的错误。Debug发现out的维度为26×1×1,执行cls_scores.resize(out.c)后cls_scores的size=1,与out尺寸不匹配,因此在cls_scores.resize(out.c)之前通过out = out.reshape(1,1,26)对out的维度进行调整,再次调试运行不再报错,如图4所示:
图3.png
图4.png -
ncnn框架默认转浮点数:
在static int print_topk函数与main函数的return 0这两行设置断点,比对两种框架最后一层输出数据发现其大小差距在50倍以上。网上有提到ncnn默认转半精度浮点数,因此将数据类型转换这句代码注释后(代码如下)重新debug,再次比对输出结果,差距缩小,部分数据整数部分一致,小数部分不同;
static int detect_squeezenet(const cv::Mat& bgr, std::vector<float>& cls_scores)
{
ncnn::Net squeezenet;
squeezenet.load_param("../tools/mxnet/debug/converted_model/multitask2.param");
squeezenet.load_model("../tools/mxnet/debug/converted_model/multitask2.bin");
//cv::Mat dst;
//bgr.convertTo(dst, CV_32FC3, 1.0);
ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols, bgr.rows, 96, 96);
const float mean_vals[3] = {123.f, 117.f, 104.f};
in.substract_mean_normalize(mean_vals, 0);
ncnn::Extractor ex = squeezenet.create_extractor();
ex.set_light_mode(true);
ex.input("data", in);
ncnn::Mat out;
ex.extract("softmax2", out);
out = out.reshape(1, 1, 26);
cls_scores.resize(out.c);
for (int j=0; j<out.c; j++)
{
const float* prob = out.channel(j);
cls_scores[j] = prob[0];
}
return 0;
}
-
elu函数的指前因子默认设置差异:
对mxnet与ncnn框架的输出结果进行逐层比对:发现在58层-convolution(共83层)时两者小数部分第2位与第3位之后基本不一致,之后差距随层数的增加逐渐增大,未找出原因。重新训练mxnet框架的模型,从只有一层开始,每训练一次增加一层,比对发现两者在elu操作后负数部分不一致,将负数部分取出分析,发现mxnet与ncnn框架的elu服从的函数分别为f(x)=0.25(e^x-1) 与f(x)=0.1(e^x-1),查找mxnet的API,其elu函数的指前因子默认设置为0.25,而ncnn的elu.cpp文件中该参数的设置值为0.1,将其更改为0.25后重新编译ncnn,加载自己训练的一个十几层的模型,输出结果基本一致,再次加载一个二十多层的模型,输出结果也基本一致;
-
pooling层pooling_convention参数的默认设置差异:
当加载之前的83层的模型时,仍然在第58层时输出结果的小数部分第三位开始不一致,未找到原因;重新训练了一个四十多层的模型,发现输出结果维度不匹配,如mxnet的out为64×24×24,ncnn的为64×25×25,在nihui大佬指点下,将mxnet框架模型文件的pooling层pooling_convention参数设置为full(因为该参数mxnet默认设置为valid,而ncnn默认设置为full),重新训练83层的那个模型,比对最终层的输出结果,整数部分一致,小数部分仍有差异;
-
batchnorm(bn)层eps参数的默认设置差异:
mxnet模型bn层的参数eps设置值为2e-5,查看ncnn中mxnet2ncnn.cpp文件的bn层eps的设置值为1e-3,因此,将其修改一致后重新编译得到mxnet2ncnn.exe文件;为避免多gpu训练的模型其bn层的gamma出现问题,nihui建议看看vsooda的帖子,建议训练mxnet的模型时使用单gpu;
用单gpu重新训练83层的那个模型,用修改eps后重新生成的mxnet2ncnn.exe转换模型,再次加载后比对mxnet与ncnn框架的最终输出结果,整数部分及小数部分的前五位已基本一致。(但我后来下载最新的ncnn编译后,mxnet模型训练时使用双gpu,输出结果也能基本一致)。
PS:本人为初学者,有经验的前辈们对涉及的基础部分请飘过~~~
网友评论