mxnet虽好,但是mxnet框架还是有点小众,MXnet现在越来越受欢迎了,不过现在要把mxnet训练的模型部落地到移动端,有必要在Inference阶段将其转换为其他框架,以便后续统一部署和管理。Caffe作为小巧灵活的老资格框架,使用灵活,部署方便,所以尝试将mxnet训练的mobilefacenet模型转换为Caffe。这里简单记录用mxnet训的mobilefacenet模型转换为Caffe模型过程遇到的坑,以及完美解决(转换后精度几乎无损,并没有网上说的会掉点)
先写到这里 以后再慢慢补充(逃跑.JPG)
继续填坑。。。。。。
首先展示下结果,下面这个图是mxnet训的mobilefacenet模型对对一张人脸图片输出的128维特征值
下面这张是将maxnet模型转为caffemodel后,对同一张人脸图片用caffemodel输出的结果
可以看出,基本上是无损的,小数点后5、6位才会出现误差。
下面说明一下实现流程
环境:ubuntu16.04 64bit
mxnet版本1.4
主要是站在巨人肩膀上,从github上下载相关mxnet2caffe的demo,然后做各种完善。 从此博主开始了填坑之旅~~
首先下载相应的demo
git clone https://github.com/cypw/MXNet2Caffe.git
阅览下代码,大概知道流程,就是通过 json2prototxt.py将json转为prototxt, 再运行mxnet2caffe.py,将params转为caffemodel, 这里我们要做一些修改,如下:
打开json2prototxt.py ,将模型改为我要转的mobilefacenet模型;
打开prototxt_basic.py,将输入维度改为要转换的模型对应的维度
打开json文件,看一下里面格式是怎么样的
里面有 "op" 、"name" 和 "attrs"三个索引,“attrs”中又包含其他索引
但是,prototxt_basic.py中,是下面这种形式
如果直接运行json2prototxt.py,会报下面这个错误
修改如下:将'param'改为'attrs' ,就可以了
并添加PReLU和Eltwise层(我们模型需要这两层,原代码中并没有实现,原代码对层支持的很少,不知道为什么还有那么对 start 无奈.gif)
def LeakyReLU(txt_file, info):
if info['attrs']['act_type'] == 'elu':
txt_file.write('layer {\n')
txt_file.write(' bottom: "%s"\n' % info['bottom'][0])
txt_file.write(' top: "%s"\n' % info['top'])
txt_file.write(' name: "%s"\n' % info['top'])
txt_file.write(' type: "ELU"\n')
txt_file.write(' elu_param { alpha: 0.25 }\n')
txt_file.write('}\n')
txt_file.write('\n')
elif info['attrs']['act_type'] == 'prelu':
txt_file.write('layer {\n')
txt_file.write(' bottom: "%s"\n' % info['bottom'][0])
txt_file.write(' top: "%s"\n' % info['top'])
txt_file.write(' name: "%s"\n' % info['top'])
txt_file.write(' type: "PReLU"\n')
txt_file.write('}\n')
txt_file.write('\n')
else:
raise Exception("unsupported Activation")
def Eltwise(txt_file, info, op):
txt_file.write('layer {\n')
txt_file.write(' type: "Eltwise"\n')
txt_file.write(' top: "%s"\n' % info['top'])
txt_file.write(' name: "%s"\n' % info['top'])
for btom in info['bottom']:
txt_file.write(' bottom: "%s"\n' % btom)
txt_file.write(' eltwise_param { operation: %s }\n' % op)
txt_file.write('}\n')
txt_file.write('\n')
在prototxt_basic.py文件的write_node函数中添加下面几行
elif info['op'] == 'LeakyReLU':
LeakyReLU(txt_file, info)
elif info['op'] == 'elemwise_add':
ElementWiseSum(txt_file, info)
运行json2prototxt.py ,结果出现下面这个警告并退出
从警告可以看出,代码无法识别_minus_scalar这个op
打开model_mobile_glint_amsoftmax-symbol.json
可以看出,_minus_scalar和_mul_scalar这两个op对输入数据做了归一化,mxnet数据预处理放在了模型里面
在prototxt_basic中,程序没有对这个op做处理,其实也没必要对它做处理,这个op可以直接skip,
在basic_prototxt.py的Convolution函数中,添加如下代码
if('scalar' in info['bottom'][0] ):
info['bottom'][0] = 'data'
如下图所示
至此,我们就可以成功将json转为prototxt了
为了解决在转参数时的名字匹配问题
打开mxnet2caffe.py,将第42行的elif '_gamma' in key_i: 改为 elif '_gamma' in key_i and 'relu' not in key_i :
取消第44行到底46行的注释,如下图所示
至此,就可以成功运行mxnet2caffe.py了
但是,校验最后一层输出的时候 ,发现数据对不上,差异非常大,后来又对比了第一个卷积层的输出,数据完全对的上。
后来发现是卷积之后第一个全连接层的问题,,名字不匹配,转出来的caffe模型全连接层的name="pre_fc1", 全连接层之后接的batchnorm层名字叫"fc1",但是mxnet获得的全连接层权重信息字段是“fc1_weight”,replace("_weight")后变为“fc1”,程序相当于把mxnet全连接层的权重信息赋值给net.params["fc1"],也就是赋值给caffe全连接之后的batchnorm层,所有出现了错误
手动将mxnet全连接层的参数赋值给caffe对应的全连接层,全连接之后的batchnorm和scale层的值也有重新赋值
有一点要注意,我们mobilefacenet全连接之后的bn用的是fix_gamma形式
但是mxnet模型对应的gamma参数为0.00030139,这个值并没有用到,caffe 相应的gamma参数有等于1才行,eps的值也要改为2e-5。
至此,模型转换成功~~~
对mxnet转caffe的一点见解:
如果出现误差,往往都是eps引起的,mxnet模型1e-3 caffe默认1e-5 这点要注意,fix_gamma的问题也要注意,同时也要保证每一层的参数都要正确赋值。在验证的时候,一定会要保证数据预处理的方式一致。