From a4074cb6c94d0d033f2bf7bd85279c8fad0ef9c0 Mon Sep 17 00:00:00 2001 From: Xavier Dupre Date: Fri, 28 Jul 2023 12:10:43 +0200 Subject: [PATCH] lint Signed-off-by: Xavier Dupre --- docs/examples/plot_convert_lightgbm.py | 7 +- docs/examples/plot_convert_sklearn.py | 7 +- docs/examples/plot_convert_sparkml.py | 8 +- docs/examples/plot_convert_xgboost.py | 7 +- onnxmltools/convert/common/_container.py | 9 +- onnxmltools/convert/common/interface.py | 3 +- onnxmltools/convert/coreml/_parse.py | 242 ++++++++++++------ onnxmltools/convert/coreml/convert.py | 32 ++- .../operator_converters/FeatureVectorizer.py | 3 +- .../operator_converters/GLMClassifier.py | 21 +- .../convert/coreml/operator_converters/SVC.py | 8 +- .../TensorToProbabilityMap.py | 15 +- .../operator_converters/neural_network/Add.py | 3 +- .../neural_network/BatchNorm.py | 21 +- .../neural_network/Bias.py | 6 +- .../neural_network/BidirectionalLSTM.py | 18 +- .../operator_converters/neural_network/Dot.py | 9 +- .../neural_network/Embed.py | 9 +- .../operator_converters/neural_network/GRU.py | 6 +- .../neural_network/ImageScaler.py | 6 +- .../neural_network/InnerProduct.py | 9 +- .../neural_network/L2Normalize.py | 3 +- .../neural_network/LSTM.py | 33 ++- .../neural_network/MeanImage.py | 6 +- .../neural_network/Multiply.py | 3 +- .../operator_converters/neural_network/Pad.py | 12 +- pyproject.toml | 6 + 27 files changed, 339 insertions(+), 173 deletions(-) diff --git a/docs/examples/plot_convert_lightgbm.py b/docs/examples/plot_convert_lightgbm.py index fb44a67eb..3f9ce1027 100644 --- a/docs/examples/plot_convert_lightgbm.py +++ b/docs/examples/plot_convert_lightgbm.py @@ -18,7 +18,9 @@ +++++++++++++ """ - +import os +import matplotlib.pyplot as plt +from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer import numpy import onnx import sklearn @@ -83,9 +85,6 @@ # ++++++++++++++++++++++ # # Finally, let's see the graph converted with *onnxmltools*. -import os -import matplotlib.pyplot as plt -from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer pydot_graph = GetPydotGraph( onx.graph, diff --git a/docs/examples/plot_convert_sklearn.py b/docs/examples/plot_convert_sklearn.py index f2fd61f71..847e3ddee 100644 --- a/docs/examples/plot_convert_sklearn.py +++ b/docs/examples/plot_convert_sklearn.py @@ -30,7 +30,9 @@ A very basic example using random forest and the iris dataset. """ - +import os +import matplotlib.pyplot as plt +from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer import numpy import onnx import sklearn @@ -93,9 +95,6 @@ # ++++++++++++++++++++++ # # Finally, let's see the graph converted with *onnxmltools*. -import os -import matplotlib.pyplot as plt -from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer pydot_graph = GetPydotGraph( onx.graph, diff --git a/docs/examples/plot_convert_sparkml.py b/docs/examples/plot_convert_sparkml.py index 296047eed..a573b2a19 100644 --- a/docs/examples/plot_convert_sparkml.py +++ b/docs/examples/plot_convert_sparkml.py @@ -18,8 +18,11 @@ +++++++++++++ """ + import os import numpy +import matplotlib.pyplot as plt +from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer from pandas import DataFrame import onnx import sklearn @@ -29,8 +32,6 @@ import onnxruntime as rt import pyspark from pyspark.sql import SparkSession -from pyspark.ml.classification import LogisticRegression -from pyspark.ml.classification import LogisticRegression from pyspark.ml.feature import VectorAssembler, StringIndexer import onnxmltools from onnxconverter_common.data_types import FloatTensorType @@ -114,9 +115,6 @@ def stop_spark(spark): # ++++++++++++++++++++++ # # Finally, let's see the graph converted with *onnxmltools*. -import os -import matplotlib.pyplot as plt -from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer pydot_graph = GetPydotGraph( onx.graph, diff --git a/docs/examples/plot_convert_xgboost.py b/docs/examples/plot_convert_xgboost.py index e6bfb5255..444b7ff37 100644 --- a/docs/examples/plot_convert_xgboost.py +++ b/docs/examples/plot_convert_xgboost.py @@ -18,8 +18,10 @@ +++++++++++++ """ - +import os import numpy +import matplotlib.pyplot as plt +from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer import onnx import sklearn from sklearn.datasets import load_iris @@ -84,9 +86,6 @@ # ++++++++++++++++++++++ # # Finally, let's see the graph converted with *onnxmltools*. -import os -import matplotlib.pyplot as plt -from onnx.tools.net_drawer import GetPydotGraph, GetOpNodeProducer pydot_graph = GetPydotGraph( onx.graph, diff --git a/onnxmltools/convert/common/_container.py b/onnxmltools/convert/common/_container.py index de537c0bf..5d0980837 100644 --- a/onnxmltools/convert/common/_container.py +++ b/onnxmltools/convert/common/_container.py @@ -21,7 +21,8 @@ class H2OModelContainer(CommonSklearnModelContainer): class SparkmlModelContainer(RawModelContainer): def __init__(self, sparkml_model): super(SparkmlModelContainer, self).__init__(sparkml_model) - # Sparkml models have no input and output specified, so we create them and store them in this container. + # Sparkml models have no input and output specified, + # so we create them and store them in this container. self._inputs = [] self._outputs = [] @@ -34,12 +35,14 @@ def output_names(self): return [variable.raw_name for variable in self._outputs] def add_input(self, variable): - # The order of adding variables matters. The final model's input names are sequentially added as this list + # The order of adding variables matters. The final model's + # input names are sequentially added as this list if variable not in self._inputs: self._inputs.append(variable) def add_output(self, variable): - # The order of adding variables matters. The final model's output names are sequentially added as this list + # The order of adding variables matters. + # The final model's output names are sequentially added as this list if variable not in self._outputs: self._outputs.append(variable) diff --git a/onnxmltools/convert/common/interface.py b/onnxmltools/convert/common/interface.py index 35195d51a..af0721c39 100644 --- a/onnxmltools/convert/common/interface.py +++ b/onnxmltools/convert/common/interface.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # This file defines the interface of the converter internal object for callback, -# So the usage of the methods and properties list here will not be affected among the different versions. +# So the usage of the methods and properties list here +# will not be affected among the different versions. from onnxconverter_common.interface import * # noqa diff --git a/onnxmltools/convert/coreml/_parse.py b/onnxmltools/convert/coreml/_parse.py index 4164ec26e..778d91dad 100644 --- a/onnxmltools/convert/coreml/_parse.py +++ b/onnxmltools/convert/coreml/_parse.py @@ -3,20 +3,38 @@ import warnings from ..common._container import CoremlModelContainer from ..common._topology import Topology -from ..common.data_types import * +from ..common.data_types import ( + find_type_conversion, + FloatTensorType, + Int64TensorType, + StringTensorType, + DictionaryType, + Int64Type, + FloatType, + StringType, +) def _parse_coreml_feature(feature_info, target_opset, batch_size=1): """ - Encode type information from CoreML's FeatureType protobuf message in converter's type system. - - Scalar types such as Int64FeatureType, DoubleFeatureType, and StringFeatureType in CoreML are interpreted as - [batch_size, 1]-tensor. Tensor-like types such as ArrayFeature in CoreML is viewed as tensors with a prepend - batch_size; for example, we use [batch_size, C, H, W] to denote [C, H, W]-array in CoreML. - :param feature_info: CoreML FeatureDescription (https://apple.github.io/coremltools/coremlspecification/sections/DataStructuresAndFeatureTypes.html#featuretype) + Encode type information from CoreML's FeatureType protobuf message in + converter's type system. + + Scalar types such as Int64FeatureType, DoubleFeatureType, + and StringFeatureType in CoreML are interpreted as + [batch_size, 1]-tensor. Tensor-like types such as + ArrayFeature in CoreML is viewed as tensors with a prepend + batch_size; for example, we use [batch_size, C, H, W] + to denote [C, H, W]-array in CoreML. + + :param feature_info: CoreML FeatureDescription + (https://apple.github.io/coremltools/coremlspecification/ + sections/DataStructuresAndFeatureTypes.html#featuretype) :param target_opset: the target ospet number in the converted model. - :param batch_size: default batch size prepend to scalars and tensors variables from CoreML - :return: one of our Int64Type, FloatType, StringType, Int64TensorType, FloatTensorType, or DictionaryType + :param batch_size: default batch size prepend to scalars + and tensors variables from CoreML + :return: one of our Int64Type, FloatType, StringType, + Int64TensorType, FloatTensorType, or DictionaryType """ raw_type = feature_info.type doc_string = feature_info.shortDescription @@ -29,7 +47,8 @@ def _parse_coreml_feature(feature_info, target_opset, batch_size=1): elif type_name == "stringType": return StringType(doc_string=doc_string) elif type_name == "imageType": - # Produce [C, H, W]-tensor, where C is the number of color channels, H the height, and W the width. + # Produce [C, H, W]-tensor, where C is the number of color channels, + # H the height, and W the width. color_space = raw_type.imageType.colorSpace shape = [batch_size] @@ -41,17 +60,22 @@ def _parse_coreml_feature(feature_info, target_opset, batch_size=1): if color_space == 10: # gray scale shape.append(1) - doc_string += "Image(s) in gray scale. If there are N images, it is a 4-D tensor with shape [N, 1, H, W]" + doc_string += ( + "Image(s) in gray scale. If there are N images, " + "it is a 4-D tensor with shape [N, 1, H, W]" + ) elif color_space == 20: # RGB (20) shape.append(3) doc_string += ( - "Image(s) in RGB format. It is a [N, C, H, W]-tensor. The 1st/2nd/3rd slices along the " + "Image(s) in RGB format. It is a [N, C, H, W]-tensor. " + "The 1st/2nd/3rd slices along the " "C-axis are red, green, and blue channels, respectively." ) elif color_space == 30: # BGR (30) shape.append(3) doc_string += ( - "Image(s) in BGR format. It is a [N, C, H, W]-tensor. The 1st/2nd/3rd slices along the " + "Image(s) in BGR format. It is a [N, C, H, W]-tensor. " + "The 1st/2nd/3rd slices along the " "C-axis are blue, green, and red channels, respectively." ) else: @@ -124,7 +148,8 @@ def _parse_coreml_feature(feature_info, target_opset, batch_size=1): def _parse_model(topology, scope, model, inputs=None, outputs=None): """ - This is a delegate function of all top-level parsing functions. It does nothing but call a proper function + This is a delegate function of all top-level parsing + functions. It does nothing but call a proper function to parse the given model. """ @@ -151,11 +176,14 @@ def _parse_simple_model(topology, parent_scope, model, inputs, outputs): Parse a model containing only one operator (aka simple model). Steps: 1. Create local scope for allocating local variables and operators - 2. Create operator and then feed the model's inputs and outputs to the operator + 2. Create operator and then feed the model's + inputs and outputs to the operator 3. Connect local variables and their corresponding parent variables Note: - 1. Notice that a CoreML operator can contain no input and output, so we directly use model's inputs (outputs). - 2. Input and output names can be identical in CoreML, but they must be different for ONNX. + 1. Notice that a CoreML operator can contain no input + and output, so we directly use model's inputs (outputs). + 2. Input and output names can be identical in CoreML, + but they must be different for ONNX. """ # Create local scope for the considered model @@ -166,9 +194,11 @@ def _parse_simple_model(topology, parent_scope, model, inputs, outputs): # Create operator for the considered model this_operator = scope.declare_local_operator(model.WhichOneof("Type"), model) - # Allocate inputs for the operator and then connect them with inputs from outside + # Allocate inputs for the operator and then connect + # them with inputs from outside for var in model.description.input: - # We assume that no duplicated raw name exists. Note that we set prepend=True because model inputs should + # We assume that no duplicated raw name exists. + # Note that we set prepend=True because model inputs should # not hide any intermediate variables. variable = scope.declare_local_variable( var.name, @@ -179,11 +209,16 @@ def _parse_simple_model(topology, parent_scope, model, inputs, outputs): ) this_operator.inputs.append(variable) - # Connect local variables and variables passed into this scope. Our assumptions are described below. - # 1. Assume a variable with 'A' as its CoreML name is passed in. There must be at least one local variable gets a - # raw name 'A'. That is, for each parent variable, at least one local duplicate is available. - # 2. It's possible to find multiple local variables associated with the same raw name. For example, raw name 'A' can - # be associated with 'A' and 'A1' in ONNX. In this case, we connect the first one to parent input. + # Connect local variables and variables passed into this scope. + # Our assumptions are described below. + # 1. Assume a variable with 'A' as its CoreML name is passed in. + # There must be at least one local variable gets a + # raw name 'A'. That is, for each parent variable, at + # least one local duplicate is available. + # 2. It's possible to find multiple local variables associated + # with the same raw name. For example, raw name 'A' can + # be associated with 'A' and 'A1' in ONNX. In this case, + # we connect the first one to parent input. for parent_variable in inputs: raw_name = parent_variable.raw_name child_variable = scope.variables[scope.variable_name_mapping[raw_name][0]] @@ -202,11 +237,16 @@ def _parse_simple_model(topology, parent_scope, model, inputs, outputs): ) this_operator.outputs.append(variable) - # Connect local variables and variables passed into this scope. Our assumptions are described below. - # 1. Assume a variable with 'A' as its CoreML name is passed in. There must be at least one local variable gets a - # raw name 'A'. That is, for each parent variable, at least one local duplicate is available. - # 2. It's possible to find multiple local variables associated with the same raw name. For example, raw name 'A' can - # be associated with 'A' and 'A1' in ONNX. In this case, we connect the last one to parent output. + # Connect local variables and variables passed into this scope. + # Our assumptions are described below. + # 1. Assume a variable with 'A' as its CoreML name is passed in. + # There must be at least one local variable gets a + # raw name 'A'. That is, for each parent variable, at + # least one local duplicate is available. + # 2. It's possible to find multiple local variables associated + # with the same raw name. For example, raw name 'A' can + # be associated with 'A' and 'A1' in ONNX. In this case, + # we connect the last one to parent output. for parent_variable in outputs: raw_name = parent_variable.raw_name child_variable = scope.variables[scope.variable_name_mapping[raw_name][-1]] @@ -220,12 +260,16 @@ def _parse_pipeline_model(topology, parent_scope, model, inputs, outputs): Parse a pipeline including multiple sub-models. Steps: 1. Create local scope for allocating local variables and operators - 2. Sequentially parse the sub-models and create their inputs and outputs variables - 3. Connect model's (not sub-model's) inputs and outputs with proper variables created when parsing sub-models - 4. Link local variables and the corresponding parent variables (only model's inputs and outputs are considered) + 2. Sequentially parse the sub-models and create their + inputs and outputs variables + 3. Connect model's (not sub-model's) inputs and outputs + with proper variables created when parsing sub-models + 4. Link local variables and the corresponding parent + variables (only model's inputs and outputs are considered) Note: 1. A CoreML sub-model can use the same variable for its input and output. - 2. Two CoreML variables may have the same name but different types. + 2. Two CoreML variables may have the same name but + different types. """ # Create local scope @@ -246,8 +290,10 @@ def _parse_pipeline_model(topology, parent_scope, model, inputs, outputs): # Sequentially parse the sub-models for sub_model in sub_models: - # Declare the sub-model's input and output in this scope. Those input and output variables will be passed into - # the sub-model's parsing function and connected with proper child variables. + # Declare the sub-model's input and output in this scope. + # Those input and output variables will be passed into + # the sub-model's parsing function and connected with + # proper child variables. sub_inputs = [] for var in sub_model.description.input: variable = scope.get_local_variable_or_declare_one( @@ -268,11 +314,14 @@ def _parse_pipeline_model(topology, parent_scope, model, inputs, outputs): sub_outputs.append(variable) _parse_model(topology, scope, sub_model, sub_inputs, sub_outputs) - # Declare the model's (not sub-model's) inputs and then link them with sub-model's inputs + # Declare the model's (not sub-model's) inputs and then + # link them with sub-model's inputs for var in model.description.input: - # Find the first variable with the same raw name declared when parsing the sub-models + # Find the first variable with the same raw name + # declared when parsing the sub-models child_variable = scope.variables[scope.variable_name_mapping[var.name][0]] - # Create model's input variable. Note that we set prepend=True because model inputs should not hide any + # Create model's input variable. Note that we set + # prepend=True because model inputs should not hide any # intermediate variables. variable = scope.declare_local_variable( var.name, @@ -281,8 +330,10 @@ def _parse_pipeline_model(topology, parent_scope, model, inputs, outputs): ), prepend=True, ) - # Feed the input to the sub-model's input. It's possible to add type conversion here by using a casting operator - # rather than identity, but we haven't see the need of doing so in practices. + # Feed the input to the sub-model's input. It's possible + # to add type conversion here by using a casting operator + # rather than identity, but we haven't see the need + # of doing so in practices. operator = scope.declare_local_operator("identity") operator.inputs.append(variable) operator.outputs.append(child_variable) @@ -293,9 +344,11 @@ def _parse_pipeline_model(topology, parent_scope, model, inputs, outputs): operator.inputs.append(parent_variable) operator.outputs.append(child_variable) - # Declare the model's (not sub-model's) inputs and then link them with sub-model's inputs + # Declare the model's (not sub-model's) inputs and then link + # them with sub-model's inputs for var in model.description.output: - # Find the latest variable with the same raw name declared when parsing the sub-models + # Find the latest variable with the same raw name + # declared when parsing the sub-models child_variable = scope.variables[scope.variable_name_mapping[var.name][-1]] # Create model's output variable variable = scope.declare_local_variable( @@ -304,8 +357,10 @@ def _parse_pipeline_model(topology, parent_scope, model, inputs, outputs): var, topology.target_opset, topology.default_batch_size ), ) - # Connect the input and a sub-model's input. It's possible to add type conversion here by using a casting - # operator rather than identity, but we haven't see the need of doing so in practices. + # Connect the input and a sub-model's input. + # It's possible to add type conversion here by using a casting + # operator rather than identity, but we haven't see the + # need of doing so in practices. operator = scope.declare_local_operator("identity") operator.inputs.append(child_variable) operator.outputs.append(variable) @@ -323,13 +378,17 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): Steps: 1. Create local scope for allocating local variables and operators 2. Sequentially parse the preprocessors and layers - 3. Connect model's (neither layers' nor preprocessors') inputs and outputs with proper variables created when + 3. Connect model's (neither layers' nor preprocessors') + inputs and outputs with proper variables created when parsing sub-models. - 4. Link local variables and the corresponding parent variables (only model's inputs and outputs are considered) + 4. Link local variables and the corresponding parent + variables (only model's inputs and outputs are considered) Note: - 1. A CoreML preprocessor/layer can use the same variable for its input and output. + 1. A CoreML preprocessor/layer can use the same variable + for its input and output. 2. Two CoreML variables may have the same name but different types. - 3. Preprocessor sometime may not include any information about its input + 3. Preprocessor sometime may not include any information + about its input """ # Create local scope to which all subsequent variables and operators belongs @@ -353,7 +412,8 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): op.WhichOneof("preprocessor") + "Preprocessor", op ) - # Infer the variable name to be processed if feature name is an empty string + # Infer the variable name to be processed if + # feature name is an empty string name = ( op.featureName if op.featureName != "" else model.description.input[0].name ) @@ -378,8 +438,10 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): # Find out input variable and connect them with the operator for name in op.input: variable = scope.get_local_variable_or_declare_one(name) - # Although most neural network operators only accepts floats, we still need to handle the only exception, - # embedding layer. In the furture, we should create a Cast operator right inside embedding's converter. + # Although most neural network operators only accepts + # floats, we still need to handle the only exception, + # embedding layer. In the furture, we should create + # a Cast operator right inside embedding's converter. if operator.type == "embedding": variable.type = Int64TensorType() else: @@ -396,12 +458,15 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): sink_variables = scope.find_sink_variables() - # Declare the model's inputs and outputs. Then, connect them with proper variables computed by the main network + # Declare the model's inputs and outputs. + # Then, connect them with proper variables computed by the main network for var in model.description.input: - # Search for the first variable (declared when parsing network layers) associated with the considered raw name + # Search for the first variable (declared when parsing + # network layers) associated with the considered raw name child_variable = scope.variables[scope.variable_name_mapping[var.name][0]] - # Declare model input. To prevent intermediate variables form being hidden by model inputs, prepend is True. + # Declare model input. To prevent intermediate variables + # form being hidden by model inputs, prepend is True. variable = scope.declare_local_variable( var.name, _parse_coreml_feature( @@ -410,9 +475,12 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): prepend=True, ) - # A heuristic which forces the input of embedding to be integer tensor rather than float tensor. - # Ideally this should be done by adding a cast operator, but ONNX doesn't have float-to-int casting. - # If this variable is produced by another component in a CoreML pipeline, a bug may occur especially + # A heuristic which forces the input of embedding to + # be integer tensor rather than float tensor. + # Ideally this should be done by adding a cast operator, + # but ONNX doesn't have float-to-int casting. + # If this variable is produced by another component in a + # CoreML pipeline, a bug may occur especially # when the source component's output type is float tensor. if isinstance(child_variable.type, Int64TensorType): variable.type = Int64TensorType(variable.type.shape) @@ -425,7 +493,8 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): operator.inputs.append(variable) operator.outputs.append(child_variable) - # Connect local input variables with proper variables from parent scope + # Connect local input variables with proper variables + # from parent scope for parent_variable in inputs: raw_name = parent_variable.raw_name child_variable = scope.variables[scope.variable_name_mapping[raw_name][0]] @@ -434,7 +503,8 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): operator.outputs.append(child_variable) for var in model.description.output: - # CoreML's predicted label is not connected with any operator, so we handle it later as a special case. + # CoreML's predicted label is not connected with any operator, + # so we handle it later as a special case. special_variable_names = [ model.description.predictedFeatureName, model.description.predictedProbabilitiesName, @@ -444,7 +514,8 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): and var.name in special_variable_names ): continue - # Search for the latest variable (declared when parsing network layers) associated with the considered raw name + # Search for the latest variable (declared when parsing + # network layers) associated with the considered raw name child_variable = scope.variables[scope.variable_name_mapping[var.name][-1]] # Create model output variable @@ -460,12 +531,14 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): operator.inputs.append(child_variable) operator.outputs.append(variable) - # If predicted label exists, connect probability tensor and label by a special operator + # If predicted label exists, connect probability tensor and + # label by a special operator if ( model.WhichOneof("Type") == "neuralNetworkClassifier" and model.description.predictedFeatureName ): - # Find out the description of predicted label and declare a label variable + # Find out the description of predicted label and declare + # a label variable label_variable = None for var in model.description.output: if var.name == model.description.predictedFeatureName: @@ -483,11 +556,13 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): scope.variables[scope.variable_name_mapping[probability_name][-1]] ) else: - # If predicted probability tensor is missing in CoreML model, it defaults to the first sink of the network + # If predicted probability tensor is missing in CoreML model, + # it defaults to the first sink of the network operator.inputs.append(sink_variables[0]) operator.outputs.append(label_variable) - # Probability tensor is implicitly converted into a dictionary (i.e., map) in CoreML. We handle this case here. + # Probability tensor is implicitly converted into a dictionary + # (i.e., map) in CoreML. We handle this case here. if ( model.WhichOneof("Type") == "neuralNetworkClassifier" and model.description.predictedProbabilitiesName @@ -501,10 +576,12 @@ def _parse_neural_network_model(topology, parent_scope, model, inputs, outputs): scope.variables[scope.variable_name_mapping[probability_name][-1]] ) else: - # If predicted probability tensor is missing in CoreML model, it defaults to the first sink of the network + # If predicted probability tensor is missing in CoreML model, + # it defaults to the first sink of the network operator.inputs.append(sink_variables[0]) - # Find out the description of predicted probabilities and declare a variable for probability map + # Find out the description of predicted probabilities + # and declare a variable for probability map for var in model.description.output: if var.name == model.description.predictedProbabilitiesName: probability_type = _parse_coreml_feature( @@ -535,26 +612,35 @@ def parse_coreml( """ This is the root function of the whole parsing procedure. :param model: CoreML model - :param initial_types: A list providing some types for some root variables. Each element is a tuple of a variable + :param initial_types: A list providing some types for + some root variables. Each element is a tuple of a variable name and a type defined in data_types.py. - :param target_opset: number, for example, 7 for ONNX 1.2, and 8 for ONNX 1.3. - :param custom_conversion_functions: a dictionary for specifying the user customized conversion function - :param custom_shape_calculators: a dictionary for specifying the user customized shape calculator - :return: a Topology object. It's a intermediate representation of the input CoreML model + :param target_opset: number, for example, + 7 for ONNX 1.2, and 8 for ONNX 1.3. + :param custom_conversion_functions: a dictionary + for specifying the user customized conversion function + :param custom_shape_calculators: a dictionary + for specifying the user customized shape calculator + :return: a Topology object. It's a intermediate + representation of the input CoreML model """ - # Add model-level input and output names into a set. The set will be fed into our Topology so that all its elements + # Add model-level input and output names into a set. + # The set will be fed into our Topology so that all its elements # will not be used to declare variables reserved_variable_names = set() for var in list(model.description.input) + list(model.description.output): reserved_variable_names.add(var.name) - # Determine the batch size for parsing CoreML model's input and output features. Note that batch size is always + # Determine the batch size for parsing CoreML model's input + # and output features. Note that batch size is always # missing in all CoreML models. default_batch_size = "None" - # Topology is shared by both of CoreML and scikit-learn conversion frameworks, so we have a wrapper class, - # CoremlModelContainer, to make sure our topology-related functions can seamlessly handle both of CoreML and + # Topology is shared by both of CoreML and scikit-learn + # conversion frameworks, so we have a wrapper class, + # CoremlModelContainer, to make sure our topology-related + # functions can seamlessly handle both of CoreML and # scikit-learn. topology = Topology( CoremlModelContainer(model), @@ -567,7 +653,8 @@ def parse_coreml( ) scope = topology.declare_scope("__root__") - # Instead of using CoremlModelContainer, we directly pass the model in because _parse_model is CoreML-specific. + # Instead of using CoremlModelContainer, we directly + # pass the model in because _parse_model is CoreML-specific. _parse_model(topology, scope, model) topology.compile() @@ -581,7 +668,8 @@ def parse_coreml( != color_space ): warnings.warn( - "Conflicting pixel formats found. In ONNX, all input/output images must use the same pixel format." + "Conflicting pixel formats found. In ONNX, " + "all input/output images must use the same pixel format." ) # Use original CoreML names for model-level input(s)/output(s) if variable.raw_name not in reserved_variable_names: diff --git a/onnxmltools/convert/coreml/convert.py b/onnxmltools/convert/coreml/convert.py index 2cbc9046a..575a4b395 100644 --- a/onnxmltools/convert/coreml/convert.py +++ b/onnxmltools/convert/coreml/convert.py @@ -22,21 +22,30 @@ def convert( custom_shape_calculators=None, ): """ - This function converts the specified CoreML model into its ONNX counterpart. Some information such as the produced + This function converts the specified CoreML model into its + ONNX counterpart. Some information such as the produced ONNX model name can be specified. - :param model: A `CoreML model `_ or - a CoreML MLModel object - :param initial_types: A list providing some types for some root variables. Each element is a tuple of a variable + :param model: A `CoreML model + `_ + or a CoreML MLModel object + :param initial_types: A list providing some types + for some root variables. Each element is a tuple of a variable name and a type defined in *data_types.py*. - :param name: The name of the graph (type: GraphProto) in the produced ONNX model (type: ModelProto) + :param name: The name of the graph (type: GraphProto) + in the produced ONNX model (type: ModelProto) :param doc_string: A string attached onto the produced ONNX model :param target_opset: number, for example, 7 for ONNX 1.2, and 8 for ONNX 1.3. - :param targeted_onnx: A string (for example, '1.1.2' and '1.2') used to specify the targeted ONNX version of the - produced model. If ONNXMLTools cannot find a compatible ONNX python package, an error may be thrown. - :param custom_conversion_functions: a dictionary for specifying the user customized conversion function - :param custom_shape_calculators: a dictionary for specifying the user customized shape calculator - :return: An ONNX model (type: ModelProto) which is equivalent to the input CoreML model + :param targeted_onnx: A string (for example, '1.1.2' and '1.2') + used to specify the targeted ONNX version of the + produced model. If ONNXMLTools + cannot find a compatible ONNX python package, an error may be thrown. + :param custom_conversion_functions: a dictionary + for specifying the user customized conversion function + :param custom_shape_calculators: a dictionary + for specifying the user customized shape calculator + :return: An ONNX model (type: ModelProto) + which is equivalent to the input CoreML model Example of initial types: Assume that 'A' and 'B' are two root variable names used in the CoreML @@ -66,7 +75,8 @@ def convert( custom_shape_calculators, ) - # Parse CoreML description, author, and license. Those information will be attached to the final ONNX model. + # Parse CoreML description, author, and license. + # Those information will be attached to the final ONNX model. metadata = spec.description.metadata metadata_props = [] if metadata: diff --git a/onnxmltools/convert/coreml/operator_converters/FeatureVectorizer.py b/onnxmltools/convert/coreml/operator_converters/FeatureVectorizer.py index 7f316501f..2f6c45f3e 100644 --- a/onnxmltools/convert/coreml/operator_converters/FeatureVectorizer.py +++ b/onnxmltools/convert/coreml/operator_converters/FeatureVectorizer.py @@ -12,7 +12,8 @@ def convert_feature_vectorizer(scope, operator, container): input_dims = [] for variable in operator.inputs: if type(variable.type) in [Int64TensorType, Int64Type]: - # We use scaler to convert integers into floats because output is a single tensor and all tensor elements + # We use scaler to convert integers into floats + # because output is a single tensor and all tensor elements # should be in the same type. scaler_name = scope.get_unique_operator_name("Scaler") scaled_name = scope.get_unique_variable_name(variable.full_name + "_scaled") diff --git a/onnxmltools/convert/coreml/operator_converters/GLMClassifier.py b/onnxmltools/convert/coreml/operator_converters/GLMClassifier.py index fc9b75b3c..285dd1f75 100644 --- a/onnxmltools/convert/coreml/operator_converters/GLMClassifier.py +++ b/onnxmltools/convert/coreml/operator_converters/GLMClassifier.py @@ -126,10 +126,14 @@ def convert_glm_classifier(scope, operator, container): **attrs ) - # Add a normalizer to make sure that the sum of all classes' probabilities is 1. It doesn't affect binary - # classification. For multi-class clssifiers, if one applies sigmoid function independently to all raw scores, - # we have to add a normalization so that the sum of all probabilities remains 1. Of course, if softmax is used - # to convert raw scores into probabilities, this normalization doesn't change anything. + # Add a normalizer to make sure that the sum of all + # classes' probabilities is 1. It doesn't affect binary + # classification. For multi-class clssifiers, + # if one applies sigmoid function independently to all raw scores, + # we have to add a normalization so that the sum of + # all probabilities remains 1. Of course, if softmax is used + # to convert raw scores into probabilities, + # this normalization doesn't change anything. if len(class_labels) > 2: normalized_proba_tensor_name = scope.get_unique_variable_name( proba_tensor_name + "_normalized" @@ -143,10 +147,12 @@ def convert_glm_classifier(scope, operator, container): norm="L1", ) else: - # If we don't need a normalization, we just pass the original probability tensor to the following ZipMap + # If we don't need a normalization, we just pass the + # original probability tensor to the following ZipMap normalized_proba_tensor_name = proba_tensor_name - # Add ZipMap to convert normalized probability tensor into probability map + # Add ZipMap to convert normalized probability tensor + # into probability map container.add_node( "ZipMap", [normalized_proba_tensor_name], @@ -155,7 +161,8 @@ def convert_glm_classifier(scope, operator, container): **zipmap_attrs ) else: - # Add linear classifier with isolated probability output, which means that the probability + # Add linear classifier with isolated probability + # output, which means that the probability # tensor won't be accessed by any others. container.add_node( op_type, diff --git a/onnxmltools/convert/coreml/operator_converters/SVC.py b/onnxmltools/convert/coreml/operator_converters/SVC.py index e529668c3..a8e06d9f7 100644 --- a/onnxmltools/convert/coreml/operator_converters/SVC.py +++ b/onnxmltools/convert/coreml/operator_converters/SVC.py @@ -101,11 +101,13 @@ def convert_svm_classifier(scope, operator, container): else: raise ValueError("Unknown class label type") - # For classifiers, due to the different representation of classes' probabilities, we need to add some - # operators for type conversion. It turns out that we have the following topology. + # For classifiers, due to the different representation + # of classes' probabilities, we need to add some + # operators for type conversion. It turns out that we + # have the following topology. # input features ---> SupportVectorClassifier ---> label (must present) # | - # '--> probability tensor ---> ZipMap ---> probability map (optional) + # '--> probability tensor raw_model = operator.raw_operator # Find label name and probability name diff --git a/onnxmltools/convert/coreml/operator_converters/TensorToProbabilityMap.py b/onnxmltools/convert/coreml/operator_converters/TensorToProbabilityMap.py index 68f519628..ca0f41d36 100644 --- a/onnxmltools/convert/coreml/operator_converters/TensorToProbabilityMap.py +++ b/onnxmltools/convert/coreml/operator_converters/TensorToProbabilityMap.py @@ -7,13 +7,18 @@ def convert_tensor_to_probability_map(scope, operator, container): """ - This converter tries to convert a special operator 'TensorToProbabilityMap' into a sequence of some ONNX operators. - Those operators are used to create a dictionary in which keys are class labels and values are the associated - probabilities. We assume that the elements in the given probability tensor are aligned with the class labels + This converter tries to convert a special operator + 'TensorToProbabilityMap' into a sequence of some ONNX operators. + Those operators are used to create a dictionary in which keys + are class labels and values are the associated + probabilities. We assume that the elements in the given probability + tensor are aligned with the class labels specified in the CoreML model. - Notice that ONNX<1.2 doesn't support a CoreML classifier with a batch size larger than one because old ONNX ZipMap - is not able to produce a sequence of dictionaries. This issue has been fixed in ONNX-1.2. + Notice that ONNX<1.2 doesn't support a CoreML classifier + with a batch size larger than one because old ONNX ZipMap + is not able to produce a sequence of dictionaries. + This issue has been fixed in ONNX-1.2. """ attrs = {"name": scope.get_unique_operator_name("ZipMap")} diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/Add.py b/onnxmltools/convert/coreml/operator_converters/neural_network/Add.py index 716a9b0f2..a4af76692 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/Add.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/Add.py @@ -58,7 +58,8 @@ def convert_add(scope, operator, container): broadcast=1, ) - # Accumulate other inputs onto intermediate tensors. Note that we may use the original operator's output as + # Accumulate other inputs onto intermediate tensors. + # Note that we may use the original operator's output as # the last intermediate tensor. for i in range(2, len(inputs)): left_tensor = intermediate_tensor_name diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/BatchNorm.py b/onnxmltools/convert/coreml/operator_converters/neural_network/BatchNorm.py index 7f4d68877..48d81f3c4 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/BatchNorm.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/BatchNorm.py @@ -10,7 +10,8 @@ def convert_batch_normalization(scope, operator, container): if params.instanceNormalization and not params.computeMeanVar: raise ValueError( - "It is impossible to do instance normalization without re-computing mean and variance" + "It is impossible to do instance normalization " + "without re-computing mean and variance" ) if params.instanceNormalization and params.computeMeanVar: @@ -60,21 +61,27 @@ def convert_batch_normalization(scope, operator, container): spatial = 1 # True if not params.instanceNormalization and params.computeMeanVar: - # In this case, we apply batch normalization and adjust the statistics stored according the the batch + # In this case, we apply batch normalization and adjust + # the statistics stored according the the batch # being processed. - # To update "mean" and "var," we put their updated results back to the associated input tensors. + # To update "mean" and "var," we put their updated results + # back to the associated input tensors. outputs += inputs[1:3] - # We also allocate two extra output buffers to store some intermediate results, but they are not used + # We also allocate two extra output buffers to store some + # intermediate results, but they are not used # in CoreML model. outputs.append(scope.get_unique_variable_name("saved_mean")) outputs.append(scope.get_unique_variable_name("saved_var")) # We choose "training" mode because some variables need to be updated. is_test = 0 # False elif not params.instanceNormalization and not params.computeMeanVar: - # In this case, batch normalization is applied without updating mean, variance, etc. according to - # the batches being processed. It means this operator works under testing model. Because there is no - # variable update, we don't need to specify extra inputs and outputs like in previous code block. + # In this case, batch normalization is applied without + # updating mean, variance, etc. according to + # the batches being processed. It means this operator + # works under testing model. Because there is no + # variable update, we don't need to specify extra + # inputs and outputs like in previous code block. is_test = 1 # True else: raise ValueError("Unsupported operation mode") diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/Bias.py b/onnxmltools/convert/coreml/operator_converters/neural_network/Bias.py index 2fbeafd69..6841991b8 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/Bias.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/Bias.py @@ -7,14 +7,16 @@ def convert_bias(scope, operator, container): - # Feed the input (which we are going to add a bias onto) into Add operator. Its shape is [C, H, W] in CoreML but + # Feed the input (which we are going to add a bias onto) + # into Add operator. Its shape is [C, H, W] in CoreML but # [N, C, H, W] in ONNX. params = operator.raw_operator.bias # Adjust CoreML's bias shape and find a proper axis for broadcasting axis, shape = deduce_broadcast_axis_and_shape(container.target_opset, params.shape) - # No matter what shape it is, we need "broadcast" on because input shape is 4-D while bias is at most 3-D. + # No matter what shape it is, we need "broadcast" on + # because input shape is 4-D while bias is at most 3-D. broadcast = 1 # True # Create bias vector as an ONNX tensor diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/BidirectionalLSTM.py b/onnxmltools/convert/coreml/operator_converters/neural_network/BidirectionalLSTM.py index ea226356c..723805eb6 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/BidirectionalLSTM.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/BidirectionalLSTM.py @@ -265,7 +265,8 @@ def convert_bidirectional_lstm(scope, operator, container): else: lstm_inputs.append("") - # Due to the position sensitivity in ONNX argument parsing, we add an empty string for the non-existing + # Due to the position sensitivity in ONNX argument parsing, + # we add an empty string for the non-existing # sequence length lstm_inputs.append("") @@ -291,7 +292,8 @@ def convert_bidirectional_lstm(scope, operator, container): desired_shape=[2, 1, hidden_size], ) - # Add zero initializers to forward and backward initial hidden states so that they become optional + # Add zero initializers to forward and backward initial + # hidden states so that they become optional container.add_initializer( operator.inputs[1].full_name, onnx_proto.TensorProto.FLOAT, @@ -332,7 +334,8 @@ def convert_bidirectional_lstm(scope, operator, container): ) lstm_inputs.append(lstm_c_init_reshape_name) - # Add zero initializers to forward and backward initial cell states so that they become optional + # Add zero initializers to forward and backward initial + # cell states so that they become optional container.add_initializer( operator.inputs[2].full_name, onnx_proto.TensorProto.FLOAT, @@ -412,8 +415,10 @@ def convert_bidirectional_lstm(scope, operator, container): else: op_version = 7 - # Create the major ONNX LSTM operator. We assign a tensor name to each output of LSTM. However, variables can be - # undefined in some cases. For example, when output_sequence=False, the first output is not meaningful. + # Create the major ONNX LSTM operator. We assign a tensor name + # to each output of LSTM. However, variables can be + # undefined in some cases. For example, when output_sequence=False, + # the first output is not meaningful. lstm_y_name = scope.get_unique_variable_name(lstm_op_name + "_Y") lstm_y_h_name = scope.get_unique_variable_name(lstm_op_name + "_Y_h") lstm_y_c_name = scope.get_unique_variable_name(lstm_op_name + "_Y_c") @@ -453,7 +458,8 @@ def convert_bidirectional_lstm(scope, operator, container): axis=0, ) else: - # Here we ignore ONNX RNN's first output because it's useless. The second output of ONNX LSTM will be used to + # Here we ignore ONNX RNN's first output because it's useless. + # The second output of ONNX LSTM will be used to # generate the first and the second outputs of CoreML LSTM. # Directly reshape ONNX LSTM's 2nd output to CoreML LSTM's 1st output. diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/Dot.py b/onnxmltools/convert/coreml/operator_converters/neural_network/Dot.py index f6a52c5ec..6b9d7e4c6 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/Dot.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/Dot.py @@ -5,9 +5,12 @@ def convert_dot(scope, operator, container): - # To calculate cosine similarity, we first use LpNormalization to make the two input vectors unit-length. - # Then, we calculate element-wise product of the two unit-length vectors. Finally, the similarity is the - # sum of all the product's elements. Notice that we carefully specify the axis of the subsequent operators, + # To calculate cosine similarity, we first use + # LpNormalization to make the two input vectors unit-length. + # Then, we calculate element-wise product of the two + # unit-length vectors. Finally, the similarity is the + # sum of all the product's elements. Notice that + # we carefully specify the axis of the subsequent operators, # so they can work properly with a batch of vectors. if operator.raw_operator.dot.cosineSimilarity: diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/Embed.py b/onnxmltools/convert/coreml/operator_converters/neural_network/Embed.py index 6ad454918..d5ee3a566 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/Embed.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/Embed.py @@ -12,7 +12,8 @@ def convert_embedding(scope, operator, container): gather_op_name = scope.get_unique_operator_name("Gather") gather_attrs = {"name": gather_op_name} - # Reshape the indexes we want to embed to 1-D tensor. Otherwise, ONNX Gather's output may get wrong shape. + # Reshape the indexes we want to embed to 1-D tensor. + # Otherwise, ONNX Gather's output may get wrong shape. reshaped_input_name = scope.get_unique_variable_name( gather_op_name + "input_reshaped" ) # 2nd input of Gather @@ -24,7 +25,8 @@ def convert_embedding(scope, operator, container): desired_shape=[-1], ) - # ONNX Gather accepts integers so we add a Cast to enforce this before feeding input into ONNX Gather. + # ONNX Gather accepts integers so we add a Cast to enforce + # this before feeding input into ONNX Gather. casted_input_name = scope.get_unique_variable_name( gather_op_name + "input_casted" ) # 2nd input of Gather @@ -69,7 +71,8 @@ def convert_embedding(scope, operator, container): container.add_initializer( bias_name, onnx_proto.TensorProto.FLOAT, bias_shape, params.bias.floatValue ) - # Create an addition operator to add bias (shape: [C]) into Gather's tensor (shape: [N, C]) + # Create an addition operator to add bias (shape: [C]) + # into Gather's tensor (shape: [N, C]) apply_add( scope, [gather_output_name, bias_name], diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/GRU.py b/onnxmltools/convert/coreml/operator_converters/neural_network/GRU.py index 41af40418..639f745aa 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/GRU.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/GRU.py @@ -211,9 +211,11 @@ def convert_gru(scope, operator, container): "GRU", gru_inputs, gru_outputs, op_version=op_version, **gru_attrs ) - # To simulate CoreML LSTM, we add post-processing operators to adjust ONNX LSTM outputs + # To simulate CoreML LSTM, we add post-processing + # operators to adjust ONNX LSTM outputs if params.sequenceOutput: - # Again, the output shapes in ONNX's GRU is not consistent with that in CoreML, so we need + # Again, the output shapes in ONNX's GRU + # is not consistent with that in CoreML, so we need # to adjust the result produced by ONNX according to CoreML format. apply_reshape( scope, diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/ImageScaler.py b/onnxmltools/convert/coreml/operator_converters/neural_network/ImageScaler.py index 7b0804c3c..0902065b9 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/ImageScaler.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/ImageScaler.py @@ -7,7 +7,8 @@ def convert_preprocessing_scaler(scope, operator, container): params = operator.raw_operator.scaler - # Specify some of this operator's attribute. The scale parameter in CoreML is always a scalar. + # Specify some of this operator's attribute. + # The scale parameter in CoreML is always a scalar. # We just copy it and let ONNX scaler to broadcast it to all channels. color_space = operator.inputs[0].type.color_space @@ -32,7 +33,8 @@ def convert_preprocessing_scaler(scope, operator, container): **attrs ) else: - # In comments below, assume input tensor is X, the scale scalar is a, the bias vector is b. + # In comments below, assume input tensor is X, + # the scale scalar is a, the bias vector is b. # Store the scalar, a, used to scale all elements in the input tensor. aName = scope.get_unique_variable_name(operator.full_name + "_scale") diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/InnerProduct.py b/onnxmltools/convert/coreml/operator_converters/neural_network/InnerProduct.py index afbb25526..c51d0326b 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/InnerProduct.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/InnerProduct.py @@ -10,7 +10,8 @@ def convert_inner_product(scope, operator, container): # Apply pre-processing step if needed if len(operator.inputs[0].type.shape) == 4: - # Input shape is [N, C, 1, 1]. Adjust input shape because Gemm in ONNX only takes 2-D input + # Input shape is [N, C, 1, 1]. Adjust input shape + # because Gemm in ONNX only takes 2-D input reshaped_tensor_name = scope.get_unique_variable_name( operator.inputs[0].full_name + "_reshaped" ) @@ -45,7 +46,8 @@ def convert_inner_product(scope, operator, container): name_c, onnx_proto.TensorProto.FLOAT, shape_c, [0.0] * shape_b[0] ) - # Set up attributes for ONNX Gemm which is the counterpart of CoreML inner product layer in ONNX. + # Set up attributes for ONNX Gemm which is the counterpart + # of CoreML inner product layer in ONNX. attrs = {"name": operator.full_name} attrs["alpha"] = 1.0 attrs["beta"] = 1.0 @@ -66,7 +68,8 @@ def convert_inner_product(scope, operator, container): else: op_version = 11 - # Create the major ONNX operator, Gemm, to do CoreML inner product and possibly add shape adjustment + # Create the major ONNX operator, Gemm, to do CoreML + # inner product and possibly add shape adjustment if len(operator.inputs[0].type.shape) == 4: # Input shape is [N, C, 1, 1] so we expect output is also 4-D, [N, C', 1, 1]. buffer_tensor_name = scope.get_unique_variable_name( diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/L2Normalize.py b/onnxmltools/convert/coreml/operator_converters/neural_network/L2Normalize.py index dee1706ae..573711da1 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/L2Normalize.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/L2Normalize.py @@ -4,7 +4,8 @@ def convert_l2_normalization(scope, operator, container): - # The first dimension is batch size, so the normalization is done along the 2nd axis (indexed by 1). + # The first dimension is batch size, so the + # normalization is done along the 2nd axis (indexed by 1). attrs = { "name": operator.full_name, "axis": 1, diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/LSTM.py b/onnxmltools/convert/coreml/operator_converters/neural_network/LSTM.py index 6b3a91fa9..839d55f31 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/LSTM.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/LSTM.py @@ -8,14 +8,21 @@ def convert_unidirectional_lstm(scope, operator, container): - # The LSTM inputs are feature vector, X, initial hidden state, h_init, and initial cell state, c_init. - # In CorML, their shapes respectively are [S, C_in], [1, C_out], and [1, C_out], where C_in is input feature - # length, # C_out is output dimension, and S is sequence length. Note that S-axis is also known as time axis. - # In ONNX, those shapes become [S, N, C_in] (X), [D, N, C_out] (h_init), and [D, N, C_out]. To simulate - # CoreML LSTM under ONNX, we need some extra operators in addition to LSTM itself. + # The LSTM inputs are feature vector, X, initial hidden state, + # h_init, and initial cell state, c_init. + # In CorML, their shapes respectively are [S, C_in], [1, C_out], + # and [1, C_out], where C_in is input feature + # length, # C_out is output dimension, and S is sequence length. + # Note that S-axis is also known as time axis. + # In ONNX, those shapes become [S, N, C_in] (X), [D, N, C_out] + # (h_init), and [D, N, C_out]. To simulate + # CoreML LSTM under ONNX, we need some extra operators + # in addition to LSTM itself. # - # Note that N=1 and D=1 are always true in ONNX if we are considering LSTM in CoreML because there is no - # batch size in CoreML spec and CoreML LSTM is always uni-directional. + # Note that N=1 and D=1 are always true in ONNX + # if we are considering LSTM in CoreML because there is no + # batch size in CoreML spec and CoreML LSTM + # is always uni-directional. # # Below we provide a visualization of our conversion for CoreML LSTM. # @@ -24,7 +31,8 @@ def convert_unidirectional_lstm(scope, operator, container): # X: input features of CoreML LSTM # h_init: initial LSTM hidden state in CoreML # c_init: initial LSTM cell state in CoreML - # Y: CoreML LSTM's output. It can be [S, C_out] (if sequence_output is on) or [1, C_out] (if sequence_output is off) + # Y: CoreML LSTM's output. It can be [S, C_out] + # (if sequence_output is on) or [1, C_out] (if sequence_output is off) # Y_h: CoreML LSTM's last hidden state # Y_c: CoreML LSTM's last cell state # @@ -211,7 +219,8 @@ def convert_unidirectional_lstm(scope, operator, container): # Provide ONNX LSTM the initial hidden state when necessary if len(operator.inputs) > 1: - # Assign a Reshape to adjust CoreML hidden state's shape [1, C]/[1, C, 1, 1] into its ONNX counterpart [1, 1, C] + # Assign a Reshape to adjust CoreML hidden state's shape + # [1, C]/[1, C, 1, 1] into its ONNX counterpart [1, 1, C] lstm_h_init_reshape_name = scope.get_unique_variable_name( lstm_op_name + "_h_init_reshape" ) @@ -278,7 +287,8 @@ def convert_unidirectional_lstm(scope, operator, container): else: lstm_inputs.append("") - # Parse activation functions' information and add them into ONNX LSTM's attribute dictionary + # Parse activation functions' information and add + # them into ONNX LSTM's attribute dictionary activation_types = [] alphas = [] betas = [] @@ -338,7 +348,8 @@ def convert_unidirectional_lstm(scope, operator, container): desired_shape=[1, hidden_size], ) else: - # Here we ingore ONNX LSTM's first output because it's useless and use the second output of ONNX LSTM to produce + # Here we ingore ONNX LSTM's first output because + # it's useless and use the second output of ONNX LSTM to produce # the first output of CoreML LSTM apply_reshape( scope, diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/MeanImage.py b/onnxmltools/convert/coreml/operator_converters/neural_network/MeanImage.py index afc637c9d..0ebd3a0b5 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/MeanImage.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/MeanImage.py @@ -8,7 +8,8 @@ def convert_preprocessing_mean_image(scope, operator, container): mean_tensor_name = scope.get_unique_variable_name(operator.full_name + "_mean") - # We assume that the first input's shape is [N, C, H, W] so that the mean image's shape, [C, H, W], can + # We assume that the first input's shape is [N, C, H, W] + # so that the mean image's shape, [C, H, W], can # be inferred from the first input's shape. container.add_initializer( mean_tensor_name, @@ -17,7 +18,8 @@ def convert_preprocessing_mean_image(scope, operator, container): operator.raw_operator.meanImage, ) - # We assume that the first input variable's shape is [N, C, H, W] while the mean image's shape is [C, H, W]. Thus, + # We assume that the first input variable's shape is [N, C, H, W] + # while the mean image's shape is [C, H, W]. Thus, # broadcasting should be enabled starting with axis=1. apply_sub( scope, diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/Multiply.py b/onnxmltools/convert/coreml/operator_converters/neural_network/Multiply.py index 53838a446..f0d304318 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/Multiply.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/Multiply.py @@ -61,7 +61,8 @@ def convert_multiply(scope, operator, container): broadcast=1, ) - # Accumulate other inputs onto intermediate tensors. Note that we may use the original operator's output as + # Accumulate other inputs onto intermediate tensors. + # Note that we may use the original operator's output as # the last intermediate tensor. for i in range(2, len(inputs)): left_tensor = intermediate_tensor_name diff --git a/onnxmltools/convert/coreml/operator_converters/neural_network/Pad.py b/onnxmltools/convert/coreml/operator_converters/neural_network/Pad.py index f41192359..a16087df4 100644 --- a/onnxmltools/convert/coreml/operator_converters/neural_network/Pad.py +++ b/onnxmltools/convert/coreml/operator_converters/neural_network/Pad.py @@ -13,11 +13,15 @@ def convert_padding(scope, operator, container): raise ValueError("Unsupported padding mode: {}".format(pad_type)) mode = pad_table[pad_type] - # CoreML only pads for their H- and W-axes. Here we assume the shape of the tensor to be padded + # CoreML only pads for their H- and W-axes. + # Here we assume the shape of the tensor to be padded # is [N, C, H, W], so we have 8 padding amounts - # pads = [N_begin_index, C_begin_index, H_begin_index, W_begin_index, - # N_end_index, C_end_index, H_end_index, W_end_index] - # Because only H- and W-axes are padded in CoreML, we leave padding amounts of N- and C-axes zeros. + # pads = [N_begin_index, C_begin_index, + # H_begin_index, W_begin_index, + # N_end_index, C_end_index, + # H_end_index, W_end_index] + # Because only H- and W-axes are padded in CoreML, + # we leave padding amounts of N- and C-axes zeros. pads = [0, 0, 0, 0, 0, 0, 0, 0] if len(params.paddingAmounts.borderAmounts) > 0: # Set H_begin_index diff --git a/pyproject.toml b/pyproject.toml index 69888b7ac..79f48d064 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,4 +15,10 @@ max-complexity = 10 [tool.ruff.per-file-ignores] "**/__init__.py" = ["F401"] +"onnxmltools/convert/coreml/operator_converters/GLMClassifier.py" = ["E501"] +"onnxmltools/convert/coreml/operator_converters/SVC.py" = ["E501"] +"onnxmltools/convert/coreml/operator_converters/TensorToLabel.py" = ["E501"] +"onnxmltools/convert/coreml/operator_converters/TreeEnsemble.py" = ["E501"] +"onnxmltools/convert/coreml/operator_converters/neural_network/BidirectionalLSTM.py" = ["E501"] +"onnxmltools/convert/coreml/operator_converters/neural_network/GRU.py" = ["E501"] "onnxmltools/convert/coreml/operator_converters/neural_network/SimpleRNN.py" = ["E501"]