def make_model(graph, **kwargs): # type: (GraphProto, **Any) -> ModelProto model = ModelProto() # Touch model.ir_version so it is stored as the version from which it is # generated. model.ir_version = IR_VERSION model.graph.CopyFrom(graph) opset_imports = None # type: Optional[Sequence[OperatorSetIdProto]] opset_imports = kwargs.pop('opset_imports', None) # type: ignore if opset_imports is not None: model.opset_import.extend(opset_imports) else: # Default import imp = model.opset_import.add() imp.version = defs.onnx_opset_version() for k, v in kwargs.items(): # TODO: Does this work with repeated fields? setattr(model, k, v) return model
def tensorflow_graph_to_onnx_graph(cls, graph_def, output, opset=(("", 0), ), name="graph"): """Converts a Tensorflow Graph Proto to an ONNX graph This function converts a Tensorflow Graph proto to an equivalent representation of ONNX graph. :param graph_def: Tensorflow Graph Proto object. :param output: A Tensorflow NodeDef object specifying which node to be taken as output of the ONNX graph. :param opset: Opset, which should be ((str domain: int version number),). :param name: The name of the output ONNX Graph. :returns: The equivalent ONNX Graph Proto object. """ # This list holds the protobuf objects of type ValueInfoProto # representing the input to the converted ONNX graph. inputs_proto = [] # This list holds the protobuf objects of type NodeProto # representing the ops in the converted ONNX graph. ops_proto = [] # This dictionary contains a map from the name of the constant # op to the array of values it holds. This is useful because # tensorflow is less eager to know about input values at # graph construction time than ONNX. That is to say, some ONNX # attributes are input tensors in TF. This dictionary extracts # those values of constant tensors that are known at graph # construction time. consts = {} # Sometimes the constants are used as inputs to ops. This list # holds initializers that creates global constant tensors available # to be accessed by ops as inputs (as oppose to attributes which # is supplied by the `consts` map above). consts_proto = [] node_tup = [(node.name, TensorflowNode(node)) for node in graph_def.node] for name, node in node_tup: if node.op == "Placeholder": # Tensorflow requires dtype to be known. # TODO: currently `dtype` is translated to `to`. onnx_type = node.attr["dtype"] shape = node.attr["shape"] input_proto = make_tensor_value_info(name, onnx_type, shape) inputs_proto.append(input_proto) elif node.op == "Const": const_dim = len(node.attr["value"].shape) consts[name] = node.attr["value"] raw_values = ([node.attr["value"].tolist()] if const_dim == 0 else node.attr["value"].flatten().tolist()) if const_dim == 0: values = [node.attr["value"]] else: values = node.attr["value"] shape = np.array(values).shape consts_proto.append( make_tensor(name=name, data_type=node.attr["dtype"], dims=shape, vals=raw_values)) input_proto = make_tensor_value_info(name, node.attr["dtype"], shape) inputs_proto.append(input_proto) else: splitted_op_name = node.op.split(".") op_domain = "" if len(splitted_op_name) == 1 else ".".join( splitted_op_name[:-1]) op_name = splitted_op_name[-1] handler_name = "handle_" + op_name_to_lower(op_name) # TODO per domain frontend_tf_opset_version? versions = frontend_tf_opset_version[op_name_to_lower(op_name)] opset_dict = {} onnx_domain = defs.ONNX_DOMAIN for domain, version in opset: if domain == "ai.onnx": domain = "" opset_dict[domain] = version defs.ONNX_DOMAIN = domain assert isinstance( version, int ) and (version <= defs.onnx_opset_version()) and ( version >= 0 ), "Opset should be an int less than or equal to {}, but {}: {}".format( defs.onnx_opset_version(), type(version), version) defs.ONNX_DOMAIN = onnx_domain opset_ver = opset_dict[op_domain] if opset_ver == 0: version = max(versions) else: versions = sorted(versions + [opset_ver]) version = versions[max( [i for i, v in enumerate(versions) if v == opset_ver]) - 1] camel_domain = "".join(w.title() for w in op_domain.split(".")) frontend_ver = "frontend_v{}".format(version) frontend_class_name = "{}TensorflowFrontend".format( camel_domain) frontend_module = cls.frontend_version_cache.setdefault( frontend_ver, importlib.import_module("onnx_tf.frontends." + frontend_ver)) if hasattr(frontend_module, frontend_class_name): frontend = getattr(frontend_module, frontend_class_name) else: assert NotImplementedError, \ "{} for domain {} is not implemented".format(frontend_ver, op_domain) # Check if specialized handler exists. if hasattr(frontend, handler_name): method_to_call = getattr(frontend, handler_name) node = method_to_call(node, consts=consts, node_dict=dict(node_tup)) if isinstance(node, list): ops_proto.extend(node) else: ops_proto.append(node) elif node.op in TF_OP_STR_TO_ONNX_OP.keys(): # Remove tensorflow-specific attrs that are not # needed/allowed in ONNX. attr = cls.DEFAULT_TF_ATTR_PER_OP.get(node.op, {}) filtered_attr = dict( filter(lambda pair: pair[0] not in TF_ATTR_TO_REMOVE, node.attr.items())) node_output = name ops_proto.append( make_node(TF_OP_STR_TO_ONNX_OP[node.op], node.inputs, [node_output], name=name, **filtered_attr)) else: raise NotImplementedError( "{} op is not implemented.".format(node.op)) output = TensorflowNode(output) # making output proto # TODO: deal with multi-output case. # TODO: default to BOOL, cf. # https://github.com/tensorflow/tensorflow/issues/14769 output_onnx_type = output.attr.get("T", TensorProto.BOOL) output_proto = [] for i in range(len(output.attr["_output_shapes"])): output_name = output.name + ":{}".format( i) if i > 0 else output.name output_proto.append( make_tensor_value_info(output_name, output_onnx_type, output.attr["_output_shapes"][i])) inputs = list( chain.from_iterable(map(lambda p: list(p.input), ops_proto))) # Remove proto in inputs_proto and consts_proto if proto is not used as input in ONNX inputs_proto = list(filter(lambda x: x.name in inputs, inputs_proto)) consts_proto = list(filter(lambda x: x.name in inputs, consts_proto)) return make_graph(ops_proto, name, inputs_proto, output_proto, consts_proto)
onnx_node, desc=desc, expected_attributes=TopK_11.atts, **options) if self.sorted not in (True, 1): raise RuntimeError( "TopK does not implement anything for sorted=0.") def _run(self, data, ink): # pylint: disable=W0221 """ Runtime for operator *TopK*. The implementation is not the most efficient as it sorts everything then extracts the top *k* values. .. warning:: ONNX specifications may be imprecise in case of negative value for axis. The implementation follows what :epkg:`onnxruntime` does in `top_k.cc <https://github.com/Microsoft/onnxruntime/blob/master/onnxruntime/core/providers/cpu/math/top_k.cc#L63>`_. """ return _CommonTopK._common_run(self, data, ink, self.largest) if onnx_opset_version() >= 11: TopK = TopK_11 elif onnx_opset_version() >= 10: TopK = TopK_10 else: TopK = TopK_1
def get_max_opset(): """ Returns the highest available onnx opset version. """ from onnx.defs import onnx_opset_version return min(onnx_opset_version(), __max_supported_opset__)
def tensorflow_graph_to_onnx_model(cls, graph_def, output, opset=0, producer_name="onnx-tensorflow", graph_name="graph", ignore_unimplemented=False, optimizer_passes=None): """Converts a Tensorflow Graph Proto to an ONNX model This function converts a Tensorflow Graph proto to an equivalent representation of ONNX model. :param graph_def: Tensorflow Graph Proto object. :param output: List of string or a string specifying the name of the output graph node. :param opset: Opset version number, list or tuple. Default is 0 means using latest version with domain ''. List or tuple items should be (str domain, int version number). :param producer_name: The name of the producer. :param graph_name: The name of the output ONNX Graph. :param ignore_unimplemented: Convert to ONNX model and ignore all the operators that are not currently supported by onnx-tensorflow. This is an experimental feature. By enabling this feature, the model would not be guaranteed to match the ONNX specifications. :param optimizer_passes: List of optimization names c.f. https://github.com/onnx/onnx/blob/master/onnx/optimizer.py for available optimization passes. :returns: The equivalent ONNX Model Proto object. """ def get_node_by_name(nodes, name): for node in nodes: if node.name == name: return node raise ValueError( "Node {} is not found in the graph provided".format(name)) if not isinstance(opset, (int, long, list, tuple)): raise TypeError("opset is expected to int, list or tuple, but {}.".format( type(opset))) if isinstance(opset, (int, long)): opset = [(defs.ONNX_DOMAIN, opset or defs.onnx_opset_version())] opset_imports = [make_opsetid(item[0], item[1]) for item in opset] if not isinstance(output, (list, tuple)): output = [output] output_nodes = [get_node_by_name(graph_def.node, o) for o in output] if "_output_shapes" not in output_nodes[0].attr: # Add infer_shapes to GraphDef graph_def = cls._add_infer_shapes(graph_def) output_nodes = [get_node_by_name(graph_def.node, o) for o in output] onnx_graph = cls.tensorflow_graph_to_onnx_graph( graph_def, output_nodes, opset, graph_name, ignore_unimplemented) onnx_model = make_model( onnx_graph, producer_name=producer_name, opset_imports=opset_imports) if isinstance(optimizer_passes, (list, tuple)) and optimizer_passes: onnx_model = optimize(onnx_model, optimizer_passes) return onnx_model
class TestNearestNeighbourConverter(unittest.TestCase): def _fit_model_binary_classification(self, model): iris = datasets.load_iris() X = iris.data[:, :3] y = iris.target y[y == 2] = 1 model.fit(X, y) return model, X def _fit_model_multiclass_classification(self, model, use_string=False): iris = datasets.load_iris() X = iris.data[:, :3] y = iris.target if use_string: y = numpy.array(["cl%d" % _ for _ in y]) model.fit(X, y) return model, X def _fit_model(self, model, n_targets=1, label_int=False): X, y = datasets.make_regression(n_features=4, random_state=0, n_targets=n_targets) if label_int: y = y.astype(numpy.int64) model.fit(X, y) return model, X @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor(self): model, X = self._fit_model(KNeighborsRegressor(n_neighbors=2)) model_onnx = convert_sklearn(model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32)[:7], model, model_onnx, basename="SklearnKNeighborsRegressor") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(StrictVersion(onnx.__version__) < StrictVersion("1.6.0"), reason="not available") def test_model_knn_regressor_double(self): model, X = self._fit_model(KNeighborsRegressor(n_neighbors=2)) model_onnx = convert_sklearn(model, "KNN regressor", [("input", DoubleTensorType([None, 4]))], target_opset=TARGET_OPSET, options={id(model): { 'optim': 'cdist' }}, dtype=numpy.float64) self.assertIsNotNone(model_onnx) try: InferenceSession(model_onnx.SerializeToString()) except OrtImpl as e: if ("Could not find an implementation for the node " "To_TopK:TopK(11)") in str(e): # onnxruntime does not declare TopK(11) for double return raise e dump_data_and_model(X.astype(numpy.float64)[:7], model, model_onnx, basename="SklearnKNeighborsRegressor64") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor_yint(self): model, X = self._fit_model(KNeighborsRegressor(n_neighbors=2), label_int=True) model_onnx = convert_sklearn(model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32)[:7], model, model_onnx, basename="SklearnKNeighborsRegressorYInt") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor2_1(self): model, X = self._fit_model(KNeighborsRegressor(n_neighbors=1), n_targets=2) model_onnx = convert_sklearn(model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32)[:3], model, model_onnx, basename="SklearnKNeighborsRegressor2") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(StrictVersion(onnx.__version__) < StrictVersion("1.4.0"), reason="not available") def test_model_knn_regressor2_1_opset(self): model, X = self._fit_model(KNeighborsRegressor(n_neighbors=1), n_targets=2) for op in [12, 11, 10, 9]: if op > TARGET_OPSET: continue with self.subTest(opset=op): model_onnx = convert_sklearn( model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=op) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32)[:3], model, model_onnx, basename="SklearnKNeighborsRegressor2%d" % op) @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor2_2(self): model, X = self._fit_model(KNeighborsRegressor(n_neighbors=2), n_targets=2) model_onnx = convert_sklearn(model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32)[:2], model, model_onnx, basename="SklearnKNeighborsRegressor2") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(TARGET_OPSET < 9, reason="needs higher target_opset") def test_model_knn_regressor_weights_distance_11(self): model, X = self._fit_model( KNeighborsRegressor(weights="distance", algorithm="brute", n_neighbors=1)) for op in sorted(set([9, 10, 11, 12, TARGET_OPSET])): if op > TARGET_OPSET: continue with self.subTest(opset=op): model_onnx = convert_sklearn( model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=op) if op < 12 and model_onnx.ir_version > 6: raise AssertionError( "ir_version ({}, op={}) must be <= 6.".format( model_onnx.ir_version, op)) if op < 11 and model_onnx.ir_version > 5: raise AssertionError( "ir_version ({}, op={}) must be <= 5.".format( model_onnx.ir_version, op)) if op < 10 and model_onnx.ir_version > 4: raise AssertionError( "ir_version ({}, op={}) must be <= 4.".format( model_onnx.ir_version, op)) self.assertIsNotNone(model_onnx) dump_data_and_model( X.astype(numpy.float32)[:3], model, model_onnx, basename="SklearnKNeighborsRegressorWDist%d-Dec3" % op) @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor_metric_cityblock(self): model, X = self._fit_model(KNeighborsRegressor(metric="cityblock")) model_onnx = convert_sklearn(model, "KNN regressor", [("input", FloatTensorType([None, 4]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model( X.astype(numpy.float32)[:7], model, model_onnx, basename="SklearnKNeighborsRegressorMetricCityblock") @unittest.skipIf(not onnx_built_with_ml(), reason="Requires ONNX-ML extension.") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(onnx_opset_version() < TARGET_OPSET, reason="needs higher target_opset") def test_model_knn_classifier_binary_class(self): model, X = self._fit_model_binary_classification( KNeighborsClassifier()) model_onnx = convert_sklearn( model, "KNN classifier binary", [("input", FloatTensorType([None, X.shape[1]]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32), model, model_onnx, basename="SklearnKNeighborsClassifierBinary") @unittest.skipIf(not onnx_built_with_ml(), reason="Requires ONNX-ML extension.") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_classifier_multi_class(self): model, X = self._fit_model_multiclass_classification( KNeighborsClassifier()) model_onnx = convert_sklearn( model, "KNN classifier multi-class", [("input", FloatTensorType([None, X.shape[1]]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32), model, model_onnx, basename="SklearnKNeighborsClassifierMulti") @unittest.skipIf(not onnx_built_with_ml(), reason="Requires ONNX-ML extension.") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_classifier_multi_class_string(self): model, X = self._fit_model_multiclass_classification( KNeighborsClassifier(), use_string=True) model_onnx = convert_sklearn(model, "KNN classifier multi-class", [("input", FloatTensorType([None, 3]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32), model, model_onnx, basename="SklearnKNeighborsClassifierMulti") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_classifier_weights_distance(self): model, X = self._fit_model_multiclass_classification( KNeighborsClassifier(weights='distance')) model_onnx = convert_sklearn(model, 'KNN classifier', [('input', FloatTensorType([None, 3]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model( X.astype(numpy.float32)[:7], model, model_onnx, basename="SklearnKNeighborsClassifierWeightsDistance") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_classifier_metric_cityblock(self): model, X = self._fit_model_multiclass_classification( KNeighborsClassifier(metric='cityblock')) model_onnx = convert_sklearn(model, 'KNN classifier', [('input', FloatTensorType([None, 3]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model( X.astype(numpy.float32)[:7], model, model_onnx, basename="SklearnKNeighborsClassifierMetricCityblock") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_classifier_multilabel(self): model, X_test = fit_multilabel_classification_model( KNeighborsClassifier(), n_classes=7, n_labels=3, n_samples=100, n_features=10) options = {id(model): {'zipmap': False}} model_onnx = convert_sklearn( model, "scikit-learn KNN Classifier", [("input", FloatTensorType([None, X_test.shape[1]]))], options=options, target_opset=TARGET_OPSET) self.assertTrue(model_onnx is not None) assert 'zipmap' not in str(model_onnx).lower() dump_data_and_model( X_test, model, model_onnx, basename="SklearnKNNClassifierMultiLabel-Out0", allow_failure="StrictVersion(" "onnxruntime.__version__) <= StrictVersion('0.2.1')", ) @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor_int(self): model, X = self._fit_model(KNeighborsRegressor()) X = X.astype(numpy.int64) model_onnx = convert_sklearn( model, "KNN regressor", [("input", Int64TensorType([None, X.shape[1]]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X, model, model_onnx, basename="SklearnKNNRegressorInt-Dec4") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor_equal(self): X, y = datasets.make_regression(n_samples=1000, n_features=100, random_state=42) X = X.astype(numpy.int64) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=42) model = KNeighborsRegressor(algorithm='brute', metric='manhattan').fit(X_train, y_train) model_onnx = convert_sklearn( model, 'knn', [('input', Int64TensorType([None, X_test.shape[1]]))], target_opset=TARGET_OPSET) exp = model.predict(X_test) sess = InferenceSession(model_onnx.SerializeToString()) res = sess.run(None, {'input': numpy.array(X_test)})[0].ravel() # The conversion has discrepencies when # neighbours are at the exact same distance. maxd = 1000 accb = numpy.abs(exp - res) > maxd ind = [i for i, a in enumerate(accb) if a == 1] assert len(ind) == 0 accp = numpy.abs(exp - res) < maxd acc = numpy.sum(accp) ratio = acc * 1.0 / res.shape[0] assert ratio >= 0.7 # assert_almost_equal(exp, res) @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_multi_class_nocl(self): model, X = fit_classification_model(KNeighborsClassifier(), 2, label_string=True) model_onnx = convert_sklearn( model, "KNN multi-class nocl", [("input", FloatTensorType([None, X.shape[1]]))], options={id(model): { 'nocl': True }}, target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) sonx = str(model_onnx) assert 'classlabels_strings' not in sonx assert 'cl0' not in sonx dump_data_and_model(X, model, model_onnx, classes=model.classes_, basename="SklearnKNNMultiNoCl", verbose=False, allow_failure="StrictVersion(onnx.__version__)" " < StrictVersion('1.2') or " "StrictVersion(onnxruntime.__version__)" " <= StrictVersion('0.2.1')") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_model_knn_regressor2_2_pipee(self): pipe = make_pipeline(StandardScaler(), KNeighborsClassifier()) model, X = self._fit_model_binary_classification(pipe) model_onnx = convert_sklearn( model, "KNN pipe", [("input", FloatTensorType([None, X.shape[1]]))], target_opset=TARGET_OPSET) self.assertIsNotNone(model_onnx) dump_data_and_model(X.astype(numpy.float32)[:2], model, model_onnx, basename="SklearnKNeighborsRegressorPipe2") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") def test_onnx_test_knn_transform(self): iris = datasets.load_iris() X, _ = iris.data, iris.target X_train, X_test = train_test_split(X, random_state=11) clr = NearestNeighbors(n_neighbors=3) clr.fit(X_train) for to in (9, 10, 11): if to > onnx_opset_version(): break model_def = to_onnx(clr, X_train.astype(numpy.float32), target_opset=to) oinf = InferenceSession(model_def.SerializeToString()) X_test = X_test[:3] y = oinf.run(None, {'X': X_test.astype(numpy.float32)}) dist, ind = clr.kneighbors(X_test) assert_almost_equal(dist, DataFrame(y[1]).values, decimal=5) assert_almost_equal(ind, y[0]) @unittest.skipIf(NeighborhoodComponentsAnalysis is None, reason="new in 0.22") def test_sklearn_nca_default(self): model, X_test = fit_classification_model( NeighborhoodComponentsAnalysis(random_state=42), 3) model_onnx = convert_sklearn( model, "NCA", [("input", FloatTensorType((None, X_test.shape[1])))], ) self.assertIsNotNone(model_onnx) dump_data_and_model( X_test, model, model_onnx, basename="SklearnNCADefault", ) @unittest.skipIf(NeighborhoodComponentsAnalysis is None, reason="new in 0.22") def test_sklearn_nca_identity(self): model, X_test = fit_classification_model( NeighborhoodComponentsAnalysis(init='identity', max_iter=4, random_state=42), 3) model_onnx = convert_sklearn( model, "NCA", [("input", FloatTensorType((None, X_test.shape[1])))], ) self.assertIsNotNone(model_onnx) dump_data_and_model( X_test, model, model_onnx, basename="SklearnNCAIdentity", ) @unittest.skipIf(NeighborhoodComponentsAnalysis is None, reason="new in 0.22") def test_sklearn_nca_double(self): model, X_test = fit_classification_model( NeighborhoodComponentsAnalysis(n_components=2, max_iter=4, random_state=42), 3) X_test = X_test.astype(numpy.float64) model_onnx = convert_sklearn( model, "NCA", [("input", DoubleTensorType((None, X_test.shape[1])))], ) self.assertIsNotNone(model_onnx) dump_data_and_model( X_test, model, model_onnx, basename="SklearnNCADouble", ) @unittest.skipIf(NeighborhoodComponentsAnalysis is None, reason="new in 0.22") def test_sklearn_nca_int(self): model, X_test = fit_classification_model( NeighborhoodComponentsAnalysis(init='pca', max_iter=4, random_state=42), 3, is_int=True) model_onnx = convert_sklearn( model, "NCA", [("input", Int64TensorType((None, X_test.shape[1])))], ) self.assertIsNotNone(model_onnx) dump_data_and_model( X_test, model, model_onnx, basename="SklearnNCAInt", ) @unittest.skipIf(KNeighborsTransformer is None, reason="new in 0.22") def test_sklearn_k_neighbours_transformer_distance(self): model, X_test = fit_classification_model( KNeighborsTransformer(n_neighbors=4, mode='distance'), 2) model_onnx = convert_sklearn( model, "KNN transformer", [("input", FloatTensorType((None, X_test.shape[1])))], ) self.assertIsNotNone(model_onnx) dump_data_and_model( X_test, model, model_onnx, basename="SklearnKNNTransformerDistance", ) @unittest.skipIf(KNeighborsTransformer is None, reason="new in 0.22") def test_sklearn_k_neighbours_transformer_connectivity(self): model, X_test = fit_classification_model( KNeighborsTransformer(n_neighbors=3, mode='connectivity'), 3) model_onnx = convert_sklearn( model, "KNN transformer", [("input", FloatTensorType((None, X_test.shape[1])))], ) self.assertIsNotNone(model_onnx) dump_data_and_model( X_test, model, model_onnx, basename="SklearnKNNTransformerConnectivity", ) @unittest.skipIf(KNNImputer is None, reason="new in 0.22") @unittest.skipIf( (StrictVersion(onnx.__version__) < StrictVersion("1.4.1")), reason="ConstantOfShape op not available") def test_sklearn_knn_imputer(self): x_train = numpy.array([[1, 2, numpy.nan, 12], [3, numpy.nan, 3, 13], [1, 4, numpy.nan, 1], [numpy.nan, 4, 3, 12]], dtype=numpy.float32) x_test = numpy.array( [[1.3, 2.4, numpy.nan, 1], [-1.3, numpy.nan, 3.1, numpy.nan]], dtype=numpy.float32) model = KNNImputer(n_neighbors=3, metric='nan_euclidean').fit(x_train) for opset in [9, 10, 11, 12]: if opset > TARGET_OPSET: continue model_onnx = convert_sklearn( model, "KNN imputer", [("input", FloatTensorType((None, x_test.shape[1])))], target_opset=opset) self.assertIsNotNone(model_onnx) dump_data_and_model(x_test, model, model_onnx, basename="SklearnKNNImputer%d" % opset) @unittest.skipIf(KNNImputer is None, reason="new in 0.22") @unittest.skipIf( (StrictVersion(onnx.__version__) < StrictVersion("1.4.1")), reason="ConstantOfShape op not available") def test_sklearn_knn_imputer_cdist(self): x_train = numpy.array([[1, 2, numpy.nan, 12], [3, numpy.nan, 3, 13], [1, 4, numpy.nan, 1], [numpy.nan, 4, 3, 12]], dtype=numpy.float32) x_test = numpy.array( [[1.3, 2.4, numpy.nan, 1], [-1.3, numpy.nan, 3.1, numpy.nan]], dtype=numpy.float32) model = KNNImputer(n_neighbors=3, metric='nan_euclidean').fit(x_train) with self.assertRaises(NameError): convert_sklearn(model, "KNN imputer", [("input", FloatTensorType( (None, x_test.shape[1])))], target_opset=TARGET_OPSET, options={id(model): { 'optim2': 'cdist' }}) for opset in [12, 11, 10, 9]: if opset > TARGET_OPSET: continue model_onnx = convert_sklearn( model, "KNN imputer", [("input", FloatTensorType((None, x_test.shape[1])))], target_opset=opset, options={id(model): { 'optim': 'cdist' }}) self.assertIsNotNone(model_onnx) self.assertIn('op_type: "cdist"', str(model_onnx).lower()) self.assertNotIn('scan', str(model_onnx).lower()) dump_data_and_model(x_test, model, model_onnx, basename="SklearnKNNImputer%dcdist" % opset) @unittest.skipIf(not onnx_built_with_ml(), reason="Requires ONNX-ML extension.") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(onnx_opset_version() < 11, reason="needs higher target_opset") def test_model_knn_iris_regressor_multi_reg(self): iris = datasets.load_iris() X = iris.data.astype(numpy.float32) y = iris.target.astype(numpy.float32) y = numpy.vstack([y, 1 - y, y + 10]).T model = KNeighborsRegressor(algorithm='brute', weights='distance', n_neighbors=7) model.fit(X[:13], y[:13]) onx = to_onnx(model, X[:1], options={id(model): { 'optim': 'cdist' }}, target_opset=TARGET_OPSET) dump_data_and_model(X.astype(numpy.float32)[:7], model, onx, basename="SklearnKNeighborsRegressorMReg") @unittest.skipIf(not onnx_built_with_ml(), reason="Requires ONNX-ML extension.") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(onnx_opset_version() < 11, reason="needs higher target_opset") def test_model_knn_iris_classifier_multi_reg2_weight(self): iris = datasets.load_iris() X = iris.data.astype(numpy.float32) y = iris.target.astype(numpy.int64) y = numpy.vstack([(y + 1) % 2, y % 2]).T model = KNeighborsClassifier(algorithm='brute', weights='distance', n_neighbors=7) model.fit(X[:13], y[:13]) onx = to_onnx(model, X[:1], options={id(model): { 'optim': 'cdist', 'zipmap': False }}, target_opset=TARGET_OPSET) dump_data_and_model(X.astype(numpy.float32)[:11], model, onx, basename="SklearnKNeighborsClassifierMReg2-Out0") @unittest.skipIf(not onnx_built_with_ml(), reason="Requires ONNX-ML extension.") @unittest.skipIf( StrictVersion(onnxruntime.__version__) < StrictVersion("0.5.0"), reason="not available") @unittest.skipIf(onnx_opset_version() < 11, reason="needs higher target_opset") def test_model_knn_iris_classifier_multi_reg3_weight(self): iris = datasets.load_iris() X = iris.data.astype(numpy.float32) y = iris.target.astype(numpy.int64) y = numpy.vstack([y % 2, y % 2, (y + 1) % 2]).T model = KNeighborsClassifier(algorithm='brute', weights='distance', n_neighbors=7) model.fit(X[:13], y[:13]) onx = to_onnx(model, X[:1], options={id(model): { 'optim': 'cdist', 'zipmap': False }}, target_opset=TARGET_OPSET) dump_data_and_model(X.astype(numpy.float32)[:11], model, onx, basename="SklearnKNeighborsClassifierMReg3-Out0")
def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False, opset_version=None): """Convert MXNet graph to ONNX graph Parameters ---------- sym : :class:`~mxnet.symbol.Symbol` MXNet symbol object params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format in_shape : List of tuple Input shape of the model e.g [(1,3,224,224)] in_type : data type Input data type e.g. np.float32 verbose : Boolean If true will print logs of the model conversion opset_version : Int ONNX opset version to use for export, defaults to latest supported by onnx package Returns ------- graph : GraphProto ONNX graph """ try: from onnx import (checker, helper, NodeProto, ValueInfoProto, TensorProto) from onnx.helper import make_tensor_value_info from onnx.defs import onnx_opset_version except ImportError: raise ImportError("Onnx and protobuf need to be installed. " + "Instructions to install - https://github.com/onnx/onnx") if opset_version is None: opset_version = onnx_opset_version() # When MXNet model is saved to json file , MXNet adds a node for label. # The name of this node is, name of the last node + "_label" ( i.e if last node # name is "Softmax", this node will have a name "Softmax_label". Also, the new node # will always be second last node in the json graph. # Deriving the output_label name. output_label = sym.get_internals()[len(sym.get_internals()) - 1].name + "_label" weights = MXNetGraph.convert_weights_to_numpy(params) mx_graph = json.loads(sym.tojson())["nodes"] initializer = [] all_processed_nodes = [] onnx_processed_nodes = [] onnx_processed_inputs = [] onnx_processed_outputs = [] index_lookup = [] # Determine output and internal shapes graph_outputs = MXNetGraph.get_outputs(sym, params, in_shape, output_label) graph_shapes = MXNetGraph.get_outputs(sym.get_internals(), params, in_shape, output_label, verbose=False) graph_input_idx = 0 for idx, node in enumerate(mx_graph): op = node["op"] name = node["name"] if verbose: logging.info("Converting idx: %d, op: %s, name: %s", idx, op, name) # A node is an input node if its op_name is "null" and is not # in params dict if op == "null" and name not in params: # Handling graph input # Skipping output_label node, as this node is not part of graph # Refer "output_label" assignment above for more details. if name == output_label: continue converted = MXNetGraph.convert_layer( node, is_input=True, mx_graph=mx_graph, weights=weights, in_shape=in_shape[graph_input_idx], in_type=in_type, proc_nodes=all_processed_nodes, graph_shapes=graph_shapes, initializer=initializer, index_lookup=index_lookup) graph_input_idx += 1 else: # Handling graph layers converted = MXNetGraph.convert_layer( node, is_input=False, mx_graph=mx_graph, weights=weights, in_shape=in_shape, in_type=in_type, proc_nodes=all_processed_nodes, graph_shapes=graph_shapes, initializer=initializer, index_lookup=index_lookup, idx=idx, opset_version=opset_version ) if isinstance(converted, list): # Iterate for all converted nodes for converted_node in converted: # If converted node is ValueInfoProto, add it in inputs if isinstance(converted_node, ValueInfoProto): onnx_processed_inputs.append(converted_node) # If converted node is NodeProto, add it in processed nodes list elif isinstance(converted_node, NodeProto): onnx_processed_nodes.append(converted_node) # some operators have multiple outputs, # therefore, check all output node names node_names = list(converted_node.output) for nodename in node_names: if nodename in graph_outputs: onnx_processed_outputs.append( make_tensor_value_info( name=nodename, elem_type=in_type, shape=graph_outputs[nodename] ) ) if verbose: logging.info("Output node is: %s", nodename) elif isinstance(converted_node, TensorProto): raise ValueError("Did not expect TensorProto") else: raise ValueError("node is of an unrecognized type: %s" % type(node)) all_processed_nodes.append(converted_node) if idx > 0: # Handling extra node added to the graph if the MXNet model was # saved to json file, # refer "output_label" initialization above for more details. # if extra node was added then prev_index to the last node is adjusted. if idx == (len(mx_graph) - 1) and \ mx_graph[len(mx_graph)-2]["name"] == output_label: prev_index = index_lookup[idx - 2] else: prev_index = index_lookup[idx - 1] index_lookup.append(prev_index+len(converted)) else: index_lookup.append(len(converted) - 1) else: logging.info("Operator converter function should always return a list") graph = helper.make_graph( onnx_processed_nodes, "mxnet_converted_model", onnx_processed_inputs, onnx_processed_outputs ) graph.initializer.extend(initializer) checker.check_graph(graph) return graph
elif hasattr(self, 'value_floats') and self.value_floats is not None: self.cst = self.value_floats elif hasattr(self, 'value_int') and self.value_int is not None: self.cst = self.value_int elif hasattr(self, 'value_ints') and self.value_ints is not None: self.cst = self.value_ints elif hasattr(self, 'value_string') and self.value_string is not None: self.cst = self.value_string elif hasattr(self, 'value_strings') and self.value_strings is not None: self.cst = self.value_strings elif hasattr(self, 'value') and self.value is not None: self.cst = self.value else: raise AttributeError( "No constant is defined for operator 'Constant'.") def _run(self): # pylint: disable=W0221 return (self.cst, ) def _infer_shapes(self): # pylint: disable=W0221 # pref = str(hex(id(self))[2:]) return (ShapeObject(self.cst.shape, self.cst.dtype), ) if onnx_opset_version() >= 12: Constant = Constant_12 elif onnx_opset_version() >= 11: # pragma: no cover Constant = Constant_11 else: # pragma: no cover Constant = Constant_9
def create_onnx_graph_proto(self, sym, params, in_shapes, in_types, verbose=False, opset_version=None, dynamic=True, dynamic_input_shapes=None): """Convert MXNet graph to ONNX graph Parameters ---------- sym : :class:`~mxnet.symbol.Symbol` MXNet symbol object params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format in_shapes : List of tuple Input shape of the model e.g [(1,3,224,224)] in_types : List of Int Input ONNX data types verbose : Boolean If true will print logs of the model conversion opset_version : Int ONNX opset version to use for export, defaults to latest supported by onnx package dynamic: Boolean If True will allow for dynamic input shapes to the model dynamic_input_shapes: list of tuple Specifies the dynamic input_shapes. If None then all dimensions are set to None Returns ------- graph : GraphProto ONNX graph """ try: from onnx import (helper, NodeProto, ValueInfoProto, TensorProto) from onnx.helper import make_tensor_value_info from onnx.defs import onnx_opset_version except ImportError: raise ImportError( "Onnx and protobuf need to be installed. " + "Instructions to install - https://github.com/onnx/onnx") if opset_version is None: opset_version = onnx_opset_version() # When MXNet model is saved to json file , MXNet adds a node for label. # The name of this node is, name of the last node + "_label" ( i.e if last node # name is "Softmax", this node will have a name "Softmax_label". Also, the new node # will always be second last node in the json graph. # Deriving the output_label name. output_label = sym.get_internals()[len(sym.get_internals()) - 1].name + "_label" weights = MXNetGraph.convert_weights_to_numpy(params) mx_graph = json.loads(sym.tojson())["nodes"] class NodeOutput: def __init__(self, name, dtype): self.name = name self.dtype = np.dtype(dtype) initializer = [] all_processed_nodes = [] onnx_processed_nodes = [] onnx_processed_inputs = [] onnx_processed_outputs = [] outputs_lookup = [] # Determine graph output names, shapes, and dtypes. Also update in_shapes in_shapes, graph_outputs = MXNetGraph.get_outputs( sym, params, in_shapes, output_label, in_types, dynamic, dynamic_input_shapes) appeared_names = set() graph_input_idx = 0 for idx, node in enumerate(mx_graph): op = node["op"] # check if the current node has the same name as nodes before if node["name"] in appeared_names: node["name"] = 'idx_' + str(idx) + '_' + node["name"] else: appeared_names.add(node["name"]) name = node["name"] if verbose: logging.info("Converting idx: %d, op: %s, name: %s", idx, op, name) # A node is an input node if its op_name is "null" and is not # in params dict if op == "null" and name not in params: # Handle graph input # Skip output_label node, as this node is not part of graph # Refer to "output_label" assignment above for more details. if name == output_label: continue converted, dtypes = MXNetGraph.convert_layer( node, is_input=True, mx_graph=mx_graph, weights=weights, in_shape=in_shapes[graph_input_idx], in_type=in_types[graph_input_idx], proc_nodes=all_processed_nodes, initializer=initializer, outputs_lookup=outputs_lookup) graph_input_idx += 1 else: # Handle graph layers converted, dtypes = MXNetGraph.convert_layer( node, is_input=False, mx_graph=mx_graph, weights=weights, proc_nodes=all_processed_nodes, initializer=initializer, outputs_lookup=outputs_lookup, idx=idx, opset_version=opset_version) if isinstance(converted, list): # Collect all the node's output names node_possible_names = [name ] + [name + str(i) for i in range(100)] node_output_names = [] # Collect all the graph's output names graph_output_names = [] # Iterate for all converted nodes for converted_node in converted: # If converted node is ValueInfoProto, add it in inputs if isinstance(converted_node, ValueInfoProto): onnx_processed_inputs.append(converted_node) # If converted node is NodeProto, add it in processed nodes list elif isinstance(converted_node, NodeProto): onnx_processed_nodes.append(converted_node) # some operators have multiple outputs, # therefore, check all output node names node_names = list(converted_node.output) for nodename in node_names: if nodename in node_possible_names: node_output_names.append(nodename) if nodename in graph_outputs: graph_output_names.append(nodename) if verbose: logging.info("Output node is: %s", nodename) elif isinstance(converted_node, TensorProto): raise ValueError("Did not expect TensorProto") else: raise ValueError( "node is of an unrecognized type: %s" % type(node)) all_processed_nodes.append(converted_node) # if node_output_names is empty then we use the last returned node as output if not node_output_names: node_output_names = [converted[-1].name] # process node outputs (sort by output index) def str2int(s, l): if len(s) == l: return -1 else: return int(s[l:]) node_output_names = sorted(node_output_names, key=lambda x: str2int(x, len(name))) # match the output names to output dtypes if dtypes is not None: assert len(node_output_names) == len(dtypes) node_outputs = [ NodeOutput(node_output_names[i], dtypes[i]) for i in range(len(dtypes)) ] else: # in case dtypes is None, we just default to the dtype of the first input assert len(node["inputs"]) > 0 first_input = node["inputs"][0] first_input_dtype = outputs_lookup[first_input[0]][ first_input[1]].dtype node_outputs = [ NodeOutput(n, first_input_dtype) for n in node_output_names ] outputs_lookup.append(node_outputs) # process graph outputs (sort by alphabetical order) graph_output_names.sort() for nodename in graph_output_names: onnx_processed_outputs.append( make_tensor_value_info( name=nodename, elem_type=graph_outputs[nodename]['dtype'], shape=graph_outputs[nodename]['shape'])) else: logging.info( "Operator converter function should always return a list") # sometimes the graph output can also be in the intializer for i in initializer: if i.name in graph_outputs: onnx_processed_outputs.append( make_tensor_value_info( name=i.name, elem_type=graph_outputs[i.name]['dtype'], shape=graph_outputs[i.name]['shape'])) graph = helper.make_graph(onnx_processed_nodes, "mxnet_converted_model", onnx_processed_inputs, onnx_processed_outputs) graph.initializer.extend(initializer) return graph
def tensorflow_graph_to_onnx_graph(cls, tf_graph, opset=((defs.ONNX_DOMAIN, defs.onnx_opset_version()), ), ignore_unimplemented=False): """Converts a TensorflowGraph to an ONNX graph This function converts a TensorflowGraph to an equivalent representation of ONNX graph. :param tf_graph: TensorflowGraph object. :param opset: Opset, which should be ((str domain: int version number),). :param ignore_unimplemented: Convert to ONNX model and ignore all the operators that are not currently supported by onnx-tensorflow. This is an experimental feature. By enabling this feature, the graph would not be guaranteed to match the ONNX specifications. :returns: The equivalent ONNX Graph Proto object. """ onnx_graph = OnnxGraph(tf_graph.graph_name) exception.IGNORE_UNIMPLEMENTED = ignore_unimplemented training_ops_to_remove = ["RandomShuffleQueueV2"] opset_dict = {} for domain, version in opset: if domain == "ai.onnx": domain = defs.ONNX_DOMAIN opset_dict[domain] = version handlers = get_all_frontend_handlers(opset_dict) node_tup = [(n.name, n) for n in tf_graph.nodes] for name, node in node_tup: if node.op_type == "Placeholder": onnx_graph.add_input_proto(node) elif node.op_type == "Const": onnx_graph.add_const(node) onnx_graph.add_const_proto(node) onnx_graph.add_input_proto(node) elif node.op_type in training_ops_to_remove: logger.info( "A training op with name {} type {} has been removed.". format(node.name, node.op_type)) elif node.op_type == "QueueDequeueManyV2": num_output = len(node.attr["_output_shapes"]) for index, shape, onnx_type in zip( range(num_output), node.attr["_output_shapes"], node.attr["component_types"]): onnx_graph.add_input_proto_explicit(node.name + ":" + str(index), shape, onnx_dtype=onnx_type) else: onnx_graph.add_value_info_proto(node) handler = handlers.get(node.domain, {}).get(node.op_type, None) node_proto = None if handler: node_proto = handler.handle( node, consts=onnx_graph.consts, node_dict=dict(node_tup), data_type_cast_map=onnx_graph.data_type_cast_map) else: exception.OP_UNIMPLEMENTED_EXCEPT( node.op_type, domain=None if node.domain in handlers else node.domain) if node_proto is None: node_proto = FrontendHandler.make_node_from_tf_node( node, op_type=node.op_type, should_check=False) onnx_graph.add_node_proto(node_proto) for o in tf_graph.outputs: output_node = tf_graph.get_node_by_name(o) onnx_graph.add_output_proto(output_node) return onnx_graph.make_graph_proto()
def find_opset(opset): if opset is None or opset == 0: opset = defs.onnx_opset_version() if opset > PREFERRED_OPSET: opset = PREFERRED_OPSET return opset
def get_maximum_opset_supported(): return min(max(OPSET_TO_IR_VERSION.keys()), defs.onnx_opset_version())
def test_onnx_ml(self): def generate_onnx_graph(opv): node = OnnxAdd(('X1', FloatTensorType()), np.array([0.1], dtype=np.float32), op_version=opv) out = OnnxLinearRegressor(node, coefficients=[0.3, 0.3, 0.4, 0.5, 0.6], intercepts=[-50.], op_version=1) last = OnnxIdentity(out, output_names=['Y'], op_version=opv) onx = last.to_onnx([('X1', FloatTensorType((None, 5)))], outputs=[('Y', FloatTensorType())], target_opset=opv) return onx, (node, out, last) for opv in [{'': 10}] + list(range(9, TARGET_OPSET + 1)): with self.subTest(opv=opv): if isinstance(opv, dict): if opv[''] > get_latest_tested_opset_version(): continue elif (opv is not None and opv > get_latest_tested_opset_version()): continue for i, nbnode in enumerate((1, 2, 3, 100)): onx, nodes = generate_onnx_graph(opv=opv) if opv == {'': 10}: for im in onx.opset_import: if im.version > 10: raise AssertionError( "Wrong final opset\nopv={}\n{}".format( opv, onx)) else: for im in onx.opset_import: if im.version > opv: raise AssertionError( "Wrong final opset\nopv={}\n{}".format( opv, onx)) as_string = onx.SerializeToString() try: ort = InferenceSession(as_string) except (InvalidGraph, InvalidArgument) as e: if (isinstance(opv, dict) and opv[''] >= onnx_opset_version()): continue if (isinstance(opv, int) and opv >= onnx_opset_version()): continue raise AssertionError( "Unable to load opv={}\n---\n{}\n---".format( opv, onx)) from e X = (np.ones((1, 5)) * nbnode).astype(np.float32) res_out = ort.run(None, {'X1': X}) assert len(res_out) == 1 res = res_out[0] self.assertEqual(res.shape, (1, 1)) inputs = None expected = [[('Ad_C0', FloatTensorType(shape=[]))], [('Li_Y0', FloatTensorType(shape=[]))], [('Y', FloatTensorType(shape=[]))]] for i, node in enumerate(nodes): shape = node.get_output_type_inference(inputs) self.assertEqual(len(shape), 1) if isinstance(shape[0], tuple): self.assertEqual(str(expected[i]), str(shape)) else: self.assertEqual( str(expected[i]), str([(shape[0].onnx_name, shape[0].type)])) inputs = shape
def common_test_sub_graph(self, first_input, model, options=None, cls_type=FloatTensorType, start=9): def generate_onnx_graph(opv): dtype = np.float32 if cls_type == FloatTensorType else np.float64 node = OnnxAdd(first_input, np.array([0.1], dtype=dtype), op_version=opv) lr = model() lr.fit(np.ones([10, 5]), np.arange(0, 10) % 3) out = OnnxSubEstimator(lr, node, op_version=1, options=options) if model == LogisticRegression: last = OnnxIdentity(out[1], output_names=['Y'], op_version=opv) else: last = OnnxIdentity(out, output_names=['Y'], op_version=opv) onx = last.to_onnx([('X1', cls_type((None, 5)))], outputs=[('Y', cls_type())], target_opset=opv) return onx dtype = np.float32 if cls_type == FloatTensorType else np.float64 opsets = list(range(start, TARGET_OPSET + 1)) for opv in [{'': TARGET_OPSET}] + opsets: with self.subTest(opv=opv): if isinstance(opv, dict): if opv[''] > get_latest_tested_opset_version(): continue elif (opv is not None and opv > get_latest_tested_opset_version()): continue for i, nbnode in enumerate((1, 2, 3, 100)): onx = generate_onnx_graph(opv=opv) if opv == {'': TARGET_OPSET}: for im in onx.opset_import: if im.version > TARGET_OPSET: raise AssertionError( "Wrong final opset\nopv={}\n{}".format( opv, onx)) else: for im in onx.opset_import: if im.version > opv: raise AssertionError( "Wrong final opset\nopv={}\n{}".format( opv, onx)) self.assertNotIn('zipmap', str(onx).lower()) as_string = onx.SerializeToString() try: ort = InferenceSession(as_string) except (InvalidGraph, InvalidArgument, Fail, NotImplemented) as e: if (isinstance(opv, dict) and opv[''] >= onnx_opset_version()): continue if (isinstance(opv, int) and opv >= onnx_opset_version()): continue raise AssertionError( "Unable to load opv={}\n---\n{}\n---".format( opv, onx)) from e X = (np.ones((1, 5)) * nbnode).astype(dtype) res_out = ort.run(None, {'X1': X}) assert len(res_out) == 1 res = res_out[0] if model == LogisticRegression: self.assertEqual(res.shape, (1, 3)) else: self.assertEqual(res.shape, (1, 1))
######################################## # ONNX and opset # ++++++++++++++ # # The converter can convert a model to an older opset # than the default one, from 1 to the last available one. def get_domain_opset(onx): domains = onx.opset_import res = [{'domain': dom.domain, 'version': dom.version} for dom in domains] return {d['domain']: d['version'] for d in res} for opset in range(6, onnx_opset_version() + 1): try: onx = to_onnx(model, X[:1].astype(numpy.float32), target_opset={ '': opset, 'ai.onnx.ml': 2 }) except RuntimeError as e: print('target: %r error: %r' % (opset, e)) continue nodes = len(onx.graph.node) print('target: %r --> %s %d' % (opset, get_domain_opset(onx), nodes)) ######################################## # It shows that the model cannot be converted for opset
def export_model(sym, params, input_shape, input_type=np.float32, onnx_file_path='model.onnx', verbose=False, opset_version=None): """Exports the MXNet model file, passed as a parameter, into ONNX model. Accepts both symbol,parameter objects as well as json and params filepaths as input. Operator support and coverage - https://cwiki.apache.org/confluence/display/MXNET/ONNX+Operator+Coverage Parameters ---------- sym : str or symbol object Path to the json file or Symbol object params : str or symbol object Path to the params file or params dictionary. (Including both arg_params and aux_params) input_shape : List of tuple Input shape of the model e.g [(1,3,224,224)] input_type : data type Input data type e.g. np.float32 onnx_file_path : str Path where to save the generated onnx file verbose : Boolean If true will print logs of the model conversion Returns ------- onnx_file_path : str Onnx file path Notes ----- This method is available when you ``import mxnet.contrib.onnx`` """ try: from onnx import helper, mapping from onnx.defs import onnx_opset_version except ImportError: raise ImportError( "Onnx and protobuf need to be installed. " + "Instructions to install - https://github.com/onnx/onnx") converter = MXNetGraph() if opset_version is None: # default is to use latest opset version the onnx package supports opset_version = onnx_opset_version() data_format = np.dtype(input_type) # if input parameters are strings(file paths), load files and create symbol parameter objects if isinstance(sym, string_types) and isinstance(params, string_types): logging.info("Converting json and weight file to sym and params") sym_obj, params_obj = load_module(sym, params) onnx_graph = converter.create_onnx_graph_proto( sym_obj, params_obj, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], verbose=verbose, opset_version=opset_version) elif isinstance(sym, symbol.Symbol) and isinstance(params, dict): onnx_graph = converter.create_onnx_graph_proto( sym, params, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], verbose=verbose, opset_version=opset_version) else: raise ValueError( "Input sym and params should either be files or objects") # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) # Save model on disk with open(onnx_file_path, "wb") as file_handle: serialized = onnx_model.SerializeToString() file_handle.write(serialized) logging.info("Input shape of the model %s ", input_shape) logging.info("Exported ONNX file %s saved to disk", onnx_file_path) return onnx_file_path
def onnx_builtin_opset_version(): return onnx_defs.onnx_opset_version()
def test_bind_input_types(self): opset = onnx_opset_version() devices = [( C_OrtDevice(C_OrtDevice.cpu(), C_OrtDevice.default_memory(), 0), ["CPUExecutionProvider"], )] if "CUDAExecutionProvider" in onnxrt.get_all_providers(): devices.append(( C_OrtDevice(C_OrtDevice.cuda(), C_OrtDevice.default_memory(), 0), ["CUDAExecutionProvider"], )) for device, provider in devices: for dtype in [ np.float32, np.float64, np.int32, np.uint32, np.int64, np.uint64, np.int16, np.uint16, np.int8, np.uint8, np.float16, np.bool_, ]: with self.subTest(dtype=dtype, device=str(device)): x = np.arange(8).reshape((-1, 2)).astype(dtype) proto_dtype = NP_TYPE_TO_TENSOR_TYPE[x.dtype] X = helper.make_tensor_value_info("X", proto_dtype, [None, x.shape[1]]) Y = helper.make_tensor_value_info("Y", proto_dtype, [None, x.shape[1]]) # inference node_add = helper.make_node("Identity", ["X"], ["Y"]) # graph graph_def = helper.make_graph([node_add], "lr", [X], [Y], []) model_def = helper.make_model( graph_def, producer_name="dummy", ir_version=7, producer_version="0", opset_imports=[helper.make_operatorsetid("", opset)], ) sess = onnxrt.InferenceSession( model_def.SerializeToString(), providers=provider) bind = SessionIOBinding(sess._sess) ort_value = C_OrtValue.ortvalue_from_numpy(x, device) bind.bind_ortvalue_input("X", ort_value) bind.bind_output("Y", device) sess._sess.run_with_iobinding(bind, None) ortvalue = bind.get_outputs()[0] y = ortvalue.numpy() assert_almost_equal(x, y) bind = SessionIOBinding(sess._sess) bind.bind_input("X", device, dtype, x.shape, ort_value.data_ptr()) bind.bind_output("Y", device) sess._sess.run_with_iobinding(bind, None) ortvalue = bind.get_outputs()[0] y = ortvalue.numpy() assert_almost_equal(x, y)
class TestOp10(unittest.TestCase): def check_domain(self, model, domain="", target_opset=10): for op in model.opset_import: if op.domain == domain: if op.version > target_opset: raise RuntimeError("Wrong opset {} > {} expected".format( op.domain, target_opset)) @unittest.skipIf(not onnx_built_with_ml(), reason="onnx-ml") @unittest.skipIf(onnx_opset_version() < 10, reason="out of scope") def test_logistic_regression(self): model, X = fit_classification_model(linear_model.LogisticRegression(), 3) target_opset = 10 model_onnx = convert_sklearn(model, "op10", [("input", FloatTensorType([None, 3]))], target_opset=target_opset) self.check_domain(model_onnx, target_opset=target_opset) @unittest.skipIf(not onnx_built_with_ml(), reason="onnx-ml") @unittest.skipIf(onnx_opset_version() < 10, reason="out of scope") def test_kmeans(self): model, X = fit_classification_model(KMeans(), 3) target_opset = 10 model_onnx = convert_sklearn(model, "op10", [("input", FloatTensorType([None, 3]))], target_opset=target_opset) self.check_domain(model_onnx, target_opset=target_opset) @unittest.skipIf(not onnx_built_with_ml(), reason="onnx-ml") @unittest.skipIf(onnx_opset_version() < 10, reason="out of scope") def test_gaussian_mixture(self): model, X = fit_classification_model(GaussianMixture(), 3) target_opset = 10 model_onnx = convert_sklearn( model, "op10", [("input", FloatTensorType([None, X.shape[1]]))], target_opset=target_opset) self.check_domain(model_onnx, target_opset=target_opset) @unittest.skipIf(not onnx_built_with_ml(), reason="onnx-ml") @unittest.skipIf(onnx_opset_version() < 10, reason="out of scope") def test_gaussian_process_regressor(self): model, X = fit_classification_model(GaussianProcessRegressor(), 3) target_opset = 10 model_onnx = convert_sklearn(model, "op10", [("input", FloatTensorType([None, 3]))], target_opset=target_opset) self.check_domain(model_onnx, target_opset=target_opset) @unittest.skipIf(not onnx_built_with_ml(), reason="onnx-ml") @unittest.skipIf(onnx_opset_version() < 10, reason="out of scope") def test_voting_classifier(self): model = VotingClassifier( voting="hard", flatten_transform=False, estimators=[ ("lr", LogisticRegression()), ("lr2", LogisticRegression(fit_intercept=False)), ], ) model, X = fit_classification_model(model, 3) target_opset = 10 model_onnx = convert_sklearn(model, "op10", [("input", FloatTensorType([None, 3]))], target_opset=target_opset) self.check_domain(model_onnx, target_opset=target_opset)
def _get_handlers(self, opset): opset = opset or [ make_opsetid(defs.ONNX_DOMAIN, defs.onnx_opset_version()) ] opset_dict = dict([(o.domain, o.version) for o in opset]) return get_all_backend_handlers(opset_dict)
def test_cascade_add(self): def generate_onnx_graph(dim, nbnode, input_name='X1', opv=None): i1 = input_name for i in range(nbnode - 1): i2 = (np.ones((1, dim)) * nbnode * 10).astype(np.float32) node = OnnxAdd(i1, i2, op_version=opv) i1 = node i2 = (np.ones((1, dim)) * nbnode * 10).astype(np.float32) node = OnnxAdd(i1, i2, output_names=['Y'], op_version=opv) onx = node.to_onnx([(input_name, FloatTensorType((None, dim)))], outputs=[('Y', FloatTensorType())], target_opset=opv) return onx exp = [ np.array([[11., 11., 11., 11., 11.]]), np.array([[42., 42., 42., 42., 42.]]), np.array([[93., 93., 93., 93., 93.]]), np.array([[100100., 100100., 100100., 100100., 100100.]]) ] for opv in ({'': 10}, 9, 10, 11, 12, onnx_opset_version()): if isinstance(opv, dict): if opv[''] > get_latest_tested_opset_version(): continue elif opv is not None and opv > get_latest_tested_opset_version(): continue for i, nbnode in enumerate((1, 2, 3, 100)): with self.subTest(n_nodes=nbnode): onx = generate_onnx_graph(5, nbnode, opv=opv) if opv == {'': 10}: for im in onx.opset_import: if im.version > 10: raise AssertionError( "Wrong final opset\nopv={}\n{}".format( opv, onx)) else: for im in onx.opset_import: if im.version > opv: raise AssertionError( "Wrong final opset\nopv={}\n{}".format( opv, onx)) as_string = onx.SerializeToString() try: ort = InferenceSession(as_string) except (InvalidGraph, InvalidArgument) as e: if (isinstance(opv, dict) and opv[''] >= onnx_opset_version()): continue if (isinstance(opv, int) and opv >= onnx_opset_version()): continue raise AssertionError( "Unable to load opv={}\n---\n{}\n---".format( opv, onx)) from e X = (np.ones((1, 5)) * nbnode).astype(np.float32) res_out = ort.run(None, {'X1': X}) assert len(res_out) == 1 res = res_out[0] assert_almost_equal(exp[i], res) with self.subTest(n_nodes=300): dim = 10 onx = generate_onnx_graph(dim, 300, opv=11) as_string = onx.SerializeToString() ort = InferenceSession(as_string) X = (np.ones((1, dim)) * nbnode).astype(np.float32) res_out = ort.run(None, {'X1': X}) assert len(res_out) == 1 res = res_out[0] assert res.shape[1] == dim
def FindOpset(opset): if opset is None or opset == 0: opset = defs.onnx_opset_version() return opset
def tensorflow_graph_to_onnx_graph(cls, graph_def, output, opset=((defs.ONNX_DOMAIN, defs.onnx_opset_version()),), name="graph", ignore_unimplemented=False): """Converts a Tensorflow Graph Proto to an ONNX graph This function converts a Tensorflow Graph proto to an equivalent representation of ONNX graph. :param graph_def: Tensorflow Graph Proto object. :param output: List of Tensorflow NodeDef object specifying which nodes to be taken as outputs of the ONNX graph. :param opset: Opset, which should be ((str domain: int version number),). :param name: The name of the output ONNX Graph. :param ignore_unimplemented: Convert to ONNX model and ignore all the operators that are not currently supported by onnx-tensorflow. This is an experimental feature. By enabling this feature, the graph would not be guaranteed to match the ONNX specifications. :returns: The equivalent ONNX Graph Proto object. """ onnx_graph = OnnxGraph(name) exception.IGNORE_UNIMPLEMENTED = ignore_unimplemented opset_dict = {} for domain, version in opset: if domain == "ai.onnx": domain = defs.ONNX_DOMAIN opset_dict[domain] = version handlers = get_all_frontend_handlers(opset_dict) node_tup = [(node.name, TensorflowNode(node)) for node in graph_def.node] for name, node in node_tup: if node.op_type == "Placeholder": onnx_graph.add_input_proto(node) elif node.op_type == "Const": onnx_graph.add_const(node) onnx_graph.add_const_proto(node) onnx_graph.add_input_proto(node) else: onnx_graph.add_value_info_proto(node) handler = handlers.get(node.domain, {}).get(node.op_type, None) node_proto = None if handler: node_proto = handler.handle( node, consts=onnx_graph.consts, node_dict=dict(node_tup), data_type_cast_map=onnx_graph.data_type_cast_map) else: exception.OP_UNIMPLEMENTED_EXCEPT( node.op_type, domain=None if node.domain in handlers else node.domain) if node_proto is None: node_proto = FrontendHandler.make_node_from_tf_node( node, op_type=node.op_type, should_check=False) onnx_graph.add_node_proto(node_proto) for o in output: output_node = TensorflowNode(o) onnx_graph.add_output_proto(output_node) return onnx_graph.make_graph_proto()
def get_opset_number_from_onnx(): """ Returns the latest opset version supported by the *onnx* package. """ return defs.onnx_opset_version()
def gen_support_status(docs_dir, onnx_version, onnx_path, onnx_tf_release_build): # set filename if onnx_tf_release_build: # get onnx-tf version from VERSION_NUMBER file version_dir = os.path.dirname( os.path.dirname(os.path.realpath('VERSION_NUMBER'))) version_file = os.path.join(version_dir, 'VERSION_NUMBER') onnx_tf_version = subprocess.check_output('cat ' + version_file, shell=True) onnx_tf_version = 'v' + onnx_tf_version.decode().strip('\n') filename = 'support_status_' + onnx_tf_version.replace('.', '_') + '.md' else: # onnx-tf = master # get onnx-tf commit id onnx_tf_commit_id = subprocess.check_output('git rev-parse HEAD', shell=True) onnx_tf_commit_id = onnx_tf_commit_id.decode().strip('\n') onnx_tf_version = 'Master ( commit id: {} )'.format(onnx_tf_commit_id) filename = 'support_status.md' with open(os.path.join(docs_dir, filename), 'w') as status_file: status_file.write('# ONNX-Tensorflow Support Status\n') status_file.write('|||\n') status_file.write('|-:|:-|\n') status_file.write( '|ONNX-Tensorflow Version|{}|\n'.format(onnx_tf_version)) # get onnx commit id if onnx_version == 'master': onnx_commit_id = subprocess.check_output('cd ' + onnx_path + '; git rev-parse HEAD', shell=True) onnx_commit_id = onnx_commit_id.decode().strip('\n') status_file.write( '|ONNX Version|Master ( commit id: {} )|\n'.format( onnx_commit_id)) else: status_file.write('|ONNX Version|{}|\n'.format(onnx_version)) # get tf_version status_file.write('|Tensorflow Version|v{}|\n\n'.format( tf.__version__)) # display the table legend status_file.write('Notes:\n') status_file.write('* Values that are new or updated from a ') status_file.write('previous opset version are in bold.\n') status_file.write('* -: not defined in corresponding ONNX ') status_file.write('opset version\n') status_file.write('* \*: the operator is deprecated\n') status_file.write('* :small_red_triangle:: not supported yet\n') status_file.write('* :small_orange_diamond:: partially supported\n') status_file.write('* the rest are all supported\n\n') # get oll onnx ops onnx_ops = {} for schema in defs.get_all_schemas(): if schema.domain == '': # only get onnx ops onnx_ops[schema.name] = { 'versions': [], 'deprecated': schema.since_version if schema.deprecated else -1 } for schema in defs.get_all_schemas_with_history(): if schema.domain == '': # only get onnx ops op = onnx_ops[schema.name] versions = op['versions'] versions.append(schema.since_version) # get all onnx-tf supported ops onnx_tf_ops = opset_version.backend_opset_version onnx_tf_ops_ps = opset_version.backend_partial_support # get the cureent opset version current_opset = defs.onnx_opset_version() # setup table header status_file.write('||') for i in range(current_opset): status_file.write('|') status_file.write('\n|:-:|') for i in range(current_opset): status_file.write(':-:|') status_file.write('\n|**ONNX Operator**|') for opset in range(1, current_opset + 1): status_file.write('**Opset {}**|'.format(opset)) ops_count = len(onnx_ops) # fill in data for the table for key, val in sorted(onnx_ops.items()): try: status_file.write('\n|{}|'.format(key)) i = 0 vers = val['versions'] deprecated = val['deprecated'] for opset in range(1, current_opset + 1): if i <= len(vers) - 1: lb = vers[i] ub = vers[i + 1] if i < len(vers) - 1 else vers[i] if opset < lb: if i == 0: status_file.write('-') elif opset == lb: status_file.write('**{}**'.format(lb)) if lb == deprecated: status_file.write('\*') elif lb not in onnx_tf_ops[key]: status_file.write(':small_red_triangle:') if opset == current_opset: ops_count -= 1 elif key in onnx_tf_ops_ps: status_file.write(':small_orange_diamond:') else: # opset > lb if opset < ub: status_file.write('{}'.format(lb)) if lb == deprecated: status_file.write('\*') elif lb not in onnx_tf_ops[key]: status_file.write(':small_red_triangle:') if opset == current_opset: ops_count -= 1 elif key in onnx_tf_ops_ps: status_file.write(':small_orange_diamond:') elif opset == ub: status_file.write('**{}**'.format(ub)) if ub == deprecated: status_file.write('\*') elif ub not in onnx_tf_ops[key]: status_file.write(':small_red_triangle:') if opset == current_opset: ops_count -= 1 elif key in onnx_tf_ops_ps: status_file.write(':small_orange_diamond:') i += 1 else: #opset > ub status_file.write('{}'.format(ub)) if ub == deprecated: status_file.write('\*') elif ub not in onnx_tf_ops[key]: status_file.write(':small_red_triangle:') if opset == current_opset: ops_count -= 1 elif key in onnx_tf_ops_ps: status_file.write(':small_orange_diamond:') status_file.write('|') except: # ops defined in onnx but not in opset_version.backend_opset_versionn status_file.write(':small_red_triangle:|') status_file.write( '\n\nONNX-TF Supported Operators / ONNX Operators: {} / {}'.format( ops_count, len(onnx_ops))) # display partial support footnote status_file.write('\n\nNotes:\n') index = 1 for key in onnx_tf_ops_ps: status_file.write( str(index) + '. ' + key + ': ' + onnx_tf_ops_ps[key] + '\n') index += 1
def export_model(sym, params, in_shapes=None, in_types=np.float32, onnx_file_path='model.onnx', verbose=False, dynamic=False, dynamic_input_shapes=None, run_shape_inference=False, input_type=None, input_shape=None, large_model=False): """Exports the MXNet model file, passed as a parameter, into ONNX model. Accepts both symbol,parameter objects as well as json and params filepaths as input. Operator support and coverage - https://github.com/apache/incubator-mxnet/tree/v1.x/python/mxnet/onnx#operator-support-matrix Parameters ---------- sym : str or symbol object Path to the json file or Symbol object params : str or dict or list of dict str - Path to the params file dict - params dictionary (Including both arg_params and aux_params) list - list of length 2 that contains arg_params and aux_params in_shapes : List of tuple Input shape of the model e.g [(1,3,224,224)] in_types : data type or list of data types Input data type e.g. np.float32, or [np.float32, np.int32] onnx_file_path : str Path where to save the generated onnx file verbose : Boolean If True will print logs of the model conversion dynamic: Boolean If True will allow for dynamic input shapes to the model dynamic_input_shapes: list of tuple Specifies the dynamic input_shapes. If None then all dimensions are set to None run_shape_inference : Boolean If True will run shape inference on the model input_type : data type or list of data types This is the old name of in_types. We keep this parameter name for backward compatibility input_shape : List of tuple This is the old name of in_shapes. We keep this parameter name for backward compatibility large_model : Boolean Whether to export a model that is larger than 2 GB. If true will save param tensors in separate files along with .onnx model file. This feature is supported since onnx 1.8.0 Returns ------- onnx_file_path : str Onnx file path Notes ----- This method is available when you ``import mxnet.onnx`` """ try: import onnx from onnx import helper, mapping, shape_inference from onnx.defs import onnx_opset_version except ImportError: raise ImportError("Onnx and protobuf need to be installed. " + "Instructions to install - https://github.com/onnx/onnx") if input_type is not None: in_types = input_type if input_shape is not None: in_shapes = input_shape converter = MXNetGraph() opset_version = onnx_opset_version() if not isinstance(in_types, list): in_types = [in_types for _ in range(len(in_shapes))] in_types_t = [mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(i_t)] for i_t in in_types] assert len(in_types) == len(in_shapes), "The lengths of in_types and in_shapes must equal" # if input parameters are strings(file paths), load files and create symbol parameter objects if isinstance(sym, string_types) and isinstance(params, string_types): logging.info("Converting json and weight file to sym and params") sym_obj, params_obj = load_module(sym, params) onnx_graph = converter.create_onnx_graph_proto(sym_obj, params_obj, in_shapes, in_types_t, verbose=verbose, opset_version=opset_version, dynamic=dynamic, dynamic_input_shapes=dynamic_input_shapes) elif isinstance(sym, symbol.Symbol) and isinstance(params, dict): onnx_graph = converter.create_onnx_graph_proto(sym, params, in_shapes, in_types_t, verbose=verbose, opset_version=opset_version, dynamic=dynamic, dynamic_input_shapes=dynamic_input_shapes) elif isinstance(sym, symbol.Symbol) and isinstance(params, list) and len(params) == 2: # when params contains arg_params and aux_params p = {} p.update(params[0]) p.update(params[1]) onnx_graph = converter.create_onnx_graph_proto(sym, p, in_shapes, in_types_t, verbose=verbose, opset_version=opset_version, dynamic=dynamic, dynamic_input_shapes=dynamic_input_shapes) else: raise ValueError("Input sym and params should either be files or objects") # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) # Run shape inference on the model. Due to ONNX bug/incompatibility this may or may not crash if run_shape_inference: try: onnx_model = shape_inference.infer_shapes(onnx_model) except: # pylint: disable=bare-except logging.info("Shape inference failed, original export is kept.") if large_model: from onnx.external_data_helper import convert_model_to_external_data convert_model_to_external_data(onnx_model, all_tensors_to_one_file=False, location=onnx_file_path+'.data') onnx.save_model(onnx_model, onnx_file_path) onnx.checker.check_model(onnx_file_path) return onnx_file_path
def _run(self, X): # pylint: disable=W0221 return self._private_run(X, self.ratio) class Dropout_12(DropoutBase): atts = {'seed': 0} def __init__(self, onnx_node, desc=None, **options): DropoutBase.__init__(self, onnx_node, desc=desc, expected_attributes=Dropout_12.atts, **options) def _run(self, *inputs): # pylint: disable=W0221 X = inputs[0] ratio = 0.5 if len(inputs) <= 1 else inputs[1] training_mode = False if len(inputs) <= 2 else inputs[2] return self._private_run(X, seed=self.seed, ratio=ratio, training_mode=training_mode) if onnx_opset_version() >= 12: Dropout = Dropout_12 else: Dropout = Dropout_7 # pragma: no cover
def test_matmul_integer(self): if legacy_opset_pre_ver(10): raise unittest.SkipTest( "ONNX version {} doesn't support MatMulInteger.".format( defs.onnx_opset_version())) node_def = helper.make_node("MatMulInteger", ["A", "B", "a_zero_point", "b_zero_point"], ["Z"]) # A & B are 3-D tensor and a_zero_point & b_zero_point are scalar A = self._get_rnd_int(-20, 20, shape=(2, 3, 4), dtype=np.int8) B = self._get_rnd_int(-20, 20, shape=(2, 4, 6), dtype=np.int8) a_zero_point = self._get_rnd_int(-20, 20, dtype=np.int8) b_zero_point = self._get_rnd_int(-20, 20, dtype=np.int8) A_minus_zero_point = np.subtract(A.astype(np.int32), a_zero_point.astype(np.int32)) B_minus_zero_point = np.subtract(B.astype(np.int32), b_zero_point.astype(np.int32)) z = np.matmul(A_minus_zero_point, B_minus_zero_point) graph_def = helper.make_graph( [node_def], name="test_unknown_shape", inputs=[ helper.make_tensor_value_info("A", TensorProto.INT8, [None, None, None]), helper.make_tensor_value_info("B", TensorProto.INT8, [None, None, None]), helper.make_tensor_value_info("a_zero_point", TensorProto.INT8, []), helper.make_tensor_value_info("b_zero_point", TensorProto.INT8, []) ], outputs=[ helper.make_tensor_value_info("Z", TensorProto.INT32, [None, None, None]) ]) tf_rep = onnx_graph_to_tensorflow_rep(graph_def) output = tf_rep.run({ "A": A, "B": B, "a_zero_point": a_zero_point, "b_zero_point": b_zero_point }) np.testing.assert_almost_equal(output["Z"], z) # A & B are 4-D tensor and a_zero_point & b_zero_point are 1-D tensor A = self._get_rnd_int(-20, 20, shape=(2, 5, 3, 4), dtype=np.int8) B = self._get_rnd_int(-20, 20, shape=(2, 1, 4, 6), dtype=np.int8) a_zero_point = self._get_rnd_int(-20, 20, shape=(A.shape[-2]), dtype=np.int8) b_zero_point = self._get_rnd_int(-20, 20, shape=(B.shape[-1]), dtype=np.int8) a_zero_point_with_reshape = np.reshape(a_zero_point, [A.shape[-2], 1]) A_minus_zero_point = np.subtract( A.astype(np.int32), a_zero_point_with_reshape.astype(np.int32)) B_minus_zero_point = np.subtract(B.astype(np.int32), b_zero_point.astype(np.int32)) z = np.matmul(A_minus_zero_point, B_minus_zero_point) graph_def = helper.make_graph( [node_def], name="test_unknown_shape", inputs=[ helper.make_tensor_value_info("A", TensorProto.INT8, [None, None, None, None]), helper.make_tensor_value_info("B", TensorProto.INT8, [None, None, None, None]), helper.make_tensor_value_info("a_zero_point", TensorProto.INT8, [None]), helper.make_tensor_value_info("b_zero_point", TensorProto.INT8, [None]) ], outputs=[ helper.make_tensor_value_info("Z", TensorProto.INT32, [None, None, None, None]) ]) tf_rep = onnx_graph_to_tensorflow_rep(graph_def) output = tf_rep.run({ "A": A, "B": B, "a_zero_point": a_zero_point, "b_zero_point": b_zero_point }) np.testing.assert_almost_equal(output["Z"], z)