Repository: onnx/sklearn-onnx

Issue:
Version 1.9 Normalizer inputs dimension
As I suggested in #678, the the converter for `sklearn.preprocessing.Normalizer` does not support double type. So I switch to use `skl2onnx.algebra.onnx_ops.Normalizer`.
I guess that `skl2onnx.algebra.onnx_ops.Normalizer` is a wrapper `skl2onnx.algebra.onnx_ops.Normalizer` around [`ai.onnx.ml.Normalizer`](https://github.com/onnx/onnx/blob/master/docs/Operators-ml.md#aionnxmlnormalizer)
However, on the documentation for [`ai.onnx.ml.Normalizer`](https://github.com/onnx/onnx/blob/master/docs/Operators-ml.md#aionnxmlnormalizer), it is clearly suggested that the input X can be a tensor of shape [N,C] or [C].
But when I apply the following code in the converter function:
```
input: Variable = operator.inputs[0]
np_dtype = guess_numpy_type(input.type)
# normalize input
normalize_op: Union[OnnxOperator, Variable] = input
normalize_op = OnnxNormalizer(input, norm="L2", op_version=op_version)
```
And then run the transformer with the following:
```
try:
    onx = to_onnx(skm, X.astype(np.float64))
except Exception as e:
    traceback.print_exception(type(e), e, e.__traceback__)
```
It spits out the following error:
```
Traceback (most recent call last):
  File "/home/fred/Documents/research/sklearn_plugins/test/sklearn_plugins/cluster/test_spherical_kmeans_export.py", line 64, in <module>
    onx = to_onnx(skm, X.astype(np.float64))
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/convert.py", line 207, in to_onnx
    return convert_sklearn(model, initial_types=initial_types,
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/convert.py", line 157, in convert_sklearn
    onnx_model = convert_topology(topology, name, doc_string, target_opset,
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/common/_topology.py", line 1180, in convert_topology
    conv(scope, operator, container)
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/common/_registration.py", line 26, in __call__
    return self._fct(*args)
  File "/home/fred/Documents/research/sklearn_plugins/src/sklearn_plugins/cluster/_onnx_transform.py", line 102, in spherical_kmeans_converter
    proj_op.add_to(scope=scope, container=container)
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/algebra/onnx_operator.py", line 548, in add_to
    self.state.run()
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/algebra/graph_state.py", line 414, in run
    v = self._get_var_name(i, False, index=None)
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/algebra/graph_state.py", line 124, in _get_var_name
    var.add_to(self.scope, self.container, operator=operator,
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/algebra/onnx_operator.py", line 950, in add_to
    self.state.run()
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/algebra/graph_state.py", line 458, in run
    shape_calc(sub_op)
  File "/home/fred/anaconda3/envs/sklearn_plugins/lib/python3.8/site-packages/skl2onnx-1.9.0-py3.8.egg/skl2onnx/shape_calculators/svd.py", line 26, in calculate_sklearn_truncated_svd_output_shapes
    raise RuntimeError('Only 2-D tensor(s) can be input(s).')
RuntimeError: Only 2-D tensor(s) can be input(s).
```
To investigate the error, I print out the sample dummy input X I send to the `to_onnx` function:
```
print(X.shape)
```
and it prints out
```
(600, 2)
```


Discussion:

Resolving patch:
diff --git a/docs/tutorial/plot_kcustom_converter_wrapper.py b/docs/tutorial/plot_kcustom_converter_wrapper.py
--- a/docs/tutorial/plot_kcustom_converter_wrapper.py
+++ b/docs/tutorial/plot_kcustom_converter_wrapper.py
@@ -39,7 +39,6 @@
 from sklearn.datasets import load_iris
 from sklearn.decomposition import PCA
 from skl2onnx import update_registered_converter
-from skl2onnx.algebra.onnx_ops import OnnxIdentity
 from skl2onnx.algebra.onnx_operator import OnnxSubEstimator
 from skl2onnx import to_onnx
 
diff --git a/skl2onnx/__init__.py b/skl2onnx/__init__.py
--- a/skl2onnx/__init__.py
+++ b/skl2onnx/__init__.py
@@ -3,7 +3,7 @@
 """
 Main entry point to the converter from the *scikit-learn* to *onnx*.
 """
-__version__ = "1.9.0"
+__version__ = "1.9.1.dev"
 __author__ = "Microsoft"
 __producer__ = "skl2onnx"
 __producer_version__ = __version__
diff --git a/skl2onnx/common/_apply_operation.py b/skl2onnx/common/_apply_operation.py
--- a/skl2onnx/common/_apply_operation.py
+++ b/skl2onnx/common/_apply_operation.py
@@ -24,22 +24,39 @@ def apply_normalizer(scope, inputs, outputs, container,
         # Normalizer only produces floats.
         if norm == 'L1':
             norm = scope.get_unique_variable_name('norm')
+            norm_abs = scope.get_unique_variable_name('norm_abs')
+            container.add_node(
+                'Abs', input, norm_abs,
+                name=scope.get_unique_operator_name('Abs'))
 
             if container.target_opset < 13:
                 container.add_node(
-                    'ReduceSum', input, norm, axes=[1], keepdims=1,
-                    name=scope.get_unique_operator_name('NormalizerNorm'))
+                    'ReduceSum', norm_abs, norm, axes=[1], keepdims=1,
+                    name=scope.get_unique_operator_name('ReduceSum'))
             else:
                 axis_name = scope.get_unique_variable_name('axis')
                 container.add_initializer(
                     axis_name, onnx_proto.TensorProto.INT64, [1], [1])
                 container.add_node(
-                    'ReduceSum', [input, axis_name], norm, keepdims=1,
+                    'ReduceSum', [norm_abs, axis_name], norm, keepdims=1,
                     name=scope.get_unique_operator_name('ReduceSum'))
             apply_div(  # noqa
                 scope, [input, norm], output, container,
                 operator_name=scope.get_unique_operator_name(
                     'NormalizerNorm'))
+        elif norm == 'L2':
+            norm = scope.get_unique_variable_name('norm')
+            norm2 = scope.get_unique_variable_name('norm2')
+            container.add_node(
+                'ReduceSumSquare', input, norm, axes=[1], keepdims=1,
+                name=scope.get_unique_operator_name('ReduceSumSquare'))
+            container.add_node(
+                'Sqrt', [norm], norm2,
+                name=scope.get_unique_operator_name('Sqrt'))
+            apply_div(  # noqa
+                scope, [input, norm2], output, container,
+                operator_name=scope.get_unique_operator_name(
+                    'NormalizerNorm'))
         else:
             raise NotImplementedError(
                 "Normalization not implemented for norm %r." % norm)


Test patch:
diff --git a/tests/test_sklearn_normalizer_converter.py b/tests/test_sklearn_normalizer_converter.py
--- a/tests/test_sklearn_normalizer_converter.py
+++ b/tests/test_sklearn_normalizer_converter.py
@@ -7,7 +7,8 @@
 import numpy
 from sklearn.preprocessing import Normalizer
 from skl2onnx import convert_sklearn
-from skl2onnx.common.data_types import Int64TensorType, FloatTensorType
+from skl2onnx.common.data_types import (
+    Int64TensorType, FloatTensorType, DoubleTensorType)
 from test_utils import dump_data_and_model
 
 
@@ -15,44 +16,68 @@ class TestSklearnNormalizerConverter(unittest.TestCase):
     def test_model_normalizer(self):
         model = Normalizer(norm="l2")
         model_onnx = convert_sklearn(
-            model,
-            "scikit-learn normalizer",
-            [("input", Int64TensorType([None, 1]))],
-        )
+            model, "scikit-learn normalizer",
+            [("input", Int64TensorType([None, 1]))])
         self.assertTrue(model_onnx is not None)
         self.assertTrue(len(model_onnx.graph.node) == 1)
 
-    def test_model_normalizer_float(self):
+    def test_model_normalizer_float_l1(self):
+        model = Normalizer(norm="l1")
+        model_onnx = convert_sklearn(
+            model, "scikit-learn normalizer",
+            [("input", FloatTensorType([None, 3]))])
+        self.assertTrue(model_onnx is not None)
+        self.assertTrue(len(model_onnx.graph.node) == 1)
+        dump_data_and_model(
+            numpy.array([[1, -1, 3], [3, 1, 2]], dtype=numpy.float32),
+            model, model_onnx,
+            basename="SklearnNormalizerL1-SkipDim1")
+
+    def test_model_normalizer_float_l2(self):
         model = Normalizer(norm="l2")
         model_onnx = convert_sklearn(
-            model,
-            "scikit-learn normalizer",
-            [("input", FloatTensorType([None, 3]))],
-        )
+            model, "scikit-learn normalizer",
+            [("input", FloatTensorType([None, 3]))])
         self.assertTrue(model_onnx is not None)
         self.assertTrue(len(model_onnx.graph.node) == 1)
         dump_data_and_model(
-            numpy.array([[1, 1, 3], [3, 1, 2]], dtype=numpy.float32),
-            model,
-            model_onnx,
-            basename="SklearnNormalizerL2-SkipDim1",
-        )
+            numpy.array([[1, -1, 3], [3, 1, 2]], dtype=numpy.float32),
+            model, model_onnx,
+            basename="SklearnNormalizerL2-SkipDim1")
+
+    def test_model_normalizer_double_l1(self):
+        model = Normalizer(norm="l1")
+        model_onnx = convert_sklearn(
+            model, "scikit-learn normalizer",
+            [("input", DoubleTensorType([None, 3]))])
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            numpy.array([[1, -1, 3], [3, 1, 2]], dtype=numpy.float64),
+            model, model_onnx,
+            basename="SklearnNormalizerL1Double-SkipDim1")
+
+    def test_model_normalizer_double_l2(self):
+        model = Normalizer(norm="l2")
+        model_onnx = convert_sklearn(
+            model, "scikit-learn normalizer",
+            [("input", DoubleTensorType([None, 3]))])
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            numpy.array([[1, -1, 3], [3, 1, 2]], dtype=numpy.float64),
+            model, model_onnx,
+            basename="SklearnNormalizerL2Double-SkipDim1")
 
     def test_model_normalizer_float_noshape(self):
         model = Normalizer(norm="l2")
         model_onnx = convert_sklearn(
-            model,
-            "scikit-learn normalizer",
-            [("input", FloatTensorType([]))],
-        )
+            model, "scikit-learn normalizer",
+            [("input", FloatTensorType([]))])
         self.assertTrue(model_onnx is not None)
         self.assertTrue(len(model_onnx.graph.node) == 1)
         dump_data_and_model(
-            numpy.array([[1, 1, 3], [3, 1, 2]], dtype=numpy.float32),
-            model,
-            model_onnx,
-            basename="SklearnNormalizerL2-SkipDim1",
-        )
+            numpy.array([[1, -1, 3], [3, 1, 2]], dtype=numpy.float32),
+            model, model_onnx,
+            basename="SklearnNormalizerL2NoShape-SkipDim1")
 
 
 if __name__ == "__main__":