Repository: onnx/sklearn-onnx

Issue:
Support analyzer='char' for the TfidfVectorizer
Currently the converter only supports analyzer='word' mode for the TfidfVectorizer, please add the 'char' mode support.
StringNormalizer is now part of onnx. Update the converter.



Discussion:

Resolving patch:
diff --git a/skl2onnx/operator_converters/TextVectorizer.py b/skl2onnx/operator_converters/TextVectorizer.py
--- a/skl2onnx/operator_converters/TextVectorizer.py
+++ b/skl2onnx/operator_converters/TextVectorizer.py
@@ -8,6 +8,67 @@
 from ..common._registration import register_converter
 
 
+def _intelligent_split(text, op, tokenizer, existing):
+    """
+    Splits text into tokens. *scikit-learn*
+    merges tokens with ``' '.join(tokens)``
+    to name ngrams. ``'a  b'`` could be ``('a ', 'b')``
+    or ``('a', ' b')``.
+    See `ngram sequence
+    <https://github.com/scikit-learn/scikit-learn/blob/master/
+    sklearn/feature_extraction/text.py#L169>`_.
+    """
+    if op.analyzer == 'word':
+        if op.ngram_range[0] == op.ngram_range[1] == 1:
+            spl = [text]
+        elif op.ngram_range[0] == 1 and len(text) >= 2:
+            # Every element is in the vocabulary.
+            # Naive method
+            p1 = len(text) - len(text.lstrip())
+            p2 = len(text) - len(text.rstrip())
+            if p2 == 0:
+                p2 = len(text)
+            spl = text[p1:-p2].split()
+            if len(spl) == 0:
+                spl = [text]
+            else:
+                spl[0] = " " * p1 + spl[0]
+                spl[-1] = spl[-1] + " " * p2
+            if any(map(lambda g: g not in op.vocabulary_, spl)):
+                # TODO: handle this case with an algorithm
+                # which is able to break a string into
+                # known substrings.
+                raise RuntimeError("Unable to split n-grams '{}' "
+                                   "into tokens existing in the "
+                                   "vocabulary.".format(text))
+        else:
+            # We reuse the tokenizer hoping that will clear
+            # ambiguities but this might be slow.
+            spl = tokenizer(text)
+    else:
+        spl = list(text)
+
+    spl = tuple(spl)
+    if spl in existing:
+        raise RuntimeError("The converter cannot guess how to "
+                           "split an expression into tokens.")
+    if op.ngram_range[0] == 1:
+        # All grams should be existing in the vocabulary.
+        for g in spl:
+            if g not in op.vocabulary_:
+                nos = g.replace(" ", "")
+                couples = [(w, w.replace(" ", "")) for w in op.vocabulary_]
+                possible = ['{}'.format(w[0])
+                            for w in couples if w[1] == nos]
+                raise RuntimeError("Unable to split n-grams '{}' "
+                                   "due to '{}' "
+                                   "into tokens existing in the "
+                                   "vocabulary. Found:\n{}".format(
+                                       text, g, possible))
+    existing.add(spl)
+    return spl
+
+
 def convert_sklearn_text_vectorizer(scope, operator, container):
     """
     Converters for class
@@ -19,9 +80,17 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
     Additional options
     ------------------
 
+    regex: string
+        The default will change to true in version 1.6.0.
+        The tokenizer splits into words using this regular
+        expression or the regular expression specified by
+        *scikit-learn* is the value is an empty string.
+        See also note below.
+        Default value: None
     sep: list of separators
         These separators are used to split a string into words.
-        Default value: ``[' ', '.', '?', ',', ';', ':', '!']``
+        Options *sep* is ignore if options *regex* is not None.
+        Default value: ``[' ', '.', '?', ',', ';', ':', '!']``.
 
     Example (from :ref:`l-example-tfidfvectorizer`):
 
@@ -32,11 +101,32 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
         model_onnx = convert_sklearn(pipeline, "tfidf",
                                      initial_types=[("input", StringTensorType([1, 2]))],
                                      options=seps)
+
+    The default regular expression of the tokenizer is ``(?u)\\\\b\\\\w\\\\w+\\\\b``
+    (see `re <https://docs.python.org/3/library/re.html>`_).
+    This expression may not supported by the library handling the backend.
+    `onnxruntime <https://github.com/Microsoft/onnxruntime>`_ uses
+    `re2 <https://github.com/google/re2>`_. You may need to switch
+    to a custom tokenizer based on
+    `python wrapper for re2 <https://pypi.org/project/re2/>_`
+    or its sources `pyre2 <https://github.com/facebook/pyre2>`_
+    (`syntax <https://github.com/google/re2/blob/master/doc/syntax.txt>`_).
+    If the regular expression is not specified and if
+    the instance of TfidfVectorizer is using the default
+    pattern ``(?u)\\\\b\\\\w\\\\w+\\\\b``, it is replaced by
+    ``[a-zA-Z0-9_]+``. Any other case has to be
+    manually handled.
+
+    Regular expression ``[^\\\\\\\\n]`` is used to split
+    a sentance into character (and not works) if ``analyser=='char'``.
+    The mode ``analyser=='char_wb'`` is not implemented.
+    ````
+    
     """ # noqa
 
     op = operator.raw_operator
 
-    if op.analyzer != "word":
+    if op.analyzer == "char_wb":
         raise NotImplementedError(
             "CountVectorizer cannot be converted, "
             "only tokenizer='word' is supported.")
@@ -46,16 +136,42 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
             "only stip_accents=None is supported.")
 
     options = container.get_options(
-            op, dict(sep=[' ', '.', '?', ',', ';', ':', '!']))
-    if set(options) != {'sep'}:
+            op, dict(sep="DEFAULT",
+                     regex=None))
+    if set(options) != {'sep', 'regex'}:
         raise RuntimeError("Unknown option {} for {}".format(
                                 set(options) - {'sep'}, type(op)))
-    default_pattern = '(?u)\\b\\w\\w+\\b'
-    default_separators = options['sep']
-    if op.token_pattern != default_pattern:
-        raise NotImplementedError(
-            "Only the default tokenizer based on default regular expression "
-            "'{0}' is implemented.".format(default_pattern))
+
+    if op.analyzer == 'word':
+        default_pattern = '(?u)\\b\\w\\w+\\b'
+        if options['sep'] == "DEFAULT" and options['regex'] is None:
+            warnings.warn("Converter for TfidfVectorizer will use "
+                          "scikit-learn regular expression by default "
+                          "in version 1.6.",
+                          DeprecationWarning)
+            default_separators = [' ', '.', '?', ',', ';', ':', '!']
+            regex = op.token_pattern
+            if regex == default_pattern:
+                regex = '[a-zA-Z0-9_]+'
+            default_separators = None
+        elif options['regex'] is not None:
+            if options['regex']:
+                regex = options['regex']
+            else:
+                regex = op.token_pattern
+                if regex == default_pattern:
+                    regex = '[a-zA-Z0-9_]+'
+            default_separators = None
+        else:
+            regex = None
+            default_separators = options['sep']
+    else:
+        if options['sep'] != 'DEFAULT':
+            raise RuntimeError("Option sep has not effect "
+                               "if analyser != 'word'.")
+        regex = options['regex'] if options['regex'] else '.'
+        default_separators = None
+
     if op.preprocessor is not None:
         raise NotImplementedError(
             "Custom preprocessor cannot be converted into ONNX.")
@@ -66,25 +182,21 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
         raise NotImplementedError(
             "Operator StringNormalizer cannot remove accents.")
 
-    msg = ("The default regular expression '{0}' splits strings based on "
-           "anything but a space. The current specification splits strings "
-           "based on the following separators {1}.")
-    warnings.warn(msg.format(default_pattern, default_separators))
-
     if op.lowercase or op.stop_words_:
         # StringNormalizer
 
         op_type = 'StringNormalizer'
         attrs = {'name': scope.get_unique_operator_name(op_type)}
         attrs.update({
-            'casechangeaction': 'LOWER',
+            'case_change_action': 'LOWER',
             'is_case_sensitive': not op.lowercase,
         })
         if op.stop_words_:
             attrs['stopwords'] = list(sorted(op.stop_words_))
         normalized = scope.get_unique_variable_name('normalized')
         container.add_node(op_type, operator.input_full_names,
-                           normalized, op_domain='com.microsoft', **attrs)
+                           normalized, op_version=10,
+                           op_domain='ai.onnx', **attrs)
     else:
         normalized = operator.input_full_names
 
@@ -97,10 +209,13 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
     attrs = {'name': scope.get_unique_operator_name(op_type)}
     attrs.update({
         'pad_value': padvalue,
-        'separators': default_separators,
         'mark': False,
         'mincharnum': 1,
     })
+    if regex is None:
+        attrs['separators'] = default_separators
+    else:
+        attrs['tokenexp'] = regex
 
     tokenized = scope.get_unique_variable_name('tokenized')
     container.add_node(op_type, normalized, tokenized,
@@ -110,7 +225,7 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
     # Tokenizer outputs shape {1, C} or {1, 1, C}.
     # Second shape is not allowed by TfIdfVectorizer.
     # We use Flatten which produces {1, C} in both cases.
-    flatt_tokenized = scope.get_unique_variable_name('flatoken')
+    flatt_tokenized = scope.get_unique_variable_name('flattened')
     container.add_node("Flatten", tokenized, flatt_tokenized,
                        name=scope.get_unique_operator_name('Flatten'))
     tokenized = flatt_tokenized
@@ -132,7 +247,13 @@ def convert_sklearn_text_vectorizer(scope, operator, container):
 
     # Scikit-learn sorts n-grams by alphabetical order..
     # onnx assumes it is sorted by n.
-    split_words = [(w.split(), w) for w in words]
+    tokenizer = op.build_tokenizer()
+    split_words = []
+    existing = set()
+    for w in words:
+        spl = _intelligent_split(w, op, tokenizer, existing)
+        split_words.append((spl, w))
+
     ng_split_words = [(len(a[0]), a[0], i) for i, a in enumerate(split_words)]
     ng_split_words.sort()
     key_indices = [a[2] for a in ng_split_words]


Test patch:
diff --git a/tests/test_SklearnCountVectorizerConverter.py b/tests/test_SklearnCountVectorizerConverter.py
--- a/tests/test_SklearnCountVectorizerConverter.py
+++ b/tests/test_SklearnCountVectorizerConverter.py
@@ -1,15 +1,21 @@
 """
-Tests scikit-learn's binarizer converter.
+Tests scikit-learn's CountVectorizer converter.
 """
 import unittest
+from distutils.version import StrictVersion
 import numpy
 from sklearn.feature_extraction.text import CountVectorizer
 from skl2onnx import convert_sklearn
 from skl2onnx.common.data_types import StringTensorType
+import onnx
 from test_utils import dump_data_and_model
 
 
 class TestSklearnCountVectorizer(unittest.TestCase):
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_count_vectorizer11(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -28,9 +34,12 @@ def test_model_count_vectorizer11(self):
             model_onnx,
             basename="SklearnCountVectorizer11-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_count_vectorizer22(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -49,9 +58,12 @@ def test_model_count_vectorizer22(self):
             model_onnx,
             basename="SklearnCountVectorizer22-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_count_vectorizer12(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -70,9 +82,12 @@ def test_model_count_vectorizer12(self):
             model_onnx,
             basename="SklearnCountVectorizer12-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_count_vectorizer13(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -91,7 +106,7 @@ def test_model_count_vectorizer13(self):
             model_onnx,
             basename="SklearnCountVectorizer13-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
 
diff --git a/tests/test_SklearnDocumentation.py b/tests/test_SklearnDocumentation.py
--- a/tests/test_SklearnDocumentation.py
+++ b/tests/test_SklearnDocumentation.py
@@ -57,9 +57,8 @@ class TestSklearnDocumentation(unittest.TestCase):
     "Test example from the documentation of scikit-learn."
 
     @unittest.skipIf(
-        StrictVersion(onnx.__version__) <= StrictVersion("1.3"),
-        reason="Encoding issue fixed in a later version",
-    )
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Encoding issue fixed in a later version")
     def test_pipeline_tfidf(self):
         categories = ["alt.atheism", "talk.religion.misc"]
         train = fetch_20newsgroups(random_state=1,
@@ -85,10 +84,8 @@ def test_pipeline_tfidf(self):
             tfi,
             model_onnx,
             basename="SklearnDocumentationTfIdf-OneOff-SklCol",
-            allow_failure="StrictVersion(onnx.__version__)"
-                          " <= StrictVersion('1.3') or "
-                          "StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+            allow_failure="StrictVersion(onnxruntime.__version__)"
+                          " <= StrictVersion('0.4.0')",
         )
 
     @unittest.skipIf(
@@ -96,9 +93,8 @@ def test_pipeline_tfidf(self):
         reason="ColumnTransformer introduced in 0.20",
     )
     @unittest.skipIf(
-        StrictVersion(onnx.__version__) <= StrictVersion("1.3"),
-        reason="Encoding issue fixed in a later version",
-    )
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Encoding issue fixed in a later version")
     def test_pipeline_tfidf_pipeline_minmax(self):
         categories = ["alt.atheism", "talk.religion.misc"]
         train = fetch_20newsgroups(random_state=1,
@@ -154,10 +150,8 @@ def test_pipeline_tfidf_pipeline_minmax(self):
             model_onnx,
             verbose=False,
             basename="SklearnDocumentationTfIdfUnion1-OneOff-Dec2",
-            allow_failure="StrictVersion(onnx.__version__)"
-                          " <= StrictVersion('1.3') or "
-                          "StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+            allow_failure="StrictVersion(onnxruntime.__version__)"
+                          " <= StrictVersion('0.4.0')",
         )
 
 
diff --git a/tests/test_SklearnTfidfTransformerConverterSparse.py b/tests/test_SklearnTfidfTransformerConverterSparse.py
--- a/tests/test_SklearnTfidfTransformerConverterSparse.py
+++ b/tests/test_SklearnTfidfTransformerConverterSparse.py
@@ -20,8 +20,7 @@ class TestSklearnTfidfVectorizerSparse(unittest.TestCase):
     @unittest.skipIf(
         StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
         # issue with encoding
-        reason="https://github.com/onnx/onnx/pull/1734",
-    )
+        reason="https://github.com/onnx/onnx/pull/1734")
     def test_model_tfidf_transform_bug(self):
         categories = [
             "alt.atheism",
diff --git a/tests/test_SklearnTfidfVectorizerConverter.py b/tests/test_SklearnTfidfVectorizerConverter.py
--- a/tests/test_SklearnTfidfVectorizerConverter.py
+++ b/tests/test_SklearnTfidfVectorizerConverter.py
@@ -1,15 +1,24 @@
 """
-Tests scikit-learn's binarizer converter.
+Tests scikit-learn's tfidf converter.
 """
 import unittest
+from distutils.version import StrictVersion
 import numpy
 from sklearn.feature_extraction.text import TfidfVectorizer
 from skl2onnx import convert_sklearn
 from skl2onnx.common.data_types import StringTensorType
+import onnx
 from test_utils import dump_data_and_model
 
 
 class TestSklearnTfidfVectorizer(unittest.TestCase):
+
+    def get_options(self):
+        return {TfidfVectorizer: {"regex": None}}
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer11(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -20,7 +29,8 @@ def test_model_tfidf_vectorizer11(self):
         vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         dump_data_and_model(
             corpus,
@@ -28,10 +38,38 @@ def test_model_tfidf_vectorizer11(self):
             model_onnx,
             basename="SklearnTfidfVectorizer11-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
-    def test_model_tfidf_vectorizer11_empty_string(self):
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_empty_string_case1(self):
+        corpus = numpy.array([
+                'This is the first document.',
+                'This document is the second document.',
+                'And this is the third one.',
+                ' ',
+                ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
+        vect.fit(corpus[:3].ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+
+        # TfidfVectorizer in onnxruntime fails with empty strings
+        dump_data_and_model(
+            corpus[2:], vect, model_onnx,
+            basename="SklearnTfidfVectorizer11EmptyStringSepCase1-"
+                     "OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) < "
+                          "StrictVersion('0.3.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_empty_string_case2(self):
         corpus = numpy.array([
             "This is the first document.",
             "This document is the second document.",
@@ -41,7 +79,8 @@ def test_model_tfidf_vectorizer11_empty_string(self):
         vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         # onnxruntime fails with empty strings
         dump_data_and_model(
@@ -53,6 +92,9 @@ def test_model_tfidf_vectorizer11_empty_string(self):
                           " <= StrictVersion('0.3.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer11_out_vocabulary(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -63,7 +105,8 @@ def test_model_tfidf_vectorizer11_out_vocabulary(self):
         vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         corpus = numpy.array([
             "AZZ ZZ This is the first document.",
@@ -77,9 +120,12 @@ def test_model_tfidf_vectorizer11_out_vocabulary(self):
             model_onnx,
             basename="SklearnTfidfVectorizer11OutVocab-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer22(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -90,7 +136,8 @@ def test_model_tfidf_vectorizer22(self):
         vect = TfidfVectorizer(ngram_range=(2, 2), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         dump_data_and_model(
             corpus,
@@ -98,15 +145,19 @@ def test_model_tfidf_vectorizer22(self):
             model_onnx,
             basename="SklearnTfidfVectorizer22-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer21(self):
         corpus = numpy.array(["AA AA", "AA AA BB"]).reshape((2, 1))
         vect = TfidfVectorizer(ngram_range=(1, 2), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         dump_data_and_model(
             corpus,
@@ -114,9 +165,12 @@ def test_model_tfidf_vectorizer21(self):
             model_onnx,
             basename="SklearnTfidfVectorizer22S-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer12(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -127,7 +181,8 @@ def test_model_tfidf_vectorizer12(self):
         vect = TfidfVectorizer(ngram_range=(1, 2), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         dump_data_and_model(
             corpus,
@@ -135,9 +190,12 @@ def test_model_tfidf_vectorizer12(self):
             model_onnx,
             basename="SklearnTfidfVectorizer22-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer12_normL1(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -156,9 +214,12 @@ def test_model_tfidf_vectorizer12_normL1(self):
             model_onnx,
             basename="SklearnTfidfVectorizer22L1-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer12_normL2(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -169,7 +230,8 @@ def test_model_tfidf_vectorizer12_normL2(self):
         vect = TfidfVectorizer(ngram_range=(1, 2), norm="l2")
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         dump_data_and_model(
             corpus,
@@ -177,9 +239,12 @@ def test_model_tfidf_vectorizer12_normL2(self):
             model_onnx,
             basename="SklearnTfidfVectorizer22L2-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer13(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -190,7 +255,8 @@ def test_model_tfidf_vectorizer13(self):
         vect = TfidfVectorizer(ngram_range=(1, 3), norm=None)
         vect.fit(corpus.ravel())
         model_onnx = convert_sklearn(vect, "TfidfVectorizer",
-                                     [("input", StringTensorType([1, 1]))])
+                                     [("input", StringTensorType([1, 1]))],
+                                     options=self.get_options())
         self.assertTrue(model_onnx is not None)
         dump_data_and_model(
             corpus,
@@ -198,9 +264,12 @@ def test_model_tfidf_vectorizer13(self):
             model_onnx,
             basename="SklearnTfidfVectorizer13-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer11parenthesis_class(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -228,11 +297,14 @@ def test_model_tfidf_vectorizer11parenthesis_class(self):
             model_onnx,
             basename="SklearnTfidfVectorizer11ParenthesisClass-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.0') or "
+                          " <= StrictVersion('0.4.0') or "
                           "StrictVersion(onnx.__version__)"
                           " <= StrictVersion('1.3')",
         )
 
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
     def test_model_tfidf_vectorizer11_idparenthesis_id(self):
         corpus = numpy.array([
             "This is the first document.",
@@ -276,7 +348,7 @@ def test_model_tfidf_vectorizer11_idparenthesis_id(self):
             model_onnx,
             basename="SklearnTfidfVectorizer11ParenthesisId-OneOff-SklCol",
             allow_failure="StrictVersion(onnxruntime.__version__)"
-                          " <= StrictVersion('0.2.1')",
+                          " <= StrictVersion('0.4.0')",
         )
 
 
diff --git a/tests/test_SklearnTfidfVectorizerConverterChar.py b/tests/test_SklearnTfidfVectorizerConverterChar.py
new file mode 100644
--- /dev/null
+++ b/tests/test_SklearnTfidfVectorizerConverterChar.py
@@ -0,0 +1,163 @@
+"""
+Tests scikit-learn's tfidf converter.
+"""
+import unittest
+from distutils.version import StrictVersion
+import numpy
+from sklearn.feature_extraction.text import TfidfVectorizer
+from skl2onnx import convert_sklearn
+from skl2onnx.common.data_types import StringTensorType
+import onnx
+from test_utils import dump_data_and_model
+
+
+class TestSklearnTfidfVectorizerRegex(unittest.TestCase):
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_short_word(self):
+        corpus = numpy.array([
+                'This is the first document.',
+                'This document is the second document.',
+                ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None,
+                               analyzer='word', token_pattern=".{1,2}")
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))])
+        self.assertTrue(model_onnx is not None)
+
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11CharW2-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')",
+            verbose=False)
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer22_short_word(self):
+        corpus = numpy.array([
+                'This is the first document.',
+                'This document is the second document.',
+                ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm=None,
+                               analyzer='word', token_pattern=".{1,5}")
+        vect.fit(corpus.ravel())
+        try:
+            convert_sklearn(vect, 'TfidfVectorizer',
+                            [('input', StringTensorType([1, 1]))])
+        except RuntimeError as e:
+            assert ("Unable to split n-grams ' seco nd do' "
+                    "into tokens") in str(e)
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_char(self):
+        corpus = numpy.array([
+                'This is the first document.',
+                'This document is the second document.',
+                ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None,
+                               analyzer='char')
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))])
+        self.assertTrue(model_onnx is not None)
+
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11Char-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')",
+            verbose=False)
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer12_char(self):
+        corpus = numpy.array([
+                'This is the first document.',
+                'This document is the second document.',
+                ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm=None,
+                               analyzer='char')
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))])
+        self.assertTrue(model_onnx is not None)
+
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer12Char-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')",
+            verbose=False)
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer12_normL1_char(self):
+        corpus = numpy.array([
+                'This is the first document.',
+                'This document is the second document.',
+                'And this is the third one.',
+                'Is this the first document?',
+                ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm='l1', analyzer='char')
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))])
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer12L1Char-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer12_short_word_spaces(self):
+        corpus = numpy.array([
+                'This is  the  first document.',
+                'This document is the second  document.',
+                ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm=None,
+                               analyzer='word', token_pattern=".{1,3}")
+        vect.fit(corpus.ravel())
+        try:
+            model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                         [('input', StringTensorType([1, 1]))])
+            self.assertTrue(model_onnx is not None)
+        except RuntimeError as e:
+            assert "Unable to split n-grams 's i s  '" in str(e)
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_short_word_spaces(self):
+        corpus = numpy.array([
+                'This is  the  first document.',
+                'This document is the second  document.',
+                ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None,
+                               analyzer='word', token_pattern=".{1,3}")
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))])
+        self.assertTrue(model_onnx is not None)
+
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11CharW2-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')",
+            verbose=False)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/test_SklearnTfidfVectorizerConverterRegex.py b/tests/test_SklearnTfidfVectorizerConverterRegex.py
new file mode 100644
--- /dev/null
+++ b/tests/test_SklearnTfidfVectorizerConverterRegex.py
@@ -0,0 +1,311 @@
+"""
+Tests scikit-learn's tfidf converter.
+"""
+import unittest
+from distutils.version import StrictVersion
+import numpy
+from sklearn.feature_extraction.text import TfidfVectorizer
+from skl2onnx import convert_sklearn
+from skl2onnx.common.data_types import StringTensorType
+import onnx
+from test_utils import dump_data_and_model
+
+
+class TestSklearnTfidfVectorizerRegex(unittest.TestCase):
+
+    def get_options(self):
+        return {TfidfVectorizer: {"regex": ""}}
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11Regex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_word4(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(
+            1, 1), norm=None, token_pattern="[a-zA-Z]{1,4}")
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11Regex4-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_empty_string(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            '',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        # TfidfVectorizer in onnxruntime fails with empty strings
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11EmptyStringRegex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) "
+                          "<= StrictVersion('0.3.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_out_vocabulary(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        corpus = numpy.array([
+            'AZZ ZZ This is the first document.',
+            'BZZ ZZ This document is the second document.',
+            'ZZZ ZZ And this is the third one.',
+            'WZZ ZZ Is this the first document?',
+        ]).reshape((4, 1))
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11OutVocabRegex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer22(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(2, 2), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer22Regex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer12(self):
+        corpus = numpy.array([
+            'AA AA',
+            'AA AA BB',
+        ]).reshape((2, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer12SRegex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer122(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer12Regex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer12_normL1(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm='l1')
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))])
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer12L1Regex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer12_normL2(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 2), norm='l2')
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer12L2Regex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer13(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the first document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 3), norm=None)
+        vect.fit(corpus.ravel())
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=self.get_options())
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer13Regex-OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11parenthesis_class(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the (first) document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
+        vect.fit(corpus.ravel())
+        extra = {TfidfVectorizer: {'sep': [' ', '.', '?', ',', ';',
+                                           ':', '!', '(', ')'],
+                                   'regex': None}}
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=extra)
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11ParenthesisClassRegex-"
+                     "OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.3.0') or "
+                          "StrictVersion(onnx.__version__) <= "
+                          "StrictVersion('1.4.1')")
+
+    @unittest.skipIf(
+        StrictVersion(onnx.__version__) <= StrictVersion("1.4.1"),
+        reason="Requires opset 9.")
+    def test_model_tfidf_vectorizer11_idparenthesis_id(self):
+        corpus = numpy.array([
+            'This is the first document.',
+            'This document is the second document.',
+            'And this is the third one.',
+            'Is this the (first) document?',
+        ]).reshape((4, 1))
+        vect = TfidfVectorizer(ngram_range=(1, 1), norm=None)
+        vect.fit(corpus.ravel())
+
+        extra = {id(vect): {"sep2": [' ', '.', '?', ',', ';', ':',
+                                     '!', '(', ')'],
+                            'regex': None}}
+        try:
+            convert_sklearn(vect, 'TfidfVectorizer',
+                            [('input', StringTensorType([1, 1]))],
+                            options=extra)
+        except RuntimeError:
+            pass
+
+        extra = {id(vect): {"sep": [' ', '.', '?', ',', ';',
+                                    ':', '!', '(', ')'],
+                            "regex": None}}
+        model_onnx = convert_sklearn(vect, 'TfidfVectorizer',
+                                     [('input', StringTensorType([1, 1]))],
+                                     options=extra)
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            corpus, vect, model_onnx,
+            basename="SklearnTfidfVectorizer11ParenthesisIdRegex-"
+                     "OneOff-SklCol",
+            allow_failure="StrictVersion(onnxruntime.__version__) <= "
+                          "StrictVersion('0.4.0')")
+
+
+if __name__ == "__main__":
+    unittest.main()