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
833 changes: 833 additions & 0 deletions _doc/notebooks/loss_functions.ipynb

Large diffs are not rendered by default.

38 changes: 16 additions & 22 deletions _doc/sphinxdoc/source/api/onnxrt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ on the following operators :ref:`l-onnx-runtime-operators`.
.. autosignature:: mlprodict.onnxrt.onnx_micro_inference.OnnxMicroRuntime
:members: run

The following is technically implemented as a runtime but it does
shape inference.

.. autosignature:: mlprodict.onnxrt.onnx_shape_inference.OnnxShapeInference
:members: run

The execution produces a result of type:

.. autosignature:: mlprodict.onnxrt.ops_shape.shape_container.ShapeContainer
:members: get

Methods `get` returns a dictionary mapping result name and the following type:

.. autosignature:: mlprodict.onnxrt.ops_shape.shape_result.ShapeResult
:members:

Python to ONNX
++++++++++++++

Expand Down Expand Up @@ -122,25 +138,3 @@ C++ classes
.. autosignature:: mlprodict.onnxrt.ops_cpu._op_onnx_numpy.topk_element_fetch_float

.. autosignature:: mlprodict.onnxrt.ops_cpu._op_onnx_numpy.topk_element_fetch_int64

Shapes
++++++

The computation of the predictions through epkg:`ONNX` may
be optimized if the shape of every nodes is known. For example,
one possible optimisation is to do inplace computation every time
it is possible but this is only possible if the size of
the input and output are the same. We could compute the predictions
for a sample and check the sizes are the same
but that could be luck. We could also guess from a couple of samples
with different sizes and assume sizes and polynomial functions
of the input size. But in rare occasions, that could be luck too.
So one way of doing it is to implement a method
:meth:`_set_shape_inference_runtime
<mlprodict.onnxrt.onnx_inference.OnnxInference._set_shape_inference_runtime>`
which works the same say as method :meth:`_run_sequence_runtime
<mlprodict.onnxrt.onnx_inference.OnnxInference._run_sequence_runtime>`
but handles shapes instead. Following class tries to implement
a way to keep track of shape along the shape.

.. autosignature:: mlprodict.onnxrt.shape_object.ShapeObject
3 changes: 2 additions & 1 deletion _unittests/ut_module/test_code_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def test_style_test(self):
check_pep8(test, fLOG=fLOG, neg_pattern="temp_.*",
pylint_ignore=('C0103', 'C1801', 'R0201', 'R1705', 'W0108', 'W0613',
'C0111', 'W0107', 'C0415', 'R1728', 'C0209',
'R1721', 'C0302', 'C0411', 'R1735', 'W1514'),
'R1721', 'C0302', 'C0411', 'R1735', 'W1514',
'C0200', 'E1101', 'W0212'),
skip=["Instance of 'tuple' has no ",
"R1720",
'if __name__ == "__main__":',
Expand Down
6 changes: 3 additions & 3 deletions _unittests/ut_npy/test_complex_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ def test_futr_fft_abs(self):

def tf_fft(x):
import tensorflow as tf # pylint: disable=E0401
xc = tf.cast(x, tf.complex64)
xcf = tf.signal.fft(xc)
return tf.abs(xcf)
xc = tf.cast(x, tf.complex64) # pylint: disable=E1101
xcf = tf.signal.fft(xc) # pylint: disable=E1101
return tf.abs(xcf) # pylint: disable=E1101

try:
tfx = tf_fft(x)
Expand Down
60 changes: 59 additions & 1 deletion _unittests/ut_npy/test_onnx_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import mlprodict.npy.numpy_onnx_impl as nxnp
from mlprodict.npy.onnx_version import FctVersion
from mlprodict.npy import (
OnnxNumpyCompiler as ONC, NDArray, NDArraySameTypeSameShape)
OnnxNumpyCompiler as ONC, NDArray, NDArraySameTypeSameShape,
NDArrayType)


@ignore_warnings(DeprecationWarning)
Expand Down Expand Up @@ -464,8 +465,65 @@ def onnx_log_1r_mul(x: NDArray[Any, numpy.float32]) -> NDArray[Any, numpy.float3
return nxnp.log(numpy.float32(2) * x)


@onnxnumpy_np(runtime='onnxruntime',
signature=NDArrayType(("T:all", "T"), dtypes_out=('T',)))
def onnx_square_loss(X, Y):
return nxnp.sum((X - Y) ** 2, keepdims=1)


@onnxnumpy_np(runtime='onnxruntime',
signature=NDArrayType(("T:all", "T"), dtypes_out=('T',)))
def onnx_log_loss(y, s):
one = numpy.array([1], dtype=s.dtype)
ceps = numpy.array([1e-6], dtype=s.dtype)
ps = nxnp.clip(nxnp.expit(-s), ceps, 1 - ceps)
ls = (-y + one) * nxnp.log(-ps + one) + y * nxnp.log(ps)
return nxnp.sum(ls, keepdims=1)


@onnxnumpy_np(runtime='onnxruntime',
signature=NDArrayType(("T:all", "T"), dtypes_out=('T',)))
def onnx_log_loss_eps(y, s, eps=1e-6):
one = numpy.array([1], dtype=s.dtype)
ceps = numpy.array([eps], dtype=s.dtype)
ps = nxnp.clip(nxnp.expit(-s), ceps, 1 - ceps)
ls = (-y + one) * nxnp.log(one - ps) + y * nxnp.log(ps)
return nxnp.sum(ls, keepdims=1)


class TestOnnxVariable(ExtTestCase):

def test_onnx_square_loss(self):
x = numpy.array([6, 7], dtype=numpy.float32)
n1 = onnx_square_loss(x, x)
x = numpy.array([6, 7], dtype=numpy.float64)
n2 = onnx_square_loss(x, x)
self.assertEqualArray(n1, n2, decimal=4)
onx = onnx_square_loss.to_onnx(key=numpy.float32)
self.assertNotEmpty(onx)

def test_onnx_log_loss(self):
y = numpy.array([0, 1], dtype=numpy.float32)
s = numpy.array([6, 7], dtype=numpy.float32)
n1 = onnx_log_loss(y, s)
y = y.astype(numpy.float64)
s = s.astype(numpy.float64)
n2 = onnx_log_loss(y, s)
self.assertEqualArray(n1, n2, decimal=4)
onx = onnx_log_loss.to_onnx(key=numpy.float32)
self.assertNotEmpty(onx)

def test_onnx_log_loss_eps(self):
y = numpy.array([0, 1], dtype=numpy.float32)
s = numpy.array([6, 7], dtype=numpy.float32)
n1 = onnx_log_loss_eps(y, s)
y = y.astype(numpy.float64)
s = s.astype(numpy.float64)
n2 = onnx_log_loss_eps(y, s)
self.assertEqualArray(n1, n2, decimal=4)
onx = onnx_log_loss.to_onnx(key=numpy.float32)
self.assertNotEmpty(onx)

def test_py_abs(self):
x = numpy.array([[6.1, -5], [3.5, -7.8]], dtype=numpy.float32)
y = otest_abs(x)
Expand Down
134 changes: 134 additions & 0 deletions _unittests/ut_onnxrt/test_shape_inference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""
@brief test log(time=3s)
"""
import unittest
import numpy
from onnx.shape_inference import infer_shapes
from pyquickhelper.pycode import ExtTestCase
from skl2onnx.algebra.onnx_ops import ( # pylint: disable=E0611
OnnxAdd)
from skl2onnx.common.data_types import FloatTensorType
from mlprodict.onnxrt import OnnxShapeInference
from mlprodict.onnxrt.ops_shape.shape_result import (
ShapeResult, ShapeConstraint, ShapeConstraintList)
from mlprodict.plotting.text_plot import onnx_simple_text_plot
from mlprodict.tools import get_opset_number_from_onnx


class TestOnnxShapeInference(ExtTestCase):

opsets = list(range(10, get_opset_number_from_onnx() + 1))

def check_infer_shapes(self, onx, out, rt):
onnx_shapes = infer_shapes(onx)
inferred = onnx_shapes.graph.value_info # pylint: disable=
for data in inferred:
if data.name not in out:
raise AssertionError("Name %r not found." % data.name)
shape, dtype, sparse = OnnxShapeInference._get_shape(
data) # pylint: disable=W0212
for i in range(len(shape)):
if not isinstance(shape[i], str):
continue
if shape[i].startswith('unk_'):
shape[i] = shape[i][4:]
res = ShapeResult(shape, dtype, sparse)
if res != out[data.name]:
raise AssertionError(
"Unexpected differences for name %r:\nexp: %r\ngot: %r"
"\n-----\n%s" % (
data.name, res, out[data.name],
onnx_simple_text_plot(onx)))

def test_shape_constraint(self):
sh1 = ShapeConstraint('_1', {1, 2})
sh2 = ShapeConstraint('_1', {1, 2})
self.assertEqual(sh1, sh2)
shl = ShapeConstraintList()
shl.append(sh1)
self.assertIn(sh1, shl)
self.assertIn(sh2, shl)

def test_onnx_shape_inference(self):
dtype = numpy.float32
x = numpy.array([1, 2, 4, 5, 5, 4]).astype(
numpy.float32).reshape((3, 2))
for opset in TestOnnxShapeInference.opsets:
with self.subTest(opset=opset):
cop = OnnxAdd('X', numpy.array(
[[1]], dtype=dtype), op_version=opset)
cop4 = OnnxAdd(cop, numpy.array([[2]], dtype=dtype), op_version=opset,
output_names=['Y'])
model_def = cop4.to_onnx({'X': x}, target_opset=opset)
rt = OnnxShapeInference(model_def)
out = rt.run({'X': x})
self.assertIn('X', out)
self.assertIn('Y', out)
self.assertIn('Ad_Addcst', out)
self.assertEqual(len(out), 5)
self.assertIn(
"'Ad_C0': ShapeResult(['_0', 2], dtype('float32')",
str(out))
self.check_infer_shapes(model_def, rt.run(), rt)
cons = rt.known_shapes_.get_all_constraints()
self.assertEqual(len(cons), 1)
self.assertEqual(list(cons), ['_1'])
self.assertEqual(len(cons['_1']), 1)
cst = cons['_1'][0]
self.assertEqual(cst.name, '_1')
self.assertEqual(cst.values, {'_0'})
self.assertEqual(
rt.known_shapes_.names,
{'_0': ('', 'X', 0), '_1': ('', 'Y', 0)})

def test_onnx_shape_inference_missing(self):
dtype = numpy.float32
x = numpy.array([1, 2, 4, 5, 5, 4]).astype(
numpy.float32).reshape((3, 2))
for opset in TestOnnxShapeInference.opsets[-1:]:
with self.subTest(opset=opset):
cop = OnnxAdd('X', numpy.array(
[[1]], dtype=dtype), op_version=opset)
cop4 = OnnxAdd(cop, numpy.array([[2, 4]], dtype=dtype), op_version=opset,
output_names=['Y'])
model_def = cop4.to_onnx(
{'X': FloatTensorType([None, None])},
{'Y': FloatTensorType([None, None])},
target_opset=opset)
rt = OnnxShapeInference(model_def)
out = rt.run({'X': x})
self.assertIn('X', out)
self.assertIn('Y', out)
self.assertIn('Ad_Addcst', out)
self.assertEqual(len(out), 5)
self.assertIn(
"'Ad_C0': ShapeResult(['_0', '_1'], dtype('float32'))",
str(out))
out = rt.run()
self.assertIn(
"'Y': ShapeResult(['_2', '_3']", str(out))
self.check_infer_shapes(model_def, rt.run(), rt)
cons = rt.known_shapes_.get_all_constraints()
self.assertEqual(len(rt.known_shapes_.names), 4)
self.assertEqual(set(rt.known_shapes_.names),
{'_0', '_1', '_2', '_3'})
self.assertEqual(len(cons), 3)
self.assertEqual(list(cons), ['_1', '_2', '_3'])
self.assertEqual(len(cons['_1']), 1)
cst = cons['_1'][0]
self.assertEqual(cst.name, '_1')
self.assertEqual(cst.values, {1, 2})
self.assertEqual(
rt.known_shapes_.names,
{'_0': ('', 'X', 0), '_1': ('', 'X', 1),
'_2': ('', 'Y', 0), '_3': ('', 'Y', 1)})
get = out.get()
self.assertEqual(get['Ad_C0'].shape, ['d0', {1, 2}])
self.assertEqual(get['Y'].shape, ['d0', 2])
self.assertEqual(get['X'].shape, ['d0', {1, 2}])
self.assertEqual(len(get['Ad_C0'].shape), 2)
self.assertIsInstance(get['Ad_C0'].shape[0], str)


if __name__ == "__main__":
unittest.main(verbosity=2)
8 changes: 8 additions & 0 deletions _unittests/ut_sklapi/test_onnx_tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import os
import numpy
from pyquickhelper.pycode import ExtTestCase
try:
from onnxruntime_extensions import get_library_path
except ImportError:
get_library_path = None
try:
from mlprodict.sklapi.onnx_tokenizer import (
SentencePieceTokenizerTransformer, GPT2TokenizerTransformer)
Expand All @@ -29,6 +33,8 @@ def _load_piece(self):

@unittest.skipIf(GPT2TokenizerTransformer is None,
reason="onnxruntime-extensions not available")
@unittest.skipIf(get_library_path is None,
reason="onnxruntime-extensions not available")
def test_sentence_piece_tokenizer_transformer(self):
model, model_b64 = self._load_piece()
cints = bytes(model.tolist())
Expand Down Expand Up @@ -64,6 +70,8 @@ def test_sentence_piece_tokenizer_transformer(self):

@unittest.skipIf(GPT2TokenizerTransformer is None,
reason="onnxruntime-extensions not available")
@unittest.skipIf(get_library_path is None,
reason="onnxruntime-extensions not available")
def test_gpt2_tokenizer_transformer(self):
vocab = os.path.join(
os.path.dirname(__file__), "data", "gpt2.vocab")
Expand Down
2 changes: 1 addition & 1 deletion mlprodict/npy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
.. versionadded:: 0.6
"""
from .onnx_numpy_annotation import (
NDArray, NDArraySameType, NDArraySameTypeSameShape,
NDArray, NDArrayType, NDArraySameType, NDArraySameTypeSameShape,
Shape, DType)
from .onnx_numpy_compiler import OnnxNumpyCompiler
from .onnx_numpy_wrapper import onnxnumpy, onnxnumpy_default, onnxnumpy_np
Expand Down
11 changes: 7 additions & 4 deletions mlprodict/npy/onnx_numpy_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,17 @@ def to_onnx(self, **kwargs):
return self.signed_compiled[key].compiled.onnx_
found = []
for k, v in self.signed_compiled.items():
if k.args == key or (
not isinstance(key, tuple) and k.args == (key, )):
if k.args == key:
found.append((k, v))
elif isinstance(key, tuple) and k.args == key:
found.append((k, v))
elif k.args == (key, ) * len(k.args):
found.append((k, v))
if len(found) == 1:
return found[0][1].compiled.onnx_
raise ValueError(
"Unable to find signature with key=%r among %r." % (
key, list(self.signed_compiled)))
"Unable to find signature with key=%r among %r found=%r." % (
key, list(self.signed_compiled), found))


def onnxnumpy_np(op_version=None, runtime=None, signature=None):
Expand Down
5 changes: 3 additions & 2 deletions mlprodict/npy/onnx_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class OnnxVar:

.. versionadded:: 0.6
"""
__array_ufunc__ = None

def __init__(self, *inputs, op=None, select_output=None,
dtype=None, **kwargs):
Expand All @@ -76,8 +77,8 @@ def __init__(self, *inputs, op=None, select_output=None,
if (inp.size > 0 and
isinstance(inp.ravel()[0], (numpy.ndarray, OnnxVar))):
raise TypeError( # pragma: no cover
"Unexpected type for input %d: %r, %r."
"" % (i, type(inp), inp.ravel()[0]))
"Unexpected type for input %d: %r, %r, "
"op=%r" % (i, type(inp), inp.ravel()[0], op))
self.dtype = self._guess_dtype(dtype, from_init=True)

def _guess_dtype(self, dtype, from_init=False):
Expand Down
2 changes: 1 addition & 1 deletion mlprodict/onnxrt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
"""
from .onnx_inference import OnnxInference
from .onnx_micro_runtime import OnnxMicroRuntime

from .onnx_shape_inference import OnnxShapeInference
Loading