#coding=utf-8
import sys
import argparse
import json
from prototxt_basic import *
parser = argparse.ArgumentParser(description='Convert MXNet jason to Caffe prototxt')
parser.add_argument('--mx-json', type=str, default='model_mxnet/R50-symbol.json')
parser.add_argument('--cf-prototxt', type=str, default='model_caffe/r50.prototxt')
args = parser.parse_args()
with open(args.mx_json) as json_file:
jdata = json.load(json_file)
with open(args.cf_prototxt, "w") as prototxt_file:
for i_node in range(0,len(jdata['nodes'])):
# 添加字典元素,该层的信息这里已经添加了,例如 prototxt_badic.py info['attrs'] ,就是当前层的信息
node_i = jdata['nodes'][i_node] #格式打开symbol.json看一眼格式,{ "nodes":[{},{},{}]},jdata['nodes']是列表,索引访问列表元素
if str(node_i['op']) == 'null' and str(node_i['name']) != 'data':
continue
print('{}, \top:{}, name:{} -> {}'.format(i_node,node_i['op'].ljust(20),
node_i['name'].ljust(30),
node_i['name']).ljust(20)) #写到caffe prototxt左边空格的个数
info = node_i # 不要搞混了,node_i已经是一个字典变量啦,并且存储的是当前层的键值对信息
info['top'] = info['name'] #在当前层信息的字典变量添加新的键值对
info['bottom'] = [] #下面循环遍历
info['params'] = [] #
for input_idx_i in node_i['inputs']:
input_i = jdata['nodes'][input_idx_i[0]]
'''
遍历的第一个位置的第一个位置肯定是bottom,得到name,"inputs": [[0, 0, 0], [1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 0, 0]]
然后跳出循环得到遍历后面的第一个元素是该层的多个参数信息,bn层4个参数
特点是 op 不是null,是这一层的输入bottom 的name,但是数据输入层的信息,name=data,op=null,所以or
json文件顺序是,该层参数信息字典都是,该层前面位置已经放好了
'''
if str(input_i['op']) != 'null' or (str(input_i['name']) == 'data'): #这种情况是得到bottom,name
info['bottom'].append(str(input_i['name']))
## 这里应该,and input_i['name']!="data" ,不然data 后第一个卷积,会多添加data这个名字,不过实际不影响
if str(input_i['op']) == 'null' : #第一个输入层索引不走这,第二及之后的索引列表走这,获得参数信息
info['params'].append(str(input_i['name']))
'''
参数的名字开头和层的名字基本上都是一样的,这里这对卷积层,根据这个kv ,添加一个param,
一般都是参数名字conv0_weight,层的名字是conv0,不走下面的语句
但是有时候mxent层的名字不对应例如,参数名字"mobilenet0_conv1_weight",层名字"mobilenet0_conv1_fwd",多了fwd,走这边
多添加caffe该层的一个信息,
param {
name: "mobilenet0_conv1_weight"
}
'''
if not str(input_i['name']).startswith(str(node_i['name'])): #判断参数层的名字,输入层的名字在上一个if,不走这里
print(' use shared weight -> %s'% str(input_i['name']))
info['share'] = True #添加变量info 字典元素,key value,,
write_node(prototxt_file, info) #调用函数
print("succed")
'''
test model protoxt
1、~/nas/caffe/build/tools/caffe time -model ./model_caffe/r50.prototxt -gpu 1
prototxt 感觉没有问题也都转换成功了
但是res50,模型结构出不来,感觉每一层都没有问题,
运行上述protoxt,test, 层 _plus0,有问题,最后发现,json文件,有两个_plus0,所以caffe 对应名字,重复了,修改最上面的该层名字
然后_plus1又有问题,发现_plus1 上面积层有最下面 也有
2、
caffe 模型运行,一张图片加载都超出内存,所有group 大于1的卷积层加入,engine: CAFFE,使用caffe 的计算,不用cuda 计算,就可以
mobilenet 模型会有这个问题
convolution_param {
num_output: 8
kernel_size: 3
pad: 1
group: 8
stride: 1
bias_term: false
engine: CAFFE
}
3、
mxnet upsample , 后面还有一个crop,caffe 反卷积层参数是直接写的,要根据不同模型修改shape,不知道Deconvosation,shape chw,
~/nas/caffe/build/tools/caffe time -model ./model_caffe/r50.prototxt -gpu 1,运行,报错,查看打印出来层的shape ,手动修改Deconvolution参数就可以了,
这个命令用来调试prototxt文件,找不到bug,根据错误提示,找到问题
4、res50还有一个bug,mxnet pool 下取整, 160.5会变成160
caffe 最大池化是 上取整数变成161
ceil_mode:false,该层添加相关代码重新编译文件后,最大池化,变成下取整
caffe 卷积下取整,最大池化上取整
pytorch 默认都是下取整,ceil_mode=True ,是上取整
mxnet 下取整
5、mx.viz.print_summary(sym, shape={“data”: (1, 3, 640, 640)}),打印每层shape
和caffe,图显示,就能看出来,shape不一致
6、upsampe,添加到代码中之后,caffe 编译成功,mxnet3caffe.py 导caffe 模型出错,不能解析,感觉caffe 重新编译的环境没有包含一样
但是终端直接进入python环境,导入caffe,prototxt 文件就没有问题,没有解决这个bug,
所以现在还是,upsample 到 deconvolution crop ,直接转换,然后手动修改prototxt文件的这个层,
但是注意,caffe,还有个问题,删除修改没有参数的层,对应的权重文件其实有一点点不对应,不知道哪里不对应
所以用修改的caffe,prototxt 文件和原来的prototxt 文件直接拷贝权重得到修改后prototxt 文件的权重
7、
caffe 上采样层源代码
caffe 最大池化源代码,池化添加下取整,ceil_mode:false(不进行上取整)
‘’’
‘’’
上述两个循环,针对json 文件,格式,“inputs”: [[7, 0, 0], [8, 0, 0], [9, 0, 0], [10, 0, 0], [11, 0, 0]],这个输入
表示有参数的层 的键值对信息,添加
json文件,对照看,op 很多是Null, 例如bn 层,前面几层是bn层相关的参数,op=null, 不是空的时候,inputs不是空,其列表索引是bn参数信息
例如下
{
“nodes”: [
{
“op”: “null”,
“name”: “data”,
“inputs”: []
},
{
“op”: “null”,
“name”: “bn_data_gamma”,
“inputs”: []
},
{
“op”: “null”,
“name”: “bn_data_beta”,
“inputs”: []
},
{
“op”: “null”,
“name”: “bn_data_moving_mean”,
“attrs”: {
“init”: “[“zero”, {}]”,
“eps”: “2e-05”,
“fix_gamma”: “True”,
“momentum”: “0.9”,
“use_global_stats”: “False”
},
“inputs”: []
},
{
“op”: “null”,
“name”: “bn_data_moving_var”,
“attrs”: {
“init”: “[“one”, {}]”,
“eps”: “2e-05”,
“fix_gamma”: “True”,
“momentum”: “0.9”,
“use_global_stats”: “False”
},
“inputs”: []
},
{
“op”: “BatchNorm”,
“name”: “bn_data”,
“attrs”: {
“eps”: “2e-05”,
“fix_gamma”: “True”,
“momentum”: “0.9”,
“use_global_stats”: “False”
},
“inputs”: [[0, 0, 0], [1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 0, 0]]
},[0, 0, 0],第一个元素0,是该层输入层的索引bottom, name是data
[1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 0, 0] 第一个元素1,2,3,4,是该层输入参数索引,
#############一下面为例子,循环过程的操作
{
“nodes”: [
{
“op”: “null”,
“name”: “data”,
“inputs”: []
},
{
“op”: “null”,
“name”: “mobilenet0_conv0_weight”,
“attrs”: {
“dtype”: “0”,
“lr_mult”: “1.0”,
“shape”: “(8L, 3L, 3L, 3L)”,
“storage_type”: “0”,
“wd_mult”: “1.0”
},
“inputs”: []
},
{
“op”: “Convolution”,
“name”: “mobilenet0_conv0_fwd”,
“attrs”: {
“dilate”: “(1, 1)”,
“kernel”: “(3, 3)”,
“layout”: “NCHW”,
“no_bias”: “True”,
“num_filter”: “8”,
“num_group”: “1”,
“pad”: “(1, 1)”,
“stride”: “(2, 2)”
},
“inputs”: [[0, 0, 0], [1, 0, 0]]
},
这里是0 1 2索引 3键值对
遍历字典元素,op null 都continue
直到 op 不是null,这时候,字典变量node_i 等于当前的卷积层所有参数,top name 和当前层的名字一致
然后再循环"inputs"列表成员的第一个元素,得到bootom name,和而外参数,例如卷积层没有额外参数,
但是bn层除了当前层的参数,还有而外参数层,(这不在网络结构中,只是保存的时候添加的一些层,作为参数保存的多余的层)
虽然是多余的层,但是参数不多于,参数需要提取出来,info[‘params’]添加的是"inputs"列表索引第2个及以后的成员信息的json文件的名字,
例如这里存储的就是 ,mobilenet0_conv0_weight,方便 权重复制到caffe里面寻找,mxnet权重名字,得到权重的信息
info[‘params’] 存储的是层对应的参数的名字,如果层名字不是对应关系,
例如参数名字"mobilenet0_conv1_weight",层名字"mobilenet0_conv1_fwd",多了fwd,
caffe prototxt ,把这个名字,写出来,可以不写,模型本身无用,这是mxnet 这一层的权重名字,
多添加caffe该层的一个信息,
param {
name: “mobilenet0_conv1_weight”
}‘’’