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
8 changes: 5 additions & 3 deletions _doc/sphinxdoc/source/api/tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ Functions to help understand models or modify them.

.. autosignature:: mlprodict.onnx_tools.model_checker.onnx_shaker

.. autosignature:: mlprodict.onnx_tools.optimisation._main_onnx_optim.onnx_optimisations

.. autosignature:: mlprodict.onnx_tools.optim.onnx_statistics
.. autosignature:: mlprodict.onnx_tools.optim.onnx_helper.onnx_statistics

.. autosignature:: mlprodict.onnx_tools.onnx_manipulations.select_model_inputs_outputs

Expand All @@ -59,8 +57,12 @@ is left unchanged.

.. autosignature:: mlprodict.onnx_tools.onnx_tools.ensure_topological_order

.. autosignature:: mlprodict.onnx_tools.onnx_manipulations.onnx_rename_names

.. autosignature:: mlprodict.onnx_tools.optim.onnx_optimisation.onnx_remove_node

.. autosignature:: mlprodict.onnx_tools.optimisation._main_onnx_optim.onnx_optimisations

.. autosignature:: mlprodict.onnx_tools.optim.onnx_optimisation_identity.onnx_remove_node_identity

.. autosignature:: mlprodict.onnx_tools.optim.onnx_optimisation_redundant.onnx_remove_node_redundant
Expand Down
66 changes: 66 additions & 0 deletions _unittests/ut_onnx_conv/test_onnx_conv_graph_optimisation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
@brief test log(time=3s)
"""
from collections import OrderedDict
import unittest
import numpy
from pyquickhelper.pycode import ExtTestCase, ignore_warnings
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import make_scorer
from mlprodict.onnx_conv import to_onnx
from mlprodict.onnxrt import OnnxInference
from mlprodict.tools.asv_options_helper import (
get_opset_number_from_onnx)
from mlprodict.onnx_conv.scorers.cdist_score import score_cdist_sum


class TestOnnxConvGraphOptimisation(ExtTestCase):

def test_to_onnx_rename_names(self):
data = load_iris()
X, y = data.data, data.target
model = KNeighborsRegressor(n_neighbors=2).fit(X, y)

model_onnx = to_onnx(
model, X[:1], target_opset=get_opset_number_from_onnx())
oinf1 = OnnxInference(model_onnx)
y1 = oinf1.run({'X': X})['variable']

model_onnx = to_onnx(
model, X[:1], target_opset=get_opset_number_from_onnx(),
rename_strategy='simple')
oinf1 = OnnxInference(model_onnx)
y2 = oinf1.run({'X': X})['variable']
self.assertEqualArray(y1, y2)

@ignore_warnings((DeprecationWarning, UserWarning))
def test_to_onnx_rename_names_scorer(self):
X = numpy.array([[0, 1, 0, 2],
[1, 0, 4, 5],
[9, 8, 5, 6]], dtype=numpy.float64)
Y = X[:2].copy()
Y[0, :] = 0

init_types = OrderedDict([('X', X), ('Y', Y)])
opset = get_opset_number_from_onnx()
scorer = make_scorer(
score_cdist_sum, metric='sqeuclidean',
greater_is_better=False)

monx1 = to_onnx(scorer, init_types, target_opset=opset,
rewrite_ops=True)
monx2 = to_onnx(scorer, init_types, target_opset=opset,
rewrite_ops=True, rename_strategy='simple')

oinf1 = OnnxInference(monx1)
oinf2 = OnnxInference(monx2)
res0 = score_cdist_sum(X, Y, metric='sqeuclidean')
res1 = oinf1.run({'X': X, 'Y': Y})['scores']
res2 = oinf2.run({'X': X, 'Y': Y})['scores']
self.assertEqualArray(res1, res0, decimal=5)
self.assertEqualArray(res2, res0, decimal=5)


if __name__ == "__main__":
unittest.main()
138 changes: 135 additions & 3 deletions _unittests/ut_tools/test_onnx_manipulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
@brief test log(time=2s)
"""
import unittest
from collections import OrderedDict
import numpy
from pyquickhelper.pycode import ExtTestCase
from skl2onnx.algebra.onnx_ops import ( # pylint: disable=E0611
OnnxAdd, OnnxMul, OnnxSub)
OnnxAdd, OnnxMul, OnnxSub, OnnxIdentity, OnnxScan,
OnnxReduceSumSquare, OnnxSqueezeApi11)
from skl2onnx.common.data_types import FloatTensorType
from mlprodict.onnx_tools.optim.onnx_helper import onnx_statistics
from mlprodict.onnxrt import OnnxInference
from mlprodict.onnx_tools.optim import onnx_remove_node_unused
from mlprodict.onnx_tools.onnx_manipulations import (
select_model_inputs_outputs, enumerate_model_node_outputs)
select_model_inputs_outputs, enumerate_model_node_outputs,
onnx_rename_names)
from mlprodict.tools import get_opset_number_from_onnx


class TestOptimOnnxUnused(ExtTestCase):
class TestOptimOnnxManipulations(ExtTestCase):

def test_onnx_remove_unused_outputs(self):
dtype = numpy.float32
Expand Down Expand Up @@ -202,6 +206,134 @@ def test_enumerate_model_node_outputs(self):
expected = ['Ad_Addcst2', 'Ad_C0', 'inter', 'Ad_C02', 'Mu_C0', 'final']
self.assertEqual(nodes2, expected)

def test_onnx_rename_names_exc(self):
dtype = numpy.float32
x = numpy.array([1, 2, 4, 5, 5, 4]).astype(
numpy.float32).reshape((3, 2))
cop = OnnxAdd('X', numpy.array([1], dtype=dtype),
op_version=get_opset_number_from_onnx())
cop2 = OnnxAdd('X', numpy.array([1], dtype=dtype),
op_version=get_opset_number_from_onnx())
cop3 = OnnxAdd('X', numpy.array([2], dtype=dtype),
op_version=get_opset_number_from_onnx(),
output_names=['inter'])
cop4 = OnnxSub(
OnnxMul(cop, cop3, op_version=get_opset_number_from_onnx()),
cop2, output_names=['final'],
op_version=get_opset_number_from_onnx())
model_def = cop4.to_onnx({'X': x})
self.assertRaise(
lambda: onnx_rename_names(model_def, strategy="none"),
ValueError)

def test_onnx_rename_names_simple(self):
rows = []

def flog(*s):
rows.append(" ".join(map(str, s)))

dtype = numpy.float32
x = numpy.array([1, 2, 4, 5, 5, 4]).astype(
numpy.float32).reshape((3, 2))
cop = OnnxAdd('X', numpy.array([1], dtype=dtype),
op_version=get_opset_number_from_onnx())
cop2 = OnnxAdd('X', numpy.array([1], dtype=dtype),
op_version=get_opset_number_from_onnx())
cop3 = OnnxAdd('X', numpy.array([2], dtype=dtype),
op_version=get_opset_number_from_onnx(),
output_names=['inter'])
cop4 = OnnxSub(
OnnxMul(cop, cop3, op_version=get_opset_number_from_onnx()),
cop2, output_names=['final'],
op_version=get_opset_number_from_onnx())
model_def = cop4.to_onnx({'X': x})
oinf1 = OnnxInference(model_def)
new_model = onnx_rename_names(model_def, verbose=1, fLOG=flog)
total = "\n".join(rows)
self.assertIn("[onnx_rename_names] 'Ad_Addcst1' -> 'i1'", total)
oinf2 = OnnxInference(new_model)
y1 = oinf1.run({'X': x})
y2 = oinf2.run({'X': x})
self.assertEqualArray(y1['final'], y2['final'])

def test_onnx_rename_names_type(self):
rows = []

def flog(*s):
rows.append(" ".join(map(str, s)))

dtype = numpy.float32
x = numpy.array([1, 2, 4, 5, 5, 4]).astype(
numpy.float32).reshape((3, 2))
cop = OnnxAdd('X', numpy.array([1], dtype=dtype),
op_version=get_opset_number_from_onnx())
cop2 = OnnxAdd('X', numpy.array([1], dtype=dtype),
op_version=get_opset_number_from_onnx())
cop3 = OnnxAdd('X', numpy.array([2], dtype=dtype),
op_version=get_opset_number_from_onnx(),
output_names=['inter'])
cop4 = OnnxSub(
OnnxMul(cop, cop3, op_version=get_opset_number_from_onnx()),
cop2, output_names=['final'],
op_version=get_opset_number_from_onnx())
model_def = cop4.to_onnx({'X': x})
oinf1 = OnnxInference(model_def)
new_model = onnx_rename_names(
model_def, verbose=1, fLOG=flog, strategy='type')
total = "\n".join(rows)
self.assertIn("'Ad_Addcst' -> 'i_05'", total)
oinf2 = OnnxInference(new_model)
y1 = oinf1.run({'X': x})
y2 = oinf2.run({'X': x})
self.assertEqualArray(y1['final'], y2['final'])

def test_onnx_rename_node_scan(self):

def squareform_pdist(X, **kwargs):
opv = get_opset_number_from_onnx()
diff = OnnxSub('next_in', 'next', output_names=[
'diff'], op_version=opv)
id_next = OnnxIdentity('next_in', output_names=[
'next_out'], op_version=opv)
norm = OnnxReduceSumSquare(
diff, output_names=['norm'], axes=[1], op_version=opv)
flat = OnnxSqueezeApi11(
norm, output_names=['scan_out'], axes=[1], op_version=opv)
scan_body = id_next.to_onnx(
OrderedDict([('next_in', FloatTensorType()),
('next', FloatTensorType())]),
outputs=[('next_out', FloatTensorType([None, None])),
('scan_out', FloatTensorType([None]))],
other_outputs=[flat])

node = OnnxScan(X, X, output_names=['scan0_{idself}', 'scan1_{idself}'],
num_scan_inputs=1, body=scan_body.graph, op_version=opv,
**kwargs)
return node[1]

rows = []

def flog(*s):
rows.append(" ".join(map(str, s)))

opv = get_opset_number_from_onnx()
onnx_fct = OnnxIdentity(squareform_pdist(
'x'), output_names='Y', op_version=opv)
model_def = onnx_fct.to_onnx(inputs=[('x', FloatTensorType())])

oinf1 = OnnxInference(model_def)
new_model = onnx_rename_names(
model_def, verbose=1, fLOG=flog, strategy='type')
total = "\n".join(rows)
self.assertNotIn('name: "Re_ReduceSumSquare"', str(new_model))
self.assertIn("'Re_ReduceSumSquare' -> 'n_24'", total)
oinf2 = OnnxInference(new_model)
x = numpy.array([1, 2, 4, 5, 5, 4]).astype(
numpy.float32).reshape((3, 2))
y1 = oinf1.run({'x': x})
y2 = oinf2.run({'x': x})
self.assertEqualArray(y1['Y'], y2['Y'])


if __name__ == "__main__":
unittest.main()
12 changes: 11 additions & 1 deletion mlprodict/onnx_conv/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from skl2onnx import convert_sklearn
from skl2onnx.algebra.onnx_operator_mixin import OnnxOperatorMixin
from skl2onnx.algebra.type_helper import _guess_type
from ..onnx_tools.onnx_manipulations import onnx_rename_names
from .register_rewritten_converters import register_rewritten_operators
from .register import register_converters
from .scorers import CustomScorerTransform
Expand Down Expand Up @@ -243,7 +244,7 @@ def guess_schema_from_model(model, tensor_type=None, schema=None):
def to_onnx(model, X=None, name=None, initial_types=None,
target_opset=None, options=None, rewrite_ops=False,
white_op=None, black_op=None, final_types=None,
verbose=0):
rename_strategy=None, verbose=0):
"""
Converts a model using on :epkg:`sklearn-onnx`.

Expand All @@ -269,6 +270,8 @@ def to_onnx(model, X=None, name=None, initial_types=None,
initial_types but not mandatory, it is used
to overwrites the type (if type is not None)
and the name of every output.
:param rename_strategy: rename any name in the graph, select shorter
names, see @see fn onnx_rename_names
:param verbose: display information while converting the model
:return: converted model

Expand Down Expand Up @@ -348,6 +351,9 @@ def to_onnx(model, X=None, name=None, initial_types=None,

onxp = oinf.run(inputs)
print(onxp)

.. versionchanged:: 0.7
Parameter *rename_strategy* was added.
"""
if isinstance(model, OnnxOperatorMixin):
if not hasattr(model, 'op_version'):
Expand Down Expand Up @@ -435,4 +441,8 @@ def _guess_type_(X, itype, dtype):
final_types=final_types, verbose=verbose)

register_rewritten_operators(old_values, old_shapes)

# optimisation
if rename_strategy is not None:
res = onnx_rename_names(res, strategy=rename_strategy)
return res
Loading