sklearn-onnx icon indicating copy to clipboard operation
sklearn-onnx copied to clipboard

Custom converter has inputs and outputs have variables in common.

Open busFred opened this issue 3 years ago • 6 comments

I tried to get around the issue outlined in #710 with the following solution.

def rvc_converter(scope: Scope, operator: Operator,
                  container: ModelComponentContainer):
    from .rvc import RVC
    rvc: RVC = operator.raw_operator
    input: Variable = operator.inputs[0]
    op_outputs: List[Variable] = operator.outputs
    op_version: Union[int, None] = container.target_opset
    y_list: List[OnnxOperator] = [
        OnnxReshape(OnnxSubEstimator(bsvc, input, op_version=op_version),
                    np.array([-1, 1], dtype=np.int64),
                    op_version=op_version) for bsvc in rvc.binary_rvc_list_
    ]
    y_matrix: OnnxOperator = OnnxConcat(*y_list, axis=1, op_version=op_version)
    # y_matrix: OnnxOperator = OnnxConcatFromSequence(*y_list,
    #                                                 axis=1,
    #                                                 new_axis=1,
    #                                                 op_version=op_version)
    probs: OnnxOperator = OnnxSoftmax(y_matrix,
                                      axis=1,
                                      op_version=op_version,
                                      output_names=[op_outputs[1]])
    probs.add_to(scope=scope, container=container)
    labels: OnnxOperator = OnnxArgMax(probs,
                                      axis=1,
                                      keepdims=0,
                                      op_version=op_version,
                                      output_names=[op_outputs[0]])
    labels.add_to(scope=scope, container=container)

However, I get the following error:

RuntimeError: inputs and outputs cannot have variables in common {'y'} in node 'Identity' with name 'SubOpId'.
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
~/Documents/research/sklearn_plugins/test/sklearn_plugins/rvm/test_rvc_export.py in 
      34 """
      35 onx: ModelProto
----> 36 onx = to_onnx(rvc, X_train[:1, :].astype(np.float64), target_opset=13)

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/convert.py in to_onnx(model, X, name, initial_types, target_opset, options, white_op, black_op, final_types, dtype, verbose)
    214         name = "ONNX(%s)" % model.__class__.__name__
    215     initial_types = guess_initial_types(X, initial_types)
--> 216     return convert_sklearn(model, initial_types=initial_types,
    217                            target_opset=target_opset,
    218                            name=name, options=options,

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/convert.py in convert_sklearn(model, name, initial_types, doc_string, target_opset, custom_conversion_functions, custom_shape_calculators, custom_parsers, options, intermediate, white_op, black_op, final_types, dtype, verbose)
    160     if verbose >= 1:
    161         print("[convert_sklearn] convert_topology")
--> 162     onnx_model = convert_topology(topology, name, doc_string, target_opset,
    163                                   options=options,
    164                                   remove_identity=not intermediate,

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/common/_topology.py in convert_topology(topology, model_name, doc_string, target_opset, channel_first_inputs, options, remove_identity, verbose)
   1200                               type(getattr(operator, 'raw_model', None))))
   1201         container.validate_options(operator)
-> 1202         conv(scope, operator, container)
   1203 
   1204     container.ensure_topological_order()

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/common/_registration.py in __call__(self, *args)
     24             if args[1].raw_operator is not None:
     25                 args[2]._get_allowed_options(args[1].raw_operator)
---> 26         return self._fct(*args)
     27 
     28     def get_allowed_options(self):

~/Documents/research/sklearn_plugins/src/sklearn_plugins/rvm/_onnx_transfrom.py in rvc_converter(scope, operator, container)
    125                                       op_version=op_version,
    126                                       output_names=[op_outputs[1]])
--> 127     probs.add_to(scope=scope, container=container)
    128     labels: OnnxOperator = OnnxArgMax(probs,
    129                                       axis=1,

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/onnx_operator.py in add_to(self, scope, container, operator, run_converters)
    547                 output_range=self.output_range,
    548                 operator=operator, run_converters=run_converters, **kwargs)
--> 549             self.state.run()
    550         self._verify_add_to_()
    551 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in run(self)
    412             inputs = []
    413             for i in self.inputs:
--> 414                 v = self._get_var_name(i, False, index=None)
    415                 inputs.extend(v)
    416 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in _get_var_name(self, var, in_out, operator, index)
    122         "input: True for output, False for input"
    123         if hasattr(var, 'add_to'):
--> 124             var.add_to(self.scope, self.container, operator=operator,
    125                        run_converters=self.run_converters)
    126             outputs = var.outputs

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/onnx_operator.py in add_to(self, scope, container, operator, run_converters)
    547                 output_range=self.output_range,
    548                 operator=operator, run_converters=run_converters, **kwargs)
--> 549             self.state.run()
    550         self._verify_add_to_()
    551 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in run(self)
    412             inputs = []
    413             for i in self.inputs:
--> 414                 v = self._get_var_name(i, False, index=None)
    415                 inputs.extend(v)
    416 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in _get_var_name(self, var, in_out, operator, index)
    122         "input: True for output, False for input"
    123         if hasattr(var, 'add_to'):
--> 124             var.add_to(self.scope, self.container, operator=operator,
    125                        run_converters=self.run_converters)
    126             outputs = var.outputs

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/onnx_operator.py in add_to(self, scope, container, operator, run_converters)
    547                 output_range=self.output_range,
    548                 operator=operator, run_converters=run_converters, **kwargs)
--> 549             self.state.run()
    550         self._verify_add_to_()
    551 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in run(self)
    412             inputs = []
    413             for i in self.inputs:
--> 414                 v = self._get_var_name(i, False, index=None)
    415                 inputs.extend(v)
    416 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in _get_var_name(self, var, in_out, operator, index)
    122         "input: True for output, False for input"
    123         if hasattr(var, 'add_to'):
--> 124             var.add_to(self.scope, self.container, operator=operator,
    125                        run_converters=self.run_converters)
    126             outputs = var.outputs

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/onnx_operator.py in add_to(self, scope, container, operator, run_converters)
    949                 op_domain=None, onnx_prefix_name=self.onnx_prefix,
    950                 options=self.options, run_converters=run_converters, **kwargs)
--> 951             self.state.run()
    952 
    953 

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/algebra/graph_state.py in run(self)
    477                 for i, out in enumerate(sub_op.outputs):
    478                     var = outputs[i]
--> 479                     self.container.add_node(
    480                         'Identity', [out.onnx_name], [var[0]],
    481                         name=self.scope.get_unique_operator_name("SubOpId"))

~/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx/common/_container.py in add_node(self, op_type, inputs, outputs, op_domain, op_version, name, **attrs)
    512                 "" % (inputs, outputs, op_type)) from e
    513         if common:
--> 514             raise RuntimeError(
    515                 "inputs and outputs cannot have "
    516                 "variables in common {} in node '{}' "

RuntimeError: inputs and outputs cannot have variables in common {'y'} in node 'Identity' with name 'SubOpId'.

I believe it is the below 2 lines that cause the problem as each _BinaryRVC produces an output variable y. It is necessary for me to combine multiple _BinaryRVC instances to produce the output of RVC classifier.

y_list: List[OnnxOperator] = [
    OnnxReshape(OnnxSubEstimator(bsvc, input, op_version=op_version),
                np.array([-1, 1], dtype=np.int64),
                op_version=op_version) for bsvc in rvc.binary_rvc_list_
]
y_matrix: OnnxOperator = OnnxConcat(*y_list, axis=1, op_version=op_version)

I dig through the documentation, but I couldn't find a way to get around this issue. Could anyone help me with this?

busFred avatar Aug 21 '21 19:08 busFred

This is a bug. I'll propose a fix soon.

xadupre avatar Aug 24 '21 12:08 xadupre

I just merged the PR (#714) which should fix that issue. Would it be possible to check if it is working with development version?

xadupre avatar Aug 27 '21 11:08 xadupre

I still encountered the same error even after using the 1.9.3.dev. The RVC uses multiple RVRs.

The following is the shape calculator for RVR. I specified the output of the RVR to have name "y" on the last line of the function.

def rvr_shape_calculator(operator: Operator):
    check_input_and_output_types(
        operator,
        good_input_types=[FloatTensorType, DoubleTensorType],
        good_output_types=[FloatTensorType, DoubleTensorType])
    op_inputs: List[Variable] = operator.inputs
    if len(op_inputs) != 1:
        raise RuntimeError("Only one input matrix is allowed for RVR.")
    op_outputs: List[Variable] = operator.outputs
    if len(op_outputs) != 1:
        raise RuntimeError("Only one output is allowed for RVR.")
    # retrieve rvr inputs dtype
    input_var_type: DataType = op_inputs[0].type
    # confirm rvr input and output shape
    n_samples: int = input_var_type.shape[0]
    op_outputs[0].type.shape = [n_samples]
    op_outputs[0].set_onnx_name("y")

The code for the converter of RVC looks like the following.

def rvc_converter(scope: Scope, operator: Operator,
                  container: ModelComponentContainer):
    from .rvc import RVC
    rvc: RVC = operator.raw_operator
    input: Variable = operator.inputs[0]
    op_outputs: List[Variable] = operator.outputs
    op_version: Union[int, None] = container.target_opset
    y_list: List[OnnxOperator] = [
        OnnxReshape(OnnxSubEstimator(bsvc, input, op_version=op_version),
                    np.array([-1, 1], dtype=np.int64),
                    op_version=op_version) for bsvc in rvc.binary_rvc_list_
    ]
    y_matrix: OnnxOperator = OnnxConcat(*y_list, axis=1, op_version=op_version)
    probs: OnnxOperator = OnnxSoftmax(y_matrix,
                                      axis=1,
                                      op_version=op_version,
                                      output_names=[op_outputs[1]])
    probs.add_to(scope=scope, container=container)
    labels: OnnxOperator = OnnxArgMax(probs,
                                      axis=1,
                                      keepdims=0,
                                      op_version=op_version,
                                      output_names=[op_outputs[0]])
    labels.add_to(scope=scope, container=container)

As it still complains about variable name "y" is duplicating, I simply remove the op_outputs[0].set_onnx_name("y") from rvr_shape_calculator. However, I still want to let the final onnx model have more descriptive output names instead of its default output name or even unnamed.

Would you be able to suggest me the proper way of setting the variable name?

busFred avatar Aug 29 '21 14:08 busFred

Sorry for the delay. You may try the latest version of the package, I fix a couple of bugs in the way the library is converting pipeline. You should use set_onnx_name_prefix to more descriptive names.

xadupre avatar Sep 24 '21 13:09 xadupre

There were two release since this issue was raised. Did it solve your issue?

xadupre avatar Nov 26 '21 10:11 xadupre

Thanks for the followup.

The semester began late August, and since then I am just so busy on class work. I haven't had time to work on this particular project. But I might have time after the semester is over, which is around 2 weeks from now.

busFred avatar Nov 26 '21 15:11 busFred