diff --git a/onnx2fluid/README.md b/onnx2fluid/README.md index a544fe3efa8e52b47676c9464ca2eb66b5055c23..95e4ad41101a6280ef6137944e15027255321de5 100644 --- a/onnx2fluid/README.md +++ b/onnx2fluid/README.md @@ -17,13 +17,13 @@ onnx2fluid支持将ONNX模型转换为PaddlePaddle模型,并用于预测,用 在如下环境配置中测试成功: * python 3.5+ -* onnx == 1.4.0 -* paddlepaddle == 1.3.0 (可选,仅用于验证) +* onnx == 1.4.1 +* paddlepaddle == 1.5.0 (可选,仅用于验证) 使用[Anaconda](https://docs.anaconda.com/anaconda/install): ``` shell conda install -c conda-forge onnx -pip install paddlepaddle==1.3.0 +pip install paddlepaddle==1.5.0 ``` ## 动手玩 @@ -49,6 +49,8 @@ onnx2fluid sample_1.onnx -t sample_1.npz ## 使用说明 +目前支持 **ONNX opset 9+** 的部分算子,对应PyTorch版本 **1.0/1.1(stable opset)**,更多兼容信息请参考[ONNX文档](https://github.com/onnx/onnx/blob/master/docs/Operators.md) + onnx2fluid: ```shell @@ -79,5 +81,5 @@ onnx2fluid.validate [-d] [-t test_data.npz] [-p 1e-3] /path/to/onnx/model.onnx ## 参考 -* PaddlePaddle [算子](http://www.paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html) -* PaddlePaddle [加载预测模型](http://www.paddlepaddle.org/documentation/docs/zh/1.4/api_guides/low_level/inference.html#id4) +* PaddlePaddle [算子](http://www.paddlepaddle.org/documentation/docs/zh/1.5/api_cn/layers_cn.html) +* PaddlePaddle [加载预测模型](http://www.paddlepaddle.org/documentation/docs/zh/1.5/api_guides/low_level/inference.html#id4) diff --git a/onnx2fluid/examples/gen_some_samples.py b/onnx2fluid/examples/gen_some_samples.py index 2c55f5c3939ea7d570d5bc97e1373b148db82a0e..52aacefeba70ef9705f02e3c5ce37247693b43f3 100644 --- a/onnx2fluid/examples/gen_some_samples.py +++ b/onnx2fluid/examples/gen_some_samples.py @@ -20,50 +20,56 @@ from onnx2fluid.torch_export_helper import export_onnx_with_validation prefix = 'sample_' idx = 0 -######### example: RNN ######## -# -#class Model(nn.Module): -# def __init__(self): -# super(Model, self).__init__() -# self.rnn = nn.RNN(4, 6, 2) -# -# def forward(self, x): -# y = x -# y, h = self.rnn(y) -# return y -# -# -#model = Model() -#model.eval() -#xb = torch.rand((2, 3, 4)) -#yp = model(xb) -#idx += 1 -#print('index: ', idx) -#export_onnx_with_validation(model, [xb], prefix + str(idx), -# ['x'], ['y'], -# verbose=True, training=False) +######## example: RNN ######## -######### example: random ######## -# -#class Model(nn.Module): -# def __init__(self): -# super(Model, self).__init__() -# -# def forward(self, x): -# y = torch.rand((2, 3)) # + torch.rand_like(xb) -# y = y + torch.randn((2, 3)) # + torch.randn_like(xb) -# return y -# -# -#model = Model() -#model.eval() -#xb = torch.rand((2, 3)) -#yp = model(xb) -#idx += 1 -#print('index: ', idx) -#export_onnx_with_validation(model, [xb], prefix + str(idx), -# ['x'], ['y'], -# verbose=True, training=False) + +class Model(nn.Module): + def __init__(self): + super(Model, self).__init__() + self.gru = nn.GRU(4, 5, 3) + self.lstm = nn.LSTM(5, 6, 2) + + def forward(self, x): + y = x + y, h = self.gru(y) + y, h = self.lstm(y) + return y + + +model = Model() +model.eval() +xb = torch.rand((2, 3, 4)) +yp = model(xb) +idx += 1 +print('index: ', idx) +export_onnx_with_validation(model, [xb], + prefix + str(idx), ['x'], ['y'], + verbose=True, + training=False) + +######## example: random ######## + + +class Model(nn.Module): + def __init__(self): + super(Model, self).__init__() + + def forward(self, x): + y = torch.rand((2, 3)) # + torch.rand_like(xb) + y = y + torch.randn((2, 3)) # + torch.randn_like(xb) + return y + + +model = Model() +model.eval() +xb = torch.rand((2, 3)) +yp = model(xb) +idx += 1 +print('index: ', idx) +export_onnx_with_validation(model, [xb], + prefix + str(idx), ['x'], ['y'], + verbose=True, + training=False) ######## example: fc ######## @@ -175,7 +181,7 @@ class Model(nn.Module): super(Model, self).__init__() self.conv = nn.Conv2d(3, 8, 3) self.batch_norm = nn.BatchNorm2d(8) - self.pool = nn.AdaptiveAvgPool2d(2) + self.pool = nn.AdaptiveAvgPool2d(1) def forward(self, x): y = x @@ -215,9 +221,10 @@ export_onnx_with_validation(model, [xb], #yp = model(xb) #idx += 1 #print('index: ', idx) -#export_onnx_with_validation(model, [xb], prefix + str(idx), -# ['x'], ['y'], -# verbose=True, training=False) +#export_onnx_with_validation( +# model, [xb], prefix + str(idx), +# ['x'], ['y'], +# verbose=True, training=False) ######## example: empty ######## diff --git a/onnx2fluid/examples/onnx_model_zoo.sh b/onnx2fluid/examples/onnx_model_zoo.sh index ca7fa9bf2c648d93f95c7cc5461fa677dc1c6a0e..ada80dfb81d32cbc5f148899500a2b4986129513 100755 --- a/onnx2fluid/examples/onnx_model_zoo.sh +++ b/onnx2fluid/examples/onnx_model_zoo.sh @@ -24,14 +24,14 @@ bvlc_alexnet() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for npz in "$bn_tar"/*.npz + for npz in "$bn_tar/"*.npz do echo "converting $npz ..." python convert_data_npz.py "$npz" data_0 prob_1 -s python -m onnx2fluid.validation $validate_flags1 -t "$npz" python -m onnx2fluid.validation $validate_flags2 -t "$npz" done - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data_0 prob_1 @@ -54,7 +54,7 @@ bvlc_googlenet() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" data_0 prob_1 @@ -77,7 +77,7 @@ bvlc_reference_caffenet() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" data_0 prob_1 @@ -100,7 +100,7 @@ bvlc_reference_rcnn_ilsvrc13() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" data_0 fc-rcnn_1 @@ -123,14 +123,14 @@ densenet121() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for npz in "$bn_tar"/*.npz + for npz in "$bn_tar/"*.npz do echo "converting $npz ..." python convert_data_npz.py "$npz" data_0 fc6_1 -s python -m onnx2fluid.validation $validate_flags1 -t "$npz" python -m onnx2fluid.validation $validate_flags2 -t "$npz" done - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" data_0 fc6_1 @@ -153,7 +153,7 @@ emotion_ferplus() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" Input3 Plus692_Output_0 @@ -176,14 +176,14 @@ inception_v1() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for npz in "$bn_tar"/*.npz + for npz in "$bn_tar/"*.npz do echo "converting $npz ..." python convert_data_npz.py "$npz" data_0 prob_1 -s python -m onnx2fluid.validation $validate_flags1 -t "$npz" python -m onnx2fluid.validation $validate_flags2 -t "$npz" done - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data_0 prob_1 @@ -206,14 +206,14 @@ inception_v2() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for npz in "$bn_tar"/*.npz + for npz in "$bn_tar/"*.npz do echo "converting $npz ..." python convert_data_npz.py "$npz" data_0 prob_1 -s python -m onnx2fluid.validation $validate_flags1 -t "$npz" python -m onnx2fluid.validation $validate_flags2 -t "$npz" done - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data_0 prob_1 @@ -236,7 +236,7 @@ mobilenet() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data mobilenetv20_output_flatten0_reshape0 @@ -259,7 +259,7 @@ resnet18() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data resnetv15_dense0_fwd @@ -282,14 +282,14 @@ resnet50() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for npz in "$bn_tar"/*.npz + for npz in "$bn_tar/"*.npz do echo "converting $npz ..." python convert_data_npz.py "$npz" gpu_0/data_0 gpu_0/softmaxout_1 -s python -m onnx2fluid.validation $validate_flags1 -t "$npz" python -m onnx2fluid.validation $validate_flags2 -t "$npz" done - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" gpu_0/data_0 gpu_0/softmaxout_1 @@ -312,7 +312,7 @@ resnet100_arcface() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data fc1 @@ -335,7 +335,7 @@ resnet101_duc() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data seg_loss @@ -358,7 +358,7 @@ resnet152() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data resnetv27_dense0_fwd @@ -381,7 +381,7 @@ shufflenet() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" gpu_0/data_0 gpu_0/softmax_1 @@ -404,7 +404,7 @@ squeezenet() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" data_0 softmaxout_1 @@ -427,7 +427,7 @@ squeezenet1v1() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data squeezenet0_flatten0_reshape0 @@ -448,10 +448,10 @@ ssd() rm -rf "$bn_tar/" echo "extracting ..." mkdir "$bn_tar" - tar xf "$fn_tar" -C "$bn_tar"/ + tar xf "$fn_tar" -C "$bn_tar/" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" image bboxes,labels,scores @@ -474,7 +474,7 @@ tiny_yolov2() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" image grid @@ -497,7 +497,7 @@ vgg16bn() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -y - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" data vgg0_dense2_fwd @@ -520,7 +520,7 @@ vgg19() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" data_0 prob_1 @@ -543,7 +543,7 @@ yolov3() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" -x # - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir ..." python convert_data_pb.py "$pb_dir" input_1:01,image_shape:01 yolonms_layer_1/ExpandDims_1:0,yolonms_layer_1/ExpandDims_3:0,yolonms_layer_1/concat_2:0 @@ -566,7 +566,7 @@ zfnet512() tar xf "$fn_tar" python -m onnx2fluid $convert_flags "$fn_model" - for pb_dir in "$bn_tar"/*/ + for pb_dir in "$bn_tar/"*/ do echo "converting $pb_dir" python convert_data_pb.py "$pb_dir" gpu_0/data_0 gpu_0/softmax_1 diff --git a/onnx2fluid/onnx2fluid/conversion.py b/onnx2fluid/onnx2fluid/conversion.py index b566936430de98103ca4671f125e620fae489b5f..a7fee9cd45bf8c1220bdcb520623bd64a8d147bd 100644 --- a/onnx2fluid/onnx2fluid/conversion.py +++ b/onnx2fluid/onnx2fluid/conversion.py @@ -17,6 +17,22 @@ __all__ = [ DEFAULT_ONNX_OPSET_VERSION = 9 +def make_var_name(name): + """ + make a valid variable name in Python code and filename in filesystem + """ + + if name == '': + return '_' + if name[0].isdigit(): + return 'var_' + name + for s in ' \\|/:.-': + name = name.replace(s, '_') + if name.startswith('_'): + name = 'var' + name + return name + + def convert(onnx_model_filename, save_dir, model_basename='model.py', @@ -30,6 +46,12 @@ def convert(onnx_model_filename, convert an ONNX model to Paddle fluid Python code and desc pb """ + assert isinstance(onnx_model_filename, str) + assert isinstance(save_dir, str) + assert isinstance(model_basename, str) + assert isinstance(model_func_name, str) + assert onnx_opset_version is None or isinstance(onnx_opset_version, int) + import onnx from onnx.checker import ValidationError @@ -41,7 +63,6 @@ def convert(onnx_model_filename, from .onnx_utils import inferred_model_value_info from .onnx_utils import polish_model from .writer import Program, Writer - from .writer import make_var_name logger = logging.getLogger('convert') @@ -88,17 +109,21 @@ def convert(onnx_model_filename, fluid_writer = Writer() # model components - # graph_name = onnx_graph.name - graph_inputs = [value.name for value in onnx_graph.input] - graph_outputs = [value.name for value in onnx_graph.output] - graph_params = [] - graph_value_infos = inferred_model_value_info(onnx_model) + inp_vars = [make_var_name(value.name) for value in onnx_graph.input] + out_vars = [make_var_name(value.name) for value in onnx_graph.output] + par_vars = [] + value_infos = inferred_model_value_info(onnx_model) + value_infos = { + make_var_name(key): value + for key, value in value_infos.items() + } # prepare additional value_info # for weights for name, weight in graph_weights(onnx_graph): - value_info = graph_value_infos[name] - value_info['embeded_as'] = [] + var_name = make_var_name(name) + value_info = value_infos[var_name] + value_info['embedded_as'] = [] value_info['get_weight'] = (lambda w: lambda: w.tolist())( weight) # lazy getter @@ -108,19 +133,23 @@ def convert(onnx_model_filename, topo = 'forward' for name, domain, op_type, inputs, outputs, attrs in graph_ops(onnx_graph, topo=topo): - logger.debug('translating op %s %s::%s ...', name, domain, op_type) + op_name = make_var_name(name) + inputs = [make_var_name(val) for val in inputs] + outputs = [make_var_name(val) for val in outputs] + logger.debug('translating op %s(%s) %s::%s ...', name, op_name, domain, + op_type) if domain == DEFAULT_OP_DOMAIN: domain = '' try: fluid_writer.emit_op( fluid_program, - name, + op_name, domain, op_type, inputs, outputs, attrs, - graph_value_infos, + value_infos, embed_params=embed_params, ) except BaseException as e: @@ -133,17 +162,16 @@ def convert(onnx_model_filename, len(fluid_program.op_descs)) # type-shape info copy - for name, value_info in graph_value_infos.items(): - var_name = make_var_name(name) + for var_name, value_info in value_infos.items(): fluid_program.VarTypeShapeInfo(var_name, value_info, remove_batch=False) # - bad_var_names = [] + bad_vars = [] for var_name, var_desc in fluid_program.var_descs.items(): if not var_desc.type.lod_tensor.HasField('tensor'): - bad_var_names.append(var_name) - if len(bad_var_names) > 0: + bad_vars.append(var_name) + if len(bad_vars) > 0: logger.warning('type-shape not infered for var %s ...', - ', '.join(bad_var_names[:5])) + ', '.join(bad_vars[:5])) logger.warning('this causes little problem for PaddlePaddle, ' 'but Paddle Mobile may not infer correctly') logger.warning('please consider running validation with -i ' @@ -151,40 +179,41 @@ def convert(onnx_model_filename, # weight writer for name, weight in graph_weights(onnx_graph): - graph_params.append(name) - value_info = graph_value_infos[name] - var_names = value_info.get('embeded_as', []) - if var_names: - if len(var_names) > 1: + var_name = make_var_name(name) + par_vars.append(var_name) + value_info = value_infos[var_name] + embedded_names = value_info.get('embedded_as', []) + if embedded_names: + if len(embedded_names) > 1: logger.info( 'weight %s is shared between ops, more disk space will be consumed', name) logger.debug('saving weight %s(%s[%d], %dB) as %s ...', name, - weight.dtype, weight.size, weight.nbytes, var_names) - for var_name in var_names: # multiple references + weight.dtype, weight.size, weight.nbytes, + embedded_names) + for embedded_name in embedded_names: # multiple references fluid_writer.write_weight( - weight, shutil.os.path.join(save_dir, var_name)) + weight, shutil.os.path.join(save_dir, embedded_name)) else: logger.debug('saving weight %s(%s[%d], %dB) to %s ...', name, - weight.dtype, weight.size, weight.nbytes, - make_var_name(name)) - fluid_writer.write_weight( - weight, shutil.os.path.join(save_dir, make_var_name(name))) - fluid_writer.emit_param(fluid_program, name, value_info) + weight.dtype, weight.size, weight.nbytes, var_name) + fluid_writer.write_weight(weight, + shutil.os.path.join(save_dir, var_name)) + fluid_writer.emit_param(fluid_program, var_name, value_info) param_codes = fluid_program.codes fluid_program.codes = [] - logger.info('%d weights converted', len(graph_params)) + logger.info('%d weights converted', len(par_vars)) # input writer external_inputs = [] - for name in graph_inputs: - if name not in graph_params: - value_info = graph_value_infos[name] + for var_name in inp_vars: + if var_name not in par_vars: + value_info = value_infos[var_name] assert value_info['external'] - external_inputs.append(name) + external_inputs.append(var_name) fluid_writer.emit_inputs(fluid_program, external_inputs, - graph_value_infos, + value_infos, remove_batch=False) # TODO: input_codes = fluid_program.codes fluid_program.codes = [] @@ -192,11 +221,11 @@ def convert(onnx_model_filename, # output writer external_outputs = [] - for name in graph_outputs: - if name not in graph_params: - value_info = graph_value_infos[name] + for var_name in out_vars: + if var_name not in par_vars: + value_info = value_infos[var_name] assert value_info['external'] - external_outputs.append(name) + external_outputs.append(var_name) fluid_writer.emit_outputs(fluid_program, external_outputs) output_codes = [''] + fluid_program.codes # add an empty line fluid_program.codes = [] @@ -204,10 +233,18 @@ def convert(onnx_model_filename, # code generation header_codes = fluid_writer.header_code( - model_func_name, 'From: {}'.format(onnx_model_filename)) + model_func_name, + 'From: {}'.format(onnx_model_filename), + ) code_filename = shutil.os.path.join(save_dir, model_basename) - fluid_writer.write_code_file(code_filename, header_codes, input_codes, - param_codes, op_codes, output_codes) + fluid_writer.write_code_file( + code_filename, + header_codes, + input_codes, + param_codes, + op_codes, + output_codes, + ) logger.info('code saved to %s, factory function: %s', code_filename, model_func_name) diff --git a/onnx2fluid/onnx2fluid/onnx_utils.py b/onnx2fluid/onnx2fluid/onnx_utils.py index 1c86b4bbd7b08d58b17405c8d07ad8efcbdfb3f7..e8b5a472e312439f97606c02e5cc4f7f657ce4c2 100644 --- a/onnx2fluid/onnx2fluid/onnx_utils.py +++ b/onnx2fluid/onnx2fluid/onnx_utils.py @@ -87,6 +87,9 @@ def get_attribute_value2(attr): get_attribute_value enhanced """ + assert isinstance( + attr, onnx.AttributeProto), 'attr is not a AttributeProto instance' + if attr.type == onnx.AttributeProto.TENSOR: dtype = np.dtype(TENSOR_TYPE_TO_NP_TYPE[attr.t.data_type]) data = attr.t.raw_data @@ -106,6 +109,9 @@ def tensor_dtype(tensor): get ONNX tensor in np.dtype """ + assert isinstance( + tensor, onnx.ValueInfoProto), 'tensor is not a ValueInfoProto instance' + return TENSOR_TYPE_TO_NP_TYPE[tensor.type.tensor_type.elem_type] @@ -114,6 +120,9 @@ def tensor_shape(tensor): get ONNX tensor shape """ + assert isinstance( + tensor, onnx.ValueInfoProto), 'tensor is not a ValueInfoProto instance' + return tuple([dim.dim_value for dim in tensor.type.tensor_type.shape.dim]) @@ -122,6 +131,8 @@ def node_attrs(node): convert ONNX node attributes to dict """ + assert isinstance(node, onnx.NodeProto), 'node is not a NodeProto instance' + return {attr.name: get_attribute_value2(attr) for attr in node.attribute} # dict @@ -224,9 +235,8 @@ def graph_ops(graph, topo='default'): generator for ONNX node graph with given topology """ - if not isinstance(graph, onnx.GraphProto): - logger.error('graph is not a GraphProto instance') - return + assert isinstance(graph, + onnx.GraphProto), 'graph is not a GraphProto instance' return node_iter(graph.node, node_topo(graph.node, topo)) @@ -236,9 +246,8 @@ def graph_weights(graph): generator for weights of an ONNX model """ - if not isinstance(graph, onnx.GraphProto): - logger.error('graph is not a GraphProto instance') - return + assert isinstance(graph, + onnx.GraphProto), 'graph is not a GraphProto instance' for initializer in graph.initializer: name = initializer.name @@ -251,6 +260,9 @@ def inferred_model_value_info(model): collect value/type info for an ONNX model """ + assert isinstance(model, + onnx.ModelProto), 'model is not a ModelProto instance' + model = infer_shapes(model) graph = model.graph value_info = Dict() @@ -353,6 +365,10 @@ def optimize_model_skip_op_for_inference(model, op_list=None): """ skip ops can be bypassed for inference """ + + assert isinstance(model, + onnx.ModelProto), 'model is not a ModelProto instance' + if op_list is None: op_list = ('Dropout', 'Identity') @@ -415,6 +431,9 @@ def optimize_model_strip_initializer(model, keep_input_only=True): strip weights for inference """ + assert isinstance(model, + onnx.ModelProto), 'model is not a ModelProto instance' + nodes = model.graph.node input_refs, output_refs = build_value_refs(nodes) out_names = [val.name for val in model.graph.output] @@ -456,6 +475,9 @@ def optimize_model_cast(model): strip cascade and unecessary onnx::Cast-9: """ + assert isinstance(model, + onnx.ModelProto), 'model is not a ModelProto instance' + nodes = model.graph.node input_refs, output_refs = build_value_refs(nodes) value_info = inferred_model_value_info(model) @@ -513,6 +535,9 @@ def optimize_model_slice(model): strip cascade and unecessary onnx::Slice-1:9 """ + assert isinstance(model, + onnx.ModelProto), 'model is not a ModelProto instance' + nodes = model.graph.node input_refs, output_refs = build_value_refs(nodes) diff --git a/onnx2fluid/onnx2fluid/symbolic.py b/onnx2fluid/onnx2fluid/symbolic.py index a1cee35642c0ba108e60e097b940c68c773335f9..139d3bcef96159e275373def886ae7e4f37b3c7f 100644 --- a/onnx2fluid/onnx2fluid/symbolic.py +++ b/onnx2fluid/onnx2fluid/symbolic.py @@ -50,6 +50,9 @@ DEFAULT_OP_MAPPING = { dict(), None, None, False], ## unary ops ## 'Abs': ['abs', ['X'], ['Out']], + 'Acos': ['acos', ['X'], ['Out']], + 'Asin': ['asin', ['X'], ['Out']], + 'Atan': ['atan', ['X'], ['Out']], 'ArgMax': ['argmax', ['X'], ['Out'], dict(keepdims='')], 'ArgMin': ['argmin', ['X'], ['Out'], dict(keepdims='')], 'Ceil': ['ceil', ['X'], ['Out']], @@ -144,52 +147,36 @@ DEFAULT_IOA_CONSTRAINTS = { } -def _make_var_name(name): - """ - make a valid variable name in Python code and in filesystem - """ - - if name == '': - return '_' - if name[0].isdigit(): - return 'var_' + name - for s in ' \\|/:-': # - name = name.replace(s, '_') - if name.startswith('_'): - name = 'var' + name - return name +def _dtype(value_infos, name): + return _np.dtype(value_infos[name]['dtype']) -def _dtype(value_infos, val_name): - return _np.dtype(value_infos[val_name]['dtype']) - - -def _dtype_or_none(value_infos, val_name): - if val_name not in value_infos: +def _dtype_or_none(value_infos, name): + if name not in value_infos: return None - value_info = value_infos[val_name] + value_info = value_infos[name] if 'dtype' not in value_info: return None return _np.dtype(value_info['dtype']) -def _shape(value_infos, val_name): - return list(value_infos[val_name]['shape']) +def _shape(value_infos, name): + return list(value_infos[name]['shape']) -def _shape_or_none(value_infos, val_name): - if val_name not in value_infos: +def _shape_or_none(value_infos, name): + if name not in value_infos: return None - value_info = value_infos[val_name] + value_info = value_infos[name] if 'shape' not in value_info: return None return list(value_info['shape']) -def _const_weight_or_none(value_infos, val_name): - if val_name not in value_infos: +def _const_weight_or_none(value_infos, name): + if name not in value_infos: return None - value_info = value_infos[val_name] + value_info = value_infos[name] const_value = value_info.get('const_value', None) if const_value is not None: return const_value @@ -199,11 +186,11 @@ def _const_weight_or_none(value_infos, val_name): return None -def _check_embeddable(value_infos, *val_names): +def _check_embeddable(value_infos, *names): keyword = 'get_weight' - for val_name in val_names: - if keyword not in value_infos[val_name]: - _logger.warning('parameter %s not embeddable', val_name) + for name in names: + if keyword not in value_infos[name]: + _logger.warning('parameter %s not embeddable', name) return False return True @@ -239,12 +226,10 @@ def _default(prog, op_type, inputs, outputs, attrs, *args, name='', **kwargs): fluid_attrs = default_attrs.copy() fluid_attrs.update(mapped_attrs) # as new attrs - val_inps = inputs if input_perm is None else map(inputs.__getitem__, - input_perm) - val_outs = outputs if output_perm is None else map(outputs.__getitem__, - output_perm) - var_inps = [_make_var_name(val) for val in val_inps] - var_outs = [_make_var_name(val) for val in val_outs] + var_inps = inputs if input_perm is None else list( + map(inputs.__getitem__, input_perm)) + var_outs = outputs if output_perm is None else list( + map(outputs.__getitem__, output_perm)) arg_name = ', name={}'.format( repr(name)) if fill_name_field and name else '' arg_attrs = [ @@ -277,9 +262,7 @@ def _default(prog, op_type, inputs, outputs, attrs, *args, name='', **kwargs): def _assign(prog, mapping): fluid_op = 'assign' - for val_dst, val_src in mapping.items(): - var_dst = _make_var_name(val_dst) - var_src = _make_var_name(val_src) + for var_dst, var_src in mapping.items(): prog.Code('{} = {} # assign'.format(var_dst, var_src)) # prog.Code('{} = layers.{}({})' # .format(var_dst, @@ -295,18 +278,18 @@ def _assign(prog, mapping): ) -def _zeros_like(prog, val_ref, val_out, value_infos): +def _zeros_like(prog, var_ref, var_out, value_infos): prog.Op( '', 'Sub', - [val_ref, val_ref], - [val_out], # val + [var_ref, var_ref], + [var_out], {'axis': 0}, value_infos, ) -def _pad_if_asymmetric(prog, pads, val_name, value_infos): # pads: SSEE +def _pad_if_asymmetric(prog, pads, var_name, value_infos): # pads: SSEE assert len(pads) & 1 == 0 ndims = len(pads) // 2 symmetric = True @@ -315,36 +298,29 @@ def _pad_if_asymmetric(prog, pads, val_name, value_infos): # pads: SSEE symmetric = False break if symmetric: - return pads[:ndims], val_name + return pads[:ndims], var_name - val_padded = val_name + '_padded' # explicit variable + var_padded = var_name + '_padded' # explicit variable prog.Op( '', 'Pad', - [val_name], - [val_padded], # val + [var_name], + [var_padded], { 'mode': 'constant', 'value': 0., 'pads': pads, }, value_infos=value_infos, - name=val_padded, + name=var_padded, ) - return [0] * ndims, val_padded + return [0] * ndims, var_padded def _adaptive_pool(prog, pool_type, inputs, outputs, attrs, name=''): # I/O - val_x, = inputs - val_y, = outputs[:1] - var_x = _make_var_name(val_x) - var_y = _make_var_name(val_y) - - has_indices = len(outputs) > 1 - if has_indices: - val_indices = outputs[1] - var_indices = _make_var_name(val_indices) + var_x, = inputs + var_y, var_indices = (outputs + [None] * 1)[:2] # interpretation pool_size = attrs['output_size'] # required @@ -361,28 +337,28 @@ def _adaptive_pool(prog, pool_type, inputs, outputs, attrs, name=''): ', pool_type={}' '{})'.format( var_y, - ', {}'.format(var_indices) if has_indices else '', + ', {}'.format(var_indices) if var_indices else '', fluid_op, var_x, # attrs - has_indices, + bool(var_indices), pool_size, repr(pool_type), name_attr, )) fluid_op = 'pool{}d'.format(poolnd) prog.VarDesc(var_y) - if has_indices: + if var_indices: prog.VarDesc(var_indices) prog.OpDesc( fluid_op, ([var_x], 'X'), - ([var_y] + ([var_indices] if has_indices else []), 'Out', 'Indices'), + ([var_y] + ([var_indices] if var_indices else []), 'Out', 'Indices'), { 'global_pooling': False, 'adaptive': True, 'exclusive': True, - 'require_index': has_indices, + 'require_index': bool(var_indices), 'pooling_type': pool_type, 'ksize': pool_size, }, @@ -391,14 +367,12 @@ def _adaptive_pool(prog, pool_type, inputs, outputs, attrs, name=''): def _global_pool(prog, pool_type, inputs, outputs, attrs, value_infos, name=''): # I/O - val_x, = inputs - val_y, = outputs - var_x = _make_var_name(val_x) - var_y = _make_var_name(val_y) + var_x, = inputs + var_y, = outputs # interpretation - input_shape = _shape_or_none(value_infos, val_x) - output_shape = _shape_or_none(value_infos, val_y) + input_shape = _shape_or_none(value_infos, var_x) + output_shape = _shape_or_none(value_infos, var_y) assert input_shape is not None or output_shape is not None, 'poolnd not inferred' # NC... if input_shape is not None: poolnd = len(input_shape) - 2 # NC... @@ -436,14 +410,8 @@ def _global_pool(prog, pool_type, inputs, outputs, attrs, value_infos, name=''): def _pool(prog, pool_type, inputs, outputs, attrs, value_infos, name=''): # I/O - val_x, = inputs - val_y, = outputs[:1] - var_y = _make_var_name(val_y) - - has_indices = len(outputs) > 1 - if has_indices: - val_indices = outputs[1] - var_indices = _make_var_name(val_indices) + var_x, = inputs + var_y, var_indices = (outputs + [None] * 1)[:2] # interpretation assert attrs.get( @@ -457,8 +425,7 @@ def _pool(prog, pool_type, inputs, outputs, attrs, value_infos, name=''): strides = attrs.get('strides', [1] * poolnd) # optional ceil_mode = bool(attrs.get('ceil_mode', 0)) # optional pads = attrs.get('pads', [0] * (poolnd * 2)) # optional - paddings, val_x = _pad_if_asymmetric(prog, pads, val_x, value_infos) - var_x = _make_var_name(val_x) + paddings, var_x = _pad_if_asymmetric(prog, pads, var_x, value_infos) name_attr = ', name={}'.format(repr(name)) if name else '' # generation @@ -481,17 +448,17 @@ def _pool(prog, pool_type, inputs, outputs, attrs, value_infos, name=''): name_attr, )) prog.VarDesc(var_y) - if has_indices: + if var_indices: prog.VarDesc(var_indices) prog.OpDesc( fluid_op, ([var_x], 'X'), - ([var_y] + ([var_indices] if has_indices else []), 'Out', 'Indices'), + ([var_y] + ([var_indices] if var_indices else []), 'Out', 'Indices'), { 'global_pooling': False, 'adaptive': False, 'exclusive': True, - 'require_index': has_indices, + 'require_index': bool(var_indices), 'pooling_type': pool_type, 'ksize': pool_size, 'strides': strides, @@ -503,11 +470,8 @@ def _pool(prog, pool_type, inputs, outputs, attrs, value_infos, name=''): def _roi_pool(prog, fluid_op, inputs, outputs, attrs, value_infos, name): # I/O - val_x, val_rois = inputs - val_y, = outputs - var_x = _make_var_name(val_x) - var_rois = _make_var_name(val_rois) - var_y = _make_var_name(val_y) + var_x, var_rois = inputs + var_y, = outputs # interpretation spatial_scale = attrs['spatial_scale'] # required @@ -536,7 +500,7 @@ def _roi_pool(prog, fluid_op, inputs, outputs, attrs, value_infos, name): '{})'.format( var_y, fluid_op, - val_x, + var_x, var_rois, # attrs spatial_scale, @@ -546,7 +510,7 @@ def _roi_pool(prog, fluid_op, inputs, outputs, attrs, value_infos, name): )) prog.VarDesc(var_y) if is_max_pool: - var_argmax = _make_var_name(name + '.argmax') # hidden variable + var_argmax = name + '.argmax' # hidden variable prog.VarDesc(var_argmax) prog.OpDesc( fluid_op, @@ -558,19 +522,17 @@ def _roi_pool(prog, fluid_op, inputs, outputs, attrs, value_infos, name): def _interpolate(prog, inputs, outputs, attrs, value_infos, name=''): # I/O - val_x, val_scales = inputs - val_y, = outputs - var_x = _make_var_name(val_x) - var_y = _make_var_name(val_y) + var_x, var_scales = inputs + var_y, = outputs # interpretation # output shape - out_shape_ = _shape_or_none(value_infos, val_y) + out_shape_ = _shape_or_none(value_infos, var_y) if out_shape_ is not None: assert len(out_shape_) == 4, 'only 4-D Tensor as X and Y supported' out_shape_ = out_shape_[2:] # try scales - scales = _const_weight_or_none(value_infos, val_scales) + scales = _const_weight_or_none(value_infos, var_scales) if scales is not None: assert len(scales) == 4, 'only 4-D Tensor as X and Y supported' assert scales[0] == 1 and scales[ @@ -585,7 +547,7 @@ def _interpolate(prog, inputs, outputs, attrs, value_infos, name=''): else: out_shape = None if out_shape_ is None: - in_shape = _shape_or_none(value_infos, val_x) + in_shape = _shape_or_none(value_infos, var_x) assert in_shape is not None, 'out_shape required but not inferrable' assert len(in_shape) == 4, 'only 4-D Tensor as X and Y supported' out_shape_ = [in_shape[2] * scale, in_shape[3] * scale] @@ -642,10 +604,8 @@ def AffineGrid(prog, inputs, outputs, attrs, *args, name='', **kwargs): """ # I/O - val_theta, = inputs - val_grid, = outputs - var_theta = _make_var_name(val_theta) - var_grid = _make_var_name(val_grid) + var_theta, = inputs + var_grid, = outputs # interpretation fluid_op = 'affine_grid' @@ -701,10 +661,8 @@ def BatchNormalization(prog, """ # I/O - val_x, val_scale, val_b, val_mean, val_var = inputs - val_y, = outputs - var_x = _make_var_name(val_x) - var_y = _make_var_name(val_y) + var_x, var_scale, var_b, var_mean, var_var = inputs + var_y, = outputs var_saved_mean = name + '.saved_mean' # dummy output var_saved_variance = name + '.saved_variance' # dummy output @@ -714,28 +672,28 @@ def BatchNormalization(prog, epsilon = attrs.get('epsilon', 1e-5) # optional name_attr = ', name={}'.format(repr(name)) if name else '' if embed_params: - embed_params = _check_embeddable(value_infos, val_scale, val_b, - val_mean, val_var) + embed_params = _check_embeddable(value_infos, var_scale, var_b, + var_mean, var_var) if not embed_params and name: _logger.warning('for op %s(%s -> BatchNormalization -> %s)', name, inputs, outputs) _logger.warning('broken Python code will be generated') if embed_params: assert name != '' - var_scale = name + '.w_0' - var_b = name + '.b_0' - var_mean = name + '.w_1' - var_var = name + '.w_2' - value_infos[val_scale]['embeded_as'].append(var_scale) - value_infos[val_b]['embeded_as'].append(var_b) - value_infos[val_mean]['embeded_as'].append(var_mean) - value_infos[val_var]['embeded_as'].append(var_var) + embedded_scale = name + '.w_0' + embedded_b = name + '.b_0' + embedded_mean = name + '.w_1' + embedded_var = name + '.w_2' + value_infos[var_scale]['embedded_as'].append(embedded_scale) + value_infos[var_b]['embedded_as'].append(embedded_b) + value_infos[var_mean]['embedded_as'].append(embedded_mean) + value_infos[var_var]['embedded_as'].append(embedded_var) + var_scale = embedded_scale + var_b = embedded_b + var_mean = embedded_mean + var_var = embedded_var param_attr = '' else: - var_scale = _make_var_name(val_scale) - var_b = _make_var_name(val_b) - var_mean = _make_var_name(val_mean) - var_var = _make_var_name(val_var) param_attr = (', param_attr={}, bias_attr={}' ', moving_mean_name={}, moving_variance_name={}').format( repr(var_scale), repr(var_b), repr(var_mean), @@ -780,16 +738,14 @@ def Cast(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): """ # I/O - val_input, = inputs - val_output, = outputs - var_input = _make_var_name(val_input) - var_output = _make_var_name(val_output) + var_input, = inputs + var_output, = outputs # interpretation dtype = attrs['to'] # required if not isinstance(dtype, _np.dtype): # additional: possible np.dtype dtype = TENSOR_TYPE_TO_NP_TYPE[dtype] - output_dtype = _dtype_or_none(value_infos, val_output) + output_dtype = _dtype_or_none(value_infos, var_output) if output_dtype is not None: assert dtype == output_dtype, 'dtype of to unmatches output' @@ -812,7 +768,7 @@ def Cast(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): ([var_output], 'Out'), { 'in_dtype': prog.Dtype(_dtype(value_infos, - val_input)), # holy, required + var_input)), # holy, required 'out_dtype': prog.Dtype(dtype), }, ) @@ -824,9 +780,7 @@ def Concat(prog, inputs, outputs, attrs, *args, name='', **kwargs): """ # I/O - val_concat_result, = outputs - var_inps = [_make_var_name(val) for val in inputs] - var_concat_result = _make_var_name(val_concat_result) + var_ret, = outputs # interpretation fluid_op = 'concat' @@ -837,18 +791,18 @@ def Concat(prog, inputs, outputs, attrs, *args, name='', **kwargs): prog.Code('{} = layers.{}({}' ', axis={}' '{})'.format( - var_concat_result, + var_ret, fluid_op, - '[' + ', '.join(var_inps) + ']', + '[' + ', '.join(inputs) + ']', # attrs axis, name_attr, )) - prog.VarDesc(var_concat_result) + prog.VarDesc(var_ret) prog.OpDesc( fluid_op, - (var_inps, *(['X'] * len(var_inps))), - ([var_concat_result], 'Out'), + (inputs, *(['X'] * len(inputs))), + ([var_ret], 'Out'), {'axis': axis}, ) @@ -860,13 +814,12 @@ def Constant(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): # I/O assert len(inputs) == 0, 'constant op accept no inputs' - val_output, = outputs - var_output = _make_var_name(val_output) + var_output, = outputs # interpretation value = attrs['value'] # required dtype = _np.dtype(value.dtype) - output_dtype = _dtype_or_none(value_infos, val_output) + output_dtype = _dtype_or_none(value_infos, var_output) if output_dtype is not None: assert dtype == output_dtype, 'tensor dtype unmatches storage dtype' @@ -874,13 +827,13 @@ def Constant(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): # dtype = _np.dtype('float32') # HINT: force to float32 shape = attrs.get('shape', None) # if shape is None: - shape = _shape_or_none(value_infos, val_output) + shape = _shape_or_none(value_infos, var_output) if shape is None: shape = list(value.shape) _logger.warning( 'in op (Constant -> %s): ' 'attribute "shape" of %s not inferred, ' - 'using value as 1-D tensor may lead to fails', outputs, val_output) + 'using value as 1-D tensor may lead to fails', outputs, var_output) # generation value = value.tolist() @@ -911,7 +864,7 @@ def Constant(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): prog.Code('# {} = {} # passed directly as literal'.format( var_output, value)) - value_infos[val_output]['const_value'] = value + value_infos[var_output]['const_value'] = value def ConstantOfShape(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): @@ -920,13 +873,12 @@ def ConstantOfShape(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): """ # I/O - val_shape, = inputs - val_output, = outputs - var_shape = _make_var_name(val_shape) + var_shape, = inputs + var_output, = outputs - shape = _const_weight_or_none(value_infos, val_shape) + shape = _const_weight_or_none(value_infos, var_shape) if shape is None: - shape = _shape_or_none(value_infos, val_output) + shape = _shape_or_none(value_infos, var_output) assert shape is not None, ( 'given shape is neither const value nor deductible from output, ' 'this is not supported') @@ -959,53 +911,47 @@ def Conv(prog, """ # I/O - val_x, val_w = inputs[:2] - val_y, = outputs - var_y = _make_var_name(val_y) - - has_bias = len(inputs) == 3 - if has_bias: - val_b, = inputs[2:] + var_x, var_w = inputs[:2] + var_y, var_b = (outputs + [None] * 1)[:2] # interpretation assert attrs.get( 'auto_pad', 'NOTSET' ) == 'NOTSET', 'only auto_pad == NOTSET is supported' # optional - kernel_shape = _shape(value_infos, val_w)[2:] # OI... + kernel_shape = _shape(value_infos, var_w)[2:] # OI... assert kernel_shape == attrs[ 'kernel_shape'], 'kernel_shape in attr unmatches value_info' # HW convnd = len(kernel_shape) assert 2 <= convnd <= 3, 'only conv2d and conv3d is supported' - num_out_channels = _shape(value_infos, val_w)[0] # OI... + num_out_channels = _shape(value_infos, var_w)[0] # OI... fluid_op = 'conv{}d'.format(convnd) num_groups = attrs.get('group', 1) # optional strides = attrs.get('strides', [1] * convnd) # optional dilations = attrs.get('dilations', [1] * convnd) # optional pads = attrs.get('pads', [0] * (convnd * 2)) # optional - paddings, val_x = _pad_if_asymmetric(prog, pads, val_x, value_infos) - var_x = _make_var_name(val_x) + paddings, var_x = _pad_if_asymmetric(prog, pads, var_x, value_infos) name_attr = ', name={}'.format(repr(name)) if name else '' if embed_params: - embed_params = (_check_embeddable(value_infos, val_w) and not has_bias - or _check_embeddable(value_infos, val_b)) + embed_params = (_check_embeddable(value_infos, var_w) and not var_b + or _check_embeddable(value_infos, var_b)) if not embed_params and name: _logger.warning('for op %s(%s -> Conv -> %s)', name, inputs, outputs) _logger.warning('broken Python code will be generated') if embed_params: assert name != '' - var_w = name + '.w_0' - value_infos[val_w]['embeded_as'].append(var_w) - if has_bias: - var_b = name + '.b_0' - value_infos[val_b]['embeded_as'].append(var_b) + embedded_w = name + '.w_0' + value_infos[var_w]['embedded_as'].append(embedded_w) + var_w = embedded_w + if var_b: + embedded_b = name + '.b_0' + value_infos[var_b]['embedded_as'].append(embedded_b) + var_b = embedded_b param_attr = '' else: param_attr = ', bias_attr=False' else: - var_w = _make_var_name(val_w) - var_b = _make_var_name(val_b) if has_bias else False param_attr = ', param_attr={}, bias_attr={}'.format( repr(var_w), repr(var_b) if var_b else False) @@ -1036,7 +982,7 @@ def Conv(prog, prog.OpDesc( fluid_op, ([var_x, var_w], 'Input', 'Filter'), # , 'Bias', 'ResidualData' - ([var_conv if has_bias else var_y], 'Output'), + ([var_conv if var_b else var_y], 'Output'), { 'strides': strides, 'paddings': paddings, @@ -1044,13 +990,13 @@ def Conv(prog, 'groups': num_groups, }, ) - if has_bias: + if var_b: prog.VarDesc(var_conv) prog.IntermediateOp( '', 'Add', [var_conv, var_b], # - [val_y], + [var_y], {'axis': 1}, value_infos=value_infos, name=(name + '.bias'), @@ -1073,13 +1019,8 @@ def ConvTranspose(prog, """ # I/O - val_x, val_w = inputs[:2] - val_y, = outputs - var_y = _make_var_name(val_y) - - has_bias = len(inputs) == 3 - if has_bias: - val_b, = inputs[2:] + var_x, var_w = inputs[:2] + var_y, var_b = (outputs + [None] * 1)[:2] # interpretation assert attrs.get( @@ -1088,41 +1029,40 @@ def ConvTranspose(prog, assert sum(attrs.get( 'output_padding', [])) == 0, 'only zero output_padding is supported' # optional ? - kernel_shape = _shape(value_infos, val_w)[2:] # IO... + kernel_shape = _shape(value_infos, var_w)[2:] # IO... assert kernel_shape == attrs[ 'kernel_shape'], 'kernel_shape in attr unmatches value_info' # HW convnd = len(kernel_shape) assert 2 <= convnd <= 3, 'only conv2d_transpose and conv3d_transpose is supported' - num_out_channels = _shape(value_infos, val_w)[1] # IO... + num_out_channels = _shape(value_infos, var_w)[1] # IO... fluid_op = 'conv{}d_transpose'.format(convnd) num_groups = attrs.get('group', 1) # optional strides = attrs.get('strides', [1] * convnd) # optional dilations = attrs.get('dilations', [1] * convnd) # optional pads = attrs.get('pads', [0] * (convnd * 2)) # optional - paddings, val_x = _pad_if_asymmetric(prog, pads, val_x, value_infos) - var_x = _make_var_name(val_x) + paddings, var_x = _pad_if_asymmetric(prog, pads, var_x, value_infos) name_attr = ', name={}'.format(repr(name)) if name else '' if embed_params: - embed_params = (_check_embeddable(value_infos, val_w) and not has_bias - or _check_embeddable(value_infos, val_b)) + embed_params = (_check_embeddable(value_infos, var_w) and not var_b + or _check_embeddable(value_infos, var_b)) if not embed_params and name: _logger.warning('for op %s(%s -> ConvTranspose -> %s)', name, inputs, outputs) _logger.warning('broken Python code will be generated') if embed_params: assert name != '' - var_w = name + '.w_0' - value_infos[val_w]['embeded_as'].append(var_w) - if has_bias: - var_b = name + '.b_0' - value_infos[val_b]['embeded_as'].append(var_b) + embedded_w = name + '.w_0' + value_infos[var_w]['embedded_as'].append(embedded_w) + var_w = embedded_w + if var_b: + embedded_b = name + '.b_0' + value_infos[var_b]['embedded_as'].append(embedded_b) + var_b = embedded_b param_attr = '' else: param_attr = ', bias_attr=False' else: - var_w = _make_var_name(val_w) - var_b = _make_var_name(val_b) if has_bias else False param_attr = ', param_attr={}, bias_attr={}'.format( repr(var_w), repr(var_b) if var_b else False) @@ -1154,7 +1094,7 @@ def ConvTranspose(prog, prog.OpDesc( fluid_op, ([var_x, var_w], 'Input', 'Filter'), # , 'Bias', 'ResidualData' - ([var_conv if has_bias else var_y], 'Output'), + ([var_conv if var_b else var_y], 'Output'), { 'strides': strides, 'paddings': paddings, @@ -1163,13 +1103,13 @@ def ConvTranspose(prog, 'groups': num_groups, }, ) - if has_bias: + if var_b: prog.VarDesc(var_conv) prog.IntermediateOp( '', 'Add', [var_conv, var_b], # - [val_y], + [var_y], {'axis': 1}, value_infos=value_infos, name=(name + '.bias'), @@ -1184,27 +1124,27 @@ def Gemm(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): """ # due to fluid fc don't support transposed weight, we use matmul + ew_add - val_a, val_b, val_c = inputs - val_y, = outputs + var_a, var_b, var_c = inputs + var_y, = outputs alpha = attrs.get('alpha', 1.) # optional beta = attrs.get('beta', 1.) # optional trans_a = bool(attrs.get('transA', 0)) # optional trans_b = bool(attrs.get('transB', 0)) # optional - val_mm = name + '_mm' # explicit variable + var_mm = name + '_mm' # explicit variable prog.Op( '', 'MatMul', - [val_a, val_b], - [val_mm], # val + [var_a, var_b], + [var_mm], # val { 'transpose_x': trans_a, 'transpose_y': trans_b, 'alpha': alpha, }, value_infos=value_infos, - name=val_mm, + name=var_mm, ) prog.op_descs[-1].attrs.extend( prog.OpDescAttrs({ @@ -1216,17 +1156,17 @@ def Gemm(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): prog.Op( '', 'Add', - [val_mm, val_c], - [val_y], # val + [var_mm, var_c], + [var_y], # val {'axis': 1}, value_infos=value_infos, name=(name + '_beta'), ) else: - val_beta = name + '_beta' # explicit variable - val_vm = name + '_vm' # explicit variable + var_beta = name + '_beta' # explicit variable + var_vm = name + '_vm' # explicit variable if beta.is_integer(): - vm_dtype = _dtype_or_none(value_infos, val_c) + vm_dtype = _dtype_or_none(value_infos, var_c) if vm_dtype is None: vm_dtype = _np.dtype('float32') _logger.warning( @@ -1239,16 +1179,16 @@ def Gemm(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): '', 'Constant', [], - [val_beta], # val + [var_beta], # val {'value': beta}, value_infos=value_infos, - name=val_beta, + name=var_beta, ) prog.Op( '', 'Mul', - [val_c, val_beta], - [val_vm], # val + [var_c, var_beta], + [var_vm], # val dict(), value_infos=value_infos, name=(name + '_scale'), @@ -1256,8 +1196,8 @@ def Gemm(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): prog.Op( '', 'Add', - [val_mm, val_vm], - [val_y], # val + [var_mm, var_vm], + [var_y], # val {'axis': 1}, name=(name + '_bias'), ) @@ -1305,6 +1245,64 @@ def GlobalMaxPool(prog, name=name) +def GRU(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): + """ + onnx::GRU-7: + """ + + var_x, var_w, var_r, var_b, var_len, var_xh = (inputs + [None] * 3)[:6] + var_y, var_yh = (outputs + [None] * 2)[:2] + + # interpretation + fluid_op = 'gru_unit' + param_attr = '' + + # generation + prog.Code('{}, _, {} = layers.{}({}, {}, {}' + '{})'.format( + var_yh, + var_y, + fluid_op, + var_x, + var_xh, + 0, + param_attr, + )) + + +# raise NotImplementedError() + + +def LSTM(prog, inputs, outputs, attrs, value_infos, name='', *args, **kwargs): + """ + onnx::LSTM-7: + """ + + var_x, var_w, var_r, var_b, var_len, var_xh, var_xc, var_p = ( + inputs + [None] * 5)[:8] + var_y, var_yh, var_yc = (outputs + [None] * 3)[:3] + + # interpretation + fluid_op = 'lstm_unit' + param_attr = '' + + # generation + prog.Code('{}, {}, {} = layers.{}({}, {}, {}' + '{})'.format( + var_y, + var_yh, + var_yc, + fluid_op, + var_x, + var_xh, + var_xc, + param_attr, + )) + + +# raise NotImplementedError() + + def MaxPool(prog, inputs, outputs, attrs, value_infos, name='', *args, **kwargs): """ @@ -1329,17 +1327,15 @@ def Pad(prog, inputs, outputs, attrs, value_infos, name='', *args, **kwargs): """ # I/O - val_data, = inputs - val_output, = outputs - var_data = _make_var_name(val_data) - var_output = _make_var_name(val_output) + var_data, = inputs + var_output, = outputs # interpretation pads = attrs['pads'] # required mode = attrs.get('mode', 'constant') # optional value = attrs.get('value', 0.) # optional - data_shape = _shape_or_none(value_infos, val_data) - output_shape = _shape_or_none(value_infos, val_output) + data_shape = _shape_or_none(value_infos, var_data) + output_shape = _shape_or_none(value_infos, var_output) assume_pad2d = False if len(pads) == 4: assume_pad2d |= mode != 'constant' @@ -1400,14 +1396,12 @@ def PRelu(prog, """ # I/O - val_x, val_slope = inputs - val_y, = outputs - var_x = _make_var_name(val_x) - var_y = _make_var_name(val_y) + var_x, var_slope = inputs + var_y, = outputs # interpretation mode = 'channel' - slope_shape = _shape_or_none(value_infos, val_slope) + slope_shape = _shape_or_none(value_infos, var_slope) if slope_shape is not None: if len(slope_shape) == 0: mode = 'all' @@ -1418,18 +1412,18 @@ def PRelu(prog, fluid_op = 'prelu' name_attr = ', name={}'.format(repr(name)) if name else '' if embed_params: - embed_params = _check_embeddable(value_infos, val_slope) + embed_params = _check_embeddable(value_infos, var_slope) if not embed_params and name: _logger.warning('for op %s(%s -> PRelu -> %s)', name, inputs, outputs) _logger.warning('broken Python code will be generated') if embed_params: assert name != '' - var_slope = name + '.w_0' - value_infos[val_slope]['embeded_as'].append(var_slope) + embedded_slope = name + '.w_0' + value_infos[var_slope]['embedded_as'].append(embedded_slope) + var_slope = embedded_slope param_attr = '' else: - var_slope = _make_var_name(val_slope) param_attr = ', param_attr={}'.format(repr(var_slope)) # generation @@ -1467,17 +1461,14 @@ def Reshape(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): """ # I/O - val_data, val_shape = inputs - val_reshaped, = outputs - var_data = _make_var_name(val_data) - var_shape = _make_var_name(val_shape) - var_reshaped = _make_var_name(val_reshaped) + var_data, var_shape = inputs + var_reshaped, = outputs # interpretation - shape = _const_weight_or_none(value_infos, val_shape) - is_const_shape = shape and 'const_value' in value_infos[val_shape] + shape = _const_weight_or_none(value_infos, var_shape) + is_const_shape = shape and 'const_value' in value_infos[var_shape] if shape is None: - shape = _shape_or_none(value_infos, val_reshaped) + shape = _shape_or_none(value_infos, var_reshaped) # assert shape is not None, ('given shape is neither const value nor deductible from output, ' @@ -1493,8 +1484,7 @@ def Reshape(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): name_attr = ', name={}'.format(repr(name)) if name else '' # generation - val_shape_int32 = val_shape + '_int32' # explicit variable - var_shape_int32 = _make_var_name(val_shape_int32) + var_shape_int32 = var_shape + '_int32' # explicit variable prog.Code('# shape:{}={} # const as literal'.format(var_shape, shape)) if is_const_shape: prog.Code('{} = layers.{}({}' @@ -1511,8 +1501,8 @@ def Reshape(prog, inputs, outputs, attrs, value_infos, name, *args, **kwargs): prog.Op( '', 'Cast', - [val_shape], - [val_shape_int32], # var + [var_shape], + [var_shape_int32], # var {'to': _np.dtype('int32')}, # use np.dtype value_infos=value_infos, name=(name + '_cast'), @@ -1595,17 +1585,15 @@ def Slice(prog, inputs, outputs, attrs, value_infos, *args, **kwargs): """ # I/O - val_data, = inputs - val_output, = outputs - var_data = _make_var_name(val_data) - var_output = _make_var_name(val_output) + var_data, = inputs + var_output, = outputs # interpretation fluid_op = 'slice' axes = attrs['axes'] # required starts = attrs['starts'] # required ends = attrs['ends'] # required - shape = _shape_or_none(value_infos, val_data) + shape = _shape_or_none(value_infos, var_data) if shape is not None: # ndims = len(shape) # for idx, value in enumerate(axes): @@ -1654,9 +1642,7 @@ def Split(prog, inputs, outputs, attrs, *args, name='', **kwargs): """ # I/O - val_input, = inputs - var_outs = [_make_var_name(val) for val in outputs] - var_input = _make_var_name(val_input) + var_input, = inputs # interpretation fluid_op = 'split' @@ -1668,7 +1654,7 @@ def Split(prog, inputs, outputs, attrs, *args, name='', **kwargs): prog.Code('{} = layers.{}({}, {}' ', dim={}' '{})'.format( - ', '.join(var_outs), + ', '.join(outputs), fluid_op, var_input, split, @@ -1676,12 +1662,12 @@ def Split(prog, inputs, outputs, attrs, *args, name='', **kwargs): axis, name_attr, )) - for var_out in var_outs: + for var_out in outputs: prog.VarDesc(var_out) prog.OpDesc( fluid_op, (var_input, 'X'), - ([var_outs], *(['Out'] * len(var_outs))), + ([outputs], *(['Out'] * len(outputs))), { 'axis': axis, 'sections': split, @@ -1695,9 +1681,7 @@ def Sum(prog, inputs, outputs, *args, **kwargs): """ # I/O - val_sum, = outputs - var_inps = [_make_var_name(val) for val in inputs] - var_sum = _make_var_name(val_sum) + var_sum, = outputs # interpretation fluid_op = 'sums' @@ -1706,14 +1690,14 @@ def Sum(prog, inputs, outputs, *args, **kwargs): prog.Code('{} = layers.{}({})'.format( var_sum, fluid_op, - '[' + ', '.join(var_inps) + ']', + '[' + ', '.join(inputs) + ']', # attrs )) fluid_op = 'sum' prog.VarDesc(var_sum) prog.OpDesc( fluid_op, - (var_inps, *(['X'] * len(var_inps))), + (inputs, *(['X'] * len(inputs))), ([var_sum], 'Out'), dict(), ) @@ -1725,14 +1709,11 @@ def Tile(prog, inputs, outputs, attrs, value_infos, name='', *args, **kwargs): """ # I/O - val_input, val_repeats = inputs - val_output, = outputs - var_input = _make_var_name(val_input) - var_repeats = _make_var_name(val_repeats) - var_output = _make_var_name(val_output) + var_input, var_repeats = inputs + var_output, = outputs # interpretation - repeats = _const_weight_or_none(value_infos, val_repeats) + repeats = _const_weight_or_none(value_infos, var_repeats) assert repeats is not None, 'only const repeats is supported' fluid_op = 'expand' name_attr = ', name={}'.format(repr(name)) if name else '' @@ -1764,10 +1745,8 @@ def Transpose(prog, inputs, outputs, attrs, *args, name='', **kwargs): """ # I/O - val_data, = inputs - val_transposed, = outputs - var_data = _make_var_name(val_data) - var_transposed = _make_var_name(val_transposed) + var_data, = inputs + var_transposed, = outputs # interpretation fluid_op = 'transpose' diff --git a/onnx2fluid/onnx2fluid/torch_export_helper.py b/onnx2fluid/onnx2fluid/torch_export_helper.py index 39f034317c76b3372c3a969df33dc20fe264ddaf..dfb1ea0719a75657b5ac89343a6261d6e6b9e814 100644 --- a/onnx2fluid/onnx2fluid/torch_export_helper.py +++ b/onnx2fluid/onnx2fluid/torch_export_helper.py @@ -6,6 +6,9 @@ Created on Fri Mar 22 11:22:46 2019 @author: Macrobull """ +from __future__ import division + +import logging import numpy as np import torch @@ -24,6 +27,8 @@ from typing import ( Union, ) +logger = logging.getLogger(__name__) + __all__ = [ 'export_data', 'export_onnx_with_validation', @@ -76,7 +81,7 @@ def export_data(state_dict: Mapping[Text, Any], prefix: Text = '') -> None: return str(obj) prefix_ = prefix + ('_' if prefix else '') - fp = open('{}.txt'.format(prefix or 'meta'), 'w') + fp = open('{}.txt'.format(prefix or 'meta'), mode='w') for key, value in state_dict.items(): data = None if torch.is_tensor(value): @@ -93,7 +98,7 @@ def export_data(state_dict: Mapping[Text, Any], prefix: Text = '') -> None: def export_onnx_with_validation( - model: torch.nn.Module, + model: torch.nn.Module, # or JITScriptModule inputs: Sequence[Union[torch.Tensor, Sequence[object]]], export_basepath: Text, input_names: Optional[List[Text]] = None, diff --git a/onnx2fluid/onnx2fluid/validation.py b/onnx2fluid/onnx2fluid/validation.py index 22e78d9e604568931d9077802d37682ae4b7c6ae..4374c7eb448fddafbf1514657545c30acefbeaa5 100644 --- a/onnx2fluid/onnx2fluid/validation.py +++ b/onnx2fluid/onnx2fluid/validation.py @@ -43,7 +43,8 @@ def fluid_prog_shape_infer(prog): import paddle.fluid as fluid - assert isinstance(prog, fluid.framework.Program) + assert isinstance(prog, + fluid.framework.Program), 'prog is not a Program instance' logger.info('performing type-shape inference ...') for block in prog.blocks: @@ -84,6 +85,8 @@ def validate(fluid_model_filename, inference the converted Paddle fluid model, validate with given golden data """ + assert isinstance(fluid_model_filename, str) + import numpy as np import paddle.fluid as fluid @@ -153,7 +156,7 @@ def validate(fluid_model_filename, input_data = flatten_dict(input_data) output_data = flatten_dict(output_data) input_names = input_data.keys() - output_names = output_data.keys() + # output_names = output_data.keys() logger.info('with %d inputs and %d outputs', len(input_data), len(output_data)) else: diff --git a/onnx2fluid/onnx2fluid/writer.py b/onnx2fluid/onnx2fluid/writer.py index 70bd72765e067538230c5e5644d994af8d902099..9d8ba4b7990b2bf0e9357afd3a1583943ed64e6b 100644 --- a/onnx2fluid/onnx2fluid/writer.py +++ b/onnx2fluid/onnx2fluid/writer.py @@ -16,7 +16,6 @@ from collections import OrderedDict as Dict logger = logging.getLogger(__name__) from . import symbolic -from .symbolic import _make_var_name as make_var_name try: import paddle.fluid.proto.framework_pb2 as framework_pb2 @@ -63,7 +62,7 @@ def make_attr_name(name): assert name != '', 'name should not be empty' - for s in ' \\|/:-': # + for s in ' \\|/:.-': # name = name.replace(s, '_') if not name.startswith('_'): name = '_' + name @@ -207,7 +206,7 @@ class Program(object): return desc def VarDesc(self, - var_name, + name, persistable=False, value_info=None, remove_batch=None): @@ -215,18 +214,16 @@ class Program(object): add VarDesc, """ - assert var_name not in self.var_descs, 'var naming conflicted' + assert name not in self.var_descs, 'var naming conflicted' var_desc = framework_pb2.VarDesc() - var_desc.name = var_name + var_desc.name = name var_desc.persistable = persistable var_desc.type.type = framework_pb2.VarType.LOD_TENSOR - self.var_descs[var_name] = var_desc + self.var_descs[name] = var_desc if value_info: - self.VarTypeShapeInfo(var_name, - value_info, - remove_batch=remove_batch) + self.VarTypeShapeInfo(name, value_info, remove_batch=remove_batch) def Op(self, domain, op_type, *args, **kwargs): """ @@ -260,19 +257,19 @@ class Program(object): else: self.code_mutable = code_mutable - def VarTypeShapeInfo(self, var_name, value_info, remove_batch=None): + def VarTypeShapeInfo(self, name, value_info, remove_batch=None): """ set value_info for var """ - if var_name not in self.var_descs: + if name not in self.var_descs: return dtype = value_info.get('dtype', None) if dtype is None: return - var_desc = self.var_descs[var_name] + var_desc = self.var_descs[name] tensor_desc = var_desc.type.lod_tensor.tensor tensor_desc.data_type = self.Dtype(dtype) # required @@ -292,8 +289,7 @@ class Writer(object): fluid code and desc writter """ - # CODE_INDENT = ' ' * 4 - CODE_INDENT = '\t' + CODE_INDENT = ' ' * 4 # '\t' @staticmethod def header_code(func_name, info=''): @@ -313,6 +309,7 @@ class Writer(object): codes.append('from paddle.fluid import initializer, layers') codes.append('') codes.append('') + codes.append('def {}():'.format(func_name)) return codes @@ -342,24 +339,26 @@ class Writer(object): emit an ONNX weight into program """ - if value_info.get('embeded_as', []): - var_names = value_info['embeded_as'] - prog.Code('# parameter {} embeded as {}'.format(name, var_names)) - for var_name in var_names: - prog.VarDesc(var_name, persistable=True, value_info=value_info) + if value_info.get('embedded_as', []): + embedded_names = value_info['embedded_as'] + prog.Code('# parameter {} embedded as {}'.format( + name, embedded_names)) + for embedded_name in embedded_names: + prog.VarDesc(embedded_name, + persistable=True, + value_info=value_info) else: - var_name = make_var_name(name) attr_name = make_attr_name(name) - prog.Code('# parameter {}: {}'.format(name, var_name)) + prog.Code('# parameter {}'.format(name)) prog.Code('{} = ParamAttr(name={})' # , trainable=True - .format(attr_name, repr(var_name))) + .format(attr_name, repr(name))) prog.Code( '{} = layers.create_parameter(shape={}, dtype={}, name={}, attr={}' ', default_initializer=initializer.Constant(0))' #, is_bias={} - .format(var_name, value_info['shape'], + .format(name, value_info['shape'], repr(value_info['dtype'].name), repr(name), attr_name)) #, value_info.get('is_bias', False))) - prog.VarDesc(var_name, persistable=True, value_info=value_info) + prog.VarDesc(name, persistable=True, value_info=value_info) @staticmethod def emit_inputs(prog, names, value_infos, remove_batch=None): @@ -368,7 +367,6 @@ class Writer(object): """ for idx, name in enumerate(names): - var_name = make_var_name(name) value_info = value_infos[name] shape = value_info['shape'] if remove_batch is None: @@ -377,13 +375,13 @@ class Writer(object): if remove_batch: shape = shape[1:] - prog.Code('# input {}: {}'.format(name, var_name)) + prog.Code('# input {}'.format(name)) prog.Code(( '{} = layers.data(name={}, shape={}, dtype={}, ' 'append_batch_size={})' # , stop_gradient=True ).format( - var_name, - repr(var_name), + name, + repr(name), shape, repr(value_info['dtype'].name), remove_batch, @@ -391,12 +389,10 @@ class Writer(object): prog.OpDesc( 'feed', (['feed'], 'X'), - ([var_name], 'Out'), + ([name], 'Out'), {'col': idx}, ) - prog.VarDesc(var_name, - value_info=value_info, - remove_batch=remove_batch) + prog.VarDesc(name, value_info=value_info, remove_batch=remove_batch) @staticmethod def emit_outputs(prog, names): #, value_infos @@ -406,12 +402,11 @@ class Writer(object): code = 'return ' for idx, name in enumerate(names): - var_name = make_var_name(name) - code += var_name + ', ' + code += name + ', ' prog.OpDesc( 'fetch', - ([var_name], 'X'), + ([name], 'X'), (['fetch'], 'Out'), {'col': idx}, ) @@ -458,8 +453,7 @@ class Writer(object): for name, weight in weights.items(): assert isinstance(weights, dict), 'dict type weights required' - var_name = make_var_name(name) - filename = os.path.join(save_dir, var_name) + filename = os.path.join(save_dir, name) Writer.write_weight(weight, filename) logger.debug('saved weight %s to %s', name, filename) diff --git a/onnx2fluid/requirements.txt b/onnx2fluid/requirements.txt index 4d57359977be2d7106553ccd703132ef2691a3e4..9a67fa83216ba09789469855e2bcca199d8a617c 100644 --- a/onnx2fluid/requirements.txt +++ b/onnx2fluid/requirements.txt @@ -1,3 +1,3 @@ -e . onnx>=1.4 -paddlepaddle +paddlepaddle>=1.5