diff --git a/x2paddle/convert.py b/x2paddle/convert.py index 60a5eef7e421ce00a2384d5efe4cffda7d4c856e..b20bde9ab0597f7b60010278d218d6a4f682622f 100644 --- a/x2paddle/convert.py +++ b/x2paddle/convert.py @@ -172,12 +172,11 @@ def onnx2paddle(model_path, save_dir, params_merge=False): return print("Now translating model from onnx to paddle.") - from x2paddle.op_mapper.onnx.onnx_helper import ONNXOpMapperFactory + from x2paddle.op_mapper.onnx_op_mapper import ONNXOpMapper from x2paddle.decoder.onnx_decoder import ONNXDecoder from x2paddle.optimizer.onnx_optimizer import ONNXOptimizer model = ONNXDecoder(model_path) - factory = ONNXOpMapperFactory() - mapper = factory.create_onnx_op_mapper(model) + mapper = ONNXOpMapper(model) print("Model optimizing ...") optimizer = ONNXOptimizer(mapper) print("Model optimized.") diff --git a/x2paddle/core/fluid_code.py b/x2paddle/core/fluid_code.py index 72ea10788e9519f8ec4256c13220969b3a0b4959..dbf66ef9b89602b692377b993798f338d78482b4 100644 --- a/x2paddle/core/fluid_code.py +++ b/x2paddle/core/fluid_code.py @@ -25,6 +25,7 @@ class Layer(object): self.inputs = dict() self.output = None self.is_custom_layer = False + self.use_fluid = False def get_code(self): layer_code = "" @@ -38,6 +39,8 @@ class Layer(object): layer_code = layer_code + self.op + "(" elif self.op == "=": layer_code = layer_code + elif self.use_fluid: + layer_code = layer_code + "fluid." + self.op + "(" else: layer_code = layer_code + "fluid.layers." + self.op + "(" @@ -108,9 +111,11 @@ class FluidCode(object): inputs, output, param_attr=None, + use_fluid=False, is_custom_layer=False): layer = Layer() layer.op = op + layer.use_fluid = use_fluid layer.is_custom_layer = is_custom_layer if inputs is not None: layer.inputs = inputs diff --git a/x2paddle/op_mapper/onnx/onnx_helper.py b/x2paddle/op_mapper/onnx/onnx_helper.py deleted file mode 100644 index 0c639dd13365c152098f4816662a4a568dcb77ee..0000000000000000000000000000000000000000 --- a/x2paddle/op_mapper/onnx/onnx_helper.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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.onnx.opset9 import ONNXOpMapperOpSet9 - - -class ONNXOpMapperFactory: - def __init__(self): - self.support_op_sets = [9, ] - self.default_op_set = 9 - - def create_onnx_op_mapper(self, decoder): - run_op_set = self.default_op_set - OpMapper = '' - if decoder.op_set in self.support_op_sets: - OpMapper = 'ONNXOpMapperOpSet' + str(decoder.op_set) - elif decoder.op_set < self.default_op_set: - OpMapper = 'ONNXOpMapperOpSet' + 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 - OpMapper = 'ONNXOpMapperOpSet' + 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(OpMapper)(decoder) diff --git a/x2paddle/op_mapper/onnx_directly_map.py b/x2paddle/op_mapper/onnx_directly_map.py deleted file mode 100644 index a1765f064153dca3e698857065a23b6650254f33..0000000000000000000000000000000000000000 --- a/x2paddle/op_mapper/onnx_directly_map.py +++ /dev/null @@ -1,82 +0,0 @@ -# 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')], -} diff --git a/x2paddle/op_mapper/onnx_op_mapper.py b/x2paddle/op_mapper/onnx_op_mapper.py new file mode 100644 index 0000000000000000000000000000000000000000..9912fc1c1fcb6031cda01ece4552be38ae36bf73 --- /dev/null +++ b/x2paddle/op_mapper/onnx_op_mapper.py @@ -0,0 +1,92 @@ +# 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.onnx_opsets.opset9 import OpSet9 +from x2paddle.core.op_mapper import OpMapper +from x2paddle.op_mapper.onnx_opsets.custom_layer import * +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.opser.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) diff --git a/x2paddle/op_mapper/onnx/__init__.py b/x2paddle/op_mapper/onnx_opsets/__init__.py similarity index 100% rename from x2paddle/op_mapper/onnx/__init__.py rename to x2paddle/op_mapper/onnx_opsets/__init__.py diff --git a/x2paddle/op_mapper/onnx/custom_layer/InstanceNormalization.py b/x2paddle/op_mapper/onnx_opsets/custom_layer/InstanceNormalization.py similarity index 100% rename from x2paddle/op_mapper/onnx/custom_layer/InstanceNormalization.py rename to x2paddle/op_mapper/onnx_opsets/custom_layer/InstanceNormalization.py diff --git a/x2paddle/op_mapper/onnx/custom_layer/__init__.py b/x2paddle/op_mapper/onnx_opsets/custom_layer/__init__.py similarity index 100% rename from x2paddle/op_mapper/onnx/custom_layer/__init__.py rename to x2paddle/op_mapper/onnx_opsets/custom_layer/__init__.py diff --git a/x2paddle/op_mapper/onnx/custom_layer/register.py b/x2paddle/op_mapper/onnx_opsets/custom_layer/register.py similarity index 100% rename from x2paddle/op_mapper/onnx/custom_layer/register.py rename to x2paddle/op_mapper/onnx_opsets/custom_layer/register.py diff --git a/x2paddle/op_mapper/onnx/opset9.py b/x2paddle/op_mapper/onnx_opsets/opset9.py similarity index 97% rename from x2paddle/op_mapper/onnx/opset9.py rename to x2paddle/op_mapper/onnx_opsets/opset9.py index 4636898ad690d52d5608175008887a8f8b0d1b04..5cf058f58ed3c35f54ff26cec469739cded0a23c 100644 --- a/x2paddle/op_mapper/onnx/opset9.py +++ b/x2paddle/op_mapper/onnx_opsets/opset9.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from x2paddle.decoder.onnx_decoder import ONNXGraph, ONNXGraphNode, ONNXGraphDataNode from x2paddle.core.graph import GraphNode -from x2paddle.core.op_mapper import OpMapper from x2paddle.core.fluid_code import Layer from x2paddle.core.fluid_code import FluidCode -from x2paddle.decoder.onnx_decoder import ONNXGraph, ONNXGraphNode, ONNXGraphDataNode -from x2paddle.op_mapper.onnx.custom_layer import * from x2paddle.core.util import string +from functools import reduce import numpy as np import onnx import onnx.numpy_helper as numpy_helper @@ -28,7 +27,6 @@ from collections import OrderedDict import math import os import shutil -from functools import reduce _logger = _logging.getLogger(__name__) @@ -66,7 +64,7 @@ def print_mapping_info(func): return run_mapping -class ONNXOpMapperOpSet9(OpMapper): +class OpSet9(): elementwise_ops = { 'Add': 'elementwise_add', 'Div': 'elementwise_div', @@ -139,57 +137,13 @@ class ONNXOpMapperOpSet9(OpMapper): } def __init__(self, decoder): - super(ONNXOpMapperOpSet9, self).__init__() + super(OpSet9, self).__init__() self.graph = decoder.graph self.input_shapes = [] self.weights = dict() self.omit_nodes = list() self.used_custom_layers = dict() - 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, op): - func = getattr(self, op) - func(node) - elif op in self.default_op_mapping: - self.directly_map(node) - elif op in custom_layers: - self.deal_custom_layer(node) - elif op in self.elementwise_ops: - self.elementwise_map(node) - print("Nodes converted.") - - 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, op) and \ - op not in self.default_op_mapping and \ - op not in custom_layers and \ - op not in self.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 - @print_mapping_info def directly_map(self, node, name='', *args, **kwargs): inputs = node.layer.input diff --git a/x2paddle/op_mapper/paddle_custom_layer/yolo_box.py b/x2paddle/op_mapper/paddle_custom_layer/yolo_box.py index a1e49e77b32ce1d64c11ca35ac69ed6cb20ee51c..b9375c232c78e10f10d10ebe24fdd0e4ac09f9a3 100644 --- a/x2paddle/op_mapper/paddle_custom_layer/yolo_box.py +++ b/x2paddle/op_mapper/paddle_custom_layer/yolo_box.py @@ -2,6 +2,8 @@ import onnx import numpy as np from onnx import onnx_pb, helper +MAX_FLOAT = np.asarray([255, 255, 127, 127], dtype=np.uint8).view(np.float32)[0] + def get_old_name(arg, name_prefix=''): prefix_index = arg.find(name_prefix) @@ -747,36 +749,53 @@ def yolo_box(op, block): outputs_pred_box_x2_clip = [model_name + "@pred_box_x2_clip"] outputs_pred_box_y2_clip = [model_name + "@pred_box_y2_clip"] + min_const_name = model_name + "@pred_box_min_const" + max_const_name = model_name + "@pred_box_max_const" + + min_const = onnx.helper.make_node( + 'Constant', + inputs=[], + outputs=[min_const_name], + value=onnx.helper.make_tensor( + name=min_const_name, + data_type=onnx.TensorProto.FLOAT, + dims=(), + vals=[0.0])) + node_list.append(min_const) + + max_const = onnx.helper.make_node( + 'Constant', + inputs=[], + outputs=[max_const_name], + value=onnx.helper.make_tensor( + name=max_const_name, + data_type=onnx.TensorProto.FLOAT, + dims=(), + vals=[MAX_FLOAT])) + node_list.append(max_const) + node_pred_box_x1_clip = onnx.helper.make_node( 'Clip', - inputs=outputs_pred_box_x1_decode, - outputs=outputs_pred_box_x1_clip, - min=0.0, - max=float(np.inf)) + inputs=outputs_pred_box_x1_decode + [min_const_name, max_const_name], + outputs=outputs_pred_box_x1_clip) node_list.append(node_pred_box_x1_clip) node_pred_box_y1_clip = onnx.helper.make_node( 'Clip', - inputs=outputs_pred_box_y1_decode, - outputs=outputs_pred_box_y1_clip, - min=0.0, - max=float(np.inf)) + inputs=outputs_pred_box_y1_decode + [min_const_name, max_const_name], + outputs=outputs_pred_box_y1_clip) node_list.append(node_pred_box_y1_clip) node_pred_box_x2_clip = onnx.helper.make_node( 'Clip', - inputs=outputs_pred_box_x2_sub_w, - outputs=outputs_pred_box_x2_clip, - min=0.0, - max=float(np.inf)) + inputs=outputs_pred_box_x2_sub_w + [min_const_name, max_const_name], + outputs=outputs_pred_box_x2_clip) node_list.append(node_pred_box_x2_clip) node_pred_box_y2_clip = onnx.helper.make_node( 'Clip', - inputs=outputs_pred_box_y2_sub_h, - outputs=outputs_pred_box_y2_clip, - min=0.0, - max=float(np.inf)) + inputs=outputs_pred_box_y2_sub_h + [min_const_name, max_const_name], + outputs=outputs_pred_box_y2_clip) node_list.append(node_pred_box_y2_clip) outputs_pred_box_x2_res = [model_name + "@box_x2_res"] diff --git a/x2paddle/op_mapper/paddle_op_mapper.py b/x2paddle/op_mapper/paddle_op_mapper.py index 0ba7ad682528b4062dea381964835271f0177432..bbd942ab469ad075c6b1c2346088465b8f91a7c9 100644 --- a/x2paddle/op_mapper/paddle_op_mapper.py +++ b/x2paddle/op_mapper/paddle_op_mapper.py @@ -99,6 +99,18 @@ class PaddleOpMapper(object): self.name_counter[name] += 1 return name + '.{}'.format(self.name_counter[name]) + 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) + def convert_weights(self, program): var_names = program.global_block().vars nodes = list()