未验证 提交 e5f64f3e 编写于 作者: M mamingjie-China 提交者: GitHub

Merge pull request #4 from PaddlePaddle/develop_jason

Develop jason
---
name: caffe2paddle
about: Caffe模型转换至PaddlePaddle,请说明Caffe模型的来源,模型类型(例如图像分类、目标检测等)
---
Caffe模型转换至PaddlePaddle,请说明Caffe模型的来源,模型类型(例如图像分类、目标检测等)
如有原模型文件或github链接,如方便可一并附上,方便开发人员分析。
---
name: onnx2paddle
about: ONNX模型转换至PaddlePaddle,请说明ONNX模型的来源,模型类型(例如图像分类、目标检测等)
---
ONNX模型转换至PaddlePaddle,请说明ONNX模型的来源,模型类型(例如图像分类、目标检测等)
如有原模型文件或github链接,如方便可一并附上,方便开发人员分析。
---
name: 其它类型
about: 例如Bug类,需求建议类
---
---
name: paddle2onnx
about: Paddle模型转换至ONNX,请说明Paddle模型的来源,模型类型(例如图像分类、目标检测等)
---
Paddle模型转换至ONNX,请说明Paddle模型的来源,模型类型(例如图像分类、目标检测等)
如有原模型文件或github链接,如方便可一并附上,方便开发人员分析,同时建议说明模型转换的使用场景:)
---
name: tensorflow2paddle
about: TensorFlow模型转换至PaddlePaddle,请说明TensorFlow模型的来源,模型类型(例如图像分类、目标检测等)
---
TensorFlow模型转换至PaddlePaddle,请说明TensorFlow模型的来源,模型类型(例如图像分类、目标检测等)
如有原模型文件或github链接,可以一并附上,方便开发人员分析。
...@@ -11,3 +11,6 @@ x2paddle -f tensorflow -m tf.pb -s pd-model --without_data_format_optimization - ...@@ -11,3 +11,6 @@ x2paddle -f tensorflow -m tf.pb -s pd-model --without_data_format_optimization -
``` ```
> 1. 目前Tensorflow的CV模型大部分均为`NHWC`的输入格式,而Paddle的默认输入格式为`NCHW`,因此X2Paddle在转换过程中,会对如`axis`, `shape`等参数进行转换,适应Paddle的NCHW格式。但在这种情况下,可能会由于TensorFlow模型太复杂,导致出错。 指定`--without_data_format_optimization`后,会停止对`axis`,`shape`等参数的优化(这可能会带来一定数量的transpose操作) > 1. 目前Tensorflow的CV模型大部分均为`NHWC`的输入格式,而Paddle的默认输入格式为`NCHW`,因此X2Paddle在转换过程中,会对如`axis`, `shape`等参数进行转换,适应Paddle的NCHW格式。但在这种情况下,可能会由于TensorFlow模型太复杂,导致出错。 指定`--without_data_format_optimization`后,会停止对`axis`,`shape`等参数的优化(这可能会带来一定数量的transpose操作)
**Q3. ONNX模型转换过程中,提示『Unknown shape for input tensor[tensor name: "input"] -> shape: ['batch', 'sequence'], Please define shape of input here』**
A:该提示信息表示从ONNX的模型中获取到输入tensor(tensor名为"input:)的shape是语义象征性的['batch', 'sequence'],而不是dim为int类型的shape,从而可能会因为部分node的shape无法推理,导致转换失败。所以用户可以尝试手动在提示后输入详细的shape信息,如:-1,3,224,224 其中-1表示Batch
...@@ -10,12 +10,12 @@ X2Paddle在多个主流的CV模型上,测试过TensorFlow/Caffe/ONNX模型的 ...@@ -10,12 +10,12 @@ X2Paddle在多个主流的CV模型上,测试过TensorFlow/Caffe/ONNX模型的
## 环境依赖 ## 环境依赖
python == 2.7 | python >= 3.5 python == 2.7 | python >= 3.5
paddlepaddle >= 1.6.0 paddlepaddle >= 1.8.0
**按需安装以下依赖** **按需安装以下依赖**
tensorflow : tensorflow == 1.14.0 tensorflow : tensorflow == 1.14.0
caffe : 无 caffe : 无
onnx : onnx == 1.6.0 onnxruntime == 1.0.0 onnx : onnx == 1.6.0
## 安装 ## 安装
### 安装方式一(推荐) ### 安装方式一(推荐)
...@@ -61,6 +61,8 @@ x2paddle --framework=paddle2onnx --model=paddle_infer_model_dir --save_dir=onnx_ ...@@ -61,6 +61,8 @@ x2paddle --framework=paddle2onnx --model=paddle_infer_model_dir --save_dir=onnx_
|--without_data_format_optimization | **[可选]** For TensorFlow, 当指定该参数时,关闭NHWC->NCHW的优化,见[文档Q2](FAQ.md) | |--without_data_format_optimization | **[可选]** For TensorFlow, 当指定该参数时,关闭NHWC->NCHW的优化,见[文档Q2](FAQ.md) |
|--define_input_shape | **[可选]** For TensorFlow, 当指定该参数时,强制用户输入每个Placeholder的shape,见[文档Q2](FAQ.md) | |--define_input_shape | **[可选]** For TensorFlow, 当指定该参数时,强制用户输入每个Placeholder的shape,见[文档Q2](FAQ.md) |
|--params_merge | **[可选]** 当指定该参数时,转换完成后,inference_model中的所有模型参数将合并保存为一个文件__params__ | |--params_merge | **[可选]** 当指定该参数时,转换完成后,inference_model中的所有模型参数将合并保存为一个文件__params__ |
|--onnx_opset | **[可选]** 当framework为paddle2onnx时,该参数可设置转换为ONNX的OpSet版本,目前支持9、10、11,默认为10 |
## 使用转换后的模型 ## 使用转换后的模型
......
# X2Paddle支持OP列表 # X2Paddle支持OP列表
> 目前X2Paddle支持40+的TensorFlow OP,30+的Caffe Layer,覆盖了大部分CV分类模型常用的操作。我们在如下列表中给出了目前X2Paddle支持的全部OP。 > 目前X2Paddle支持50+的TensorFlow OP,30+的Caffe Layer,覆盖了大部分CV分类模型常用的操作。我们在如下列表中给出了目前X2Paddle支持的全部OP。
**注:** 目前,部分OP暂未支持,如您在转换过程中出现OP不支持的情况,可自行添加或反馈给我们。欢迎通过[ISSUE反馈](https://github.com/PaddlePaddle/X2Paddle/issues/new)的方式告知我们(模型名,代码实现或模型获取方式),我们会及时跟进:) **注:** 目前,部分OP暂未支持,如您在转换过程中出现OP不支持的情况,可自行添加或反馈给我们。欢迎通过[ISSUE反馈](https://github.com/PaddlePaddle/X2Paddle/issues/new)的方式告知我们(模型名,代码实现或模型获取方式),我们会及时跟进:)
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
| 41 | Cast | 42 | Split | 43 | Squeeze | 44 | ResizeNearestNeighbor | | 41 | Cast | 42 | Split | 43 | Squeeze | 44 | ResizeNearestNeighbor |
| 45 | Softmax | 46 | Range | 47 | ConcatV2 | 48 | MirrorPad | | 45 | Softmax | 46 | Range | 47 | ConcatV2 | 48 | MirrorPad |
| 49 | Identity | 50 | GreaterEqual | 51 | StopGradient | 52 | Minimum | | 49 | Identity | 50 | GreaterEqual | 51 | StopGradient | 52 | Minimum |
| 53 | RadnomUniform | | | | | | | | 53 | RadnomUniform | 54 | Fill | 55 | Floor | 56 | DepthToSpace |
## Caffe ## Caffe
......
__version__ = "0.7.4" __version__ = "0.7.4"
from .core.program import PaddleProgram
program = PaddleProgram()
name_counter = dict()
def gen_name(op_name, var_name):
name = "{}_{}".format(op_name, var_name)
if name not in name_counter:
name_counter[name] = 0
else:
name_counter[name] += 1
name = name + "_" + str(name_counter[name])
return name
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
from six import text_type as _text_type from six import text_type as _text_type
from x2paddle import program
import argparse import argparse
import sys import sys
...@@ -75,6 +76,12 @@ def arg_parser(): ...@@ -75,6 +76,12 @@ def arg_parser():
action="store_true", action="store_true",
default=False, default=False,
help="define input shape for tf model") help="define input shape for tf model")
parser.add_argument(
"--onnx_opset",
"-oo",
type=int,
default=10,
help="when paddle2onnx set onnx opset version to export")
parser.add_argument( parser.add_argument(
"--params_merge", "--params_merge",
"-pm", "-pm",
...@@ -114,29 +121,10 @@ def tf2paddle(model_path, ...@@ -114,29 +121,10 @@ def tf2paddle(model_path,
print("Now translating model from tensorflow to paddle.") print("Now translating model from tensorflow to paddle.")
model = TFDecoder(model_path, define_input_shape=define_input_shape) model = TFDecoder(model_path, define_input_shape=define_input_shape)
if not without_data_format_optimization:
mapper = TFOpMapper(model)
optimizer = TFOptimizer(mapper)
# neccesary optimization
optimizer.delete_redundance_code()
# optimizer below is experimental
optimizer.optimize_elementwise_op()
optimizer.merge_activation()
optimizer.merge_bias()
optimizer.optimize_sub_graph()
# optimizer.merge_batch_norm()
# optimizer.merge_prelu()
else:
mapper = TFOpMapperNHWC(model) mapper = TFOpMapperNHWC(model)
optimizer = TFOptimizer(mapper) program.build()
optimizer.delete_redundance_code() program.gen_model(save_dir)
optimizer.strip_graph()
optimizer.merge_activation()
optimizer.merge_bias()
optimizer.make_nchw_input_output()
optimizer.remove_transpose()
mapper.save_inference_model(save_dir, params_merge)
def caffe2paddle(proto, weight, save_dir, caffe_proto, params_merge=False): def caffe2paddle(proto, weight, save_dir, caffe_proto, params_merge=False):
...@@ -172,24 +160,26 @@ def onnx2paddle(model_path, save_dir, params_merge=False): ...@@ -172,24 +160,26 @@ def onnx2paddle(model_path, save_dir, params_merge=False):
return return
print("Now translating model from onnx to paddle.") print("Now translating model from onnx to paddle.")
from x2paddle.op_mapper.onnx_op_mapper import ONNXOpMapper from x2paddle.op_mapper.onnx2paddle.onnx_op_mapper import ONNXOpMapper
from x2paddle.decoder.onnx_decoder import ONNXDecoder from x2paddle.decoder.onnx_decoder import ONNXDecoder
from x2paddle.optimizer.onnx_optimizer import ONNXOptimizer from x2paddle.optimizer.onnx_optimizer import ONNXOptimizer
import onnxruntime
model = ONNXDecoder(model_path) model = ONNXDecoder(model_path)
mapper = ONNXOpMapper(model, save_dir) mapper = ONNXOpMapper(model)
print("Model optimizing ...")
optimizer = ONNXOptimizer(mapper) optimizer = ONNXOptimizer(mapper)
print("Model optimized.")
optimizer.delete_redundance_code() print("Paddle model and code generating ...")
mapper.save_inference_model(save_dir, params_merge) mapper.save_inference_model(save_dir, params_merge)
print("Paddle model and code generated.")
def paddle2onnx(model_path, save_dir): def paddle2onnx(model_path, save_dir, opset_version=10):
from x2paddle.decoder.paddle_decoder import PaddleDecoder from x2paddle.decoder.paddle_decoder import PaddleDecoder
from x2paddle.op_mapper.paddle_op_mapper import PaddleOpMapper from x2paddle.op_mapper.paddle2onnx.paddle_op_mapper import PaddleOpMapper
model = PaddleDecoder(model_path, '__model__', '__params__') model = PaddleDecoder(model_path, '__model__', '__params__')
mapper = PaddleOpMapper() mapper = PaddleOpMapper()
mapper.convert(model.program, save_dir) mapper.convert(model.program, save_dir, opset_number=opset_version)
def main(): def main():
...@@ -211,18 +201,6 @@ def main(): ...@@ -211,18 +201,6 @@ def main():
assert args.framework is not None, "--framework is not defined(support tensorflow/caffe/onnx)" assert args.framework is not None, "--framework is not defined(support tensorflow/caffe/onnx)"
assert args.save_dir is not None, "--save_dir is not defined" assert args.save_dir is not None, "--save_dir is not defined"
if args.framework == "onnx":
try:
import onnxruntime as rt
version = rt.__version__
if version != '1.0.0':
print("[ERROR] onnxruntime==1.0.0 is required")
return
except:
print(
"[ERROR] onnxruntime is not installed, use \"pip install onnxruntime==1.0.0\"."
)
try: try:
import paddle import paddle
v0, v1, v2 = paddle.__version__.split('.') v0, v1, v2 = paddle.__version__.split('.')
...@@ -261,13 +239,14 @@ def main(): ...@@ -261,13 +239,14 @@ def main():
elif args.framework == "onnx": elif args.framework == "onnx":
assert args.model is not None, "--model should be defined while translating onnx model" assert args.model is not None, "--model should be defined while translating onnx model"
params_merge = False params_merge = False
if args.params_merge: if args.params_merge:
params_merge = True params_merge = True
onnx2paddle(args.model, args.save_dir, params_merge) onnx2paddle(args.model, args.save_dir, params_merge)
elif args.framework == "paddle2onnx": elif args.framework == "paddle2onnx":
assert args.model is not None, "--model should be defined while translating paddle model to onnx" assert args.model is not None, "--model should be defined while translating paddle model to onnx"
paddle2onnx(args.model, args.save_dir) paddle2onnx(args.model, args.save_dir, args.onnx_opset)
else: else:
raise Exception( raise Exception(
......
...@@ -25,6 +25,7 @@ class Layer(object): ...@@ -25,6 +25,7 @@ class Layer(object):
self.inputs = dict() self.inputs = dict()
self.output = None self.output = None
self.is_custom_layer = False self.is_custom_layer = False
self.use_fluid = False
def get_code(self): def get_code(self):
layer_code = "" layer_code = ""
...@@ -38,6 +39,8 @@ class Layer(object): ...@@ -38,6 +39,8 @@ class Layer(object):
layer_code = layer_code + self.op + "(" layer_code = layer_code + self.op + "("
elif self.op == "=": elif self.op == "=":
layer_code = layer_code layer_code = layer_code
elif self.use_fluid:
layer_code = layer_code + "fluid." + self.op + "("
else: else:
layer_code = layer_code + "fluid.layers." + self.op + "(" layer_code = layer_code + "fluid.layers." + self.op + "("
...@@ -108,9 +111,11 @@ class FluidCode(object): ...@@ -108,9 +111,11 @@ class FluidCode(object):
inputs, inputs,
output, output,
param_attr=None, param_attr=None,
use_fluid=False,
is_custom_layer=False): is_custom_layer=False):
layer = Layer() layer = Layer()
layer.op = op layer.op = op
layer.use_fluid = use_fluid
layer.is_custom_layer = is_custom_layer layer.is_custom_layer = is_custom_layer
if inputs is not None: if inputs is not None:
layer.inputs = inputs layer.inputs = inputs
......
...@@ -29,11 +29,14 @@ def export_paddle_param(param, param_name, dir): ...@@ -29,11 +29,14 @@ def export_paddle_param(param, param_name, dir):
"bool": [framework_pb2.VarType.BOOL, None] "bool": [framework_pb2.VarType.BOOL, None]
} }
shape = param.shape shape = param.shape
if str(param.dtype) in ['uint8', 'uint_8', 'bool']:
param = param.astype('int64')
if len(shape) == 0: if len(shape) == 0:
assert param.size == 1, "Unexpected situation happend!" assert param.size == 1, "Unexpected situation happend!"
shape = [1] shape = [1]
assert str(param.dtype) in dtype_map, "Unknown dtype of params." assert str(
param.dtype) in dtype_map, "Unknown dtype {} of params: {}.".format(
str(param.dtype), param_name)
fp = open(os.path.join(dir, param_name), 'wb') fp = open(os.path.join(dir, param_name), 'wb')
numpy.array([0], dtype='int32').tofile(fp) numpy.array([0], dtype='int32').tofile(fp)
numpy.array([0], dtype='int64').tofile(fp) numpy.array([0], dtype='int64').tofile(fp)
...@@ -52,6 +55,24 @@ def export_paddle_param(param, param_name, dir): ...@@ -52,6 +55,24 @@ def export_paddle_param(param, param_name, dir):
def run_net(param_dir="./"): def run_net(param_dir="./"):
import os import os
inputs, outputs = x2paddle_net() inputs, outputs = x2paddle_net()
ops = fluid.default_main_program().global_block().ops
used_vars = list()
for op in ops:
used_vars += op.input_arg_names
tmp = list()
for input in inputs:
if isinstance(input, list):
for ipt in input:
if ipt.name not in used_vars:
continue
tmp.append(ipt)
else:
if input.name not in used_vars:
continue
tmp.append(input)
inputs = tmp
for i, out in enumerate(outputs): for i, out in enumerate(outputs):
if isinstance(out, list): if isinstance(out, list):
for out_part in out: for out_part in out:
...@@ -119,12 +140,30 @@ class OpMapper(object): ...@@ -119,12 +140,30 @@ class OpMapper(object):
import model import model
try: try:
inputs, outputs = model.x2paddle_net() inputs, outputs = model.x2paddle_net()
ops = fluid.default_main_program().global_block().ops
used_vars = list()
for op in ops:
used_vars += op.input_arg_names
for i, out in enumerate(outputs): for i, out in enumerate(outputs):
if isinstance(out, list): if isinstance(out, list):
for out_part in out: for out_part in out:
outputs.append(out_part) outputs.append(out_part)
del outputs[i] del outputs[i]
input_names = [input.name for input in inputs]
input_names = list()
for input in inputs:
if isinstance(input, list):
for ipt in input:
if ipt.name not in used_vars:
continue
input_names.append(ipt.name)
else:
if input.name not in used_vars:
continue
input_names.append(input.name)
exe = fluid.Executor(fluid.CPUPlace()) exe = fluid.Executor(fluid.CPUPlace())
exe.run(fluid.default_startup_program()) exe.run(fluid.default_startup_program())
......
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from __future__ import division
import paddle.fluid as fluid
from paddle.fluid.proto import framework_pb2
import numpy
import collections
import sys
import os
import six
class PaddleLayer(object):
def __init__(self, kernel, inputs, outputs, **kwargs):
assert isinstance(
inputs,
dict), "parameter 'inputs' for PaddleLayer should be type of dict"
assert isinstance(
outputs,
list), "parameter 'outputs' for PaddleLayer should be type of list"
for k, v in inputs.items():
if isinstance(v, list):
for i in v:
assert isinstance(
i, six.string_types
), "value in inputs should be type of string or list of string"
else:
assert isinstance(v, six.string_types) or isinstance(
v, list
), "value in inputs should be type of string or list of string"
for v in outputs:
assert isinstance(
v, six.
string_types), "elements in outputs should be type of string"
self.kernel = kernel
self.inputs = inputs
self.outputs = outputs
self.attrs = kwargs
class PaddleProgram(object):
def __init__(self):
self.layers = list()
self.edges_out = dict()
self.edges_in = dict()
self.inputs = list()
self.outputs = list()
self.parameters = dict()
def clear(self):
self.layers = list()
self.edges_out = dict()
self.edges_in = dict()
self.inputs = list()
self.outputs = list()
self.parameters = dict()
def add_layer(self, kernel, inputs, outputs, **kwargs):
layer = PaddleLayer(kernel, inputs, outputs, **kwargs)
index = len(self.layers)
self.layers.append(layer)
return index
def build(self):
outputs_from_nodes = dict()
for i, layer in enumerate(self.layers):
for input_key, input_var in layer.inputs.items():
vs = input_var
if not isinstance(vs, list):
vs = [vs]
for v in vs:
assert v in outputs_from_nodes, "Couldn't find {} in previous layers, the layers should be make by topological sort".format(
v)
in_layer_index = outputs_from_nodes[v]
if in_layer_index not in self.edges_out:
self.edges_out[in_layer_index] = list()
self.edges_out[in_layer_index].append(i)
if i not in self.edges_in:
self.edges_in[i] = list()
self.edges_in[i].append(in_layer_index)
for output in layer.outputs:
outputs_from_nodes[output] = i
def get_layer_outputs(self, i):
return self.edges_out[i]
def get_layer_inputs(self, i):
return self.edges_in[i]
def gen_code(self, code_dir):
def write_code(f, code_list, indent=0):
indent_blank = " " * indent
for code_line in code_list:
if code_line.strip() == "":
f.write('\n')
else:
f.write(indent_blank + code_line + '\n')
if not os.path.exists(code_dir):
os.makedirs(code_dir)
f = open(os.path.join(code_dir, 'x2paddle_model.py'), 'w')
write_code(
f, [
"from paddle.fluid.initializer import Constant",
"from paddle.fluid.param_attr import ParamAttr",
"import paddle.fluid as fluid"
"", "def x2paddle_net():"
],
indent=0)
for i, layer in enumerate(self.layers):
edges_in = self.edges_in.get(i, [])
edges_out = self.edges_out.get(i, [])
if len(edges_in) == 0 and len(edges_out) == 0:
continue
line = ""
if len(layer.outputs) == 1:
line = layer.outputs[0]
else:
for output in layer.outputs:
line += "{}, ".format(output)
line = line.strip(", ")
line += " = {}(".format(layer.kernel)
for k, v in layer.inputs.items():
if isinstance(v, list):
line += "{}=[{}], ".format(k, ", ".join(v))
else:
line += "{}={}, ".format(k, v)
for k, v in layer.attrs.items():
line += "{}={}, ".format(k, v)
line = line.strip(", ")
line += ")"
write_code(f, [line], indent=1)
write_code(
f, [
"return [{}], [{}]".format(", ".join(self.inputs),
", ".join(self.outputs))
],
indent=1)
f.close()
def gen_model(self, save_dir):
code_dir = os.path.join(save_dir, 'model_with_code')
infer_dir = os.path.join(save_dir, 'inference_model')
self.gen_code(code_dir)
sys.path.append(code_dir)
import x2paddle_model
scope = fluid.Scope()
startup_program = fluid.Program()
main_program = fluid.Program()
with fluid.scope_guard(scope):
with fluid.program_guard(main_program, startup_program):
inputs, outputs = x2paddle_model.x2paddle_net()
exe = fluid.Executor(fluid.CPUPlace())
exe.run(startup_program)
param_dir = os.path.join(code_dir, 'weights')
for k, v in self.parameters.items():
if scope.find_var(k):
self.dump_parameter(k, v, param_dir)
def if_exist(var):
b = os.path.exists(
os.path.join(os.path.join(param_dir, var.name)))
return b
fluid.io.load_vars(
exe, param_dir, main_program, predicate=if_exist)
fluid.io.save_inference_model(
dirname=infer_dir,
feeded_var_names=[i.name for i in inputs],
target_vars=outputs,
executor=exe)
def dump_parameter(self, param_name, param, save_dir):
if not os.path.exists(save_dir):
os.makedirs(save_dir)
dtype_map = {
"int16": [framework_pb2.VarType.INT16, 'h'],
"int32": [framework_pb2.VarType.INT32, 'i'],
"int64": [framework_pb2.VarType.INT64, 'q'],
"float16": [framework_pb2.VarType.FP16, 'e'],
"float32": [framework_pb2.VarType.FP32, 'f'],
"float64": [framework_pb2.VarType.FP64, 'd'],
"bool": [framework_pb2.VarType.BOOL, None]
}
shape = param.shape
if str(param.dtype) in ['uint8', 'uint_8', 'bool']:
param = param.astype('int64')
if len(shape) == 0:
assert param.size == 1, "Unexpected situation happend!"
shape = [1]
assert str(
param.dtype) in dtype_map, "Unknown dtype {} of params: {}.".format(
str(param.dtype), param_name)
fp = open(os.path.join(save_dir, param_name), 'wb')
numpy.array([0], dtype='int32').tofile(fp)
numpy.array([0], dtype='int64').tofile(fp)
numpy.array([0], dtype='int32').tofile(fp)
tensor_desc = framework_pb2.VarType.TensorDesc()
tensor_desc.data_type = dtype_map[str(param.dtype)][0]
tensor_desc.dims.extend(shape)
desc_size = tensor_desc.ByteSize()
numpy.array([desc_size], dtype='int32').tofile(fp)
fp.write(tensor_desc.SerializeToString())
param.tofile(fp)
fp.close()
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
from x2paddle.core.graph import GraphNode, Graph from x2paddle.core.graph import GraphNode, Graph
from x2paddle.core.fluid_code import FluidCode from x2paddle.core.fluid_code import FluidCode
from x2paddle.decoder.onnx_shape_inference import SymbolicShapeInference
from onnx.checker import ValidationError from onnx.checker import ValidationError
from onnx.checker import check_model from onnx.checker import check_model
from onnx.utils import polish_model from onnx.utils import polish_model
...@@ -53,7 +54,7 @@ class ONNXGraphNode(GraphNode): ...@@ -53,7 +54,7 @@ class ONNXGraphNode(GraphNode):
convert ONNX node attributes to dict convert ONNX node attributes to dict
""" """
return { return {
attr.name: self.get_attribute_value2(attr) attr.name: self.get_attribute_value(attr)
for attr in self.layer.attribute for attr in self.layer.attribute
} }
...@@ -64,7 +65,7 @@ class ONNXGraphNode(GraphNode): ...@@ -64,7 +65,7 @@ class ONNXGraphNode(GraphNode):
return None return None
return self.attr_map['value'] return self.attr_map['value']
def get_attribute_value2(self, attr): def get_attribute_value(self, attr):
""" """
get_attribute_value enhanced get_attribute_value enhanced
""" """
...@@ -130,43 +131,90 @@ class ONNXGraphDataNode(GraphNode): ...@@ -130,43 +131,90 @@ class ONNXGraphDataNode(GraphNode):
class ONNXGraph(Graph): class ONNXGraph(Graph):
def __init__(self, onnx_model): def __init__(self, onnx_model):
super(ONNXGraph, self).__init__(onnx_model.graph) super(ONNXGraph, self).__init__(onnx_model)
self.onnx_model = onnx_model self.fixed_input_shape = {}
self.initializer = {} self.initializer = {}
self.place_holder_nodes = list() self.place_holder_nodes = list()
self.value_infos = {}
self.graph = onnx_model.graph
self.get_place_holder_nodes() self.get_place_holder_nodes()
self.value_infos = self.inferred_model_value_info(self.model) print("shape inferencing ...")
self.results_of_inference = dict() self.graph = SymbolicShapeInference.infer_shapes(
onnx_model, fixed_input_shape=self.fixed_input_shape)
print("shape inferenced.")
self.build()
self.collect_value_infos()
self.allocate_shapes()
def get_inner_nodes(self): def get_inner_nodes(self):
""" """
generate inner node of ONNX model generate inner node of ONNX model
""" """
inner_nodes = [] inner_nodes = []
if not isinstance(self.model, onnx.GraphProto): if not isinstance(self.graph, onnx.GraphProto):
logger.error('graph is not a GraphProto instance') logger.error('graph is not a GraphProto instance')
return return
for initializer in self.model.initializer: for initializer in self.graph.initializer:
name = initializer.name name = initializer.name
inner_nodes.append(name) inner_nodes.append(name)
return inner_nodes return inner_nodes
def get_symbolic_shape(self, dims):
shape = []
for dim in dims:
if dim.HasField('dim_param'):
shape.append(dim.dim_param)
else:
shape.append(dim.dim_value)
return shape
def check_input_shape(self, vi):
if vi.type.HasField('tensor_type'):
for dim in vi.type.tensor_type.shape.dim:
if dim.HasField(
'dim_param') and vi.name not in self.fixed_input_shape:
shape = self.get_symbolic_shape(
vi.type.tensor_type.shape.dim)
print(
"Unknown shape for input tensor[tensor name: '{}'] -> shape: {}, Please define shape of input here,\nNote:you can use visualization tools like Netron to check input shape."
.format(vi.name, shape))
right_shape_been_input = False
while not right_shape_been_input:
try:
shape = raw_input(
"Shape of Input(e.g. -1,3,224,224), enter 'N' to skip: "
)
except:
shape = input(
"Shape of Input(e.g. -1,3,224,224), enter 'N' to skip: "
)
if shape.count("-1") > 1:
print("Only 1 dimension can be -1, type again:)")
else:
right_shape_been_input = True
if shape == 'N':
break
shape = [int(dim) for dim in shape.strip().split(',')]
assert shape.count(-1) <= 1, "Only one dimension can be -1"
self.fixed_input_shape[vi.name] = shape
break
def get_place_holder_nodes(self): def get_place_holder_nodes(self):
""" """
generate place_holder node of ONNX model generate place_holder node of ONNX model
""" """
inner_nodes = self.get_inner_nodes() inner_nodes = self.get_inner_nodes()
input_nodes = [value.name for value in self.model.input] for ipt_vi in self.graph.input:
for ipt_data in input_nodes: if ipt_vi.name not in inner_nodes:
if ipt_data not in inner_nodes: self.check_input_shape(ipt_vi)
self.place_holder_nodes.append(ipt_data) self.place_holder_nodes.append(ipt_vi.name)
def get_output_nodes(self): def get_output_nodes(self):
""" """
generate output_nodes node of ONNX model generate output_nodes node of ONNX model
""" """
inner_nodes = self.get_inner_nodes() inner_nodes = self.get_inner_nodes()
output_nodes = [value.name for value in self.model.output] output_nodes = [value.name for value in self.graph.output]
for opt_data in output_nodes: for opt_data in output_nodes:
if opt_data not in inner_nodes: if opt_data not in inner_nodes:
self.output_nodes.append(opt_data) self.output_nodes.append(opt_data)
...@@ -183,11 +231,11 @@ class ONNXGraph(Graph): ...@@ -183,11 +231,11 @@ class ONNXGraph(Graph):
""" """
build topo_sort of ONNX model build topo_sort of ONNX model
""" """
for layer in self.model.node: for layer in self.graph.node:
node = ONNXGraphNode(layer) node = ONNXGraphNode(layer)
self.node_map[layer.name] = node self.node_map[layer.name] = node
for layer in self.model.input: for layer in self.graph.input:
if layer.name not in self.node_map: if layer.name not in self.node_map:
is_place_holder = self.is_place_holder_nodes(layer.name) is_place_holder = self.is_place_holder_nodes(layer.name)
self.node_map[layer.name] = ONNXGraphDataNode( self.node_map[layer.name] = ONNXGraphDataNode(
...@@ -196,7 +244,7 @@ class ONNXGraph(Graph): ...@@ -196,7 +244,7 @@ class ONNXGraph(Graph):
is_global_input=is_place_holder) is_global_input=is_place_holder)
#set data node's weight #set data node's weight
for initializer in self.model.initializer: for initializer in self.graph.initializer:
name = initializer.name name = initializer.name
weight = to_array(initializer) weight = to_array(initializer)
if name in self.node_map: if name in self.node_map:
...@@ -228,7 +276,7 @@ class ONNXGraph(Graph): ...@@ -228,7 +276,7 @@ class ONNXGraph(Graph):
continue continue
if in_node not in self.node_map: if in_node not in self.node_map:
flag = 0 flag = 0
for nd in self.model.node: for nd in self.graph.node:
for idx, opt in enumerate(nd.output): for idx, opt in enumerate(nd.output):
if opt == in_node: if opt == in_node:
self.connect(nd.name, layer_name) self.connect(nd.name, layer_name)
...@@ -256,81 +304,68 @@ class ONNXGraph(Graph): ...@@ -256,81 +304,68 @@ class ONNXGraph(Graph):
ipt_node.index = node.which_child[ipt_node.layer_name] ipt_node.index = node.which_child[ipt_node.layer_name]
return ipt_node return ipt_node
def graph_weights(self, graph): def graph_weights(self):
""" """
generator for weights generator for weights
""" """
if not isinstance(graph, onnx.GraphProto): if not isinstance(self.graph, onnx.GraphProto):
logger.error('graph is not a GraphProto instance') logger.error('graph is not a GraphProto instance')
return return
for initializer in graph.initializer: for initializer in self.graph.initializer:
name = initializer.name name = initializer.name
weight = to_array(initializer) weight = to_array(initializer)
yield name, weight yield name, weight
def inferred_model_value_info(self, graph): def collect_value_infos(self):
""" """
collect value/type info for an ONNX model collect value/type info for an ONNX model
""" """
assert isinstance(graph, assert isinstance(self.graph,
onnx.GraphProto), 'model is not a ModelProto instance' onnx.GraphProto), 'model is not a ModelProto instance'
value_info = Dict() for item in self.graph.value_info:
for item in graph.value_info: self.value_infos[item.name] = {
value_info[item.name] = {
'dtype': 'dtype':
TENSOR_TYPE_TO_NP_TYPE[item.type.tensor_type.elem_type], TENSOR_TYPE_TO_NP_TYPE[item.type.tensor_type.elem_type],
'shape': 'shape':
[dim.dim_value for dim in item.type.tensor_type.shape.dim], [dim.dim_value for dim in item.type.tensor_type.shape.dim],
'external': False 'external': False
} }
for item in graph.input:
assert item.name not in value_info def allocate_shapes(self):
value_info[item.name] = { """
'dtype': run shape inference
TENSOR_TYPE_TO_NP_TYPE[item.type.tensor_type.elem_type], """
'shape': for layer in self.graph.node:
[dim.dim_value for dim in item.type.tensor_type.shape.dim], node = self.node_map[layer.name]
'external': True for opt in layer.output:
} if opt in self.value_infos:
for item in graph.output: value_info = self.value_infos[opt]
assert item.name not in value_info #if len(value_info['shape']) == 0 or value_info[
value_info[item.name] = { # 'dtype'] is None or 0 in value_info['shape']:
'dtype': # #TODO add node shape inference
TENSOR_TYPE_TO_NP_TYPE[item.type.tensor_type.elem_type], node.dtype = value_info['dtype']
'shape': node.out_shapes.append(value_info['shape'])
[dim.dim_value for dim in item.type.tensor_type.shape.dim], else:
'external': True node.out_shapes.append([])
}
return value_info
class ONNXDecoder(object): class ONNXDecoder(object):
def __init__(self, onnx_model): def __init__(self, onnx_model):
model = onnx.load(onnx_model) onnx_model = onnx.load(onnx_model)
print('model ir_version: {}, op version: {}'.format( print('model ir_version: {}, op version: {}'.format(
model.ir_version, model.opset_import[0].version)) onnx_model.ir_version, onnx_model.opset_import[0].version))
if model.opset_import[0].version < 9: self.op_set = onnx_model.opset_import[0].version
_logger.warning(
'Now, onnx2paddle support convert onnx model opset_verison == 9,' check_model(onnx_model)
'opset_verison of your onnx model is %d < 9,'
'some operator maybe unsuccessful in convertion.', onnx_model = self.optimize_model_skip_op(onnx_model)
model.opset_import[0].version) onnx_model = self.optimize_model_strip_initializer(onnx_model)
onnx_model = self.optimize_node_name(onnx_model)
check_model(model) self.graph = ONNXGraph(onnx_model)
self.check_model_running_state(onnx_model) #self.onnx_model = onnx_model
model = onnx.shape_inference.infer_shapes(model)
model = self.optimize_model_skip_op_for_inference(model)
model = self.optimize_model_strip_initializer(model)
self.standardize_variable_name(model.graph)
self.model = model
graph = model.graph
self.onnx_graph = ONNXGraph(model)
self.onnx_graph.build()
def build_value_refs(self, nodes): def build_value_refs(self, nodes):
""" """
...@@ -373,14 +408,13 @@ class ONNXDecoder(object): ...@@ -373,14 +408,13 @@ class ONNXDecoder(object):
processed += 1 processed += 1
return processed return processed
def optimize_model_skip_op_for_inference(self, model, op_list=None): def optimize_model_skip_op(self, model, op_list=None):
""" """
skip ops can be bypassed for inference skip ops can be bypassed for inference
""" """
nodes = model.graph.node
if op_list is None: if op_list is None:
op_list = ['Dropout'] op_list = ['Dropout']
nodes = model.graph.node
input_refs, output_refs = self.build_value_refs(nodes) input_refs, output_refs = self.build_value_refs(nodes)
ret = type(model)() ret = type(model)()
ret.CopyFrom(model) ret.CopyFrom(model)
...@@ -473,38 +507,11 @@ class ONNXDecoder(object): ...@@ -473,38 +507,11 @@ class ONNXDecoder(object):
name = name.replace(s, '_') name = name.replace(s, '_')
return 'x2paddle_' + name return 'x2paddle_' + name
def check_model_running_state(self, model_path): def optimize_node_name(self, model):
import onnxruntime as rt
model = onnx.load(model_path)
model = onnx.shape_inference.infer_shapes(model)
if len(model.graph.value_info) < len(model.graph.node) - 1:
_logger.warning(
'During conversion of your model, some operators will be assignd node.out_shape==None, '
'refer to https://github.com/onnx/onnx/blob/master/docs/ShapeInference.md'
)
try:
datatype_map = {
'tensor(int64)': 'int',
'tensor(float)': 'float32',
'tensor(int32)': 'int32'
}
input_dict = {}
sess = rt.InferenceSession(model_path)
for ipt in sess.get_inputs():
datatype = datatype_map[ipt.type]
input_dict[ipt.name] = np.random.random(ipt.shape).astype(
datatype)
res = sess.run(None, input_feed=input_dict)
except:
raise Exception(
"onnxruntime inference onnx model failed, Please confirm the correctness of onnx model by onnxruntime, if onnx model is correct, please submit issue in github."
)
def standardize_variable_name(self, graph):
""" """
standardize variable name for paddle's code standardize variable name for paddle's code
""" """
graph = model.graph
for initializer in graph.initializer: for initializer in graph.initializer:
initializer.name = self.make_variable_name(initializer.name) initializer.name = self.make_variable_name(initializer.name)
for ipt in graph.input: for ipt in graph.input:
...@@ -523,3 +530,4 @@ class ONNXDecoder(object): ...@@ -523,3 +530,4 @@ class ONNXDecoder(object):
node.input[i] = self.make_variable_name(node.input[i]) node.input[i] = self.make_variable_name(node.input[i])
for i in range(len(node.output)): for i in range(len(node.output)):
node.output[i] = self.make_variable_name(node.output[i]) node.output[i] = self.make_variable_name(node.output[i])
return model
此差异已折叠。
...@@ -48,7 +48,7 @@ class TFGraphNode(GraphNode): ...@@ -48,7 +48,7 @@ class TFGraphNode(GraphNode):
@property @property
def out_shapes(self): def out_shapes(self):
if self.layer_type == "OneShotIterator": if self.layer_type == "OneShotIterator" or self.layer_type == "IteratorV2":
values = self.layer.attr["output_shapes"].list.shape values = self.layer.attr["output_shapes"].list.shape
else: else:
values = self.layer.attr["_output_shapes"].list.shape values = self.layer.attr["_output_shapes"].list.shape
...@@ -68,7 +68,8 @@ class TFGraphNode(GraphNode): ...@@ -68,7 +68,8 @@ class TFGraphNode(GraphNode):
if dtype == 0: if dtype == 0:
dtype = self.layer.attr['output_types'].list.type[0] dtype = self.layer.attr['output_types'].list.type[0]
if dtype not in self.dtype_map: if dtype not in self.dtype_map:
raise Exception("Dtype[{}] not in dtype_map".format(dtype)) raise Exception("Dtype[{}] of node({}) not in dtype_map".format(
dtype, self.layer.name))
return self.dtype_map[dtype] return self.dtype_map[dtype]
@property @property
...@@ -88,6 +89,16 @@ class TFGraphNode(GraphNode): ...@@ -88,6 +89,16 @@ class TFGraphNode(GraphNode):
field = getattr(attr, attr.WhichOneof('value')) field = getattr(attr, attr.WhichOneof('value'))
return tensor_util.MakeNdarray(field) return tensor_util.MakeNdarray(field)
@property
def name(self):
multi_out_ops = ['Split', 'SplitV', 'IteratorV2']
if self.layer_type in multi_out_ops:
if self.layer_name.count(':') > 0:
return self.layer_name.replace(':', '_p')
else:
return "{}_p0".format(self.layer_name)
return self.layer_name
def get_attr(self, name): def get_attr(self, name):
if name not in self.layer.attr: if name not in self.layer.attr:
return None return None
...@@ -114,16 +125,20 @@ class TFGraph(Graph): ...@@ -114,16 +125,20 @@ class TFGraph(Graph):
def __init__(self, model, data_format="NHWC"): def __init__(self, model, data_format="NHWC"):
super(TFGraph, self).__init__(model) super(TFGraph, self).__init__(model)
self.identity_map = dict() self.identity_map = dict()
self.multi_out_ops = ['Split', 'SplitV'] self.multi_out_ops = ['Split', 'SplitV', 'IteratorV2']
self.tf_data_format = data_format self.tf_data_format = data_format
def build(self): def build(self):
for layer in self.model.node: for layer in self.model.node:
if layer.op == 'Assert':
continue
self.node_map[layer.name.replace('/', '_').replace( self.node_map[layer.name.replace('/', '_').replace(
'-', '_')] = TFGraphNode( '-', '_')] = TFGraphNode(
layer, data_format=self.tf_data_format) layer, data_format=self.tf_data_format)
for layer_name, node in self.node_map.items(): for layer_name, node in self.node_map.items():
if node.layer_type == 'Const':
continue
for in_node in node.layer.input: for in_node in node.layer.input:
in_node = in_node.replace('/', '_').replace('-', '_').replace( in_node = in_node.replace('/', '_').replace('-', '_').replace(
'^', '') '^', '')
...@@ -139,6 +154,14 @@ class TFGraph(Graph): ...@@ -139,6 +154,14 @@ class TFGraph(Graph):
super(TFGraph, self).build() super(TFGraph, self).build()
for layer in self.model.node:
if layer.op == 'Assert':
for ipt in layer.input:
ipt_name = ipt.replace('-', '_').replace('/', '_')
if ipt_name in self.output_nodes:
idx = self.output_nodes.index(ipt_name)
del self.output_nodes[idx]
# tensorflow graph optimize # tensorflow graph optimize
self._remove_isolated_node() self._remove_isolated_node()
self._optimize_dialiation_conv() self._optimize_dialiation_conv()
...@@ -272,7 +295,6 @@ class TFGraph(Graph): ...@@ -272,7 +295,6 @@ class TFGraph(Graph):
def data_format_propagation(self, node): def data_format_propagation(self, node):
current_node = self.node_map[node.layer_name] current_node = self.node_map[node.layer_name]
current_node = node.tf_data_format
outputs = current_node.outputs outputs = current_node.outputs
if len(outputs) == 0: if len(outputs) == 0:
return return
...@@ -322,7 +344,7 @@ class TFDecoder(object): ...@@ -322,7 +344,7 @@ class TFDecoder(object):
graph_def = cp.deepcopy(graph_def) graph_def = cp.deepcopy(graph_def)
input_map = dict() input_map = dict()
for layer in graph_def.node: for layer in graph_def.node:
if layer.op != "Placeholder" and layer.op != "OneShotIterator": if layer.op != "Placeholder" and layer.op != "OneShotIterator" and layer.op != "IteratorV2":
continue continue
graph_node = TFGraphNode(layer) graph_node = TFGraphNode(layer)
dtype = graph_node.layer.attr['dtype'].type dtype = graph_node.layer.attr['dtype'].type
...@@ -403,7 +425,7 @@ class TFDecoder(object): ...@@ -403,7 +425,7 @@ class TFDecoder(object):
else: else:
value = graph_node.layer.attr["shape"].shape value = graph_node.layer.attr["shape"].shape
shape = [dim.size for dim in value.dim] shape = [dim.size for dim in value.dim]
self.input_info[graph_node.layer_name] = (shape, dtype) self.input_info[layer.name] = (shape, dtype)
return input_map return input_map
......
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from x2paddle.op_mapper.onnx2paddle.opset9 import OpSet9, custom_layers
from x2paddle.core.op_mapper import OpMapper
from x2paddle.decoder.onnx_decoder import ONNXGraph, ONNXGraphNode, ONNXGraphDataNode
class ONNXOpMapper(OpMapper):
def __init__(self, decoder):
super(ONNXOpMapper, self).__init__()
self.support_op_sets = [9, ]
self.default_op_set = 9
self.graph = decoder.graph
self.opset = self.create_opset(decoder)
if not self.op_checker():
raise Exception("Model are not supported yet.")
#mapping op
print("Total nodes: {}".format(
sum([
isinstance(node, ONNXGraphNode)
for name, node in self.graph.node_map.items()
])))
print("Nodes converting ...")
for node_name in self.graph.topo_sort:
node = self.graph.get_node(node_name)
op = node.layer_type
if hasattr(self.opset, op):
func = getattr(self.opset, op)
func(node)
elif op in self.opset.default_op_mapping:
self.opset.directly_map(node)
elif op in custom_layers:
self.opset.deal_custom_layer(node)
elif op in self.opset.elementwise_ops:
self.opset.elementwise_map(node)
print("Nodes converted.")
self.weights = self.opset.weights
self.omit_nodes = self.opset.omit_nodes
self.used_custom_layers = self.opset.used_custom_layers
def op_checker(self):
unsupported_ops = set()
for node_name in self.graph.topo_sort:
node = self.graph.get_node(node_name)
op = node.layer_type
if not hasattr(self.opset, op) and \
op not in self.opset.default_op_mapping and \
op not in custom_layers and \
op not in self.opset.elementwise_ops:
unsupported_ops.add(op)
if len(unsupported_ops) == 0:
return True
else:
print("There are {} ops not supported yet, list as below".format(
len(unsupported_ops)))
for op in unsupported_ops:
print(op)
return False
def create_opset(self, decoder):
run_op_set = self.default_op_set
opset = ''
if decoder.op_set in self.support_op_sets:
opset = 'OpSet' + str(decoder.op_set)
elif decoder.op_set < self.default_op_set:
opset = 'OpSet' + str(self.default_op_set)
else:
for op_set in self.support_op_sets:
if decoder.op_set > op_set:
run_op_set = op_set
else:
break
opset = 'OpSet' + str(run_op_set)
print(
'Now, onnx2paddle support convert onnx model opset_verison {},'
'opset_verison of your onnx model is {}, automatically treated as op_set: {}.'
.format(self.support_op_sets, decoder.op_set, run_op_set))
return eval(opset)(decoder)
from .opset import OpSet9
from .custom_layer import custom_layers
...@@ -13,10 +13,6 @@ ...@@ -13,10 +13,6 @@
# limitations under the License. # limitations under the License.
from .register import get_registered_layers from .register import get_registered_layers
#custom layer import begins
from . import InstanceNormalization
#custom layer import ends
custom_layers = get_registered_layers() custom_layers = get_registered_layers()
......
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import OrderedDict as _dict
import numpy as _np
default_op_mapping_field_values = _dict()
default_op_mapping_field_values['FLUID_OP'] = ''
default_op_mapping_field_values['FLUID_INPUT_ARGS'] = None
default_op_mapping_field_values['FLUID_OUTPUT_ARGS'] = None
default_op_mapping_field_values['ATTR_MAPPING'] = dict()
default_op_mapping_field_values['DEFAULTS'] = dict()
default_op_mapping_field_values['INPUT_PERM'] = None
default_op_mapping_field_values['OUTPUT_PERM'] = None
default_op_mapping_field_values['FILL_NAME_FIELD'] = True
default_op_mapping = {
'Shape': ['shape', ['X'], ['Out']],
'Clip': [
'clip', ['X'], ['Out'], dict(), dict(
min=(_np.asarray(
[255, 255, 127, 255], dtype=_np.uint8).view(_np.float32)[0]),
max=(_np.asarray(
[255, 255, 127, 127], dtype=_np.uint8).view(_np.float32)[0]), )
],
'Erf': ['erf', ['X'], ['Out']],
'Ceil': ['ceil', ['X'], ['Out']],
'ReduceMean': [
'reduce_mean', ['X'], ['Out'], dict(
axes='dim', keepdims='keep_dim'), dict(keep_dim=1)
],
'ReduceSum': [
'reduce_sum', ['X'], ['Out'], dict(
axes='dim', keepdims='keep_dim'), dict(keep_dim=1)
],
'ReduceMin': [
'reduce_min', ['X'], ['Out'], dict(
axes='dim', keepdims='keep_dim'), dict(keep_dim=1)
],
'ReduceMax': [
'reduce_max', ['X'], ['Out'], dict(
axes='dim', keepdims='keep_dim'), dict(keep_dim=1)
],
#active function
'Relu': ['relu', ['X'], ['Out']],
'LeakyRelu': ['leaky_relu', ['X'], ['Out'], dict(), dict(alpha=.01)],
'Elu': ['elu', ['X'], ['Out'], dict(), dict(alpha=1.)],
'ThresholdedRelu': [
'thresholded_relu', ['X'], ['Out'], dict(alpha='threshold'),
dict(alpha=1.)
],
'Tanh': ['tanh', ['X'], ['Out']],
'Sigmoid': ['sigmoid', ['X'], ['Out']],
'HardSigmoid': [
'hard_sigmoid', ['X'], ['Out'], dict(
alpha='slope', beta='offset'), dict(
slope=.2, offset=.5)
],
'Softsign': ['softsign', ['X'], ['Out']],
'Softplus': ['softplus', ['X'], ['Out']],
'Exp': ['exp', ['X'], ['Out']],
'Softmax': ['softmax', ['X'], ['Out'], dict(), dict(axis=1)],
'Sqrt': ['sqrt', ['X'], ['Out']],
'Floor': ['floor', ['X'], ['Out']],
'Abs': ['abs', ['X'], ['Out']],
}
default_ioa_constraint = {
'Gather': [(lambda i, o, a: a.get('axis', 0) == 0,
'only axis = 0 is supported')],
}
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import sys
import x2paddle
import os
import numpy as np
import paddle.fluid.core as core
import paddle.fluid as fluid
import onnx
from onnx import helper, onnx_pb
from x2paddle.op_mapper.paddle2onnx.opset9.opset import OpSet9
class OpSet10(OpSet9):
def __init__(self):
super(OpSet10, self).__init__()
def slice(self, op, block):
axes = op.attr('axes')
starts = op.attr('starts')
ends = op.attr('ends')
axes_name = self.get_name(op.type, 'axes')
starts_name = self.get_name(op.type, 'starts')
ends_name = self.get_name(op.type, 'ends')
axes_node = self.make_constant_node(axes_name,
onnx_pb.TensorProto.INT64, axes)
starts_node = self.make_constant_node(starts_name,
onnx_pb.TensorProto.INT64, starts)
ends_node = self.make_constant_node(ends_name,
onnx_pb.TensorProto.INT64, ends)
node = helper.make_node(
"Slice",
inputs=[op.input('Input')[0], starts_name, ends_name, axes_name],
outputs=op.output('Out'), )
return [starts_node, ends_node, axes_node, node]
def im2sequence(self, op, block):
from .paddle_custom_layer.im2sequence import im2sequence
return im2sequence(op, block)
def yolo_box(self, op, block):
from .paddle_custom_layer.yolo_box import yolo_box
return yolo_box(op, block)
def multiclass_nms(self, op, block):
from .paddle_custom_layer.multiclass_nms import multiclass_nms
return multiclass_nms(op, block)
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import onnx
import numpy as np
from onnx import onnx_pb, helper
from x2paddle.op_mapper.paddle2onnx.opset9.paddle_custom_layer.im2sequence import im2sequence as im2sequence9
def im2sequence(op, block):
return im2sequence9(op, block)
...@@ -12,45 +12,21 @@ ...@@ -12,45 +12,21 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .register import register import math
import sys
import os
def InstanceNormalization_shape(input_shape): import numpy as np
return input_shape import paddle.fluid.core as core
import paddle.fluid as fluid
import onnx
def InstanceNormalization_layer(inputs, name=None): import warnings
# TODO(lvmengsi@baidu.com): Check the accuracy when using fluid.layers.layer_norm. from onnx import helper, onnx_pb
epsilon = 1e-5 from x2paddle.op_mapper.paddle2onnx.opset9.paddle_custom_layer.multiclass_nms import multiclass_nms as multiclass_nms9
input_ = inputs[0]
mean = fluid.layers.reduce_mean(input_, dim=[2, 3], keep_dim=True)
var = fluid.layers.reduce_mean( def multiclass_nms(op, block):
fluid.layers.square(input_ - mean), dim=[2, 3], keep_dim=True) """
if name is not None: Convert the paddle multiclass_nms to onnx op.
scale_name = name + "_scale" This op is get the select boxes from origin boxes.
offset_name = name + "_offset" """
return multiclass_nms9(op, block)
scale_param = inputs[1]
offset_param = inputs[2]
scale = fluid.layers.create_parameter(
name=scale_param.name, shape=input_.shape[1:2], dtype="float32")
offset = fluid.layers.create_parameter(
name=offset_param.name, shape=input_.shape[1:2], dtype="float32")
tmp = fluid.layers.elementwise_mul(x=(input_ - mean), y=scale, axis=1)
tmp = tmp / fluid.layers.sqrt(var + epsilon)
tmp = fluid.layers.elementwise_add(tmp, offset, axis=1)
return tmp
def InstanceNormalization_weights(name, data=None):
weights_name = [name + '_scale']
return weights_name
register(
kind='InstanceNormalization',
shape=InstanceNormalization_shape,
layer=InstanceNormalization_layer,
child_func=None,
weights=InstanceNormalization_weights)
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import onnx
import numpy as np
from onnx import onnx_pb, helper
from x2paddle.op_mapper.paddle2onnx.opset9.paddle_custom_layer.yolo_box import yolo_box as yolo_box9
def yolo_box(op, block):
return yolo_box9(op, block)
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import sys
import x2paddle
import os
import numpy as np
import paddle.fluid.core as core
import paddle.fluid as fluid
import onnx
from onnx import helper, onnx_pb
from x2paddle.op_mapper.paddle2onnx.opset10.opset import OpSet10
class OpSet11(OpSet10):
def __init__(self):
super(OpSet11, self).__init__()
def relu6(self, op, block):
min_name = self.get_name(op.type, 'min')
max_name = self.get_name(op.type, 'max')
min_node = self.make_constant_node(min_name, onnx_pb.TensorProto.FLOAT,
0)
max_node = self.make_constant_node(max_name, onnx_pb.TensorProto.FLOAT,
op.attr('threshold'))
node = helper.make_node(
'Clip',
inputs=[op.input('X')[0], min_name, max_name],
outputs=op.output('Out'), )
return [min_node, max_node, node]
def pad2d(self, op, block):
x_shape = block.var(op.input('X')[0]).shape
paddings = op.attr('paddings')
onnx_pads = []
#TODO support pads is Variable
if op.attr('data_format') == 'NCHW':
pads = [
0, 0, paddings[0], paddings[2], 0, 0, paddings[1], paddings[3]
]
else:
pads = [
0, paddings[0], paddings[2], 0, 0, paddings[1], paddings[3], 0
]
pads_name = self.get_name(op.type, 'pads')
pads_node = self.make_constant_node(pads_name,
onnx_pb.TensorProto.INT64, pads)
constant_value_name = self.get_name(op.type, 'constant_value')
constant_value_node = self.make_constant_node(constant_value_name,
onnx_pb.TensorProto.FLOAT,
op.attr('pad_value'))
node = helper.make_node(
'Pad',
inputs=op.input('X') + [pads_name, constant_value_name],
outputs=op.output('Out'),
mode=op.attr('mode'))
return [pads_node, constant_value_node, node]
def bilinear_interp(self, op, block):
input_names = op.input_names
coordinate_transformation_mode = ''
align_corners = op.attr('align_corners')
align_mode = op.attr('align_mode')
if align_corners:
coordinate_transformation_mode = 'align_corners'
elif align_mode == 1:
coordinate_transformation_mode = 'asymmetric'
else:
coordinate_transformation_mode = 'half_pixel'
if ('OutSize' in input_names and len(op.input('OutSize')) > 0) or (
'SizeTensor' in input_names and
len(op.input('SizeTensor')) > 0):
node_list = list()
roi_node = self.make_constant_node(
self.get_name(op.type, 'roi'), onnx_pb.TensorProto.FLOAT,
[1, 1, 1, 1, 1, 1, 1, 1])
roi_name = self.get_name(op.type, 'roi')
roi_node = self.make_constant_node(
roi_name, onnx_pb.TensorProto.FLOAT, [1, 1, 1, 1, 1, 1, 1, 1])
empty_name = self.get_name(op.type, 'empty')
empty_tensor = helper.make_tensor(
empty_name,
onnx_pb.TensorProto.FLOAT, (0, ),
np.array([]).astype('float32'),
raw=False)
empty_node = helper.make_node(
'Constant', [], outputs=[empty_name], value=empty_tensor)
shape_name0 = self.get_name(op.type, 'shape')
shape_node0 = helper.make_node(
'Shape', inputs=op.input('X'), outputs=[shape_name0])
starts_name = self.get_name(op.type, 'slice.starts')
starts_node = self.make_constant_node(
starts_name, onnx_pb.TensorProto.INT64, [0])
ends_name = self.get_name(op.type, 'slice.ends')
ends_node = self.make_constant_node(ends_name,
onnx_pb.TensorProto.INT64, [2])
shape_name1 = self.get_name(op.type, 'shape')
shape_node1 = helper.make_node(
'Slice',
inputs=[shape_name0, starts_name, ends_name],
outputs=[shape_name1])
node_list.extend([
roi_node, empty_node, shape_node0, starts_node, ends_node,
shape_node1
])
if 'OutSize' in input_names and len(op.input('OutSize')) > 0:
cast_shape_name = self.get_name(op.type, "shape.cast")
cast_shape_node = helper.make_node(
'Cast',
inputs=op.input('OutSize'),
outputs=[cast_shape_name],
to=onnx_pb.TensorProto.INT64)
node_list.append(cast_shape_node)
else:
concat_shape_name = self.get_name(op.type, "shape.concat")
concat_shape_node = helper.make_node(
"Concat",
inputs=op.input('SizeTensor'),
outputs=[concat_shape_name],
axis=0)
cast_shape_name = self.get_name(op.type, "shape.cast")
cast_shape_node = helper.make_node(
'Cast',
inputs=[concat_shape_name],
outputs=[cast_shape_name],
to=onnx_pb.TensorProto.INT64)
node_list.extend([concat_shape_node, cast_shape_node])
shape_name3 = self.get_name(op.type, "shape.concat")
shape_node3 = helper.make_node(
'Concat',
inputs=[shape_name1, cast_shape_name],
outputs=[shape_name3],
axis=0)
result_node = helper.make_node(
'Resize',
inputs=[op.input('X')[0], roi_name, empty_name, shape_name3],
outputs=op.output('Out'),
mode='linear',
coordinate_transformation_mode=coordinate_transformation_mode)
node_list.extend([shape_node3, result_node])
return node_list
elif 'Scale' in input_names and len(op.input('Scale')) > 0:
node = helper.make_node(
'Resize',
inputs=[op.input('X')[0], op.input('Scale')[0]],
outputs=op.output('Out'),
mode='linear',
coordinate_transformation_mode=coordinate_transformation_mode)
else:
out_shape = [op.attr('out_h'), op.attr('out_w')]
scale = op.attr('scale')
if out_shape.count(-1) > 0:
scale_name = self.get_name(op.type, 'scale')
scale_node = self.make_constant_node(scale_name,
onnx_pb.TensorProto.FLOAT,
[1, 1, scale, scale])
roi_name = self.get_name(op.type, 'roi')
roi_node = self.make_constant_node(roi_name,
onnx_pb.TensorProto.FLOAT,
[1, 1, 1, 1, 1, 1, 1, 1])
node = helper.make_node(
'Resize',
inputs=[op.input('X')[0], roi_name, scale_name],
outputs=op.output('Out'),
mode='nearest',
coordinate_transformation_mode=coordinate_transformation_mode
)
return [scale_node, roi_node, node]
else:
raise Exception("Unexpected situation happend")
return node
def nearest_interp(self, op, block):
input_names = op.input_names
coordinate_transformation_mode = ''
align_corners = op.attr('align_corners')
if align_corners:
coordinate_transformation_mode = 'align_corners'
else:
coordinate_transformation_mode = 'asymmetric'
if 'OutSize' in input_names and len(op.input('OutSize')) > 0:
node = helper.make_node(
'Resize',
inputs=[op.input('X')[0], '', op.input('OutSize')[0]],
outputs=op.output('Out'),
mode='nearest',
coordinate_transformation_mode=coordinate_transformation_mode)
elif 'Scale' in input_names and len(op.input('Scale')) > 0:
node = helper.make_node(
'Resize',
inputs=[op.input('X')[0], op.input('Scale')[0]],
outputs=op.output('Out'),
mode='nearest',
coordinate_transformation_mode=coordinate_transformation_mode)
else:
out_shape = [op.attr('out_h'), op.attr('out_w')]
scale = op.attr('scale')
if out_shape.count(-1) > 0:
scale_name = self.get_name(op.type, 'scale')
scale_node = self.make_constant_node(scale_name,
onnx_pb.TensorProto.FLOAT,
[1, 1, scale, scale])
roi_name = self.get_name(op.type, 'roi')
roi_node = self.make_constant_node(roi_name,
onnx_pb.TensorProto.FLOAT,
[1, 1, 1, 1, 1, 1, 1, 1])
node = helper.make_node(
'Resize',
inputs=[op.input('X')[0], roi_name, scale_name],
outputs=op.output('Out'),
mode='nearest',
coordinate_transformation_mode=coordinate_transformation_mode
)
return [scale_node, roi_node, node]
else:
raise Exception("Unexpected situation happend")
return node
def hard_swish(self, op, block):
min_name = self.get_name(op.type, 'min')
max_name = self.get_name(op.type, 'max')
scale_name = self.get_name(op.type, 'scale')
offset_name = self.get_name(op.type, 'offset')
min_node = self.make_constant_node(min_name, onnx_pb.TensorProto.FLOAT,
0)
max_node = self.make_constant_node(max_name, onnx_pb.TensorProto.FLOAT,
op.attr('threshold'))
scale_node = self.make_constant_node(scale_name,
onnx_pb.TensorProto.FLOAT,
op.attr('scale'))
offset_node = self.make_constant_node(offset_name,
onnx_pb.TensorProto.FLOAT,
op.attr('offset'))
name0 = self.get_name(op.type, 'add')
node0 = helper.make_node(
'Add', inputs=[op.input('X')[0], offset_name], outputs=[name0])
name1 = self.get_name(op.type, 'relu')
node1 = helper.make_node(
'Clip',
inputs=[name0, min_name, max_name],
outputs=[name1], )
name2 = self.get_name(op.type, 'mul')
node2 = helper.make_node(
'Mul', inputs=[op.input('X')[0], name1], outputs=[name2])
node3 = helper.make_node(
'Div', inputs=[name2, scale_name], outputs=op.output('Out'))
return [
min_node, max_node, scale_node, offset_node, node0, node1, node2,
node3
]
def im2sequence(self, op, block):
from .paddle_custom_layer.im2sequence import im2sequence
return im2sequence(op, block)
def yolo_box(self, op, block):
from .paddle_custom_layer.yolo_box import yolo_box
return yolo_box(op, block)
def multiclass_nms(self, op, block):
from .paddle_custom_layer.multiclass_nms import multiclass_nms
return multiclass_nms(op, block)
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import onnx
import numpy as np
from onnx import onnx_pb, helper
from x2paddle.op_mapper.paddle2onnx.opset10.paddle_custom_layer.im2sequence import im2sequence as im2sequence10
def im2sequence(op, block):
return im2sequence10(op, block)
...@@ -125,7 +125,7 @@ def multiclass_nms(op, block): ...@@ -125,7 +125,7 @@ def multiclass_nms(op, block):
vals=[value])) vals=[value]))
node_list.append(node) node_list.append(node)
# Ine this code block, we will deocde the raw score data, reshape N * C * M to 1 * N*C*M # In this code block, we will deocde the raw score data, reshape N * C * M to 1 * N*C*M
# and the same time, decode the select indices to 1 * D, gather the select_indices # and the same time, decode the select indices to 1 * D, gather the select_indices
outputs_gather_1 = [result_name + "@gather_1"] outputs_gather_1 = [result_name + "@gather_1"]
node_gather_1 = onnx.helper.make_node( node_gather_1 = onnx.helper.make_node(
...@@ -405,12 +405,36 @@ def multiclass_nms(op, block): ...@@ -405,12 +405,36 @@ def multiclass_nms(op, block):
inputs_concat_final_results = outputs_cast_topk_class + outputs_unsqueeze_topk_scores +\ inputs_concat_final_results = outputs_cast_topk_class + outputs_unsqueeze_topk_scores +\
outputs_gather_select_boxes outputs_gather_select_boxes
outputs_concat_final_results = outputs['Out'] outputs_sort_by_socre_results = [result_name + "@concat_topk_scores"]
node_concat_final_results = onnx.helper.make_node( node_sort_by_socre_results = onnx.helper.make_node(
'Concat', 'Concat',
inputs=inputs_concat_final_results, inputs=inputs_concat_final_results,
outputs=outputs_concat_final_results, outputs=outputs_sort_by_socre_results,
axis=2) axis=2)
node_list.append(node_concat_final_results) node_list.append(node_sort_by_socre_results)
# select topk classes indices
outputs_squeeze_cast_topk_class = [result_name + "@squeeze_cast_topk_class"]
node_squeeze_cast_topk_class = onnx.helper.make_node(
'Squeeze',
inputs=outputs_cast_topk_class,
outputs=outputs_squeeze_cast_topk_class,
axes=[0, 2])
node_list.append(node_squeeze_cast_topk_class)
outputs_topk_select_classes_indices = [result_name + "@topk_select_topk_classes_scores",\
result_name + "@topk_select_topk_classes_indices"]
node_topk_select_topk_indices = onnx.helper.make_node(
'TopK',
inputs=outputs_squeeze_cast_topk_class + outputs_cast_topk_indices,
outputs=outputs_topk_select_classes_indices,
largest=0)
node_list.append(node_topk_select_topk_indices)
outputs_concat_final_results = outputs['Out']
node_concat_final_results = onnx.helper.make_node(
'Gather',
inputs=outputs_sort_by_socre_results +
[outputs_topk_select_classes_indices[1]],
outputs=outputs_concat_final_results,
axis=1)
node_list.append(node_concat_final_results)
return node_list return node_list
...@@ -23,7 +23,7 @@ import onnx ...@@ -23,7 +23,7 @@ import onnx
from onnx import helper, onnx_pb from onnx import helper, onnx_pb
class PaddleOpMapper(object): class OpSet9(object):
def __init__(self): def __init__(self):
self.paddle_onnx_dtype_map = { self.paddle_onnx_dtype_map = {
core.VarDesc.VarType.FP32: onnx_pb.TensorProto.FLOAT, core.VarDesc.VarType.FP32: onnx_pb.TensorProto.FLOAT,
...@@ -34,63 +34,8 @@ class PaddleOpMapper(object): ...@@ -34,63 +34,8 @@ class PaddleOpMapper(object):
core.VarDesc.VarType.INT64: onnx_pb.TensorProto.INT64, core.VarDesc.VarType.INT64: onnx_pb.TensorProto.INT64,
core.VarDesc.VarType.BOOL: onnx_pb.TensorProto.BOOL core.VarDesc.VarType.BOOL: onnx_pb.TensorProto.BOOL
} }
self.name_counter = dict() self.name_counter = dict()
def convert(self, program, save_dir):
weight_nodes = self.convert_weights(program)
op_nodes = list()
input_nodes = list()
output_nodes = list()
unsupported_ops = set()
print("Translating PaddlePaddle to ONNX...\n")
for block in program.blocks:
for i, op in enumerate(block.ops):
sys.stdout.write(
"\rTotal:{}, Current:{} : {} ".format(
len(block.ops), i + 1, op.type))
sys.stdout.flush()
if not hasattr(self, op.type):
unsupported_ops.add(op.type)
continue
if len(unsupported_ops) > 0:
continue
node = getattr(self, op.type)(op, block)
if op.type == 'feed':
input_nodes.append(node)
elif op.type == 'fetch':
output_nodes.append(node)
else:
if isinstance(node, list):
op_nodes = op_nodes + node
else:
op_nodes.append(node)
if len(unsupported_ops) > 0:
print("\nThere's {} ops are not supported yet".format(
len(unsupported_ops)))
for op in unsupported_ops:
print("=========== {} ===========".format(op))
return
graph = helper.make_graph(
nodes=weight_nodes + op_nodes,
name='onnx_model_from_paddle',
initializer=[],
inputs=input_nodes,
outputs=output_nodes)
model = helper.make_model(graph, producer_name='X2Paddle')
onnx.checker.check_model(model)
if not os.path.isdir(save_dir):
os.makedirs(save_dir)
with open(os.path.join(save_dir, 'x2paddle_model.onnx'), 'wb') as f:
f.write(model.SerializeToString())
print("\nTranslated model saved in {}".format(
os.path.join(save_dir, 'x2paddle_model.onnx')))
def get_name(self, op_name, var_name): def get_name(self, op_name, var_name):
name = 'p2o.{}.{}'.format(op_name, var_name) name = 'p2o.{}.{}'.format(op_name, var_name)
if name not in self.name_counter: if name not in self.name_counter:
...@@ -99,6 +44,21 @@ class PaddleOpMapper(object): ...@@ -99,6 +44,21 @@ class PaddleOpMapper(object):
self.name_counter[name] += 1 self.name_counter[name] += 1
return name + '.{}'.format(self.name_counter[name]) return name + '.{}'.format(self.name_counter[name])
def make_constant_node(self, name, dtype, value=None):
if isinstance(value, list):
dims = (len(value), )
elif value is None:
dims = ()
value = []
else:
dims = ()
value = [value]
tensor = helper.make_tensor(
name=name, data_type=dtype, dims=dims, vals=value)
node = helper.make_node(
'Constant', inputs=[], outputs=[name], value=tensor)
return node
def convert_weights(self, program): def convert_weights(self, program):
var_names = program.global_block().vars var_names = program.global_block().vars
nodes = list() nodes = list()
...@@ -119,21 +79,6 @@ class PaddleOpMapper(object): ...@@ -119,21 +79,6 @@ class PaddleOpMapper(object):
nodes.append(node) nodes.append(node)
return nodes return nodes
def make_constant_node(self, name, dtype, value=None):
if isinstance(value, list):
dims = (len(value), )
elif value is None:
dims = ()
value = []
else:
dims = ()
value = [value]
tensor = helper.make_tensor(
name=name, data_type=dtype, dims=dims, vals=value)
node = helper.make_node(
'Constant', inputs=[], outputs=[name], value=tensor)
return node
def conv2d(self, op, block): def conv2d(self, op, block):
kernel_shape = block.var(op.input('Filter')[0]).shape kernel_shape = block.var(op.input('Filter')[0]).shape
node = helper.make_node( node = helper.make_node(
...@@ -251,6 +196,8 @@ class PaddleOpMapper(object): ...@@ -251,6 +196,8 @@ class PaddleOpMapper(object):
pool_type[op.attr('pooling_type')][1], pool_type[op.attr('pooling_type')][1],
inputs=op.input('X'), inputs=op.input('X'),
outputs=op.output('Out'), ) outputs=op.output('Out'), )
elif op.attr('adaptive'):
raise Excpetion("ONNX cannot support adaptive pool")
else: else:
input_shape = block.var(op.input('X')[0]).shape input_shape = block.var(op.input('X')[0]).shape
k_size = op.attr('ksize') k_size = op.attr('ksize')
...@@ -268,6 +215,28 @@ class PaddleOpMapper(object): ...@@ -268,6 +215,28 @@ class PaddleOpMapper(object):
pads=op.attr('paddings') + op.attr('paddings')) pads=op.attr('paddings') + op.attr('paddings'))
return node return node
def pad2d(self, op, block):
x_shape = block.var(op.input('X')[0]).shape
paddings = op.attr('paddings')
onnx_pads = []
if op.attr('data_format') == 'NCHW':
pads = [
0, 0, paddings[0], paddings[2], 0, 0, paddings[1], paddings[3]
]
else:
pads = [
0, paddings[0], paddings[2], 0, 0, paddings[1], paddings[3], 0
]
#TODO support pads is Variable
node = helper.make_node(
'Pad',
inputs=op.input('X'),
outputs=op.output('Out'),
mode=op.attr('mode'),
value=op.attr('pad_value'),
pads=pads)
return node
def softmax(self, op, block): def softmax(self, op, block):
axis = op.attr('axis') axis = op.attr('axis')
shape = block.var(op.output('Out')[0]).shape shape = block.var(op.output('Out')[0]).shape
...@@ -397,17 +366,14 @@ class PaddleOpMapper(object): ...@@ -397,17 +366,14 @@ class PaddleOpMapper(object):
return self.conv2d(op, block) return self.conv2d(op, block)
def relu6(self, op, block): def relu6(self, op, block):
min_name = self.get_name(op.type, 'min') threshold = op.attr('threshold')
max_name = self.get_name(op.type, 'max')
min_node = self.make_constant_node(min_name, onnx_pb.TensorProto.FLOAT,
0)
max_node = self.make_constant_node(max_name, onnx_pb.TensorProto.FLOAT,
op.attr('threshold'))
node = helper.make_node( node = helper.make_node(
'Clip', 'Clip',
inputs=[op.input('X')[0], min_name, max_name], inputs=[op.input('X')[0]],
outputs=op.output('Out'), ) outputs=op.output('Out'),
return [min_node, max_node, node] max=threshold,
min=0.0)
return [node]
def shape(self, op, block): def shape(self, op, block):
node = helper.make_node( node = helper.make_node(
...@@ -435,21 +401,14 @@ class PaddleOpMapper(object): ...@@ -435,21 +401,14 @@ class PaddleOpMapper(object):
axes = op.attr('axes') axes = op.attr('axes')
starts = op.attr('starts') starts = op.attr('starts')
ends = op.attr('ends') ends = op.attr('ends')
axes_name = self.get_name(op.type, 'axes')
starts_name = self.get_name(op.type, 'starts')
ends_name = self.get_name(op.type, 'ends')
axes_node = self.make_constant_node(axes_name,
onnx_pb.TensorProto.INT64, axes)
starts_node = self.make_constant_node(starts_name,
onnx_pb.TensorProto.INT64, starts)
ends_node = self.make_constant_node(ends_name,
onnx_pb.TensorProto.INT64, ends)
node = helper.make_node( node = helper.make_node(
"Slice", "Slice",
inputs=[op.input('Input')[0], starts_name, ends_name, axes_name], inputs=[op.input('Input')[0], starts_name, ends_name, axes_name],
outputs=op.output('Out'), ) outputs=op.output('Out'),
return [starts_node, ends_node, axes_node, node] axes=axes,
starts=starts,
ends=ends)
return [node]
def fill_constant(self, op, block): def fill_constant(self, op, block):
value = op.attr('value') value = op.attr('value')
...@@ -544,27 +503,15 @@ class PaddleOpMapper(object): ...@@ -544,27 +503,15 @@ class PaddleOpMapper(object):
def bilinear_interp(self, op, block): def bilinear_interp(self, op, block):
input_names = op.input_names input_names = op.input_names
coordinate_transformation_mode = 'half_pixel' input_shape = block.vars[op.input('X')[0]].shape
if op.attr('align_corners'): if op.attr('align_corners') or op.attr('align_mode') == 0:
coordinate_transformation_mode = 'align_corners' raise Exception(
"Resize in onnx(opset<=10) only support coordinate_transformation_mode: 'asymmetric'."
)
if ('OutSize' in input_names and len(op.input('OutSize')) > 0) or ( if ('OutSize' in input_names and len(op.input('OutSize')) > 0) or (
'SizeTensor' in input_names and 'SizeTensor' in input_names and
len(op.input('SizeTensor')) > 0): len(op.input('SizeTensor')) > 0):
node_list = list() node_list = list()
roi_node = self.make_constant_node(
self.get_name(op.type, 'roi'), onnx_pb.TensorProto.FLOAT,
[1, 1, 1, 1, 1, 1, 1, 1])
roi_name = self.get_name(op.type, 'roi')
roi_node = self.make_constant_node(
roi_name, onnx_pb.TensorProto.FLOAT, [1, 1, 1, 1, 1, 1, 1, 1])
empty_name = self.get_name(op.type, 'empty')
empty_tensor = helper.make_tensor(
empty_name,
onnx_pb.TensorProto.FLOAT, (0, ),
np.array([]).astype('float32'),
raw=False)
empty_node = helper.make_node(
'Constant', [], outputs=[empty_name], value=empty_tensor)
shape_name0 = self.get_name(op.type, 'shape') shape_name0 = self.get_name(op.type, 'shape')
shape_node0 = helper.make_node( shape_node0 = helper.make_node(
'Shape', inputs=op.input('X'), outputs=[shape_name0]) 'Shape', inputs=op.input('X'), outputs=[shape_name0])
...@@ -579,16 +526,7 @@ class PaddleOpMapper(object): ...@@ -579,16 +526,7 @@ class PaddleOpMapper(object):
'Slice', 'Slice',
inputs=[shape_name0, starts_name, ends_name], inputs=[shape_name0, starts_name, ends_name],
outputs=[shape_name1]) outputs=[shape_name1])
node_list.extend([ node_list.extend([shape_node0, starts_node, ends_node, shape_node1])
roi_node, empty_node, shape_node0, starts_node, ends_node,
shape_node1
])
# shape_name2 = self.get_name(op.type, "shape.cast")
# shape_node2 = helper.make_node(
# 'Cast',
# inputs=op.input('OutSize'),
# outputs=[shape_name2],
# to=onnx_pb.TensorProto.INT64)
if 'OutSize' in input_names and len(op.input('OutSize')) > 0: if 'OutSize' in input_names and len(op.input('OutSize')) > 0:
cast_shape_name = self.get_name(op.type, "shape.cast") cast_shape_name = self.get_name(op.type, "shape.cast")
cast_shape_node = helper.make_node( cast_shape_node = helper.make_node(
...@@ -598,7 +536,8 @@ class PaddleOpMapper(object): ...@@ -598,7 +536,8 @@ class PaddleOpMapper(object):
to=onnx_pb.TensorProto.INT64) to=onnx_pb.TensorProto.INT64)
node_list.append(cast_shape_node) node_list.append(cast_shape_node)
else: else:
concat_shape_name = self.get_name(op.type, "shape.concat") concat_shape_name = self.get_name(
op.type, op.output('Out')[0] + "shape.concat")
concat_shape_node = helper.make_node( concat_shape_node = helper.make_node(
"Concat", "Concat",
inputs=op.input('SizeTensor'), inputs=op.input('SizeTensor'),
...@@ -611,27 +550,46 @@ class PaddleOpMapper(object): ...@@ -611,27 +550,46 @@ class PaddleOpMapper(object):
outputs=[cast_shape_name], outputs=[cast_shape_name],
to=onnx_pb.TensorProto.INT64) to=onnx_pb.TensorProto.INT64)
node_list.extend([concat_shape_node, cast_shape_node]) node_list.extend([concat_shape_node, cast_shape_node])
shape_name3 = self.get_name(op.type, "shape.concat") shape_name2 = self.get_name(op.type, "shape.concat")
shape_node3 = helper.make_node( shape_node2 = helper.make_node(
'Concat', 'Concat',
inputs=[shape_name1, cast_shape_name], inputs=[shape_name1, cast_shape_name],
outputs=[shape_name3], outputs=[shape_name2],
axis=0) axis=0)
node_list.append(shape_node2)
cast_shape_name2 = self.get_name(op.type, "shape.cast")
cast_shape_node2 = helper.make_node(
'Cast',
inputs=[shape_name2],
outputs=[cast_shape_name2],
to=onnx_pb.TensorProto.FLOAT)
node_list.append(cast_shape_node2)
cast_shape_name0 = self.get_name(op.type, "shape.cast")
cast_shape_node0 = helper.make_node(
'Cast',
inputs=[shape_name0],
outputs=[cast_shape_name0],
to=onnx_pb.TensorProto.FLOAT)
node_list.append(cast_shape_node0)
outputs_h_w_scales = op.output('Out')[0] + "@out_hw_scales"
node_h_w_scales = helper.make_node(
'Div',
inputs=[cast_shape_name2, cast_shape_name0],
outputs=[outputs_h_w_scales])
node_list.append(node_h_w_scales)
result_node = helper.make_node( result_node = helper.make_node(
'Resize', 'Resize',
inputs=[op.input('X')[0], roi_name, empty_name, shape_name3], inputs=[op.input('X')[0], outputs_h_w_scales],
outputs=op.output('Out'), outputs=op.output('Out'),
mode='linear', mode='linear')
coordinate_transformation_mode=coordinate_transformation_mode) node_list.extend([result_node])
node_list.extend([shape_node3, result_node])
return node_list return node_list
elif 'Scale' in input_names and len(op.input('Scale')) > 0: elif 'Scale' in input_names and len(op.input('Scale')) > 0:
node = helper.make_node( node = helper.make_node(
'Resize', 'Resize',
inputs=[op.input('X')[0], op.input('Scale')[0]], inputs=[op.input('X')[0], op.input('Scale')[0]],
outputs=op.output('Out'), outputs=op.output('Out'),
mode='linear', mode='linear')
coordinate_transformation_mode=coordinate_transformation_mode)
else: else:
out_shape = [op.attr('out_h'), op.attr('out_w')] out_shape = [op.attr('out_h'), op.attr('out_w')]
scale = op.attr('scale') scale = op.attr('scale')
...@@ -640,41 +598,34 @@ class PaddleOpMapper(object): ...@@ -640,41 +598,34 @@ class PaddleOpMapper(object):
scale_node = self.make_constant_node(scale_name, scale_node = self.make_constant_node(scale_name,
onnx_pb.TensorProto.FLOAT, onnx_pb.TensorProto.FLOAT,
[1, 1, scale, scale]) [1, 1, scale, scale])
roi_name = self.get_name(op.type, 'roi')
roi_node = self.make_constant_node(roi_name,
onnx_pb.TensorProto.FLOAT,
[1, 1, 1, 1, 1, 1, 1, 1])
node = helper.make_node( node = helper.make_node(
'Resize', 'Resize',
inputs=[op.input('X')[0], roi_name, scale_name], inputs=[op.input('X')[0], scale_name],
outputs=op.output('Out'), outputs=op.output('Out'),
mode='nearest', mode='linear')
coordinate_transformation_mode=coordinate_transformation_mode return [scale_node, node]
)
return [scale_node, roi_node, node]
else: else:
raise Exception("Unexpected situation happend") raise Exception("Unexpected situation happend")
return node return node
def nearest_interp(self, op, block): def nearest_interp(self, op, block):
input_names = op.input_names input_names = op.input_names
coordinate_transformation_mode = 'half_pixel'
if op.attr('align_corners'): if op.attr('align_corners'):
coordinate_transformation_mode = 'align_corners' raise Exception(
"Resize in onnx(opset<=10) only support coordinate_transformation_mode: 'asymmetric'."
)
if 'OutSize' in input_names and len(op.input('OutSize')) > 0: if 'OutSize' in input_names and len(op.input('OutSize')) > 0:
node = helper.make_node( node = helper.make_node(
'Resize', 'Resize',
inputs=[op.input('X')[0], '', op.input('OutSize')[0]], inputs=[op.input('X')[0], op.input('OutSize')[0]],
outputs=op.output('Out'), outputs=op.output('Out'),
mode='nearest', mode='nearest')
coordinate_transformation_mode=coordinate_transformation_mode)
elif 'Scale' in input_names and len(op.input('Scale')) > 0: elif 'Scale' in input_names and len(op.input('Scale')) > 0:
node = helper.make_node( node = helper.make_node(
'Resize', 'Resize',
inputs=[op.input('X')[0], op.input('Scale')[0]], inputs=[op.input('X')[0], op.input('Scale')[0]],
outputs=op.output('Out'), outputs=op.output('Out'),
mode='nearest', mode='nearest')
coordinate_transformation_mode=coordinate_transformation_mode)
else: else:
out_shape = [op.attr('out_h'), op.attr('out_w')] out_shape = [op.attr('out_h'), op.attr('out_w')]
scale = op.attr('scale') scale = op.attr('scale')
...@@ -683,18 +634,12 @@ class PaddleOpMapper(object): ...@@ -683,18 +634,12 @@ class PaddleOpMapper(object):
scale_node = self.make_constant_node(scale_name, scale_node = self.make_constant_node(scale_name,
onnx_pb.TensorProto.FLOAT, onnx_pb.TensorProto.FLOAT,
[1, 1, scale, scale]) [1, 1, scale, scale])
roi_name = self.get_name(op.type, 'roi')
roi_node = self.make_constant_node(roi_name,
onnx_pb.TensorProto.FLOAT,
[1, 1, 1, 1, 1, 1, 1, 1])
node = helper.make_node( node = helper.make_node(
'Resize', 'Resize',
inputs=[op.input('X')[0], roi_name, scale_name], inputs=[op.input('X')[0], scale_name],
outputs=op.output('Out'), outputs=op.output('Out'),
mode='nearest', mode='nearest')
coordinate_transformation_mode=coordinate_transformation_mode return [scale_node, node]
)
return [scale_node, roi_node, node]
else: else:
raise Exception("Unexpected situation happend") raise Exception("Unexpected situation happend")
return node return node
...@@ -711,14 +656,8 @@ class PaddleOpMapper(object): ...@@ -711,14 +656,8 @@ class PaddleOpMapper(object):
return node return node
def hard_swish(self, op, block): def hard_swish(self, op, block):
min_name = self.get_name(op.type, 'min')
max_name = self.get_name(op.type, 'max')
scale_name = self.get_name(op.type, 'scale') scale_name = self.get_name(op.type, 'scale')
offset_name = self.get_name(op.type, 'offset') offset_name = self.get_name(op.type, 'offset')
min_node = self.make_constant_node(min_name, onnx_pb.TensorProto.FLOAT,
0)
max_node = self.make_constant_node(max_name, onnx_pb.TensorProto.FLOAT,
op.attr('threshold'))
scale_node = self.make_constant_node(scale_name, scale_node = self.make_constant_node(scale_name,
onnx_pb.TensorProto.FLOAT, onnx_pb.TensorProto.FLOAT,
op.attr('scale')) op.attr('scale'))
...@@ -730,19 +669,20 @@ class PaddleOpMapper(object): ...@@ -730,19 +669,20 @@ class PaddleOpMapper(object):
node0 = helper.make_node( node0 = helper.make_node(
'Add', inputs=[op.input('X')[0], offset_name], outputs=[name0]) 'Add', inputs=[op.input('X')[0], offset_name], outputs=[name0])
name1 = self.get_name(op.type, 'relu') name1 = self.get_name(op.type, 'relu')
min_value = op.attr('min')
max_value = op.attr('max')
node1 = helper.make_node( node1 = helper.make_node(
'Clip', 'Clip',
inputs=[name0, min_name, max_name], inputs=[name0],
outputs=[name1], ) outputs=[name1],
max=max_value,
min=min_value)
name2 = self.get_name(op.type, 'mul') name2 = self.get_name(op.type, 'mul')
node2 = helper.make_node( node2 = helper.make_node(
'Mul', inputs=[op.input('X')[0], name1], outputs=[name2]) 'Mul', inputs=[op.input('X')[0], name1], outputs=[name2])
node3 = helper.make_node( node3 = helper.make_node(
'Div', inputs=[name2, scale_name], outputs=op.output('Out')) 'Div', inputs=[name2, scale_name], outputs=op.output('Out'))
return [ return [scale_node, offset_node, node0, node1, node2, node3]
min_node, max_node, scale_node, offset_node, node0, node1, node2,
node3
]
def elementwise_mul(self, op, block): def elementwise_mul(self, op, block):
axis = op.attr('axis') axis = op.attr('axis')
...@@ -818,3 +758,11 @@ class PaddleOpMapper(object): ...@@ -818,3 +758,11 @@ class PaddleOpMapper(object):
def im2sequence(self, op, block): def im2sequence(self, op, block):
from .paddle_custom_layer.im2sequence import im2sequence from .paddle_custom_layer.im2sequence import im2sequence
return im2sequence(op, block) return im2sequence(op, block)
def yolo_box(self, op, block):
from .paddle_custom_layer.yolo_box import yolo_box
return yolo_box(op, block)
def multiclass_nms(self, op, block):
from .paddle_custom_layer.multiclass_nms import multiclass_nms
return multiclass_nms(op, block)
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import onnx import onnx
import numpy as np import numpy as np
from onnx import onnx_pb, helper from onnx import onnx_pb, helper
......
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import onnx import onnx
import numpy as np import numpy as np
from onnx import onnx_pb, helper from onnx import onnx_pb, helper
......
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import sys
import x2paddle
import os
import numpy as np
import paddle.fluid.core as core
import paddle.fluid as fluid
import onnx
from onnx import helper, onnx_pb
from x2paddle.op_mapper.paddle2onnx.opset9.opset import OpSet9
from x2paddle.op_mapper.paddle2onnx.opset10.opset import OpSet10
from x2paddle.op_mapper.paddle2onnx.opset11.opset import OpSet11
class PaddleOpMapper(object):
def __init__(self):
self.support_opsets = [9, 10, 11]
self.default_opset = 10
self.name_counter = dict()
self.op_set = None
def convert(self, program, save_dir, opset_number=10):
self.op_set = self.create_opset(opset_number)
weight_nodes = self.op_set.convert_weights(program)
op_nodes = list()
input_nodes = list()
output_nodes = list()
unsupported_ops = set()
print("Translating PaddlePaddle to ONNX...\n")
for block in program.blocks:
for i, op in enumerate(block.ops):
sys.stdout.write("\rTotal:{}, Current:{} : {} ".format(
len(block.ops), i + 1, op.type))
sys.stdout.flush()
if not hasattr(self.op_set, op.type):
unsupported_ops.add(op.type)
continue
if len(unsupported_ops) > 0:
continue
node = getattr(self.op_set, op.type)(op, block)
if op.type == 'feed':
print(node.name)
input_nodes.append(node)
elif op.type == 'fetch':
output_nodes.append(node)
else:
if isinstance(node, list):
op_nodes = op_nodes + node
else:
op_nodes.append(node)
if len(unsupported_ops) > 0:
print("\nThere's {} ops are not supported yet".format(
len(unsupported_ops)))
for op in unsupported_ops:
print("=========== {} ===========".format(op))
return
graph = helper.make_graph(
nodes=weight_nodes + op_nodes,
name='onnx_model_from_paddle',
initializer=[],
inputs=input_nodes,
outputs=output_nodes)
opset_imports = [helper.make_opsetid("", opset_number)]
model = helper.make_model(
graph, producer_name='X2Paddle', opset_imports=opset_imports)
onnx.checker.check_model(model)
if not os.path.isdir(save_dir):
os.makedirs(save_dir)
with open(os.path.join(save_dir, 'x2paddle_model.onnx'), 'wb') as f:
f.write(model.SerializeToString())
print("\nTranslated model saved in {}".format(
os.path.join(save_dir, 'x2paddle_model.onnx')))
def create_opset(self, opset_number):
run_opset = self.default_opset
opset = ''
if opset_number in self.support_opsets:
run_opset = opset_number
else:
for support_opset_number in self.support_opsets:
if support_opset_number < opset_number:
run_opset = support_opset_number
else:
break
print(
'Now, onnx2paddle support convert onnx model opset_verison {},'
'opset_verison of your onnx model is {}, automatically treated as op_set: {}.'
.format(self.support_opsets, opset_number, run_opset))
opset = 'OpSet' + str(run_opset)
return eval(opset)()
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
# limitations under the License. # limitations under the License.
# TODO useless node remove # TODO useless node remove
from x2paddle.op_mapper.onnx_op_mapper import ONNXOpMapper
class ONNXOptimizer(object): class ONNXOptimizer(object):
......
...@@ -236,26 +236,18 @@ class TFOptimizer(object): ...@@ -236,26 +236,18 @@ class TFOptimizer(object):
def remove_transpose(self): def remove_transpose(self):
graph_copy = cp.deepcopy(self.graph) graph_copy = cp.deepcopy(self.graph)
nhwc_insensitive_ops = [
'Relu', 'Relu6', 'Abs', 'Sigmoid', 'Exp', 'Rsqrt', 'swish_f32',
'LeakyRelu', 'Cast', 'Tanh'
]
elementwise_ops = [ elementwise_ops = [
'Sub', 'Add', 'RealDiv', 'Maximum', 'Mul', 'FloorDiv', 'Sub', 'Add', 'RealDiv', 'Maximum', 'Mul', 'FloorDiv',
'GreaterEqual' 'GreateerEqual'
]
optimize_ops = [
'Conv2D', 'MaxPool', 'FusedBatchNorm', 'DepthwiseConv2dNative',
'AvgPool', 'Pad', 'Conv2DBackpropInput', 'ResizeNearestNeighbor',
'ResizeBilinear', "Placeholder"
] ]
can_be_optimized_ops = [ can_be_optimized_ops = [
'Conv2D', 'MaxPool', 'FusedBatchNorm', 'DepthwiseConv2dNative', 'Conv2D', 'MaxPool', 'FusedBatchNorm', 'DepthwiseConv2dNative',
'AvgPool', 'Pad', 'Conv2DBackpropInput', 'ResizeNearestNeighbor', 'AvgPool', 'Pad', 'Conv2DBackpropInput', 'ResizeNearestNeighbor',
'ResizeBilinear', "Placeholder", 'Relu', 'Relu6', 'Abs', 'Sigmoid', 'Placeholder', 'Relu', 'Relu6', 'Abs', 'Sigmoid', 'Exp', 'Rsqrt',
'Exp', 'Rsqrt', 'swish_f32', 'LeakyRelu', 'Cast', 'Tanh' 'swish_f32', 'LeakyRelu', 'Cast', 'Tanh'
] ]
# These ops may have one more Variable input
can_be_optimized_special_ops = ['ResizeBilinear']
for node_name in self.graph.topo_sort: for node_name in self.graph.topo_sort:
node = graph_copy.get_node(node_name) node = graph_copy.get_node(node_name)
if node is None: if node is None:
...@@ -278,9 +270,10 @@ class TFOptimizer(object): ...@@ -278,9 +270,10 @@ class TFOptimizer(object):
0].param_attr["perm"] != [0, 3, 1, 2]: 0].param_attr["perm"] != [0, 3, 1, 2]:
can_be_removed = False can_be_removed = False
break break
elif out_node.layer_type in elementwise_ops: elif out_node.layer_type in elementwise_ops or out_node.layer_type in can_be_optimized_special_ops:
can_be_removed = False can_be_removed = False
break break
if can_be_removed and len(node.fluid_code.layers) > 1: if can_be_removed and len(node.fluid_code.layers) > 1:
true_node = self.graph.get_node(node_name) true_node = self.graph.get_node(node_name)
if true_node.layer_type == "Placeholder": if true_node.layer_type == "Placeholder":
...@@ -298,6 +291,7 @@ class TFOptimizer(object): ...@@ -298,6 +291,7 @@ class TFOptimizer(object):
-2].output = true_node.fluid_code.layers[-1].output -2].output = true_node.fluid_code.layers[-1].output
node.removed = True node.removed = True
del true_node.fluid_code.layers[-1] del true_node.fluid_code.layers[-1]
for out_name in output_names: for out_name in output_names:
out_node = self.graph.get_node(out_name) out_node = self.graph.get_node(out_name)
out_node.fluid_code.layers[ out_node.fluid_code.layers[
......
# X2Paddle模型测试库 # X2Paddle模型测试库
> 目前X2Paddle支持40+的TensorFlow OP,40+的Caffe Layer,覆盖了大部分CV分类模型常用的操作。我们在如下模型列表中测试了X2Paddle的转换。 > 目前X2Paddle支持50+的TensorFlow OP,40+的Caffe Layer,覆盖了大部分CV分类模型常用的操作。我们在如下模型列表中测试了X2Paddle的转换。
**注:** 受限于不同框架的差异,部分模型可能会存在目前无法转换的情况,如TensorFlow中包含控制流的模型,NLP模型等。对于CV常见的模型,如若您发现无法转换或转换失败,存在较大diff等问题,欢迎通过[ISSUE反馈](https://github.com/PaddlePaddle/X2Paddle/issues/new)的方式告知我们(模型名,代码实现或模型获取方式),我们会及时跟进:) **注:** 受限于不同框架的差异,部分模型可能会存在目前无法转换的情况,如TensorFlow中包含控制流的模型,NLP模型等。对于CV常见的模型,如若您发现无法转换或转换失败,存在较大diff等问题,欢迎通过[ISSUE反馈](https://github.com/PaddlePaddle/X2Paddle/issues/new)的方式告知我们(模型名,代码实现或模型获取方式),我们会及时跟进:)
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
| UNet | [code1](https://github.com/jakeret/tf_unet )/[code2](https://github.com/lyatdawn/Unet-Tensorflow) |-| | UNet | [code1](https://github.com/jakeret/tf_unet )/[code2](https://github.com/lyatdawn/Unet-Tensorflow) |-|
|MTCNN | [code](https://github.com/AITTSMD/MTCNN-Tensorflow) |-| |MTCNN | [code](https://github.com/AITTSMD/MTCNN-Tensorflow) |-|
|YOLO-V3| [code](https://github.com/YunYang1994/tensorflow-yolov3) | 转换需要关闭NHWC->NCHW的优化,见[文档Q2](FAQ.md) | |YOLO-V3| [code](https://github.com/YunYang1994/tensorflow-yolov3) | 转换需要关闭NHWC->NCHW的优化,见[文档Q2](FAQ.md) |
|Inception_ResNet_V2| [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) | - | | FALSR | [code](https://github.com/xiaomi-automl/FALSR) | - |
| DCSCN | [code](https://modelzoo.co/model/dcscn-super-resolution) | - |
## Caffe ## Caffe
...@@ -48,8 +49,8 @@ ...@@ -48,8 +49,8 @@
## ONNX ## ONNX
**注:** 部分模型来源于PyTorch,PyTorch的转换可参考[pytorch_to_onnx.md](pytorch_to_onnx.md) **注:** 部分模型来源于PyTorch,PyTorch的转换可参考[pytorch_to_onnx.md](pytorch_to_onnx.md)
| 模型 | 来源 | operator version| | 模型 | 来源 | operator version|备注|
|-------|--------|---------| |-------|--------|---------|---------|
| ResNet18 | [torchvison.model.resnet18](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |9| | ResNet18 | [torchvison.model.resnet18](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |9|
| ResNet34 | [torchvison.model.resnet34](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |9| | ResNet34 | [torchvison.model.resnet34](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |9|
| ResNet50 | [torchvison.model.resnet50](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |9| | ResNet50 | [torchvison.model.resnet50](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |9|
...@@ -65,4 +66,6 @@ ...@@ -65,4 +66,6 @@
| mNASNet | [pytorch(personal practice)](https://github.com/rwightman/gen-efficientnet-pytorch) |9| | mNASNet | [pytorch(personal practice)](https://github.com/rwightman/gen-efficientnet-pytorch) |9|
| EfficientNet | [pytorch(personal practice)](https://github.com/rwightman/gen-efficientnet-pytorch) |9| | EfficientNet | [pytorch(personal practice)](https://github.com/rwightman/gen-efficientnet-pytorch) |9|
| SqueezeNet | [onnx official](https://s3.amazonaws.com/download.onnx/models/opset_9/squeezenet.tar.gz) |9| | SqueezeNet | [onnx official](https://s3.amazonaws.com/download.onnx/models/opset_9/squeezenet.tar.gz) |9|
|Ultra-Light-Fast-Generic-Face-Detector-1MB| [onnx_model](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/tree/master/models/onnx)| | |Ultra-Light-Fast-Generic-Face-Detector-1MB| [onnx_model](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/tree/master/models/onnx)|9 |
|BERT| [pytorch(huggingface)](https://github.com/huggingface/transformers/blob/master/notebooks/04-onnx-export.ipynb)|11|转换时需指定input shape,见[文档Q3](FAQ.md)|
|GPT2| [pytorch(huggingface)](https://github.com/huggingface/transformers/blob/master/notebooks/04-onnx-export.ipynb)|11|转换时需指定input shape,见[文档Q3](FAQ.md)|
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册