From a5b69c1c47470c4a6609bddba38970e0981b3a45 Mon Sep 17 00:00:00 2001 From: SunAhong1993 Date: Wed, 31 Jul 2019 16:34:59 +0800 Subject: [PATCH] add the custom layer --- x2paddle/convert.py | 18 +- x2paddle/core/fluid_code.py | 86 ++++--- x2paddle/decoder/caffe_decoder.py | 55 ++--- .../op_mapper/caffe_custom_layer/__init__.py | 82 +++++++ .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 2557 bytes .../convolutiondepthwise.cpython-35.pyc | Bin 0 -> 3196 bytes .../__pycache__/register.cpython-35.pyc | Bin 0 -> 1467 bytes .../convolutiondepthwise.py | 129 ++++++++++ .../op_mapper/caffe_custom_layer/register.py | 43 ++++ x2paddle/op_mapper/caffe_op_mapper.py | 225 +++++++++++------- .../{decoder => op_mapper}/caffe_shape.py | 9 + 11 files changed, 492 insertions(+), 155 deletions(-) create mode 100644 x2paddle/op_mapper/caffe_custom_layer/__init__.py create mode 100644 x2paddle/op_mapper/caffe_custom_layer/__pycache__/__init__.cpython-35.pyc create mode 100644 x2paddle/op_mapper/caffe_custom_layer/__pycache__/convolutiondepthwise.cpython-35.pyc create mode 100644 x2paddle/op_mapper/caffe_custom_layer/__pycache__/register.cpython-35.pyc create mode 100644 x2paddle/op_mapper/caffe_custom_layer/convolutiondepthwise.py create mode 100644 x2paddle/op_mapper/caffe_custom_layer/register.py rename x2paddle/{decoder => op_mapper}/caffe_shape.py (98%) diff --git a/x2paddle/convert.py b/x2paddle/convert.py index d7150b4..c2e24db 100644 --- a/x2paddle/convert.py +++ b/x2paddle/convert.py @@ -23,11 +23,11 @@ def arg_parser(): type=_text_type, default=None, help="model file path") - parser.add_argument("--proto", + parser.add_argument("--prototxt", "-p", type=_text_type, default=None, - help="proto file of caffe model") + help="prototxt file of caffe model") parser.add_argument("--weight", "-w", type=_text_type, @@ -43,6 +43,11 @@ def arg_parser(): type=_text_type, default=None, help="define which deeplearning framework") + parser.add_argument("--caffe_proto", + "-c", + type=_text_type, + default=None, + help="caffe proto file of caffe model") return parser @@ -57,12 +62,12 @@ def tf2paddle(model_path, save_dir): mapper.save_python_model(save_dir) -def caffe2paddle(proto, weight, save_dir): +def caffe2paddle(proto, weight, save_dir, caffe_proto): from x2paddle.decoder.caffe_decoder import CaffeDecoder from x2paddle.op_mapper.caffe_op_mapper import CaffeOpMapper print("Now translating model from caffe to paddle.") - model = CaffeDecoder(proto, weight) + model = CaffeDecoder(proto, weight, caffe_proto) mapper = CaffeOpMapper(model) mapper.run() mapper.save_python_model(save_dir) @@ -80,8 +85,9 @@ def main(): tf2paddle(args.model, args.save_dir) elif args.framework == "caffe": - assert args.proto is not None, "--proto and --weight should be defined while translating caffe model" - caffe2paddle(args.proto, args.weight, args.save_dir) + assert args.prototxt is not None and args.weight is not None, "--prototxt and --weight should be defined while translating caffe model" + caffe2paddle(args.prototxt, args.weight, args.save_dir, + args.caffe_proto) else: raise Exception("--framework only support tensorflow/caffe now") diff --git a/x2paddle/core/fluid_code.py b/x2paddle/core/fluid_code.py index f8f4912..66cfb93 100644 --- a/x2paddle/core/fluid_code.py +++ b/x2paddle/core/fluid_code.py @@ -13,7 +13,6 @@ # limitations under the License. from x2paddle.core.graph import GraphNode -import collections class Layer(object): @@ -22,6 +21,7 @@ class Layer(object): self.param_attr = dict() self.inputs = dict() self.output = None + self.is_new = False def get_code(self): layer_code = "" @@ -36,34 +36,25 @@ class Layer(object): if isinstance(self.inputs, list): in_list = "[" for input in self.inputs: - if isinstance(input, GraphNode): - if hasattr(input, "index"): - in_list += (input.layer_name + - "[{}]".format(input.index) + ", ") - else: - in_list += (input.layer_name + ", ") - elif isinstance(input, str): - in_list += (input + ", ") + assert isinstance( + input, GraphNode), "Type of input should be GraphNode" + if hasattr(input, "index"): + in_list += (input.layer_name + "[{}]".format(input.index) + + ", ") else: - raise Exception( - "Element of inputs should GraphNode or String") + in_list += (input.layer_name + ", ") in_list = in_list.strip(", ") + "], " layer_code += in_list elif isinstance(self.inputs, dict): - inputs = collections.OrderedDict(self.inputs) - for key, input in inputs.items(): - if isinstance(input, GraphNode): - if hasattr(input, "index"): - layer_code = layer_code + key + "={}, ".format( - input.layer_name + "[{}]".format(input.index)) - else: - layer_code = layer_code + key + "={}, ".format( - input.layer_name) - elif isinstance(input, str): - layer_code = layer_code + key + "={}, ".format(input) + for key, input in self.inputs.items(): + assert isinstance( + input, GraphNode), "Type of input should be GraphNode" + if hasattr(input, "index"): + layer_code = layer_code + key + "={}, ".format( + input.layer_name + "[{}]".format(input.index)) else: - raise Exception( - "Element of inputs should GraphNode or String") + layer_code = layer_code + key + "={}, ".format( + input.layer_name) elif isinstance(self.inputs, GraphNode): if hasattr(self.inputs, "index"): layer_code += (self.inputs.layer_name + @@ -75,8 +66,38 @@ class Layer(object): else: raise Exception("Unknown type of inputs.") - param_attr = collections.OrderedDict(self.param_attr) - for key, value in param_attr.items(): + for key, value in self.param_attr.items(): + layer_code = layer_code + key + "={}, ".format(value) + layer_code = layer_code.strip(", ") + + return layer_code + ")" + + def get_custom_code(self): + layer_code = "" + if self.output is not None: + if isinstance(self.output, str): + layer_code = self.output + " = " + else: + layer_code = self.output.layer_name + " = " + + layer_code = layer_code + self.op + "(" + + if isinstance(self.inputs, list): + in_list = "[" + for input in self.inputs: + assert isinstance( + input, GraphNode), "Type of input should be GraphNode" + if hasattr(input, "index"): + in_list += (input.layer_name + "[{}]".format(input.index) + + ", ") + else: + in_list += (input.layer_name + ", ") + in_list = in_list.strip(", ") + "], " + layer_code += in_list + else: + raise Exception("Unknown type of inputs.") + + for key, value in self.param_attr.items(): layer_code = layer_code + key + "={}, ".format(value) layer_code = layer_code.strip(", ") @@ -87,9 +108,15 @@ class FluidCode(object): def __init__(self): self.layers = list() - def add_layer(self, op, inputs, output, param_attr=None): + def add_layer(self, + op, + inputs, + output, + param_attr=None, + is_custom_layer=False): layer = Layer() layer.op = op + layer.is_custom_layer = is_custom_layer if inputs is not None: layer.inputs = inputs layer.output = output @@ -108,7 +135,10 @@ class FluidCode(object): codes = list() for layer in self.layers: if isinstance(layer, Layer): - codes.append(layer.get_code()) + if layer.is_custom_layer: + codes.append(layer.get_custom_code()) + else: + codes.append(layer.get_code()) elif isinstance(layer, str): codes.append(layer) return codes diff --git a/x2paddle/decoder/caffe_decoder.py b/x2paddle/decoder/caffe_decoder.py index 26104b4..03abdf6 100644 --- a/x2paddle/decoder/caffe_decoder.py +++ b/x2paddle/decoder/caffe_decoder.py @@ -18,19 +18,20 @@ from google.protobuf import text_format import numpy as np from x2paddle.core.graph import GraphNode, Graph from x2paddle.core.fluid_code import FluidCode -from x2paddle.decoder import caffe_shape +from x2paddle.op_mapper import caffe_shape class CaffeResolver(object): - def __init__(self, use_default=True): - self.use_default = use_default + def __init__(self, caffe_proto_folder=None): + self.proto_path = caffe_proto_folder + if self.proto_path == None: + self.use_default = True + else: + self.use_default = False self.import_caffe() def import_caffepb(self): - p = os.path.realpath(__file__) - p = os.path.dirname(p) - p = os.path.join(p, './proto') - sys.path.insert(0, p) + sys.path.append(self.proto_path) import caffe_pb2 return caffe_pb2 @@ -60,11 +61,13 @@ class CaffeResolver(object): class CaffeGraphNode(GraphNode): def __init__(self, layer, layer_name=None): if layer_name is None: - super(CaffeGraphNode, self).__init__(layer, - layer.name.replace('/', '_')) + super(CaffeGraphNode, + self).__init__(layer, + layer.name.replace('/', '_').replace('-', '_')) else: - super(CaffeGraphNode, self).__init__(layer, - layer_name.replace('/', '_')) + super(CaffeGraphNode, + self).__init__(layer, + layer_name.replace('/', '_').replace('-', '_')) self.layer_type = layer.type self.fluid_code = FluidCode() self.data = None @@ -72,10 +75,13 @@ class CaffeGraphNode(GraphNode): def set_params(self, params): self.data = params - def set_output_shape(self, input_shape): + def set_output_shape(self, input_shape, is_input=True): func_name = 'shape_' + self.layer_type.lower() - self.output_shape = getattr(caffe_shape, func_name)(self.layer, - input_shape) + if is_input: + self.output_shape = getattr(caffe_shape, func_name)(self.layer, + input_shape) + else: + self.output_shape = input_shape def set_input_shape(self, input_shape): self.input_shape = input_shape @@ -135,7 +141,7 @@ class CaffeGraph(Graph): ]))).to_proto().layer[0]) except: raise ImportError( - 'You must install the caffe first when you use old style prototxt.' + 'The .proto file does not work for the old style prototxt. You must install the caffe or modify the old style to new style in .protottx file.' ) data.name = self.model.input[i] data.top[0] = self.model.input[i] @@ -151,7 +157,7 @@ class CaffeGraph(Graph): ]))).to_proto().layer[0]) except: raise ImportError( - 'You must install the caffe first when you use old style prototxt.' + 'The .proto file does not work for the old style prototxt. You must install the caffe or modify the old style to new style in .protottx file.' ) data.name = self.model.input[i] data.top[0] = self.model.input[i] @@ -180,19 +186,6 @@ class CaffeGraph(Graph): else: notice('Ignoring parameters for non-existent layer: %s' % \ layer_name) - for layer_name in self.node_map: - node = self.node_map[layer_name] - inputs = node.inputs - i = 0 - input_shape = [] - for nm in inputs: - last_node = self.get_node(nm) - tmp = node.layer.bottom[i] - i = i + 1 - idx = list(last_node.layer.top).index(tmp) - input_shape.append(last_node.output_shape[idx]) - node.set_output_shape(input_shape) - node.set_input_shape(input_shape) super(CaffeGraph, self).build() @@ -210,11 +203,11 @@ class CaffeGraph(Graph): class CaffeDecoder(object): - def __init__(self, proto_path, model_path, use_caffe=True): + def __init__(self, proto_path, model_path, caffe_proto_folder=None): self.proto_path = proto_path self.model_path = model_path - self.resolver = CaffeResolver(use_default=use_caffe) + self.resolver = CaffeResolver(caffe_proto_folder=caffe_proto_folder) self.net = self.resolver.NetParameter() with open(proto_path, 'rb') as proto_file: proto_str = proto_file.read() diff --git a/x2paddle/op_mapper/caffe_custom_layer/__init__.py b/x2paddle/op_mapper/caffe_custom_layer/__init__.py new file mode 100644 index 0000000..0837699 --- /dev/null +++ b/x2paddle/op_mapper/caffe_custom_layer/__init__.py @@ -0,0 +1,82 @@ +from .register import get_registered_layers + +#custom layer import begins +# from . import roipooling +# from . import priorbox +# from . import permute +# from . import detection_out +# from . import normalize +# from . import select +from . import convolutiondepthwise +#custom layer import ends + +custom_layers = get_registered_layers() + + +def set_args(f, params): + """ set args for function 'f' using the parameters in node.layer.param + Args: + f (function): a python function object + params (object): a object contains attributes needed by f's arguments + Returns: + arg_names (list): a list of argument names + kwargs (dict): a dict contains needed arguments + """ + argc = f.__code__.co_argcount + arg_list = f.__code__.co_varnames[0:argc] + kwargs = {} + for arg_name in arg_list: + if hasattr(params, arg_name) and params is not None: + kwargs[arg_name] = getattr(params, arg_name) + return arg_list, kwargs + + +def has_layer(layer_type): + """ test whether this layer exists in custom layer + """ + return layer_type in custom_layers + + +def get_params(layer, layer_type): + if layer_type.lower() == "deconvolution" or layer_type.lower( + ) == "convolutiondepthwise": + param_name = '_'.join(('convolution', 'param')) + elif layer_type.lower() == "normalize": + param_name = '_'.join(('norm', 'param')) + else: + param_name = '_'.join((layer_type.lower(), 'param')) + return getattr(layer, param_name, None) + + +def compute_output_shape(node): + """ compute the output shape of custom layer + """ + layer_type = node.layer_type + assert layer_type in custom_layers, "layer[%s] not exist in custom layers" % ( + layer_type) + shape_func = custom_layers[layer_type]['shape'] + layer = node.layer + params = get_params(layer, layer_type) + arg_names, kwargs = set_args(shape_func, params) + input_shape = node.input_shape + return shape_func(input_shape, **kwargs) + + +def make_custom_layer(node): + """ get the code which implement the custom layer function + """ + layer_type = node.layer_type + assert layer_type in custom_layers, "layer[%s] not exist in custom layers" % ( + layer_type) + layer_func = custom_layers[layer_type]['layer'] + import inspect + return inspect.getsource(layer_func), layer_func + + +def deal_weights(node, data=None): + """ deal the weights of the custom layer + """ + layer_type = node.layer_type + weights_func = custom_layers[layer_type]['weights'] + name = node.layer_name + return weights_func(name, data) diff --git a/x2paddle/op_mapper/caffe_custom_layer/__pycache__/__init__.cpython-35.pyc b/x2paddle/op_mapper/caffe_custom_layer/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1bc876dd55ec04921c0ff0c92eba1d4b4eedd41 GIT binary patch literal 2557 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 Hy3N(!%sGxg literal 0 HcmV?d00001 diff --git a/x2paddle/op_mapper/caffe_custom_layer/convolutiondepthwise.py b/x2paddle/op_mapper/caffe_custom_layer/convolutiondepthwise.py new file mode 100644 index 0000000..103b455 --- /dev/null +++ b/x2paddle/op_mapper/caffe_custom_layer/convolutiondepthwise.py @@ -0,0 +1,129 @@ +from .register import register +from x2paddle.core.util import * +import numbers + + +def convolutiondepthwise_shape(input_shape, + num_output=None, + pad=None, + kernel_size=None, + stride=None, + dilation=None, + pad_h=None, + pad_w=None, + kernel_h=None, + kernel_w=None, + stride_h=None, + stride_w=None): + [k_h, k_w] = [1, 1] + if isinstance(kernel_size, numbers.Number): + [k_h, k_w] = [kernel_size] * 2 + elif isinstance(kernel_size, list): + k_h = kernel_h if kernel_h else kernel_size[0] + k_w = kernel_w if kernel_w else kernel_size[len(kernel_size) - 1] + [s_h, s_w] = [1, 1] + if isinstance(stride, numbers.Number): + [s_h, s_w] = [stride] * 2 + elif isinstance(stride, list): + s_h = stride_h if stride_h else stride[0] + s_w = stride_w if stride_w else stride[len(stride) - 1] + [p_h, p_w] = [0, 0] + if isinstance(pad, numbers.Number): + [p_h, p_w] = [pad] * 2 + elif isinstance(pad, list): + p_h = pad_h if pad_h else pad[0] + p_w = pad_w if pad_w else pad[len(pad) - 1] + dila_len = len(dilation) + dila_h = 1 + dila_w = 1 + if dila_len == 2: + dila_h = dilation[0] + dila_w = dilation[1] + elif dila_len == 1: + dila_h = dila_w = dilation[0] + else: + assert dila_len == 0, "invalid length[%s] of dilation in convolution" % ( + dila_len) + i_w = input_shape[0][2] + i_h = input_shape[0][3] + o_h = (i_h + 2 * p_h - (dila_h * (k_h - 1) + 1)) / float(s_h) + 1 + o_w = (i_w + 2 * p_w - (dila_w * (k_w - 1) + 1)) / float(s_w) + 1 + import math + o_h = int(math.floor(o_h)) + o_w = int(math.floor(o_w)) + c = num_output if num_output is not None else input_shape[0][1] + return [[input_shape[0][0], c, o_h, o_w]] + + +def convolutiondepthwise_layer(inputs, + num_output=None, + pad=None, + kernel_size=None, + stride=None, + dilation=None, + pad_h=None, + pad_w=None, + kernel_h=None, + kernel_w=None, + stride_h=None, + stride_w=None, + input_shape=[], + name=None): + [k_h, k_w] = [1, 1] + if isinstance(kernel_size, numbers.Number): + [k_h, k_w] = [kernel_size] * 2 + elif isinstance(kernel_size, list): + k_h = kernel_h if kernel_h else kernel_size[0] + k_w = kernel_w if kernel_w else kernel_size[len(kernel_size) - 1] + [s_h, s_w] = [1, 1] + if isinstance(stride, numbers.Number): + [s_h, s_w] = [stride] * 2 + elif isinstance(stride, list): + s_h = stride_h if stride_h else stride[0] + s_w = stride_w if stride_w else stride[len(stride) - 1] + [p_h, p_w] = [0, 0] + if isinstance(pad, numbers.Number): + [p_h, p_w] = [pad] * 2 + elif isinstance(pad, list): + p_h = pad_h if pad_h else pad[0] + p_w = pad_w if pad_w else pad[len(pad) - 1] + input = inputs[0] + dila_len = len(dilation) + dila_h = 1 + dila_w = 1 + if dila_len == 2: + dila_h = dilation[0] + dila_w = dilation[1] + elif dila_len == 1: + dila_h = dila_w = dilation[0] + else: + assert dila_len == 0, "invalid length[%s] of dilation in convolution" % ( + dila_len) + c_in = input_shape[0][1] + c_out = num_output if num_output is not None else input_shape[0][1] + group = int(c_in / (c_in / c_out)) if c_in > c_out else int(c_in / + (c_out / c_in)) + out = fluid.layers.conv2d(input, + dilation=[dila_h, dila_w], + filter_size=[k_h, k_w], + stride=[s_h, s_w], + padding=[p_h, p_w], + groups=group, + num_filters=c_out, + param_attr=name + '_weights', + bias_attr=name + '_bias', + name=name) + return out + + +def convolutiondepthwise_weights(name, data=None): + weights_name = [] + weights_name.append(name + '_weights') + weights_name.append(name + '_bias') + return weights_name + + +register(kind='ConvolutionDepthwise', + shape=convolutiondepthwise_shape, + layer=convolutiondepthwise_layer, + weights=convolutiondepthwise_weights) diff --git a/x2paddle/op_mapper/caffe_custom_layer/register.py b/x2paddle/op_mapper/caffe_custom_layer/register.py new file mode 100644 index 0000000..9600018 --- /dev/null +++ b/x2paddle/op_mapper/caffe_custom_layer/register.py @@ -0,0 +1,43 @@ +""" this module provides 'register' for registering customized layers +""" + +g_custom_layers = {} + + +def register(kind, shape, layer, weights): + """ register a custom layer or a list of custom layers + + Args: + @kind (str or list): type name of the layer + @shape (function): a function to generate the shape of layer's output + @layer (function): a function to generate the paddle code of layer + @weights (function): a function to deal with weights data + + Returns: + None + """ + assert type(shape).__name__ == 'function', 'shape should be a function' + assert type(layer).__name__ == 'function', 'layer should be a function' + + if type(kind) is str: + kind = [kind] + else: + assert type( + kind + ) is list, 'invalid param "kind" for register, not a list or str' + + for k in kind: + assert type( + k) is str, 'invalid param "kind" for register, not a list of str' + assert k not in g_custom_layers, 'this type[%s] has already been registered' % ( + k) + print('register layer[%s]' % (k)) + g_custom_layers[k] = { + 'shape': shape, + 'layer': layer, + 'weights': weights + } + + +def get_registered_layers(): + return g_custom_layers diff --git a/x2paddle/op_mapper/caffe_op_mapper.py b/x2paddle/op_mapper/caffe_op_mapper.py index abb8d24..9357581 100644 --- a/x2paddle/op_mapper/caffe_op_mapper.py +++ b/x2paddle/op_mapper/caffe_op_mapper.py @@ -17,6 +17,7 @@ import numpy as np from x2paddle.decoder.caffe_decoder import CaffeGraph from x2paddle.core.op_mapper import OpMapper from x2paddle.core.util import * +from x2paddle.op_mapper.caffe_custom_layer import * class CaffeOpMapper(OpMapper): @@ -25,34 +26,72 @@ class CaffeOpMapper(OpMapper): self.graph = decoder.caffe_graph self.weights = dict() resolver = decoder.resolver + self.mylayers = {} if resolver.has_pycaffe(): self.did_use_pb = False else: self.did_use_pb = True + 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 custom_layers: + 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 run(self): print("Total nodes: {}".format(len(self.graph.topo_sort))) - # check if ops in model are all supported if not self.op_checker(): raise Exception("Model are not supported yet.") - for node_name in self.graph.topo_sort: node = self.graph.get_node(node_name) op = node.layer_type if hasattr(self, op): + self.set_shape(node) func = getattr(self, op) func(node) + elif op in custom_layers: + self.set_shape(node, is_fluid_op=False) + self.deal_custom_layer(node) + else: + raise Exception("Model are not supported yet.") + for key in self.mylayers: + self.net_code.append(self.mylayers[key]) for i in range(len(self.graph.topo_sort)): node_name = self.graph.topo_sort[i] node = self.graph.get_node(node_name) self.net_code += node.fluid_code.gen_codes() - def adjust_parameters(self, node, data): + def set_shape(self, node, is_fluid_op=True): + inputs = node.inputs + input_shape = [] + for i, nm in enumerate(inputs): + last_node = self.graph.get_node(nm) + tmp = node.layer.bottom[i] + idx = list(last_node.layer.top).index(tmp) + input_shape.append(last_node.output_shape[idx]) + node.set_input_shape(input_shape) + if is_fluid_op: + node.set_output_shape(input_shape) + else: + node.set_output_shape(compute_output_shape(node), + is_input=is_fluid_op) + + def adjust_parameters(self, node): + data = node.data if not self.did_use_pb: return data - # When using the protobuf-backend, each parameter initially has four dimensions. # In certain cases (like FC layers), we want to eliminate the singleton dimensions. # This implementation takes care of the common cases. However, it does leave the @@ -61,7 +100,7 @@ class CaffeOpMapper(OpMapper): data = list(data) squeeze_indices = [1] # Squeeze biases. - if node.kind == NodeKind.InnerProduct: + if node.layer_type == 'InnerProduct': squeeze_indices.append(0) # Squeeze FC. for idx in squeeze_indices: @@ -85,55 +124,44 @@ class CaffeOpMapper(OpMapper): data[idx] = np.squeeze(d, axis=sq_axis) shape_new = data[idx].shape + print('shape-old' + str(shape_old)) + print('shape-new' + str(shape_new)) if len(shape_old) != shape_new: - debug('squeeze idx:%d, with kind:%s,name:%s' % \ - (idx, node.kind, node.name)) + print('squeeze idx:%d, with kind:%s,name:%s' % \ + (idx, node.layer_type, node.layer.name)) return data - @staticmethod - def get_kernel_value(scalar, repeated, idx, default=None): - if scalar: - return scalar - if repeated: - if isinstance(repeated, numbers.Number): - return repeated - if len(repeated) == 1: - # Same value applies to all spatial dimensions - return int(repeated[0]) - assert idx < len(repeated) - # Extract the value for the given spatial dimension - return repeated[idx] - if default is None: - raise ValueError('Unable to determine kernel parameter!') - return default - def get_kernel_parameters(self, kind, params): - assert kind in ['Convolution', 'Pooling', 'Deconvolution'] - - k_h = self.get_kernel_value(params.kernel_h, - params.kernel_size, - 0, - default=1) - k_w = self.get_kernel_value(params.kernel_w, - params.kernel_size, - 1, - default=1) - s_h = self.get_kernel_value(params.stride_h, - params.stride, - 0, - default=1) - s_w = self.get_kernel_value(params.stride_w, - params.stride, - 1, - default=1) - p_h = self.get_kernel_value(params.pad_h, params.pad, 0, default=0) - p_w = self.get_kernel_value(params.pad_w, params.pad, 1, default=0) + assert kind in [ + 'Convolution', 'Pooling', 'Deconvolution', 'ConvolutionDepthwise' + ] + [k_h, k_w] = [1, 1] + print(params.kernel_size) + if isinstance(params.kernel_size, numbers.Number): + [k_h, k_w] = [params.kernel_size] * 2 + else: + k_h = params.kernel_h if params.kernel_h else params.kernel_size[0] + k_w = params.kernel_w if params.kernel_w else params.kernel_size[ + len(params.kernel_size) - 1] + [s_h, s_w] = [1, 1] + if isinstance(params.stride, numbers.Number): + [s_h, s_w] = [params.stride] * 2 + else: + s_h = params.stride_h if params.stride_h else params.stride[0] + s_w = params.stride_w if params.stride_w else params.stride[ + len(params.stride) - 1] + [p_h, p_w] = [0, 0] + if isinstance(params.pad, numbers.Number): + [p_h, p_w] = [params.pad] * 2 + else: + p_h = params.pad_h if params.pad_h else params.pad[0] + p_w = params.pad_w if params.pad_w else params.pad[len(params.pad) - + 1] dila_h = dila_w = 1 group = 1 c_o = 1 - if kind in ['Convolution', 'Deconvolution']: + if kind in ['Convolution', 'Deconvolution', 'ConvolutionDepthwise']: c_o = params.num_output - group = params.group dila_len = len(params.dilation) if dila_len == 2: dila_h = params.dilation[0] @@ -143,12 +171,12 @@ class CaffeOpMapper(OpMapper): else: assert dila_len == 0, "invalid length[%s] of dilation in convolution" % ( dila_len) - + if kind in ['Convolution', 'Deconvolution']: + group = params.group kernel = [k_h, k_w] stride = [s_h, s_w] pad = [p_h, p_w] dilation = [dila_h, dila_w] - return c_o, kernel, stride, pad, dilation, group def get_input_name(self, node): @@ -180,7 +208,7 @@ class CaffeOpMapper(OpMapper): data = node.data assert data is not None, 'The parameter of {} (type is {}) is not set. You need to use python package of caffe to set the default value.'.format( node.layer_name, node.layer_type) - data = self.adjust_parameters(node, data) + data = self.adjust_parameters(node) self.weights[node.layer_name + '_weights'] = data[0] if len(data) == 2: self.weights[node.layer_name + '_bias'] = data[1] @@ -224,7 +252,7 @@ class CaffeOpMapper(OpMapper): data = node.data assert data is not None, 'The parameter of {} (type is {}) is not set. You need to use python package of caffe to set the default value.'.format( node.layer_name, node.layer_type) - data = self.adjust_parameters(node, data) + data = self.adjust_parameters(node) self.weights[node.layer_name + '_weights'] = data[0] if len(data) == 2: self.weights[node.layer_name + '_bias'] = data[1] @@ -343,7 +371,7 @@ class CaffeOpMapper(OpMapper): data = node.data assert data is not None, 'The parameter of {} (type is {}) is not set. You need to use python package of caffe to set the default value.'.format( node.layer_name, node.layer_type) - data = self.adjust_parameters(node, data) + data = self.adjust_parameters(node) # Reshape the parameters to Paddle's ordering transpose_order = (1, 0) w = data[0] @@ -396,40 +424,11 @@ class CaffeOpMapper(OpMapper): shape = node.input_shape[0] dims = len(shape) axis = axis + dims if axis < 0 else axis - need_transpose = False - if axis + 1 != dims: - need_transpose = True - if need_transpose: - in_order = list(range(dims)) - in_order.remove(axis) - in_order.append(axis) - attr = { - 'perm': in_order, - 'name': string(node.layer_name + '_transpose_in') - } - node.fluid_code.add_layer("transpose", - inputs=input, - output=node, - param_attr=attr) - attr = {'name': string(node.layer_name + '_softmax')} + attr = {'axis': axis, 'name': string(node.layer_name + '_softmax')} node.fluid_code.add_layer("softmax", - inputs=node if need_transpose else input, + inputs=input, output=node, param_attr=attr) - if need_transpose: - out_order = [ - 0, - ] * dims - for id, v in enumerate(in_order): - out_order[v] = id - attr = { - 'perm': out_order, - 'name': string(node.layer_name + '_transpose_out') - } - node.fluid_code.add_layer("transpose", - inputs=node, - output=node, - param_attr=attr) def Slice(self, node): assert len( @@ -451,13 +450,11 @@ class CaffeOpMapper(OpMapper): attr = { 'axes': [axis], 'starts': [points[i]], - 'ends': [points[i + 1]], - 'name': string(node.layer_name + '_' + str(i)) + 'ends': [points[i + 1]] } node.fluid_code.add_layer("slice", inputs=input, - output=string(node.layer_name + '_' + - str(i)), + output=node.layer_name + '_' + str(i), param_attr=attr) node.fluid_code.add_note('{}.append({})'.format( node.layer_name, node.layer_name + '_' + str(i))) @@ -503,7 +500,7 @@ class CaffeOpMapper(OpMapper): node.layer_name, node.layer_type) self.weights[node.layer_name + '_weights'] = data[0] attr = { - 'mode': mode, + 'mode': string(mode), 'param_attr': string(node.layer_name + '_weights'), 'name': string(node.layer_name) } @@ -731,7 +728,7 @@ class CaffeOpMapper(OpMapper): def Scale(self, node): assert len( - node.outputs) == 1, 'The count of Scale node\'s output is not 1.' + node.inputs) == 1, 'The count of Scale node\'s input is not 1.' if len(node.inputs) == 1 and self.graph.get_node( node.inputs[0]).layer_type == 'BatchNorm': return @@ -1058,7 +1055,9 @@ class CaffeOpMapper(OpMapper): param_attr=attr) input_name = self.get_input_name(input) data = node.data - data = self.adjust_parameters(node, data) + assert data is not None, 'The parameter of {} (type is {}) is not set. You need to use python package of caffe to set the default value.'.format( + node.layer_name, node.layer_type) + data = self.adjust_parameters(node) self.weights[node.layer_name + '_scale'] = data[0] node.fluid_code.add_note( '{}_scale_attr = ParamAttr(name=\'{}\')'.format( @@ -1297,7 +1296,7 @@ class CaffeOpMapper(OpMapper): def Select(self, node): assert len( - node.inputs) == 1, 'The count of Select node\'s input is not 2.' + node.inputs) == 1, 'The count of Select node\'s input is not 1.' input = self.graph.get_bottom_node(node, idx=0, copy=True) if self.is_Scale(input): tmp = self.graph.get_bottom_node(input, idx=0, copy=True) @@ -1327,3 +1326,49 @@ class CaffeOpMapper(OpMapper): node.layer_name, node.layer_name + '_' + str(i))) if i == len(slice_point) - 2: break + + def ShuffleChannel(self, node): + assert len(node.inputs + ) == 1, 'The count of ShuffleChannel node\'s input is not 1.' + params = node.layer.shuffle_channel_param + group = params.group + input = self.graph.get_bottom_node(node, idx=0, copy=True) + if self.is_Scale(input): + tmp = self.graph.get_bottom_node(input, idx=0, copy=True) + if self.is_BN(tmp): + input = tmp + attr = {'group': group, 'name': string(node.layer_name)} + node.fluid_code.add_layer("shuffle_channel", + inputs=input, + output=node, + param_attr=attr) + + def deal_custom_layer(self, node): + op = node.layer_type + custom_code, func = make_custom_layer(node) + params = get_params(node.layer, node.layer_type) + arg_names, kwargs = set_args(func, params) + kwargs['name'] = string(node.layer_name) + kwargs['input_shape'] = node.input_shape + data = node.data + assert data is not None, 'The parameter of {} (type is {}) is not set. You need to use python package of caffe to set the default value.'.format( + node.layer_name, node.layer_type) + data = self.adjust_parameters(node) + weights_name = deal_weights(node) + for i in range(len(data)): + self.weights[weights_name[i]] = data[i] + inputs_node = [] + for i in range(len(node.inputs)): + input = self.graph.get_bottom_node(node, idx=i, copy=True) + if self.is_Scale(input): + tmp = self.graph.get_bottom_node(input, idx=0, copy=True) + if self.is_BN(tmp): + input = tmp + inputs_node.append(input) + node.fluid_code.add_layer(func.__code__.co_name, + inputs=inputs_node, + output=node, + param_attr=kwargs, + is_custom_layer=True) + if op not in self.mylayers: + self.mylayers[op] = custom_code diff --git a/x2paddle/decoder/caffe_shape.py b/x2paddle/op_mapper/caffe_shape.py similarity index 98% rename from x2paddle/decoder/caffe_shape.py rename to x2paddle/op_mapper/caffe_shape.py index 2bca90a..103174b 100644 --- a/x2paddle/decoder/caffe_shape.py +++ b/x2paddle/op_mapper/caffe_shape.py @@ -453,3 +453,12 @@ def shape_select(layer, input_shape): output_shape = input_shape output_shape[axis] = end - start return [output_shape] + + +def shape_shufflechannel(layer, input_shape): + return input_shape + + +# def shape_convolutiondepthwise(layer, input_shape): +# params = layer.convolution_param +# return get_strided_kernel_output_shape(params, input_shape[0], math.floor) -- GitLab