.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "gyexamples/plot_mcustom_parser.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_gyexamples_plot_mcustom_parser.py: .. _l-custom-parser: Change the number of outputs by adding a parser =============================================== .. index:: parser By default, :epkg:`sklearn-onnx` assumes that a classifier has two outputs (label and probabilities), a regressor has one output (prediction), a transform has one output (the transformed data). What if it is not the case? The following example creates a custom converter and a custom parser which defines the number of outputs expected by the converted model. Example :ref:`l-plot-custom-options` shows a converter which selects two ways to compute the same outputs. In this one, the converter produces both. That would not be a very efficient converter but that's just for the sake of using a parser. By default, a transformer only returns one output but both are needed. .. contents:: :local: A new transformer +++++++++++++++++ .. GENERATED FROM PYTHON SOURCE LINES 30-90 .. code-block:: default from pyquickhelper.helpgen.graphviz_helper import plot_graphviz from mlprodict.onnxrt import OnnxInference import numpy from onnxruntime import InferenceSession from sklearn.base import TransformerMixin, BaseEstimator from sklearn.datasets import load_iris from skl2onnx import update_registered_converter from skl2onnx.common.data_types import guess_numpy_type from skl2onnx.algebra.onnx_ops import ( OnnxSub, OnnxMatMul, OnnxGemm) from skl2onnx import to_onnx, get_model_alias class DecorrelateTransformer(TransformerMixin, BaseEstimator): """ Decorrelates correlated gaussian features. :param alpha: avoids non inversible matrices by adding *alpha* identity matrix *Attributes* * `self.mean_`: average * `self.coef_`: square root of the coveriance matrix """ def __init__(self, alpha=0.): BaseEstimator.__init__(self) TransformerMixin.__init__(self) self.alpha = alpha def fit(self, X, y=None, sample_weights=None): if sample_weights is not None: raise NotImplementedError( "sample_weights != None is not implemented.") self.mean_ = numpy.mean(X, axis=0, keepdims=True) X = X - self.mean_ V = X.T @ X / X.shape[0] if self.alpha != 0: V += numpy.identity(V.shape[0]) * self.alpha L, P = numpy.linalg.eig(V) Linv = L ** (-0.5) diag = numpy.diag(Linv) root = P @ diag @ P.transpose() self.coef_ = root return self def transform(self, X): return (X - self.mean_) @ self.coef_ data = load_iris() X = data.data dec = DecorrelateTransformer() dec.fit(X) pred = dec.transform(X[:5]) print(pred) .. rst-class:: sphx-glr-script-out .. code-block:: none [[ 0.0167562 0.52111756 -1.24946737 -0.56194325] [-0.0727878 -0.80853732 -1.43841018 -0.37441392] [-0.69971891 -0.09950908 -1.2138161 -0.3499275 ] [-1.13063404 -0.13540568 -0.79087008 -0.73938966] [-0.35790036 0.91900236 -1.04034399 -0.6509266 ]] .. GENERATED FROM PYTHON SOURCE LINES 91-95 Conversion into ONNX with two outputs +++++++++++++++++++++++++++++++++++++ Let's try to convert it and see what happens. .. GENERATED FROM PYTHON SOURCE LINES 95-146 .. code-block:: default def decorrelate_transformer_shape_calculator(operator): op = operator.raw_operator input_type = operator.inputs[0].type.__class__ input_dim = operator.inputs[0].type.shape[0] output_type = input_type([input_dim, op.coef_.shape[1]]) operator.outputs[0].type = output_type def decorrelate_transformer_converter(scope, operator, container): op = operator.raw_operator opv = container.target_opset out = operator.outputs X = operator.inputs[0] dtype = guess_numpy_type(X.type) Y1 = OnnxMatMul( OnnxSub(X, op.mean_.astype(dtype), op_version=opv), op.coef_.astype(dtype), op_version=opv, output_names=out[:1]) Y2 = OnnxGemm(X, op.coef_.astype(dtype), (- op.mean_ @ op.coef_).astype(dtype), op_version=opv, alpha=1., beta=1., output_names=out[1:2]) Y1.add_to(scope, container) Y2.add_to(scope, container) def decorrelate_transformer_parser( scope, model, inputs, custom_parsers=None): alias = get_model_alias(type(model)) this_operator = scope.declare_local_operator(alias, model) # inputs this_operator.inputs.append(inputs[0]) # outputs cls_type = inputs[0].type.__class__ val_y1 = scope.declare_local_variable('nogemm', cls_type()) val_y2 = scope.declare_local_variable('gemm', cls_type()) this_operator.outputs.append(val_y1) this_operator.outputs.append(val_y2) # ends return this_operator.outputs .. GENERATED FROM PYTHON SOURCE LINES 147-148 The registration needs to declare the parser as well. .. GENERATED FROM PYTHON SOURCE LINES 148-157 .. code-block:: default update_registered_converter( DecorrelateTransformer, "SklearnDecorrelateTransformer", decorrelate_transformer_shape_calculator, decorrelate_transformer_converter, parser=decorrelate_transformer_parser) .. GENERATED FROM PYTHON SOURCE LINES 158-159 And conversion. .. GENERATED FROM PYTHON SOURCE LINES 159-183 .. code-block:: default onx = to_onnx(dec, X.astype(numpy.float32), target_opset={'': 14, 'ai.onnx.ml': 2}) sess = InferenceSession(onx.SerializeToString(), providers=['CPUExecutionProvider']) exp = dec.transform(X.astype(numpy.float32)) results = sess.run(None, {'X': X.astype(numpy.float32)}) y1 = results[0] y2 = results[1] def diff(p1, p2): p1 = p1.ravel() p2 = p2.ravel() d = numpy.abs(p2 - p1) return d.max(), (d / numpy.abs(p1)).max() print(diff(exp, y1)) print(diff(exp, y2)) .. rst-class:: sphx-glr-script-out .. code-block:: none (1.0531815934911037e-06, 0.0003497831114202993) (2.0175704165126263e-06, 0.0005483764974245708) .. GENERATED FROM PYTHON SOURCE LINES 184-185 It works. The final looks like the following. .. GENERATED FROM PYTHON SOURCE LINES 185-189 .. code-block:: default oinf = OnnxInference(onx, runtime="python_compiled") print(oinf) .. rst-class:: sphx-glr-script-out .. code-block:: none OnnxInference(...) def compiled_run(dict_inputs, yield_ops=None, context=None, attributes=None): if yield_ops is not None: raise NotImplementedError('yields_ops should be None.') # init: Ge_Gemmcst1 (Ge_Gemmcst1) # init: Ma_MatMulcst (Ma_MatMulcst) # init: Su_Subcst (Su_Subcst) # inputs X = dict_inputs['X'] (Su_C0, ) = n0_sub(X, Su_Subcst) (nogemm, ) = n1_matmul(Su_C0, Ma_MatMulcst) (gemm, ) = n2_gemm(X, Ma_MatMulcst, Ge_Gemmcst1) return { 'nogemm': nogemm, 'gemm': gemm, } .. GENERATED FROM PYTHON SOURCE LINES 190-192 Final graph +++++++++++ .. GENERATED FROM PYTHON SOURCE LINES 192-196 .. code-block:: default ax = plot_graphviz(oinf.to_dot()) ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) .. image-sg:: /gyexamples/images/sphx_glr_plot_mcustom_parser_001.png :alt: plot mcustom parser :srcset: /gyexamples/images/sphx_glr_plot_mcustom_parser_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 0.832 seconds) .. _sphx_glr_download_gyexamples_plot_mcustom_parser.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_mcustom_parser.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_mcustom_parser.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_