一、前言
应网友所托,帮忙转换 nanodet
模型(注:原版 nanodet
上采样是线性插值法,海思中不支持,换成反卷积了),花了一天多时间思考与尝试,终于搞定它,过程如下。
二、主要过程
仔细想了下,其实 nanodet
并没有什么生僻的 op
,全部是很普通的、常见的那种,但是它们的组合方式以及数据的shape
太令人难受了,下面我截图说明一下。
1. split
不支持
这里说一下,onnx
的 split
在 caffe
里面是 slice
,slice 相关博客。相关转换代码如下:
# onnx2caffe/_operators.py 文件中
def _convert_Split(node, graph, err):
node_name = node.name
input_name = str(node.inputs[0])
#print(node.attrs)
#print(node.outputs)
layer = myf("Slice", node_name, [input_name], node.outputs,
axis=node.attrs['axis'],
slice_point=node.attrs['split'][0]
)
#print(graph.channel_dims)
graph.channel_dims[node.outputs[0]] = node.attrs['split'][0]
graph.channel_dims[node.outputs[1]] = node.attrs['split'][1]
#print(graph.channel_dims)
return layer
2. transpose
的问题
在之前博客里提过,海思 caffe
的 permute
局限很大,即其支持的 order
只能为 (0,2,3, 1)
。这就让人很难受了,而且从上图可以看出transpose
输入的数据是三维的,难上加难了。
仔细查看输入数据shape
,transpose
上面那个reshape
的输入是四维,满足transpose
需要,那我们是否可以调换一下顺序,下面试着写一下。
# reshape transpose
原始: (1, 1, 52, 52) ---------> (1, 1, 2704) ------------> (1, 2704, 1)
# 现在我们试着这样
# transpose (0, 2, 3, 1) reshape
现在: (1, 1, 52, 52) ---------> (1, 52, 52, 1) ----------> (1, 2704, 1)
这样一来,海思caffe
的 permute
的要求输入shape
满足了,order
也满足了。
另一边分支的 transpose
也同样处理。相关转换代码如下:
# onnx2caffe/_operators.py 文件中
def _convert_transpose_reshape(node, input_name_shape):
node_name = node.name
input_name = input_name_shape[0]
output_name = str(node.outputs[0])
layers = []
transpose_out_name = node_name + "_tran_out"
layer = myf("Permute", node_name, [input_name], [transpose_out_name], permute_param=dict(order=[0, 2, 3, 1]))
layers.append(layer)
#reshape_out_name = node_name + "reshape_out"
layer = myf("Reshape", node_name + "_reshape", [transpose_out_name], [output_name],
reshape_param=dict(shape=dict(dim=[1, -1, input_name_shape[1]])))
layers.append(layer)
return tuple(layers)
3. MatMul
的问题
这个还是先看图吧,直接点。
输入是三维数据,权值是二维的,虽然在 pc
上写了段代码,试验了下,发现这样的数据做矩阵运算是可以的,但是模型推理时数据还是不对。针对这样的情况,想着干脆加两个 reshape ,将其 shape 换过来后再做打算,流程如下:
# matmul
原始:(1, 10816, 11) ---------> (1, 10816, 1)
# reshape matmul reshape
现在:(1, 10816, 11) ---------> (10816, 11) ---------> (10816, 1) ---------> (1, 10816, 1)
相关转换代码如下:
# onnx2caffe/_operators.py 文件中
def _convert_matmul_nanodet(node, graph, err): # 建立网络结构图
layers = []
node_name = node.name
output_name = str(node.outputs[0]) # 输出节点名
input_name = str(node.inputs[0]) # 上层节点名
reshape_1_out = node_name + "_reshape1_out"
layer = myf("Reshape", node_name + "_reshape1", [input_name], [reshape_1_out],
reshape_param=dict(shape=dict(dim=[-1, 11])))
layers.append(layer)
matmul_out = node_name + "_matmul_out"
weight_name = node.inputs[1] # 本层参数名
if weight_name in node.input_tensors: # 判断参数数组是否真的存在
W = node.input_tensors[weight_name] # 获得参数数组
else: # 没有的话也就没意义继续了
err.missing_initializer(node,
"MatMul weight tensor: {} not found in the graph initializer".format(weight_name, ))
return
b = None
bias_flag = False
if len(node.inputs) > 2: # 如果只有上层节点名和 W 权值,则为 2
b = node.input_tensors[node.inputs[2]]
# 权值 shape 不对,也没意义继续了
if len(W.shape) != 2 or (b is not None and len(b.shape) != 1):
return err.unsupported_op_configuration(node, "MatMul is supported only for inner_product layer")
if b is not None:
bias_flag = True
if W.shape[1] != b.shape[0]: # FC 中,二者 shape[0] 是输出通道数, 一定相等,shape[1] 是输入通道数。
return err.unsupported_op_configuration(node,
"MatMul is supported only for inner_product layer")
# 不同于 gemm ,matmul 不做转置操作,w = (A, B), A 是输入通道数, B 是输出通道数
layer = myf("InnerProduct", node_name, [reshape_1_out], [matmul_out], num_output=W.shape[1], bias_term=bias_flag)
graph.channel_dims[output_name] = W.shape[1] # 获得输出通道数
layers.append(layer)
#reshape_2_out = node_name + "_reshape2_out"
layer = myf("Reshape", node_name + "_reshape2", [matmul_out], [output_name],
reshape_param=dict(shape=dict(dim=[1, -1, 1])))
layers.append(layer)
return tuple(layers)
主要是这三个问题吧,其余都是些小问题,就不写出来干扰大家视线了。
三、验证与后语
出于谨慎,对比转换前后模型差异是必不可少的,代码如下:
# 1. onnx 推理 demo
import onnxruntime
import numpy as np
session = onnxruntime.InferenceSession(r"nanodet_weight\m\model_deconv_796_bs16.onnx")
img = np.ones((1, 3, 416, 416)).astype(np.float32)
result = session.run(["236"], {"input.1": img})
print(result)
# 2. caffe 推理 demo
import caffe
import numpy as np
def caffe_test():
weight_file = r"nanodet_weight\m\nanodet.caffemodel"
model_file = r"nanodet_weight\m\nanodet.prototxt"
net = caffe.Net(model_file, weight_file, caffe.TEST)
img = np.ones((1, 3, 416, 416))
net.blobs['input.1'].data[...] = img
out = net.forward()
print(out["236"].shape)
print(out["236"])
结果表明,二者除了精度差异外,其余一模一样,所以各位放心使用!
最后,附上完整转换demo链接,仓促之下写成难免有误,还请各位多多包涵,谢谢。本篇博客是今年第一篇,一段时间没写,确实手生不少,难怪说 ”学如逆水行舟,不进则退“,以后要多总结,多分享。