Repository: onnx/sklearn-onnx

Issue:
saving model works with binary classifier, fails with multiclass classifier
I am using Sklearn's SVC for a classifier, with TfidfVectorizer as the feature embedding method. Following #478  I am using `skl2onnx` version 1.7.1. My code is here:

```
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import SVC
import numpy as np

from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType


#import json
#with open('fake_data.json', 'r') as f:
#    dataset = json.load(f)
#docs = [doc for (doc, _) in dataset]
#labels = [label for (_, label) in dataset]

data = [
    ["schedule a meeting", 0],
    ["schedule a sync with the team", 0],
    ["slot in a meeting", 0],
    ["call ron", 1],
    ["make a phone call", 1],
    ["call in on the phone", 2] # changing this from 2 to 1 will allow code to successfully run
]
docs = [doc for (doc, _) in data]
labels = [label for (_, label) in data]


vectorizer = TfidfVectorizer()
vectorizer.fit_transform(docs)
embeddings = vectorizer.transform(docs)
dim = embeddings.shape[1]
#embeddings = np.vstack(embeddings)

clf = SVC()
clf.fit(embeddings, labels)

initial_type = [('float_input', FloatTensorType([1, dim]))]
onnx_model = convert_sklearn(clf, initial_types=initial_type) # this is line 37, where the crash occurs
with open('model.onnx', 'wb') as f:
    f.write(onnx_model.SerializeToString())
```

When I run the above, I get:

```
Traceback (most recent call last):
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\common\_container.py", line 536, in add_node
    node = make_node(op_type, inputs, outputs, name=name,
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\proto\onnx_helper_modified.py", line 66, in make_node
    node.attribute.extend(
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\google\protobuf\internal\containers.py", line 410, in extend
    for message in elem_seq:
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\proto\onnx_helper_modified.py", line 67, in <genexpr>
    make_attribute(key, value, dtype=_dtype, domain=domain)
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\proto\onnx_helper_modified.py", line 175, in make_attribute
    raise ValueError(
ValueError: You passed in an iterable attribute but I cannot figure out its applicable type, key='coefficients', type=<class 'numpy.matrix'>, dtype=float32, types=[<class 'numpy.matrix'>, <class 'numpy.matrix'>].

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File ".\dust.py", line 37, in <module>
    onnx_model = convert_sklearn(clf, initial_types=initial_type)
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\convert.py", line 160, in convert_sklearn
    onnx_model = convert_topology(topology, name, doc_string, target_opset,
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\common\_topology.py", line 1069, in convert_topology
    conv(scope, operator, container)
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\common\_registration.py", line 29, in __call__
    return self._fct(*args)
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\operator_converters\support_vector_machines.py", line 214, in convert_sklearn_svm_classifier
    container.add_node(
  File "C:\Users\Stefan Larson\AppData\Local\Programs\Python\Python38\lib\site-packages\skl2onnx\common\_container.py", line 539, in add_node
    raise ValueError("Unable to create node '{}' with name='{}'."
ValueError: Unable to create node 'SVMClassifier' with name='SVMc'.
```

The code crashes at the `convert_sklearn` function call. However, when I alter the dataset to have only two classes ('1', and '0'), the code runs successfully.


Discussion:

Resolving patch:
diff --git a/skl2onnx/proto/onnx_helper_modified.py b/skl2onnx/proto/onnx_helper_modified.py
--- a/skl2onnx/proto/onnx_helper_modified.py
+++ b/skl2onnx/proto/onnx_helper_modified.py
@@ -171,6 +171,10 @@ def make_attribute(
         elif all(isinstance(v, GraphProto) for v in value):
             attr.graphs.extend(value)
             attr.type = AttributeProto.GRAPHS
+        elif isinstance(value, np.matrix):
+            return make_attribute(
+                key, np.asarray(value).ravel(), dtype=dtype, domain=domain,
+                doc_string=doc_string)
         else:
             raise ValueError(
                 "You passed in an iterable attribute but I cannot figure out "


Test patch:
diff --git a/tests/test_sklearn_tfidf_vectorizer_converter.py b/tests/test_sklearn_tfidf_vectorizer_converter.py
--- a/tests/test_sklearn_tfidf_vectorizer_converter.py
+++ b/tests/test_sklearn_tfidf_vectorizer_converter.py
@@ -6,13 +6,14 @@
 import numpy
 from numpy.testing import assert_almost_equal
 from sklearn.feature_extraction.text import TfidfVectorizer
+from sklearn.svm import SVC
 try:
     from sklearn.compose import ColumnTransformer
 except ImportError:
     # Old scikit-learn
     ColumnTransformer = None
 from skl2onnx import convert_sklearn
-from skl2onnx.common.data_types import StringTensorType
+from skl2onnx.common.data_types import StringTensorType, FloatTensorType
 import onnx
 import onnxruntime
 from onnxruntime import InferenceSession
@@ -504,6 +505,41 @@ def test_model_tfidf_vectorizer11_64(self):
         res = sess.run(None, {'input': corpus.ravel()})[0]
         assert res.shape == (4, 9)
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    @unittest.skipIf(
+        StrictVersion(onnxruntime.__version__) < StrictVersion("1.3.0"),
+        reason="Requires opset 9.")
+    def test_tfidf_svm(self):
+        data = [
+            ["schedule a meeting", 0],
+            ["schedule a sync with the team", 0],
+            ["slot in a meeting", 0],
+            ["call ron", 1],
+            ["make a phone call", 1],
+            ["call in on the phone", 2]
+        ]
+        docs = [doc for (doc, _) in data]
+        labels = [label for (_, label) in data]
+
+        vectorizer = TfidfVectorizer()
+        vectorizer.fit_transform(docs)
+        embeddings = vectorizer.transform(docs)
+        dim = embeddings.shape[1]
+
+        clf = SVC()
+        clf.fit(embeddings, labels)
+        embeddings = embeddings.astype(numpy.float32).todense()
+        exp = clf.predict(embeddings)
+
+        initial_type = [('input', FloatTensorType([None, dim]))]
+        model_onnx = convert_sklearn(clf, initial_types=initial_type,
+                                     target_opset=TARGET_OPSET)
+        sess = InferenceSession(model_onnx.SerializeToString())
+        res = sess.run(None, {'input': embeddings})[0]
+        assert_almost_equal(exp, res)
+
 
 if __name__ == "__main__":
     unittest.main()