Skip to content
This repository was archived by the owner on Jan 13, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions _doc/sphinxdoc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@

mathdef_link_only = True

intersphinx_mapping.update({
'cpyquickhelper': (
'http://www.xavierdupre.fr/app/cpyquickhelper/helpsphinx/', None),
'jyquickhelper': (
'http://www.xavierdupre.fr/app/jyquickhelper/helpsphinx/', None),
'lightgbm': ('https://lightgbm.readthedocs.io/en/latest/', None),
'mlinsights': (
'http://www.xavierdupre.fr/app/mlinsights/helpsphinx/', None),
'onnxmltools': (
'http://www.xavierdupre.fr/app/onnxmltools/helpsphinx/', None),
'onnxruntime': (
'http://www.xavierdupre.fr/app/onnxruntime/helpsphinx/', None),
'skl2onnx': ('http://onnx.ai/sklearn-onnx/', None),
)}

epkg_dictionary.update({
'_PredictScorer': 'https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/metrics/scorer.py#L168',
'airspeed-velocity': 'https://github.com/airspeed-velocity/asv',
Expand Down
83 changes: 83 additions & 0 deletions _unittests/ut_npy/test_onnx_if.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
"""
@brief test log(time=3s)
"""
import unittest
from typing import Any
import numpy
from pyquickhelper.pycode import ExtTestCase
from mlprodict.npy import onnxnumpy
import mlprodict.npy.numpy_onnx_impl as nxnp
from mlprodict.npy import NDArray


class TestOnnxVariableIf(ExtTestCase):

@staticmethod
def numpy_onnx_if(x):
y = x * 2
z = x + 7
if x.sum() > 0:
return x + y
return x - y + z

@staticmethod
def fct_onnx_if_sub(x: NDArray[Any, numpy.float32],
) -> NDArray[Any, numpy.float32]:
"onnx numpy abs"
y = x * numpy.float32(2)
z = x + numpy.float32(7)
xif = nxnp.onnx_if(
nxnp.sum(x) > numpy.float32(0),
then_branch=nxnp.if_then_else(
lambda x, y: x / y, x, y),
else_branch=nxnp.if_then_else(
lambda x, y, z: x - y - z, x, y, z))
return xif + numpy.float32(-7)

@staticmethod
def fct_onnx_if(x: NDArray[Any, numpy.float32],
) -> NDArray[Any, numpy.float32]:
"onnx numpy abs"
xif = nxnp.onnx_if(
nxnp.sum(x) > numpy.float32(0),
then_branch=nxnp.if_then_else(
numpy.array([-1], dtype=numpy.float32)),
else_branch=numpy.array([1], dtype=numpy.float32))
return xif + numpy.float32(-7)

def test_exc(self):

self.assertRaise(
lambda: nxnp.onnx_if(
None,
then_branch=nxnp.if_then_else(
lambda x, y: x + y, "DEBUG", "DEBUG"),
else_branch="DEBUG"),
(TypeError, NotImplementedError, AttributeError))
self.assertRaise(lambda: nxnp.onnx_if(
"DEBUG", then_branch="DEBUG", else_branch="DEBUG"),
(TypeError, NotImplementedError, AttributeError))

def test_onnx_if(self):
x = numpy.array([[6.1, -5], [3.5, -7.8]], dtype=numpy.float32)
fct_if = onnxnumpy()(TestOnnxVariableIf.fct_onnx_if)
with open("debug.onnx", "wb") as f:
f.write(fct_if.compiled.onnx_.SerializeToString())
y = fct_if(x)
self.assertEqualArray(
y, numpy.array([-6], dtype=numpy.float32))

@unittest.skipIf(True, reason="does not work yet")
def test_onnx_if_sub(self):
x = numpy.array([[6.1, -5], [3.5, -7.8]], dtype=numpy.float32)
fct_if = onnxnumpy()(TestOnnxVariableIf.fct_onnx_if_sub)
with open("debug.onnx", "wb") as f:
f.write(fct_if.compiled.onnx_.SerializeToString())
y = fct_if(x)
self.assertEqualArray(
y, TestOnnxVariableIf.fct_onnx_if_sub(x))


if __name__ == "__main__":
unittest.main()
3 changes: 1 addition & 2 deletions _unittests/ut_onnxrt/test_onnxrt_python_runtime_.py
Original file line number Diff line number Diff line change
Expand Up @@ -4240,8 +4240,7 @@ def test_make_constant(self):
self.assertIsInstance(ope, expected_type[opset])
got = oinf.run({'X': X})
if opset >= 11:
self.assertEqual(list(sorted(got)), [
'Ad_C0', 'Co_output0'])
self.assertEqual(list(sorted(got)), ['Ad_C0'])
self.assertEqualArray(exp, got['Ad_C0'])
else:
self.assertEqual(list(sorted(got)), ['Ad_C0'])
Expand Down
21 changes: 14 additions & 7 deletions _unittests/ut_onnxrt/test_onnxrt_python_runtime_control_if.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from logging import getLogger
from collections import OrderedDict
import numpy
from pyquickhelper.pycode import ExtTestCase
from pyquickhelper.pycode import ExtTestCase, ignore_warnings
from skl2onnx.algebra.onnx_ops import ( # pylint: disable=E0611
OnnxIf, OnnxConstant, OnnxGreater)
from skl2onnx.common.data_types import FloatTensorType
Expand All @@ -20,20 +20,27 @@ def setUp(self):
logger = getLogger('skl2onnx')
logger.disabled = True

@ignore_warnings(DeprecationWarning)
def test_if(self):

tensor_type = FloatTensorType
op_version = get_opset_number_from_onnx()
bthen = OnnxConstant(value_floats=numpy.array([0], dtype=numpy.float32),
op_version=op_version, output_names=['res'])
belse = OnnxConstant(value_floats=numpy.array([1], dtype=numpy.float32),
op_version=op_version, output_names=['res'])
bthen = OnnxConstant(
value_floats=numpy.array([0], dtype=numpy.float32),
op_version=op_version, output_names=['res_then'])
bthen.set_onnx_name_prefix('then')

belse = OnnxConstant(
value_floats=numpy.array([1], dtype=numpy.float32),
op_version=op_version, output_names=['res_else'])
belse.set_onnx_name_prefix('else')

bthen_body = bthen.to_onnx(
OrderedDict(), outputs=[('res', tensor_type())],
OrderedDict(), outputs=[('res_then', tensor_type())],
target_opset=op_version)
belse_body = belse.to_onnx(
OrderedDict(),
outputs=[('res', tensor_type())],
outputs=[('res_else', tensor_type())],
target_opset=op_version)

onx = OnnxIf(OnnxGreater('X', numpy.array([0], dtype=numpy.float32),
Expand Down
2 changes: 1 addition & 1 deletion _unittests/ut_tools/test_model_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def test_knnc_onnx(self):
onx = to_onnx(model, numpy.zeros((3, 4), dtype=numpy.float32))
info = analyze_model(onx)
self.assertIn('op_Identity', info)
self.assertEqual(info['op_Identity'], 1)
self.assertEqual(info['op_Identity'], 2)

@skipif_circleci('issue, too long')
def test_gbc(self):
Expand Down
2 changes: 1 addition & 1 deletion _unittests/ut_tools/test_optim_onnx_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def onnx_test_knn_single_regressor(self, dtype, n_targets=1, debug=False,
self.assertIn('subgraphs_optim', stats)

def test_onnx_test_knn_single_regressor32(self):
self.onnx_test_knn_single_regressor(numpy.float32, expected=[1, 1])
self.onnx_test_knn_single_regressor(numpy.float32, expected=[2, 1])


if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions mlprodict/asv_benchmark/_create_asv_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ def _sklearn_subfolder(model):
mod = model.__module__
if mod is not None and mod.startswith('mlinsights'):
return ['mlinsights', model.__name__] # pragma: no cover
if mod is not None and mod.startswith('skl2onnx.sklapi'):
return ['skl2onnx.sklapi', model.__name__] # pragma: no cover
spl = mod.split('.')
try:
pos = spl.index('sklearn')
Expand Down
29 changes: 28 additions & 1 deletion mlprodict/npy/numpy_onnx_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
OnnxErf,
OnnxExp,
OnnxFloor,
OnnxIdentity, OnnxIsNaN,
OnnxIdentity, OnnxIf, OnnxIsNaN,
OnnxLog,
OnnxMatMul,
OnnxPad,
Expand All @@ -51,6 +51,7 @@
OnnxUnsqueeze,
OnnxWhere)
from .onnx_variable import OnnxVar, MultiOnnxVar as xtuple
from .numpy_onnx_impl_body import if_then_else, OnnxVarGraph


def abs(x):
Expand Down Expand Up @@ -347,6 +348,32 @@ def mean(x, axis=None, keepdims=0):
return OnnxVar(x, op=OnnxReduceMean, keepdims=keepdims, axes=axis)


def onnx_if(condition, then_branch, else_branch):
"""
Implements a test with onnx syntax.

:param condition: condition (@see cl OnnxVar)
:param then_branch: then branch, of type @see cl if_then_else
:param else_branch: else branch, of type @see cl if_then_else
:return: result (@see cl OnnxVar)
"""
if isinstance(then_branch, numpy.ndarray):
then_branch = if_then_else(then_branch)
if not isinstance(then_branch, if_then_else):
raise TypeError(
"Parameter then_branch is not of type "
"'if_then_else' but %r." % type(then_branch))
if isinstance(else_branch, numpy.ndarray):
else_branch = if_then_else(else_branch)
if not isinstance(else_branch, if_then_else):
raise TypeError(
"Parameter then_branch is not of type "
"'if_then_else' but %r." % type(else_branch))
return OnnxVarGraph(
condition, then_branch=then_branch,
else_branch=else_branch, op=OnnxIf)


def pad(x, pads, constant_value=None, mode='constant'):
"""
It does not implement :epkg:`numpy:pad` but the ONNX version
Expand Down
148 changes: 148 additions & 0 deletions mlprodict/npy/numpy_onnx_impl_body.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""
@file
@brief Design to implement graph as parameter.

.. versionadded:: 0.8
"""
import numpy
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx.algebra.onnx_ops import ( # pylint: disable=E0611
OnnxIdentity)
from .onnx_variable import OnnxVar


class AttributeGraph:
"""
Class wrapping a function to make it simple as
a parameter.

:param fct: function taking the list of inputs defined
as @see cl OnnxVar, the function returns an @see cl OnnxVar
:param inputs: list of input as @see cl OnnxVar

.. versionadded:: 0.8
"""

def __init__(self, fct, *inputs):
if isinstance(fct, numpy.ndarray) and len(inputs) == 0:
self.cst = fct
fct = None
else:
self.cst = None
self.fct = fct
self.inputs = inputs
self.alg_ = None

def __repr__(self):
"usual"
return "%s(...)" % self.__class__.__name__

def _graph_guess_dtype(self, i, var):
"""
Guesses the graph inputs.

:param i: attribute index (integer)
:param var: the input (@see cl OnnxVar)
:return: input type
"""
dtype = var._guess_dtype(None)
if dtype is None:
dtype = numpy.float32

if dtype == numpy.float32:
skl2onnx_type = FloatTensorType()
else:
raise TypeError(
"Unexpected type %r." % dtype)

input_type = ('graph_%d_%d' % (id(self), i),
skl2onnx_type)
var.set_onnx_name(input_type)
return input_type, OnnxVar(input_type[0], dtype=dtype)

def to_algebra(self, op_version=None):
"""
Converts the variable into an operator.
"""
if self.alg_ is not None:
return self.alg_

if self.cst is not None:
self.alg_ = OnnxIdentity(self.cst, op_version=op_version)
self.alg_inputs_ = None
return self.alg_

new_inputs = [self._graph_guess_dtype(i, inp)
for i, inp in enumerate(self.inputs)]
self.alg_inputs_ = new_inputs
vars = [v[1] for v in new_inputs]
var = self.fct(*vars)
if not isinstance(var, OnnxVar):
raise RuntimeError( # pragma: no cover
"var is not from type OnnxVar but %r." % type(var))

self.alg_ = var.to_algebra(op_version=op_version)
return self.alg_


class OnnxVarGraph(OnnxVar):
"""
Overloads @see cl OnnxVar to handle graph attribute.

:param inputs: variable name or object
:param op: :epkg:`ONNX` operator
:param select_output: if multiple output are returned by
ONNX operator *op*, it takes only one specifed by this
argument
:param dtype: specifies the type of the variable
held by this class (*op* is None) in that case
:param fields: list of attributes with the graph type
:param kwargs: addition argument to give operator *op*

.. versionadded:: 0.8
"""

def __init__(self, *inputs, op=None, select_output=None,
dtype=None, **kwargs):
OnnxVar.__init__(
self, *inputs, op=op, select_output=select_output,
dtype=dtype, **kwargs)

def to_algebra(self, op_version=None):
"""
Converts the variable into an operator.
"""
if self.alg_ is not None:
return self.alg_

# Conversion of graph attributes from InputGraph
# ONNX graph.
updates = dict()
self.alg_hidden_var_ = {}
self.alg_hidden_var_inputs = {}
for att, var in self.onnx_op_kwargs.items():
if not isinstance(var, AttributeGraph):
continue
alg = var.to_algebra(op_version=op_version)
alg.set_onnx_name_prefix("g_%s_%d" % (att, id(var)))
if var.alg_inputs_ is None:
onnx_inputs = []
else:
onnx_inputs = [i[0] for i in var.alg_inputs_]
onx = alg.to_onnx(onnx_inputs, target_opset=op_version)
updates[att] = onx.graph
self.alg_hidden_var_[id(var)] = var
self.alg_hidden_var_inputs[id(var)] = onnx_inputs
self.onnx_op_kwargs_before = {
k: self.onnx_op_kwargs[k] for k in updates}
self.onnx_op_kwargs.update(updates)
return OnnxVar.to_algebra(self, op_version=op_version)


class if_then_else(AttributeGraph):
"""
Overloads class @see cl OnnxVarGraph.
"""

def __init__(self, fct, *inputs):
AttributeGraph.__init__(self, fct, *inputs)
Loading