示例#1
0
def lightgbm_classifier_shape_extractor(operator):
    N = operator.inputs[0].type.shape[0]

    class_labels = operator.raw_operator.classes_
    if all(isinstance(i, numpy.ndarray) for i in class_labels):
        class_labels = numpy.concatenate(class_labels)
    if all(isinstance(i, str) for i in class_labels):
        operator.outputs[0].type = StringTensorType(shape=[N])
        operator.outputs[1].type = SequenceType(DictionaryType(StringTensorType([]), FloatTensorType([])), N)
    elif all(isinstance(i, (numbers.Real, bool, numpy.bool_)) for i in class_labels):
        operator.outputs[0].type = Int64TensorType(shape=[N])
        operator.outputs[1].type = SequenceType(DictionaryType(Int64TensorType([]), FloatTensorType([])), N)
    else:
        raise ValueError('Unsupported or mixed label types')
示例#2
0
 def test_model_dict_vectorizer_issue(self):
     key_value_map = [{1: 'A', 2: 'B'}, {1: 'C', 3: 'D'}, {1: 'C', 3: 'A'}]
     model = DictVectorizer(sparse=False).fit(key_value_map)
     with self.assertRaises(RuntimeError):
         convert_sklearn(model, 'dv',
                         [("input",
                           DictionaryType(Int64TensorType([1]),
                                          StringTensorType([1])))])
示例#3
0
 def test_model_dict_vectorizer(self):
     model = DictVectorizer()
     data = [{'amy': 1., 'chin': 200.}, {'nice': 3., 'amy': 1.}]
     model.fit_transform(data)
     model_onnx = convert_sklearn(model, 'dictionary vectorizer',
                                  [('input', DictionaryType(StringTensorType([1]), FloatTensorType([1])))])
     self.assertTrue(model_onnx is not None)
     dump_data_and_model(data, model, model_onnx, basename="SklearnDictVectorizer-OneOff-SkipDim1",
                         allow_failure="StrictVersion(onnxruntime.__version__) <= StrictVersion('0.1.3') or StrictVersion(onnx.__version__) < StrictVersion('1.3.0')")
示例#4
0
def _problem_for_feature_hasher(dtype=numpy.float32):
    """
    Returns a problem for the :epkg:`sklearn:feature_extraction:DictVectorizer`.
    """
    data = load_iris()
    # X = data.data
    y = data.target
    y2 = [{("cl%d" % _): dtype(1000 + i)} for i, _ in enumerate(y)]
    y2[0]["cl2"] = -2
    itt = [("X", DictionaryType(StringTensorType([1]), FloatTensorType([1])))]
    y2 = numpy.array(y2)
    return (y2, y, itt, 'transform', 0, y2)
示例#5
0
 def test_dict_vectorizer(self):
     model = DictVectorizer()
     data = [{"amy": 1.0, "chin": 200.0}, {"nice": 3.0, "amy": 1.0}]
     model.fit_transform(data)
     exp = model.transform(data)
     model_def = convert_sklearn(model, "dictionary vectorizer",
                                 [("input", DictionaryType(StringTensorType([1]), FloatTensorType([1])))])
     oinf = OnnxInference(model_def)
     array_data = numpy.array(data)
     got = oinf.run({'input': array_data})
     self.assertEqual(list(sorted(got)), ['variable'])
     self.assertEqualArray(exp.todense(), got['variable'].todense())
示例#6
0
def _problem_for_dict_vectorizer(dtype=numpy.float32, n_features=None):
    """
    Returns a problem for the :epkg:`sklearn:feature_extraction:DictVectorizer`.
    """
    data = load_iris()
    # X = data.data
    y = data.target
    y2 = [{_: dtype(1000 + i)} for i, _ in enumerate(y)]
    y2[0][2] = -2
    cltype = FloatTensorType if dtype == numpy.float32 else DoubleTensorType
    itt = [("X", DictionaryType(StringTensorType([1]), cltype([1])))]
    y2 = numpy.array(y2)
    y = y.astype(numpy.int64)
    return (y2, y, itt, 'transform', 0, y2)
 def test_model_dict_vectorizer(self):
     model = DictVectorizer()
     data = [{"amy": 1.0, "chin": 200.0}, {"nice": 3.0, "amy": 1.0}]
     model.fit_transform(data)
     model_onnx = convert_sklearn(
         model,
         "dictionary vectorizer", [(
             "input",
             DictionaryType(StringTensorType([1]), FloatTensorType([1])),
         )],
         target_opset=TARGET_OPSET)
     self.assertTrue(model_onnx is not None)
     dump_data_and_model(data,
                         model,
                         model_onnx,
                         basename="SklearnDictVectorizer-OneOff-SkipDim1")
 def test_model_dict_vectorizer_sort_false(self):
     model = DictVectorizer(sparse=False, sort=False)
     data = [{1: 1.0, 2: 200.0}, {1: 3.0, 3: 1.0}]
     model.fit_transform(data)
     model_onnx = convert_sklearn(
         model,
         "dictionary vectorizer", [(
             "input",
             DictionaryType(Int64TensorType([1]), FloatTensorType([1])),
         )],
         target_opset=TARGET_OPSET)
     self.assertTrue(model_onnx is not None)
     dump_data_and_model(
         data,
         model,
         model_onnx,
         basename="SklearnDictVectorizerSortFalse-OneOff-SkipDim1")
示例#9
0
 def test_model_dict_vectorizer(self):
     model = DictVectorizer()
     data = [{"amy": 1.0, "chin": 200.0}, {"nice": 3.0, "amy": 1.0}]
     model.fit_transform(data)
     model_onnx = convert_sklearn(model, "dictionary vectorizer", [(
         "input",
         DictionaryType(StringTensorType([1]), FloatTensorType([1])),
     )])
     self.assertTrue(model_onnx is not None)
     dump_data_and_model(
         data,
         model,
         model_onnx,
         basename="SklearnDictVectorizer-OneOff-SkipDim1",
         allow_failure="StrictVersion(onnxruntime.__version__)"
         " <= StrictVersion('0.1.3') or "
         "StrictVersion(onnx.__version__)"
         " < StrictVersion('1.3.0')")
 def test_model_dict_vectorizer_sort_false(self):
     model = DictVectorizer(sparse=False, sort=False)
     data = [{1: 1.0, 2: 200.0}, {1: 3.0, 3: 1.0}]
     model.fit_transform(data)
     model_onnx = convert_sklearn(
         model,
         "dictionary vectorizer",
         [(
             "input",
             DictionaryType(Int64TensorType([1]), FloatTensorType([1])),
         )],
     )
     self.assertTrue(model_onnx is not None)
     dump_data_and_model(
         data,
         model,
         model_onnx,
         basename="SklearnDictVectorizerSortFalse-OneOff-SkipDim1",
         allow_failure="StrictVersion(onnxruntime.__version__)"
         " <= StrictVersion('0.1.3') or "
         "StrictVersion(onnx.__version__)"
         " < StrictVersion('1.3.0')",
     )
示例#11
0
        def custom_parser(scope, model, inputs, custom_parsers=None):
            if custom_parsers is not None and model in custom_parsers:
                return custom_parsers[model](scope,
                                             model,
                                             inputs,
                                             custom_parsers=custom_parsers)
            if all(
                    isinstance(i, (numbers.Real, bool, np.bool_))
                    for i in model.classes_):
                label_type = Int64TensorType()
            else:
                label_type = StringTensorType()
            output_label = scope.declare_local_variable(
                'output_label', label_type)

            this_operator = scope.declare_local_operator(
                'LgbmClassifier', model)
            this_operator.inputs = inputs
            probability_map_variable = scope.declare_local_variable(
                'output_probability',
                SequenceType(DictionaryType(label_type, scope.tensor_type())))
            this_operator.outputs.append(output_label)
            this_operator.outputs.append(probability_map_variable)
            return this_operator.outputs
示例#12
0
print(r2_score(y_test, pred))

####################################
# Conversion to ONNX format
# +++++++++++++++++++++++++
#
# We use module
# `sklearn-onnx <https://github.com/onnx/sklearn-onnx>`_
# to convert the model into ONNX format.

from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import DictionaryType, FloatTensorType, Int64TensorType, SequenceType

# initial_type = [('float_input', DictionaryType(Int64TensorType([1]), FloatTensorType([])))]
initial_type = [("float_input",
                 DictionaryType(Int64TensorType([1]), FloatTensorType([])))]
onx = convert_sklearn(pipe, initial_types=initial_type)
with open("pipeline_vectorize.onnx", "wb") as f:
    f.write(onx.SerializeToString())

##################################
# We load the model with ONNX Runtime and look at
# its input and output.
import onnxruntime as rt
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidArgument

sess = rt.InferenceSession("pipeline_vectorize.onnx",
                           providers=rt.get_available_providers())

import numpy
示例#13
0
def get_defined_outputs(outputs,
                        onnx_node,
                        typed_inputs=None,
                        variables=None,
                        dtype=None,
                        schema=None,
                        schema_inputs=None):
    """
    Gets types of predefined outputs when they cannot be inferred.
    Some part of it should be automated based
    on type constraints.

    :param outputs: requested outputs
    :param onnx_node: :epkg:`ONNX` node definition
    :param typed_inputs: known typed inputs of the node as `tuple(name, type)`
    :param variables: registered variables created by previous operators
    :param dtype: float computational type
    :param schema: defined outputs by schema (*expected_outputs*)
    :param schema_inputs: defined inputs by schema (*expected_inputs*)
    :return: typed outputs as ``tuple(name, type)``
    """
    if schema is None:
        ft = DoubleTensorType if dtype == numpy.float64 else FloatTensorType
    elif len(schema) != 1:
        raise ValueError(  # pragma: no cover
            "schema should only contain one output not {}.".format(schema))
    else:
        if isinstance(schema, DataType):
            ft = schema[0].__class__
        else:
            ft = schema[0][1].__class__

    if onnx_node.op_type in {
            'ZipMap', 'ArgMin', 'ArgMax', 'Shape', 'Greater', 'Less', 'Equal',
            'TopK', 'Cast', 'ArrayFeatureExtractor', 'Reshape', 'Transpose',
            'Scan', 'ConstantOfShape'
    }:
        if onnx_node.op_type == "ZipMap":
            # ZipMap
            otype = SequenceType(DictionaryType(Int64Type(), ft()))
            outputs = [(name, otype) for name in outputs]
        elif (onnx_node.op_type in ("ArgMin", "ArgMax", 'Shape')
              and len(outputs) == 1):
            # ArgMin, ArgMax, Shape
            outputs = [(outputs[0], Int64TensorType())]
        elif (onnx_node.op_type in ("Greater", "Less", 'Equal')
              and len(outputs) == 1):
            # Greater, Less, Equal
            outputs = [(outputs[0], BooleanTensorType())]
        elif onnx_node.op_type == "TopK" and len(outputs) == 2:
            # TopK
            if len(typed_inputs) != 2:
                raise RuntimeError(  # pragma: no cover
                    "Wrong typed_inputs, got {}.".format(typed_inputs))
            outputs = [(outputs[0], typed_inputs[0][1]),
                       (outputs[1], Int64TensorType())]
        elif onnx_node.op_type == "Cast" and len(outputs) == 1:
            # Cast
            ttyp = _guess_type_proto(onnx_node.attribute[0].i, dims=None)
            outputs = [(outputs[0], ttyp)]
        elif onnx_node.op_type == "ArrayFeatureExtractor":
            # ArrayFeatureExtractor
            if len(typed_inputs) != 2:
                raise RuntimeError(  # pragma: no cover
                    "Wrong typed_inputs, got {}.".format(typed_inputs))
            outputs = [(outputs[0], typed_inputs[0][1])]
        elif onnx_node.op_type in ('Reshape', 'Transpose'):
            # Reshape
            outputs = [(outputs[0], typed_inputs[0][1].__class__())]
        elif onnx_node.op_type == 'Scan':
            # Scan
            if len(outputs) != len(typed_inputs):
                raise RuntimeError(  # pragma: no cover
                    "Dimension mismatch, operator Scan should have "
                    "the same number of inputs and outputs {} != {}"
                    ".".format(len(outputs), len(typed_inputs)))
            outputs = [(o, t[1].__class__())
                       for o, t in zip(outputs, typed_inputs)]
        elif onnx_node.op_type == "ConstantOfShape":
            # ConstantOfShape
            outputs = [(outputs[0], ft())]
    elif 'Classifier' in onnx_node.op_type:
        # Good chance that's a classifier.
        outputs = [(outputs[0], Int64TensorType()), (outputs[1], ft())]
    else:
        if schema_inputs is not None and schema is not None:
            dt = {}
            for got, exp in zip(typed_inputs, schema_inputs):
                if isinstance(exp[1], str):
                    dt[exp[1]] = got
            out = []
            for i in range(len(outputs)):  # pylint: disable=C0200
                o = outputs[i]
                if isinstance(o, str):
                    exp = schema[i]
                    if exp[1] in dt:
                        out.append((o, dt[exp[1]][1].__class__()))
                    else:
                        nt = _guess_type_proto_str(exp[1], None)
                        out.append((o, nt))
                elif (isinstance(o, tuple)
                      and (isinstance(o[1], str) or o[1] is None)):
                    exp = schema[i]
                    if exp[1] in dt:
                        out.append((o[0], dt[exp[1]][1].__class__()))
                    else:
                        nt = _guess_type_proto_str(exp[1], None)
                        out.append((o[0], nt))
                else:
                    out.append(o)
            outputs = out
        elif len(typed_inputs) == 1 and len(outputs) == 1:
            # Default case
            # Assuming the only output is the same as the only input.
            outputs = [(outputs[0], typed_inputs[0][1])]
        else:
            # Default
            outputs = [(name, ft()) for name in outputs]

    for name, typ in outputs:
        if typ in ('T', None, '', 'I'):
            raise NotImplementedError(  # pragma: no cover
                "Undefined output type: %r (outputs=%r, typed_inputs=%r, "
                "dtype=%r, schema=%r, schema_inputs=%r, onnx_node=%r, "
                "variables=%r)." % (typ, outputs, typed_inputs, dtype, schema,
                                    schema_inputs, onnx_node, variables))
        if not isinstance(name, str):
            raise NotImplementedError(  # pragma: no cover
                "Undefined output type: %r (outputs=%r, typed_inputs=%r, "
                "dtype=%r, schema=%r, schema_inputs=%r, onnx_node=%r, "
                "variables=%r)." % (typ, outputs, typed_inputs, dtype, schema,
                                    schema_inputs, onnx_node, variables))
    return outputs
示例#14
0
def proto2vars(values):
    """
    Converts proto values to Variables.
    """
    def ptype2vttype(it, shape):
        if it == TensorProto.FLOAT:  # pylint: disable=E1101
            return FloatTensorType(shape)
        if it == TensorProto.DOUBLE:  # pylint: disable=E1101
            return DoubleTensorType(shape)
        if it == TensorProto.INT64:  # pylint: disable=E1101
            return Int64TensorType(shape)
        if it == TensorProto.INT32:  # pylint: disable=E1101
            return Int32TensorType(shape)
        if it == TensorProto.BOOL:  # pylint: disable=E1101
            return BooleanTensorType(shape)
        if it == TensorProto.STRING:  # pylint: disable=E1101
            return StringTensorType(shape)
        if Float16TensorType is None:
            if it == TensorProto.FLOAT16:  # pylint: disable=E1101
                return Float16TensorType(shape)
        raise NotImplementedError(  # pragma: no cover
            "Unrecognized proto type {} with shape {}".format(it, shape))

    def ptype2vtype(it):
        if it == TensorProto.FLOAT:  # pylint: disable=E1101
            return FloatType()
        if it == TensorProto.INT64:  # pylint: disable=E1101
            return Int64Type()
        raise NotImplementedError(  # pragma: no cover
            "Unrecognized proto type {}".format(it))

    res = []
    for v_ in values:
        v = v_
        name = v.name if hasattr(v, 'name') else None
        if hasattr(v, 'type') and str(v.type) != '':
            t = v.type
            v = proto2vars([t])[0][1]
        elif hasattr(v, 'sequence_type') and str(v.sequence_type) != '':
            subtype = proto2vars([v.sequence_type.elem_type])[0][1]
            v = SequenceType(subtype)
        elif hasattr(v, 'tensor_type') and str(v.tensor_type) != '':
            tt = v.tensor_type
            el = tt.elem_type
            shape = tt.shape
            dim = shape.dim
            if len(dim) == 0:
                shape = []
            else:
                shape = [dim[i].dim_value for i in range(len(dim))]
            v = ptype2vttype(el, shape)
        elif hasattr(v, 'map_type') and str(v.map_type) != '':
            mt = v.map_type
            keyt = ptype2vtype(mt.key_type)
            valt = proto2vars([mt.value_type])[0][1]
            v = DictionaryType(keyt, valt)
        else:
            raise RuntimeError(  # pragma: no cover
                "Unable to build a variable from {}.".format(v))
        if v.shape is not None and 0 in v.shape:
            # Replaces 0 by None
            new_shape = tuple(None if d == 0 else d for d in v.shape)
            if new_shape in ((None, ), None):
                v = v.__class__()
            else:
                v = v.__class__(new_shape)
        if v.shape is not None and 0 in v.shape:
            raise RuntimeError(  # pragma: no cover
                "Shape cannot be empty: '{}': {}.".format(name, v_))
        res.append((name, v))
    return res
pred = pipe.predict(X_test_dict)
print(r2_score(y_test, pred))

####################################
# Conversion to ONNX format
# +++++++++++++++++++++++++
#
# We use module 
# `sklearn-onnx <https://github.com/onnx/sklearn-onnx>`_
# to convert the model into ONNX format.

from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType, Int64TensorType, DictionaryType, SequenceType

# initial_type = [('float_input', DictionaryType(Int64TensorType([1]), FloatTensorType([])))]
initial_type = [('float_input', DictionaryType(Int64TensorType([1]), FloatTensorType([])))]
onx = convert_sklearn(pipe, initial_types=initial_type)
with open("pipeline_vectorize.onnx", "wb") as f:
    f.write(onx.SerializeToString())

##################################
# We load the model with ONNX Runtime and look at
# its input and output.
import onnxruntime as rt
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidArgument

sess = rt.InferenceSession("pipeline_vectorize.onnx")

import numpy
inp, out = sess.get_inputs()[0], sess.get_outputs()[0]
print("input name='{}' and shape={} and type={}".format(inp.name, inp.shape, inp.type))
def get_defined_outputs(outputs, onnx_node, typed_inputs=None, variables=None, dtype=None):
    """
    Gets types of predefined outputs when they cannot be inferred.
    Some part of it should be automated based
    on type constraints.

    @param      outputs         requested outputs
    @param      onnx_node       :epkg:`ONNX` node definition
    @param      typed_inputs    known typed inputs of the node
                                as ``tuple(name, type)``
    @param      variables       registered variables created
                                by previous operators
    @param      dtype           float computational type
    @return                     typed outputs
                                as ``tuple(name, type)``
    """
    ft = DoubleTensorType if dtype == numpy.float64 else FloatTensorType

    # ZipMap
    if onnx_node.op_type == "ZipMap":
        otype = SequenceType(DictionaryType(
            Int64Type(), ft()))
        outputs = [(name, otype) for name in outputs]
    # ArgMin, ArgMax, Shape
    elif onnx_node.op_type in ("ArgMin", "ArgMax", 'Shape') and len(outputs) == 1:
        outputs = [(outputs[0], Int64TensorType())]
    # Greater, Less, Equal
    elif onnx_node.op_type in ("Greater", "Less", 'Equal') and len(outputs) == 1:
        outputs = [(outputs[0], BooleanTensorType())]
    # TopK
    elif onnx_node.op_type == "TopK" and len(outputs) == 2:
        if len(typed_inputs) != 2:
            raise RuntimeError(
                "Wrong typed_inputs, got {}.".format(typed_inputs))
        outputs = [(outputs[0], typed_inputs[0][1]),
                   (outputs[1], Int64TensorType())]
    # Cast
    elif onnx_node.op_type == "Cast" and len(outputs) == 1:
        ttyp = _guess_type_proto(onnx_node.attribute[0].i, dims=None)
        outputs = [(outputs[0], ttyp)]
    # ArrayFeatureExtractor
    elif onnx_node.op_type == "ArrayFeatureExtractor":
        if len(typed_inputs) != 2:
            raise RuntimeError(
                "Wrong typed_inputs, got {}.".format(typed_inputs))
        outputs = [(outputs[0], typed_inputs[0][1])]
    elif 'Classifier' in onnx_node.op_type:
        # Good chance that's a classifier.
        outputs = [(outputs[0], Int64TensorType()),
                   (outputs[1], ft())]
    # Reshape
    elif onnx_node.op_type in ('Reshape', 'Transpose'):
        outputs = [(outputs[0], typed_inputs[0][1].__class__())]
    # Scan
    elif onnx_node.op_type == 'Scan':
        if len(outputs) != len(typed_inputs):
            raise RuntimeError("Dimension mismatch, operator Scan should have "
                               "the same number of inputs and outputs {} != {}"
                               ".".format(len(outputs), len(typed_inputs)))
        outputs = [(o, t[1].__class__())
                   for o, t in zip(outputs, typed_inputs)]
    # ConstantOfShape
    elif onnx_node.op_type == "ConstantOfShape":
        outputs = [(outputs[0], ft())]

    # Default case
    # Assuming the only output is the same as the only input.
    elif len(typed_inputs) == 1 and len(outputs) == 1:
        outputs = [(outputs[0], typed_inputs[0][1])]
    # Default
    else:
        outputs = [(name, ft()) for name in outputs]
    return outputs
示例#17
0
    def _init(self, variables=None):
        """
        Initializes the node.

        :param variables: registered variables created by previous operators

        The current implementation for operator *Scan*
        only works for matrices.
        """
        custom_nodes = self.options.get('nodes', None)
        if (custom_nodes is not None and
                self.onnx_node.op_type in custom_nodes):
            self.alg_class = custom_nodes[self.onnx_node.op_type]
        else:
            try:
                self.alg_class = getattr(alg2, 'Onnx' + self.onnx_node.op_type)
            except AttributeError:
                try:
                    self.alg_class = getattr(
                        alg, 'Onnx' + self.onnx_node.op_type)
                except AttributeError:
                    self.alg_class = getattr(
                        alg3, 'Onnx' + self.onnx_node.op_type)

        inputs = list(self.onnx_node.input)
        self.mapping, self.inputs = self._name_mapping(inputs)
        self.outputs = list(self.onnx_node.output)

        options = self.options.copy()
        options.pop('nodes', None)
        target_opset = options.pop('target_opset', None)
        domain = options.pop('domain', None)
        disable_optimisation = options.pop('disable_optimisation', False)
        session_options = options.pop('session_options', False)
        ir_version = options.pop('ir_version', None)

        if domain == '' and target_opset < 9:
            # target_opset should be >= 9 not {} for main domain.
            # We assume it was the case when the graph was created.
            pass

        if self.onnx_node.op_type == 'ZipMap':
            self.inst_ = self.alg_class(*self.inputs, output_names=self.outputs,
                                        op_version=target_opset, **options)
            inputs = get_defined_inputs(
                self.inputs, variables, dtype=self.dtype)
            name = (self.outputs[0] if len(self.outputs) == 1
                    else self.inst_.expected_outputs[0][0])
            otype = (Int64TensorType if 'classlabels_int64s' in options
                     else StringTensorType)
            outvar = [(name, DictionaryType(otype([1]), FloatTensorType([1])))]
            self.onnx_ = self.inst_.to_onnx(inputs, outputs=outvar)
            forced = True
        elif self.onnx_node.op_type == 'ConstantOfShape':
            for k in options:
                v = options[k]
                if isinstance(v, numpy.ndarray):
                    options[k] = make_tensor(
                        k, self._guess_proto_type(v.dtype),
                        v.shape, v.tolist())

            self.inst_ = self.alg_class(*self.inputs, output_names=self.outputs,
                                        op_version=target_opset, **options)
            inputs = get_defined_inputs(
                self.inputs, variables, dtype=self.dtype)
            try:
                self.onnx_ = self.inst_.to_onnx(inputs, target_opset=target_opset,
                                                domain=domain)
                if "dim_value: 0" in str(self.onnx_):
                    raise RuntimeError(  # pragma: no cover
                        "Probable issue as one dimension is null.\n--\n{}".format(
                            self.onnx_))
            except AttributeError as e:  # pragma: no cover
                # older version of skl2onnx
                self.onnx_ = self.inst_.to_onnx(inputs)
                if "dim_value: 0" in str(self.onnx_):
                    raise RuntimeError(
                        "Probable issue as one dimension is null.\n--\n{}".format(
                            self.onnx_)) from e
            forced = False
        elif self.onnx_node.op_type == 'Scan':
            self.inst_ = self.alg_class(
                *self.inputs, output_names=self.outputs,
                op_version=target_opset, **options)
            inputs = get_defined_inputs(
                self.inputs, variables, dtype=self.dtype)
            outputs = get_defined_outputs(
                self.outputs, self.onnx_node, inputs, variables,
                dtype=self.dtype)
            inputs = [(name, cl.__class__([None, None]))
                      for (name, cl) in inputs]
            outputs = [(name, cl.__class__([None, None]))
                       for (name, cl) in outputs]
            self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs,
                                            target_opset=target_opset,
                                            domain=domain)
            if "dim_value: 0" in str(self.onnx_):
                raise RuntimeError(  # pragma: no cover
                    "Probable issue as one dimension is null.\n--\n{}".format(
                        self.onnx_))
            forced = True
        else:
            self.inst_ = self.alg_class(*self.inputs, output_names=self.outputs,
                                        op_version=target_opset, domain=domain,
                                        **options)
            inputs = get_defined_inputs(
                self.inputs, variables, dtype=self.dtype,
                schema=self.alg_class.expected_inputs)

            try:
                self.onnx_ = self.inst_.to_onnx(
                    inputs, target_opset=target_opset, domain=domain)
                if "dim_value: 0" in str(self.onnx_):
                    raise RuntimeError(  # pragma: no cover
                        "Probable issue as one dimension is null.\n--\n{}\n---\n{}".format(
                            self.onnx_, inputs))
                forced = False
            except (RuntimeError, ValueError, InferenceError) as eo:
                # Let's try again by forcing output types.
                forced = True
                outputs = get_defined_outputs(
                    self.outputs, self.onnx_node, inputs, variables,
                    dtype=self.dtype, schema=self.alg_class.expected_outputs,
                    schema_inputs=self.alg_class.expected_inputs)
                try:
                    self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs,
                                                    target_opset=target_opset,
                                                    domain=domain)
                except NotImplementedError as e:  # pragma: no cover
                    raise NotImplementedError(
                        "Unable to instantiate node {} inputs={} "
                        "self.inputs={} outputs={} variables={} "
                        "dtype={} e={} eo={}".format(
                            self.alg_class, inputs, self.inputs,
                            outputs, variables, self.dtype, e, eo)) from e
                if "dim_value: 0" in str(self.onnx_):
                    raise RuntimeError(  # pragma: no cover
                        "Probable issue as one dimension is null.\n--\n{}".format(
                            self.onnx_)) from e

        if len(self.onnx_.graph.output) != len(self.outputs):  # pragma: no cover
            # Something is wrong, falls back to default plan.
            forced = True
            outputs = get_defined_outputs(
                self.outputs, self.onnx_node, inputs, variables,
                dtype=self.dtype, schema=self.alg_class.expected_outputs)
            self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs,
                                            target_opset=target_opset,
                                            domain=domain)
            if "dim_value: 0" in str(self.onnx_):
                raise RuntimeError(  # pragma: no cover
                    "Probable issue as one dimension is null.\n--\n{}".format(
                        self.onnx_))
        else:
            lo = list(self.onnx_.graph.output)
            outputs = proto2vars(lo)

        sess_options = session_options or SessionOptions()
        self.run_options = RunOptions()

        if session_options is None:
            try:
                sess_options.session_log_severity_level = 3
                # sess_options.sessions_log_verbosity_level = 0
            except AttributeError:  # pragma: no cover
                # onnxruntime not recent enough.
                pass
            try:
                self.run_options.run_log_severity_level = 3
                # self.run_options.run_log_verbosity_level = 0
            except AttributeError:  # pragma: no cover
                # onnxruntime not recent enough.
                pass
            if disable_optimisation:
                sess_options.graph_optimization_level = (  # pragma: no cover
                    GraphOptimizationLevel.ORT_DISABLE_ALL)
        elif disable_optimisation:
            raise RuntimeError(  # pragma: no cover
                "session_options and disable_optimisation cannot be defined "
                "at the same time.")

        if ir_version is not None:
            self.onnx_.ir_version = ir_version
        try:
            self.sess_ = InferenceSession(
                self.onnx_.SerializeToString(), sess_options=sess_options)
        except (RuntimeError, OrtNotImplemented, OrtInvalidGraph, OrtFail) as e:
            raise RuntimeError(
                "Unable to load node '{}' (output type was {}) inputs={} "
                "self.inputs={} self.onnx_node.input={} "
                "variables={} mapping={} "
                "expected_inputs={}\n{}".format(
                    self.onnx_node.op_type,
                    "guessed" if forced else "inferred",
                    inputs, self.inputs, self.onnx_node.input,
                    variables, self.mapping,
                    self.alg_class.expected_inputs,
                    self.onnx_)) from e
        self.typed_outputs_ = outputs