Exemplo n.º 1
0
    def test_dead_layer_partial_branch(self):
        convergence_tolerance = 1e-8

        input_features = [("input", datatypes.Array(*(2,)))]
        output_features = [("out", None)]

        builder = neural_network.NeuralNetworkBuilder(
            input_features, output_features, disable_rank5_shape_mapping=True
        )
        # add condition to break from the loop, if convergence criterion is met
        builder.add_less_than("cond", ["input"], "cond", alpha=convergence_tolerance)
        branch_layer = builder.add_branch("branch_layer", "cond")
        builder_ifbranch = neural_network.NeuralNetworkBuilder(
            nn_spec=branch_layer.branch.ifBranch
        )
        builder_ifbranch.add_activation("relu1", "RELU", "input", "relu1_out")
        builder_ifbranch.add_activation("relu2_out", "RELU", "relu1_out", "relu2_out")
        builder_elsebranch = neural_network.NeuralNetworkBuilder(
            nn_spec=branch_layer.branch.elseBranch
        )
        builder_elsebranch.add_activation("linear1", "LINEAR", "input", "linear1_out")
        builder_elsebranch.add_activation(
            "linear_red_1", "LINEAR", "input", "linear_red1_out"
        )
        builder_elsebranch.add_activation(
            "linear_red_2", "LINEAR", "linear_red1_out", "linear_red2_out"
        )
        builder_elsebranch.add_activation(
            "linear2", "LINEAR", "linear1_out", "relu2_out"
        )
        builder.add_squeeze("out", "relu2_out", "out", squeeze_all=True)

        mlmodel = MLModel(builder.spec, compute_units=ComputeUnit.CPU_ONLY)

        if not _IS_MACOS:
            # Can not get predictions unless on macOS.
            return

        data = np.random.rand(2,)
        data_dict = {"input": data}
        before_pass_out = mlmodel.predict(data_dict)["out"]
        if DEBUG:
            print("\n mlmodel description before remove disconnected layers pass: \n")
            print_network_spec(builder.spec, style="coding")
        old_spec = copy.copy(builder.spec)
        remove_disconnected_layers(builder.spec)
        if DEBUG:
            print("\n mlmodel description after remove disconnected layers pass: \n")
            print_network_spec(builder.spec, style="coding")
        mlmodel = MLModel(builder.spec, compute_units=ComputeUnit.CPU_ONLY)
        after_pass_out = mlmodel.predict(data_dict)["out"]

        np.testing.assert_almost_equal(before_pass_out, after_pass_out, decimal=2)
        np.testing.assert_equal(
            len(old_spec.neuralNetwork.layers[1].branch.ifBranch.layers),
            len(builder.spec.neuralNetwork.layers[1].branch.ifBranch.layers),
        )
        np.testing.assert_equal(
            len(builder.spec.neuralNetwork.layers[1].branch.elseBranch.layers), 2
        )
Exemplo n.º 2
0
def assert_model_is_valid(
    program, inputs, backend="nn_proto", verbose=True, expected_output_shapes=None
):
    """
    Assert Core ML model is valid.

    Inputs:

    - input: str -> shape tuple. All program input names need to appear in str.
      shape tuple can only contain positive integers.
    """
    input_dict = dict()
    for name, shape in inputs.items():
        input_dict[name] = np.random.rand(*shape)

    # Avoid circular import
    from coremltools.converters.mil.testing_reqs import ct
    mlmodel = ct.convert(program, source="mil", convert_to=backend)
    assert mlmodel is not None

    if verbose:
        from coremltools.models.neural_network.printer import print_network_spec
        print_network_spec(mlmodel.get_spec(), style="coding")

    if _IS_MACOS:
        prediction = mlmodel.predict(input_dict, useCPUOnly=True)
        assert prediction is not None
        if expected_output_shapes is not None:
            for out_name, out_shape in expected_output_shapes.items():
                assert out_name in prediction
                assert out_shape == prediction[out_name].shape
Exemplo n.º 3
0
    def test_dead_layer_remove_branch(self):
        convergence_tolerance = 1e-8

        input_features = [('input', datatypes.Array(*(2,)))]
        output_features = [('out', None)]

        builder = neural_network.NeuralNetworkBuilder(input_features, output_features, disable_rank5_shape_mapping=True)
        # add condition to break from the loop, if convergence criterion is met
        builder.add_less_than('cond', ['input'], 'cond', alpha=convergence_tolerance)
        branch_layer = builder.add_branch('branch_layer', 'cond')
        builder_ifbranch = neural_network.NeuralNetworkBuilder(nn_spec=branch_layer.branch.ifBranch)
        builder_ifbranch.add_activation('relu1', 'RELU', 'input', 'relu1_out')
        builder_ifbranch.add_activation('relu2_out', 'RELU', 'relu1_out', 'relu2_out')
        builder_elsebranch = neural_network.NeuralNetworkBuilder(nn_spec=branch_layer.branch.elseBranch)
        builder_elsebranch.add_activation('linear1', 'LINEAR', 'input', 'linear1_out')
        builder_elsebranch.add_activation('linear2', 'LINEAR', 'linear1_out', 'relu2_out')
        builder.add_squeeze('out', 'input', 'out', squeeze_all=True)

        mlmodel = MLModel(builder.spec)
        data = np.random.rand(2,)
        data_dict = {'input': data}
        before_pass_out = mlmodel.predict(data_dict)['out']
        if DEBUG:
            print('\n mlmodel description before remove disconnected layers pass: \n')
            print_network_spec(builder.spec, style='coding')
        remove_disconnected_layers(builder.spec)
        if DEBUG:
            print('\n mlmodel description after remove disconnected layers pass: \n')
            print_network_spec(builder.spec, style='coding')
        mlmodel = MLModel(builder.spec)
        after_pass_out = mlmodel.predict(data_dict)['out']

        np.testing.assert_almost_equal(before_pass_out, after_pass_out, decimal=4)
        np.testing.assert_equal(len(builder.spec.neuralNetwork.layers), 1)
Exemplo n.º 4
0
    def _test_keras_model_tf2(self, model, data_mode, decimal, use_cpu_only, has_variables, verbose):

        core_ml_model_file = self.model_file.rsplit('.')[0] + '.mlmodel'

        input_dict = {inp.op.name: inp.shape.as_list() for inp in model.inputs}
        for name, shape in input_dict.items():
            input_dict[name] = [dim if dim is not None else 1 for dim in shape]
        output_list = ['Identity']
        model.save(self.model_file)

        # convert Keras model into Core ML model format
        core_ml_model = coremltools.converters.tensorflow.convert(
            filename=self.model_file,
            inputs=input_dict,
            outputs=output_list,
            use_cpu_only=use_cpu_only)

        if verbose:
            print('\nKeras model saved at {}'.format(self.model_file))
            print('\nCore ML model description:')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(core_ml_model.get_spec(), style='coding')
            core_ml_model.save(core_ml_model_file)
            print('\nCore ML model saved at {}'.format(core_ml_model_file))

        core_ml_inputs = {
            name: generate_data(shape, data_mode) for name, shape in input_dict.items()
        }

        # run prediction and compare results
        keras_output = model.predict(list(core_ml_inputs.values())[0])
        core_ml_output = core_ml_model.predict(
            core_ml_inputs, useCPUOnly=use_cpu_only)[output_list[0]]

        if verbose:
            print('\nPredictions', keras_output.shape, ' vs.', core_ml_output.shape)
            print(keras_output.flatten()[:6])
            print(core_ml_output.flatten()[:6])

        np.testing.assert_array_equal(
            keras_output.shape, core_ml_output.shape)
        np.testing.assert_almost_equal(
            keras_output.flatten(), core_ml_output.flatten(), decimal=decimal)
Exemplo n.º 5
0
def assert_model_is_valid(program,
                          inputs,
                          backend=("neuralnetwork", "fp32"),
                          verbose=True,
                          expected_output_shapes=None):
    """
    Assert Core ML model is valid.

    Inputs:

    - input: str -> shape tuple. All program input names need to appear in str.
      shape tuple can only contain positive integers.
    """
    # Avoid circular import
    from coremltools.converters.mil.testing_reqs import ct

    input_dict = dict()
    for name, shape in inputs.items():
        input_dict[name] = np.random.rand(*shape)

    mlmodel = ct_convert(program,
                         source="milinternal",
                         convert_to=backend,
                         compute_units=ct.ComputeUnit.CPU_ONLY)
    assert mlmodel is not None

    if verbose:
        from coremltools.models.neural_network.printer import print_network_spec
        print_network_spec(mlmodel.get_spec(), style="coding")

    if _IS_MACOS and (not mlmodel.is_package
                      or coremltoolsutils._macos_version() >= (12, 0)):
        prediction = mlmodel.predict(input_dict)
        assert prediction is not None
        if expected_output_shapes is not None:
            for out_name, out_shape in expected_output_shapes.items():
                assert out_name in prediction
                assert out_shape == prediction[out_name].shape, \
                        "{} != {}".format(out_shape, prediction[out_name].shape)
Exemplo n.º 6
0
    def _test_keras_model(self,
                          model,
                          data_mode='random',
                          delta=1e-2,
                          use_cpu_only=True,
                          has_variables=True):
        """Saves out the backend TF graph from the Keras model and tests it  
        """

        model_dir = tempfile.mkdtemp()
        graph_def_file = os.path.join(model_dir, 'tf_graph.pb')
        checkpoint_file = os.path.join(model_dir, 'tf_model.ckpt')
        frozen_model_file = os.path.join(model_dir, 'tf_frozen.pb')
        coreml_model_file = os.path.join(model_dir, 'coreml_model.mlmodel')

        input_shapes = {
            inp.op.name: inp.shape.as_list()
            for inp in model.inputs
        }
        for name, shape in input_shapes.items():
            input_shapes[name] = [
                dim if dim is not None else 1 for dim in shape
            ]

        output_node_names = [output.op.name for output in model.outputs]

        tf_graph = K.get_session().graph
        tf.reset_default_graph()
        if has_variables:
            with tf_graph.as_default() as g:
                saver = tf.train.Saver()

        # TODO - if Keras backend has_variable is False, we're not making variables constant
        with tf.Session(graph=tf_graph) as sess:
            sess.run(tf.global_variables_initializer())
            feed_dict = {}
            for name, shape in input_shapes.items():
                tensor_name = tf_graph.get_operation_by_name(
                    name).outputs[0].name
                feed_dict[tensor_name] = generate_data(shape, data_mode)
            # run the result
            fetches = [
                tf_graph.get_operation_by_name(name).outputs[0]
                for name in output_node_names
            ]
            result = sess.run(fetches, feed_dict=feed_dict)
            # save graph definition somewhere
            tf.train.write_graph(sess.graph,
                                 model_dir,
                                 graph_def_file,
                                 as_text=False)

            # freeze_graph() has been raising error with tf.keras models since no
            # later than TensorFlow 1.6, so we're not using freeze_graph() here.
            # See: https://github.com/tensorflow/models/issues/5387
            output_graph_def = tf.graph_util.convert_variables_to_constants(
                sess,  # The session is used to retrieve the weights
                tf_graph.as_graph_def(
                ),  # The graph_def is used to retrieve the nodes
                output_node_names  # The output node names are used to select the useful nodes
            )
            with tf.gfile.GFile(frozen_model_file, "wb") as f:
                f.write(output_graph_def.SerializeToString())

        K.clear_session()

        # convert to CoreML
        mlmodel = coremltools.converters.tensorflow.convert(
            frozen_model_file,
            inputs=input_shapes,
            outputs=output_node_names,
            use_cpu_only=use_cpu_only)

        if DEBUG:
            print('\n mlmodel description: \n')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(mlmodel.get_spec(), style='coding')
            mlmodel.save(coreml_model_file)
            print('\n mlmodel saved at %s' % (coreml_model_file))

        # Transpose input data as CoreML requires
        coreml_inputs = {
            name:
            tf_transpose(feed_dict[self._get_tf_tensor_name(tf_graph, name)])
            for name in input_shapes
        }

        # Run predict in CoreML
        coreml_output = mlmodel.predict(coreml_inputs, useCPUOnly=use_cpu_only)

        for idx, out_name in enumerate(output_node_names):
            tf_out = result[idx]
            if len(tf_out.shape) == 0:
                tf_out = np.array([tf_out])
            tp = tf_out.flatten()
            coreml_out = coreml_output[out_name]
            cp = coreml_out.flatten()
            self.assertTrue(tf_out.shape == coreml_out.shape)
            for i in range(len(tp)):
                max_den = max(1.0, tp[i], cp[i])
                self.assertAlmostEqual(tp[i] / max_den,
                                       cp[i] / max_den,
                                       delta=delta)

        # Cleanup files - models on disk no longer useful
        if os.path.exists(model_dir):
            shutil.rmtree(model_dir)
Exemplo n.º 7
0
    def _test_tf_model(self,
                       graph,
                       input_shapes,
                       output_node_names,
                       data_mode='random',
                       input_refs=None,
                       delta=1e-2,
                       use_cpu_only=False,
                       use_freeze=True,
                       quantize_tf_model=False):
        """
        Common entry to testing routine.
        graph - defined TensorFlow graph.
        input_shapes -  dict str:shape for each input op (placeholder)
        output_node_names - output_node_names, a list of strings
        data_mode - auto-generated input vectors, can be 'random', 'zeros', 'ones', 'linear', etc.
        input_refs - a dictionary of reference input in tensorFlow axis order, each entry is str:shape.
            When using auto-generated input vectors, set input_refs to None.
        delta - maximum difference of normalized TensorFlow and CoreML outputs
        use_cpu_only - If True, instantiate and run CoreML model with CPU only
        use_freeze - If True, force TensorFlow graph to be frozen before converting.
        quantize_tf_model - If True, try to quantize TensorFlow model before converting
        """
        # Some file processing
        model_dir = tempfile.mkdtemp()
        graph_def_file = os.path.join(model_dir, 'tf_graph.pbtxt')
        checkpoint_file = os.path.join(model_dir, 'tf_model.ckpt')
        static_model_file = os.path.join(model_dir, 'tf_static.pb')
        coreml_model_file = os.path.join(model_dir, 'coreml_model.mlmodel')

        # add a saver
        tf.reset_default_graph()
        if use_freeze:
            with graph.as_default() as g:
                saver = tf.train.Saver()

        if input_refs is None:
            feed_dict = {
                self._get_tf_tensor_name(graph, name):
                generate_data(input_shapes[name], data_mode)
                for name in input_shapes
            }
        else:
            feed_dict = {
                self._get_tf_tensor_name(graph, name): input_refs[name]
                for name in list(input_refs.keys())
            }

        with tf.Session(graph=graph) as sess:
            # initialize
            sess.run(tf.global_variables_initializer())
            # run the result
            fetches = [
                graph.get_operation_by_name(name).outputs[0]
                for name in output_node_names
            ]
            result = sess.run(fetches, feed_dict=feed_dict)
            # save graph definition somewhere
            tf.train.write_graph(sess.graph,
                                 model_dir,
                                 graph_def_file,
                                 as_text=False)
            # save the weights if freezing is needed
            if use_freeze:
                saver.save(sess, checkpoint_file)
            else:
                output_graph_def = tf.graph_util.convert_variables_to_constants(
                    sess, graph.as_graph_def(), output_node_names)
                with tf.gfile.GFile(static_model_file, "wb") as f:
                    f.write(output_graph_def.SerializeToString())

        # freeze the graph
        if use_freeze:
            self._simple_freeze(input_graph=graph_def_file,
                                input_checkpoint=checkpoint_file,
                                output_graph=static_model_file,
                                output_node_names=",".join(output_node_names))

            if DEBUG:
                self._simple_freeze(
                    input_graph=graph_def_file,
                    input_checkpoint=checkpoint_file,
                    output_graph='/tmp/model.pb',
                    output_node_names=",".join(output_node_names))

        # if TF needs to be quantized, quantize the graph
        if quantize_tf_model:
            static_model_file = self._quantize_static_tf_model(
                model_dir, static_model_file, output_node_names)

        # convert to CoreML
        mlmodel = coremltools.converters.tensorflow.convert(
            static_model_file,
            inputs=input_shapes,
            outputs=output_node_names,
            use_cpu_only=use_cpu_only)

        if DEBUG:
            print('\n mlmodel description: \n')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(mlmodel.get_spec(), style='coding')
            mlmodel.save(coreml_model_file)
            print('\n mlmodel saved at %s' % coreml_model_file)

        # Transpose input data as CoreML requires
        coreml_inputs = {
            name: tf_transpose(feed_dict[self._get_tf_tensor_name(graph,
                                                                  name)])
            for name in input_shapes
        }

        # Run predict in CoreML
        coreml_output = mlmodel.predict(coreml_inputs, useCPUOnly=use_cpu_only)

        for idx, out_name in enumerate(output_node_names):
            tf_out = result[idx]
            if len(tf_out.shape) == 0:
                tf_out = np.array([tf_out])

            tp = tf_out.flatten()
            coreml_out = coreml_output[out_name]
            cp = coreml_out.flatten()

            self.assertTrue(tf_out.shape == coreml_out.shape)
            for i in range(len(tp)):
                max_den = max(1.0, tp[i], cp[i])
                self.assertAlmostEqual(tp[i] / max_den,
                                       cp[i] / max_den,
                                       delta=delta)

        # Cleanup files - models on disk no longer useful
        if os.path.exists(model_dir):
            shutil.rmtree(model_dir)
Exemplo n.º 8
0
    def _test_tf_model_constant(self,
                                graph,
                                input_shapes,
                                output_node_names,
                                data_mode='random',
                                delta=1e-2,
                                use_cpu_only=False,
                                one_dim_seq_flags=None):
        """
        Common entry to testing routine for graphs that have no variables.
        graph - defined TensorFlow graph.
        input_tensor_shapes -  dict str:shape for each input (placeholder)
        output_node_names - output_node_names, a list of strings
        output_tensor_names - output tensor names, a list of strings, usually
            just output_node_names each appended with ':0'
        """

        model_dir = tempfile.mkdtemp()
        frozen_model_file = os.path.join(model_dir, 'tf_frozen.pb')
        coreml_model_file = os.path.join(model_dir, 'coreml_model.mlmodel')

        feed_dict = {
            self._get_tf_tensor_name(graph, name):
            generate_data(input_shapes[name], data_mode)
            for name in input_shapes
        }

        with tf.Session(graph=graph) as sess:
            # initialize
            sess.run(tf.global_variables_initializer())
            # run the result
            fetches = [
                graph.get_operation_by_name(name).outputs[0]
                for name in output_node_names
            ]
            result = sess.run(fetches, feed_dict=feed_dict)

            output_graph_def = tf.graph_util.convert_variables_to_constants(
                sess,  # The session is used to retrieve the weights
                tf.get_default_graph().as_graph_def(
                ),  # The graph_def is used to retrieve the nodes
                output_node_names  # The output node names are used to select the useful nodes
            )
            with tf.gfile.GFile(frozen_model_file, 'wb') as f:
                f.write(output_graph_def.SerializeToString())

        # convert to CoreML
        mlmodel = coremltools.converters.tensorflow.convert(
            frozen_model_file,
            inputs=input_shapes,
            outputs=output_node_names,
            use_cpu_only=use_cpu_only)

        if DEBUG:
            print('\n mlmodel description: \n')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(mlmodel.get_spec(), style='coding')
            mlmodel.save(coreml_model_file)
            print('\n mlmodel saved at %s' % coreml_model_file)

        # Transpose input data as CoreML requires
        coreml_inputs = {
            name: tf_transpose(feed_dict[self._get_tf_tensor_name(graph,
                                                                  name)])
            for name in input_shapes
        }

        # Run predict in CoreML
        coreml_output = mlmodel.predict(coreml_inputs, useCPUOnly=use_cpu_only)

        for idx, out_name in enumerate(output_node_names):
            tf_out = result[idx]
            if len(tf_out.shape) == 0:
                tf_out = np.array([tf_out])
            tp = tf_out.flatten()
            coreml_out = coreml_output[out_name]
            cp = coreml_out.flatten()
            self.assertTrue(tf_out.shape == coreml_out.shape)
            for i in range(len(tp)):
                max_den = max(1.0, tp[i], cp[i])
                self.assertAlmostEqual(tp[i] / max_den,
                                       cp[i] / max_den,
                                       delta=delta)

        # Cleanup files - models on disk no longer useful
        if os.path.exists(model_dir):
            shutil.rmtree(model_dir)
Exemplo n.º 9
0
def convert(
        model,  # type: Union[onnx.ModelProto, Text]
        mode=None,  # type: Optional[Text]
        image_input_names=[],  # type: Sequence[Text]
        preprocessing_args={},  # type: Dict[Text, Any]
        image_output_names=[],  # type: Sequence[Text]
        deprocessing_args={},  # type: Dict[Text, Any]
        class_labels=None,  # type: Union[Text, Iterable[Text], None]
        predicted_feature_name='classLabel',  # type: Text
        add_custom_layers=False,  # type: bool
        custom_conversion_functions={},  #type: Dict[Text, Any]
        onnx_coreml_input_shape_map={},  # type: Dict[Text, List[int,...]]
        disable_coreml_rank5_mapping=False):
    # type: (...) -> MLModel
    """
    Convert ONNX model to CoreML.
    Parameters
    ----------
    model:
        An ONNX model with parameters loaded in onnx package or path to file
        with models.
    mode: 'classifier', 'regressor' or None
        Mode of the converted coreml model:
        'classifier', a NeuralNetworkClassifier spec will be constructed.
        'regressor', a NeuralNetworkRegressor spec will be constructed.
    preprocessing_args:
        'is_bgr', 'red_bias', 'green_bias', 'blue_bias', 'gray_bias',
        'image_scale' keys with the same meaning as
        https://apple.github.io/coremltools/generated/coremltools.models.neural_network.html#coremltools.models.neural_network.NeuralNetworkBuilder.set_pre_processing_parameters
    deprocessing_args:
        Same as 'preprocessing_args' but for deprocessing.
    class_labels:
        As a string it represents the name of the file which contains
        the classification labels (one per line).
        As a list of strings it represents a list of categories that map
        the index of the output of a neural network to labels in a classifier.
    predicted_feature_name:
        Name of the output feature for the class labels exposed in the Core ML
        model (applies to classifiers only). Defaults to 'classLabel'
    add_custom_layers: bool
        Flag to turn on addition of custom CoreML layers for unsupported ONNX ops or attributes within
        a supported op.
    custom_conversion_functions: dict()
        A dictionary with keys corresponding to the names/types of onnx ops and values as functions taking
        an object of class coreml-tools's 'NeuralNetworkBuilder', Graph' (see onnx-coreml/_graph.Graph),
        'Node' (see onnx-coreml/_graph.Node), ErrorHandling (see onnx-coreml/_error_utils.ErrorHandling).
        This custom conversion function gets full control and responsibility for converting given onnx op.
        This function returns nothing and is responsible for adding a equivalent CoreML layer via 'NeuralNetworkBuilder'
    onnx_coreml_input_shape_map: dict()
        (Optional) A dictionary with keys corresponding to the model input names. Values are a list of integers that specify
        how the shape of the input is mapped to CoreML. Convention used for CoreML shapes is
        0: Sequence, 1: Batch, 2: channel, 3: height, 4: width.
        For example, an input of rank 2 could be mapped as [3,4] (i.e. H,W) or [1,2] (i.e. B,C) etc.
        This is ignored if "disable_coreml_rank5_mapping" is set to True.
    disable_coreml_rank5_mapping: bool
        If True, then it disables the "RANK5_ARRAY_MAPPING" or enables the "EXACT_ARRAY_MAPPING"
        option in CoreML (https://github.com/apple/coremltools/blob/655b3be5cc0d42c3c4fa49f0f0e4a93a26b3e492/mlmodel/format/NeuralNetwork.proto#L67)
        Thus, no longer, onnx tensors are forced to map to rank 5 CoreML tensors.
        With this flag on, a rank r ONNX tensor, (1<=r<=5), will map to a rank r tensor in CoreML as well.
        This flag must be on to utilize any of the new layers added in CoreML 3 (i.e. specification version 4, iOS13)

    Returns
    -------
    model: A coreml model.
    """
    if isinstance(model, Text):
        onnx_model = onnx.load(model)
    elif isinstance(model, onnx.ModelProto):
        onnx_model = model
    else:
        raise TypeError(
            "Model must be file path to .onnx file or onnx loaded model")

    global USE_SHAPE_MAPPING
    if disable_coreml_rank5_mapping:
        USE_SHAPE_MAPPING = False
    '''
    First, apply a few optimizations to the ONNX graph,
    in preparation for conversion to CoreML. 
    '''

    # Using Dummy transformation to conditionally disable certain transformation
    class DummyTransformation(object):
        def __call__(self, graph):
            return graph

    transformers = [
        ConstantsToInitializers(),
        ShapeOpRemover(),
        ReshapeInitTensorFuser(),
        DropoutRemover(),
        UnsqueezeConstantRemover(),
        TransposeConstantRemover(),
        SliceConstantRemover(),
        ConcatConstantRemover(),
        ConvAddFuser(),
        BNBroadcastedMulFuser(),
        BNBroadcastedAddFuser(),
        ReshapeTransposeReshape_pattern1(),
        PixelShuffleFuser(),
        AddModelInputsOutputs()
        if not disable_coreml_rank5_mapping else DummyTransformation(),
        DivMulConstantRemover(),
        GatherConstantRemover(),
        ConstantFillToInitializers(),
    ]  # type: Iterable[Transformer]

    onnx_model = onnx.shape_inference.infer_shapes(onnx_model)
    graph = _prepare_onnx_graph(onnx_model.graph, transformers)
    '''
    Check for ImageScalar nodes in ONNX, this will indicate whether input image preprocessing needs
    to be added to the CoreML graph or not. 
    '''
    # are there ImageScaler nodes in the Graph?
    # If yes then add the info from it to the "preprocessing_args" dictionary, if the dictionary is not
    # already provided by the user
    if not bool(preprocessing_args):
        for node in graph.nodes:
            if node.op_type == 'ImageScaler':
                inp_name = node.inputs[0]
                scale = node.attrs.get('scale', 1.0)
                bias = node.attrs.get('bias', [0, 0, 0])
                if not (len(bias) == 1 or len(bias) == 3):
                    continue
                if 'image_scale' in preprocessing_args:
                    preprocessing_args['image_scale'][inp_name] = scale
                else:
                    preprocessing_args['image_scale'] = {inp_name: scale}
                if len(bias) == 3:
                    for i, color in enumerate(['red', 'green', 'blue']):
                        if color + '_bias' in preprocessing_args:
                            preprocessing_args[color +
                                               '_bias'][inp_name] = bias[i]
                        else:
                            preprocessing_args[color + '_bias'] = {
                                inp_name: bias[i]
                            }
                else:
                    if 'gray_bias' in preprocessing_args:
                        preprocessing_args['gray_bias'][inp_name] = bias[0]
                    else:
                        preprocessing_args['gray_bias'] = {inp_name: bias[0]}
                if inp_name not in image_input_names:
                    image_input_names.append(inp_name)  # type: ignore

    # remove all ImageScaler ops
    graph = graph.transformed([ImageScalerRemover()])
    '''
    Gather information (name, shape) for model inputs and outputs
    This information is then used to initialize the neural network builder object of coremltools. 
    The builder object is later used to add layers to the CoreML model. 
    '''

    #Make CoreML input and output features by gathering shape info and
    #interpreting it for CoreML
    input_features = _make_coreml_input_features(graph,
                                                 onnx_coreml_input_shape_map,
                                                 disable_coreml_rank5_mapping)
    if len(image_output_names) > 0:
        output_features = _make_coreml_output_features(
            graph,
            forceShape=True,
            disable_coreml_rank5_mapping=disable_coreml_rank5_mapping)
    else:
        output_features = _make_coreml_output_features(
            graph, disable_coreml_rank5_mapping=disable_coreml_rank5_mapping)

    builder = NeuralNetworkBuilder(
        input_features,
        output_features,
        mode=mode,
        disable_rank5_shape_mapping=disable_coreml_rank5_mapping)
    '''
    Set CoreML input,output types (float, double, int) same as onnx types, if supported
    '''
    _transform_coreml_dtypes(builder, graph.inputs, graph.outputs)
    '''what follows is some book-keeping to support outputs of type image. 
    '''

    is_deprocess_bgr_only = (len(deprocessing_args) == 1) and \
                            ("is_bgr" in deprocessing_args)
    add_deprocess = (len(image_output_names) > 0) and \
                    (len(deprocessing_args) > 0) and \
                    (not is_deprocess_bgr_only)

    if add_deprocess:
        mapping = {}
        for f in output_features:
            output_name = f[0]
            mapping[output_name] = graph.get_unique_edge_name(output_name)
        graph = OutputRenamer(mapping)(graph)

    if len(image_input_names) > 0:
        builder.set_pre_processing_parameters(
            image_input_names=image_input_names,
            is_bgr=preprocessing_args.get('is_bgr', False),
            red_bias=preprocessing_args.get('red_bias', 0.0),
            green_bias=preprocessing_args.get('green_bias', 0.0),
            blue_bias=preprocessing_args.get('blue_bias', 0.0),
            gray_bias=preprocessing_args.get('gray_bias', 0.0),
            image_scale=preprocessing_args.get('image_scale', 1.0))

    preprocessing_args.clear()

    if len(image_output_names) > 0:
        for f in output_features:
            f_name = f[0]
            if f_name in image_output_names:
                is_bgr = deprocessing_args.get('is_bgr', False)
                _convert_multiarray_output_to_image(builder.spec,
                                                    f_name,
                                                    is_bgr=is_bgr)
    '''
    Iterate through all the ONNX ops and translate them to CoreML layers, one by one. 
    '''
    '''
    before proceeding to start the layer translation process,
    check whether there is an op in the ONNX graph, whose translation function is not yet
    implemented in the converter or which is not supported in the CoreML framework. If so, 
    raise an error before starting the process.
    (if the user desires to add a custom layer then this check is not required)
    '''
    if not add_custom_layers:
        _check_unsupported_ops(graph.nodes, disable_coreml_rank5_mapping)
    '''
    ErrorHandling is a generic class, useful to store a variety of parameters during the conversion process  
    '''
    err = ErrorHandling(
        add_custom_layers,
        custom_conversion_functions,
        disable_coreml_rank5_mapping=disable_coreml_rank5_mapping)

    for i, node in enumerate(graph.nodes):
        print("%d/%d: Converting Node Type %s" %
              (i + 1, len(graph.nodes), node.op_type))
        if disable_coreml_rank5_mapping:
            _convert_node_nd(builder, node, graph, err)
        else:
            _add_const_inputs_if_required(builder, node, graph, err)
            _convert_node(builder, node, graph, err)

    if DEBUG:
        plot_graph(graph,
                   graph_img_path='/tmp/after_conversion.pdf',
                   show_coreml_mapped_shapes=not disable_coreml_rank5_mapping)

    if add_deprocess:
        for f in output_features:
            output_name = f[0]
            if output_name not in image_output_names:
                continue
            output_shape = f[1].dimensions
            if len(output_shape) == 2 or output_shape[0] == 1:
                is_grayscale = True
            elif output_shape[0] == 3:
                is_grayscale = False
            else:
                raise ValueError('Output must be RGB image or Grayscale')
            _set_deprocessing(is_grayscale, builder, deprocessing_args,
                              mapping[output_name], output_name)

    if class_labels is not None:
        if isinstance(class_labels, Text):
            labels = [l.strip() for l in open(class_labels).readlines()
                      ]  # type: Sequence[Text]
        elif isinstance(class_labels, list):
            labels = class_labels
        else:
            raise TypeError("synset variable of unknown type. Type found: {}. \
                Expected either string or list of strings.".format(
                type(class_labels), ))

        builder.set_class_labels(class_labels=labels,
                                 predicted_feature_name=predicted_feature_name)

    def _add_informative_description(feature, raise_error=True):
        if feature.type.WhichOneof('Type') == 'multiArrayType':
            if feature.name in graph.onnx_coreml_shape_mapping and feature.name in graph.shape_dict:
                mapp = graph.onnx_coreml_shape_mapping[feature.name]
                onnx_shape = graph.shape_dict[feature.name]
                if raise_error:
                    assert len(mapp) == len(
                        onnx_shape), "Something wrong in shape"
                if len(mapp) == len(onnx_shape):
                    shape = []
                    for i in range(5):
                        if i in mapp:
                            shape += [int(onnx_shape[mapp.index(i)])]
                        else:
                            shape += [1]
                    msg = 'MultiArray of shape {}. The first and second dimensions correspond to sequence and batch size, respectively'.format(
                        str(tuple(shape)))
                    feature.shortDescription += msg

    optional_input_names = []
    for tup in graph.optional_inputs:
        optional_input_names.append(tup[0])
    optional_output_names = []
    for tup in graph.optional_outputs:
        optional_output_names.append(tup[0])

    # add description for inputs and outputs shapes
    remove_input_id = []
    for i, input_ in enumerate(builder.spec.description.input):
        if input_.name not in optional_input_names:
            if not disable_coreml_rank5_mapping:
                _add_informative_description(input_)
        else:
            remove_input_id.append(i)
    remove_output_id = []
    for i, output_ in enumerate(builder.spec.description.output):
        if output_.name not in optional_output_names:
            if not disable_coreml_rank5_mapping:
                _add_informative_description(output_, raise_error=False)
        else:
            remove_output_id.append(i)

    for index in sorted(remove_input_id, reverse=True):
        del builder.spec.description.input[index]
    for index in sorted(remove_output_id, reverse=True):
        del builder.spec.description.output[index]

    if len(graph.optional_inputs) > 0 or len(graph.optional_outputs):
        builder.add_optionals(graph.optional_inputs, graph.optional_outputs)

    print(
        "Translation to CoreML spec completed. Now compiling the CoreML model."
    )
    try:
        if DEBUG:
            import coremltools
            coremltools.models.utils.save_spec(
                builder.spec, '/tmp/node_model_raw_spec.mlmodel')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(builder.spec, style='coding')
        mlmodel = MLModel(builder.spec)
    except RuntimeError as e:
        raise ValueError('Compilation failed: {}'.format(str(e)))
    print('Model Compilation done.')

    # print information about all ops for which custom layers have been added
    if len(err.custom_layer_nodes) > 0:
        print('\n')
        print("Custom layers have been added to the CoreML model "
              "corresponding to the following ops in the onnx model: ")
        for i, node in enumerate(err.custom_layer_nodes):
            input_info = []
            for input_ in node.inputs:
                input_info.append(
                    (str(input_),
                     graph.shape_dict.get(input_, str("Shape not available"))))
            output_info = []
            for output_ in node.outputs:
                output_info.append(
                    (str(output_),
                     graph.shape_dict.get(output_,
                                          str("Shape not available"))))
            print(
                "{}/{}: op type: {}, op input names and shapes: {}, op output names and shapes: {}"
                .format(i + 1, len(err.custom_layer_nodes), node.op_type,
                        str(input_info), str(output_info)))

    return mlmodel
Exemplo n.º 10
0
    def _test_tf_model_constant(self,
                                graph,
                                input_shapes,
                                output_node_names,
                                data_mode='random_zero_mean',
                                delta=1e-2,
                                use_cpu_only=False,
                                validate_bool_only=False):
        """
        Common entry to testing routine for graphs that have no variables.

        Parameters
        ----------
        graph: tf.Graph()
            TensorFlow graph.
        input_shapes: dict [str : shape]
            Shapes for each input (placeholder).
        output_node_names: list of str
            Output tensor names.
        data_mode: str
            Data mode for the placeholder data generation.
        input_refs: a dictionary of reference input in tensorFlow axis order.
            Each entry is str:shape. When using auto-generated input vectors,
            set input_refs to None.
        delta: float
            Delta for error checking, default 1e-2.
        use_cpu_only: bool
            If true, force use CPU only, default False.
        validate_bool_only: bool
            If true, only validate it's zero or non-zero, otherwise, validate
            float values, default False.
        """

        model_dir = tempfile.mkdtemp()
        frozen_model_file = os.path.join(model_dir, 'tf_frozen.pb')
        coreml_model_file = os.path.join(model_dir, 'coreml_model.mlmodel')

        feed_dict = {
            self._get_tf_tensor_name(graph, name):
            generate_data(input_shapes[name], data_mode)
            for name in input_shapes
        }

        with tf.Session(graph=graph) as sess:
            # initialize
            sess.run(tf.global_variables_initializer())
            # run the result
            fetches = [
                graph.get_operation_by_name(name).outputs[0]
                for name in output_node_names
            ]
            result = sess.run(fetches, feed_dict=feed_dict)

            output_graph_def = tf.graph_util.convert_variables_to_constants(
                sess,  # The session is used to retrieve the weights
                tf.get_default_graph().as_graph_def(
                ),  # The graph_def is used to retrieve the nodes
                output_node_names  # The output node names are used to select the useful nodes
            )
            with tf.gfile.GFile(frozen_model_file, 'wb') as f:
                f.write(output_graph_def.SerializeToString())

        # convert to CoreML
        mlmodel = coremltools.converters.tensorflow.convert(
            frozen_model_file,
            inputs=input_shapes,
            outputs=output_node_names,
            use_cpu_only=use_cpu_only)

        if DEBUG:
            print('\n mlmodel description: \n')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(mlmodel.get_spec(), style='coding')
            mlmodel.save(coreml_model_file)
            print('\n mlmodel saved at %s' % coreml_model_file)

        # Transpose input data as CoreML requires
        coreml_inputs = {
            name: tf_transpose(feed_dict[self._get_tf_tensor_name(graph,
                                                                  name)])
            for name in input_shapes
        }

        # Run predict in CoreML
        coreml_output = mlmodel.predict(coreml_inputs, useCPUOnly=use_cpu_only)

        for idx, out_name in enumerate(output_node_names):
            tf_out = result[idx]
            if len(tf_out.shape) == 0:
                tf_out = np.array([tf_out])
            tp = tf_out.flatten()
            coreml_out = coreml_output[out_name]
            cp = coreml_out.flatten()

            self.assertTrue(tf_out.shape == coreml_out.shape,
                            msg=(tf_out.shape, 'vs.', coreml_out.shape))

            if validate_bool_only:
                cp = np.logical_and(cp, cp)
            for i in range(len(tp)):
                max_den = max(1.0, tp[i], cp[i])
                self.assertAlmostEqual(tp[i] / max_den,
                                       cp[i] / max_den,
                                       delta=delta)

        # Cleanup files - models on disk no longer useful
        if os.path.exists(model_dir):
            shutil.rmtree(model_dir)
Exemplo n.º 11
0
def convert(
    model,  # type: Union[onnx.ModelProto, Text]
    mode=None,  # type: Optional[Text]
    image_input_names=[],  # type: Sequence[Text]
    preprocessing_args={},  # type: Dict[Text, Any]
    image_output_names=[],  # type: Sequence[Text]
    deprocessing_args={},  # type: Dict[Text, Any]
    class_labels=None,  # type: Union[Text, Iterable[Text], None]
    predicted_feature_name="classLabel",  # type: Text
    add_custom_layers=False,  # type: bool
    custom_conversion_functions={},  # type: Dict[Text, Any]
    onnx_coreml_input_shape_map={},  # type: Dict[Text, List[int,...]]
    minimum_ios_deployment_target="12",
):
    # type: (...) -> MLModel
    """
    WARNING: This function is deprecated. It will be removed in the 6.0.

    Convert ONNX model to CoreML.
    
    Parameters
    ----------
    model:
        An ONNX model with parameters loaded in the ONNX package, or path to file
        with models.
        
    mode: 'classifier', 'regressor' or None
    
        Mode of the converted coreml model:
        
        * ``'classifier'``: a NeuralNetworkClassifier spec will be constructed.
        * ``'regressor'``: a NeuralNetworkRegressor spec will be constructed.
        
    preprocessing_args:
        The ``'is_bgr'``, ``'red_bias'``, ``'green_bias'``, ``'blue_bias'``, ``'gray_bias'``,
        and ``'image_scale'`` keys have the same meaning as the pre-processing arguments for
        `NeuralNetworkBuilder <https://coremltools.readme.io/reference/modelsneural_network>`_.
    
    deprocessing_args:
        Same as ``'preprocessing_args'`` but for de-processing.
    
    class_labels:
        * As a string, it represents the name of the file which contains
          the classification labels (one per line).
        * As a list of strings, it represents a list of categories that map
          the index of the output of a neural network to labels in a classifier.
    
    predicted_feature_name:
        Name of the output feature for the class labels exposed in the Core ML
        model (applies to classifiers only). Defaults to ``'classLabel'``.
    
    add_custom_layers: bool
        Flag to turn on additional custom CoreML layers for unsupported ONNX ops or
    	attributes within a supported op.
    
    custom_conversion_functions: dict()
        * A dictionary with keys corresponding to the names/types of ONNX ops and values as 
          functions taking an object of the ``coreml-tools`` class:
          ``'NeuralNetworkBuilder'``, ``'Graph'`` (see ``onnx-coreml/_graph.Graph``),
          ``'Node'`` (see ``onnx-coreml/_graph.Node``), and 
          ``'ErrorHandling'`` (see ``onnx-coreml/_error_utils.ErrorHandling``).
        * This custom conversion function gets full control and responsibility for 
          converting a given ONNX op.
        * The function returns nothing and is responsible for adding an equivalent CoreML
          layer via ``'NeuralNetworkBuilder'``.
    
    onnx_coreml_input_shape_map: dict() (Optional) 
        * A dictionary with keys corresponding to the model input names.
        * Values are a list of integers that specify how the shape of the input is mapped
          to CoreML.
        * Convention used for CoreML shapes is ``0: Sequence``, ``1: Batch``,
          ``2: channel``, ``3: height``, ``4: width``. For example, an input of rank 2
          could be mapped as ``[3,4]`` (H,W) or ``[1,2]`` (B,C), and so on. This is
          ignored if ``minimum_ios_deployment_target`` is set to ``13``.
    
    minimum_ios_deployment_target: str
        Target Deployment iOS Version (default: ``'12'``). Supported iOS version options:
        ``'11.2'``, ``'12'``, ``'13'``. CoreML model produced by the converter will be
        compatible with the iOS version specified in this argument. For example, if
        ``minimum_ios_deployment_target = '12'``, the converter would utilize only CoreML
        features released up to version iOS12 (equivalent to macOS 10.14, watchOS 5, and
        so on). iOS 11.2 (CoreML 0.8) does not support ``resize_bilinear`` and
        ``crop_resize`` layers. See `supported v0.8 features <https://github.com/apple/coremltools/releases/tag/v0.8>`_.
        iOS 12 (CoreML 2.0), see `supported v2.0 features <https://github.com/apple/coremltools/releases/tag/v2.0>`_.
        iSO 13 (CoreML 3.0), see `supported v3.0 features <https://github.com/apple/coremltools/releases/tag/3.0-beta6>`_.
    
    
    Returns
    -------
    model: A coreml model.
    """
    if not _HAS_ONNX:
        raise ModuleNotFoundError("Missing ONNX package.")

    if isinstance(model, Text):
        onnx_model = onnx.load(model)
    elif isinstance(model, onnx.ModelProto):
        onnx_model = model
    else:
        raise TypeError(
            "Model must be file path to .onnx file or onnx loaded model")

    if not SupportedVersion.ios_support_check(minimum_ios_deployment_target):
        raise TypeError(
            "{} not supported. Please provide one of target iOS: {}",
            minimum_ios_deployment_target,
            SupportedVersion.get_supported_ios(),
        )

    global USE_SHAPE_MAPPING
    disable_coreml_rank5_mapping = False
    if SupportedVersion.is_nd_array_supported(minimum_ios_deployment_target):
        disable_coreml_rank5_mapping = True

    if disable_coreml_rank5_mapping:
        USE_SHAPE_MAPPING = False
    else:
        USE_SHAPE_MAPPING = True
    """
    First, apply a few optimizations to the ONNX graph,
    in preparation for conversion to CoreML.
    """

    # Using Dummy transformation to conditionally disable certain transformation
    class DummyTransformation(object):
        def __call__(self, graph):
            return graph

    transformers = [
        ConstantsToInitializers(),
        ShapeOpRemover(),
        ConstantRemover(),
        CastOpRemover(),
        PaddingOpRemover(),
        ReshapeInitTensorFuser(),
        DropoutRemover(),
        DeadCodeElimination(),
        ConvAddFuser(),
        BNBroadcastedMulFuser(),
        BNBroadcastedAddFuser(),
        ReshapeTransposeReshape_pattern1(),
        PixelShuffleFuser(),
        AddModelInputsOutputs()
        if not disable_coreml_rank5_mapping else DummyTransformation(),
        ConstantFillToInitializers(),
    ]  # type: Iterable[Transformer]

    onnx_model = onnx.shape_inference.infer_shapes(onnx_model)
    graph = _prepare_onnx_graph(onnx_model.graph, transformers,
                                onnx_model.ir_version)
    """
    Check for ImageScalar nodes in ONNX, this will indicate whether input image preprocessing needs
    to be added to the CoreML graph or not.
    """
    # are there ImageScaler nodes in the Graph?
    # If yes then add the info from it to the "preprocessing_args" dictionary, if the dictionary is not
    # already provided by the user
    if not bool(preprocessing_args):
        for node in graph.nodes:
            if node.op_type == "ImageScaler":
                inp_name = node.inputs[0]
                scale = node.attrs.get("scale", 1.0)
                bias = node.attrs.get("bias", [0, 0, 0])
                if not (len(bias) == 1 or len(bias) == 3):
                    continue
                if "image_scale" in preprocessing_args:
                    preprocessing_args["image_scale"][inp_name] = scale
                else:
                    preprocessing_args["image_scale"] = {inp_name: scale}
                if len(bias) == 3:
                    for i, color in enumerate(["red", "green", "blue"]):
                        if color + "_bias" in preprocessing_args:
                            preprocessing_args[color +
                                               "_bias"][inp_name] = bias[i]
                        else:
                            preprocessing_args[color + "_bias"] = {
                                inp_name: bias[i]
                            }
                else:
                    if "gray_bias" in preprocessing_args:
                        preprocessing_args["gray_bias"][inp_name] = bias[0]
                    else:
                        preprocessing_args["gray_bias"] = {inp_name: bias[0]}
                if inp_name not in image_input_names:
                    image_input_names.append(inp_name)  # type: ignore

    # remove all ImageScaler ops
    graph = graph.transformed([ImageScalerRemover()])
    """
    Gather information (name, shape) for model inputs and outputs
    This information is then used to initialize the neural network builder object of coremltools.
    The builder object is later used to add layers to the CoreML model.
    """

    # Make CoreML input and output features by gathering shape info and
    # interpreting it for CoreML
    input_features = _make_coreml_input_features(graph,
                                                 onnx_coreml_input_shape_map,
                                                 disable_coreml_rank5_mapping)
    if len(image_output_names) > 0:
        output_features = _make_coreml_output_features(
            graph,
            forceShape=True,
            disable_coreml_rank5_mapping=disable_coreml_rank5_mapping,
        )
    else:
        output_features = _make_coreml_output_features(
            graph, disable_coreml_rank5_mapping=disable_coreml_rank5_mapping)

    builder = NeuralNetworkBuilder(
        input_features,
        output_features,
        mode=mode,
        disable_rank5_shape_mapping=disable_coreml_rank5_mapping,
    )

    # TODO: To be removed once, auto-downgrading of spec version is enabled
    builder.spec.specificationVersion = SupportedVersion.get_specification_version(
        minimum_ios_deployment_target)
    """
    Set CoreML input,output types (float, double, int) same as onnx types, if supported
    """
    _transform_coreml_dtypes(builder, graph.inputs, graph.outputs)
    """what follows is some book-keeping to support outputs of type image.
    """

    is_deprocess_bgr_only = (len(deprocessing_args)
                             == 1) and ("is_bgr" in deprocessing_args)
    add_deprocess = ((len(image_output_names) > 0)
                     and (len(deprocessing_args) > 0)
                     and (not is_deprocess_bgr_only))

    if add_deprocess:
        mapping = {}
        for f in output_features:
            output_name = f[0]
            mapping[output_name] = graph.get_unique_edge_name(output_name)
        graph = OutputRenamer(mapping)(graph)

    if len(image_input_names) > 0:
        builder.set_pre_processing_parameters(
            image_input_names=image_input_names,
            is_bgr=preprocessing_args.get("is_bgr", False),
            red_bias=preprocessing_args.get("red_bias", 0.0),
            green_bias=preprocessing_args.get("green_bias", 0.0),
            blue_bias=preprocessing_args.get("blue_bias", 0.0),
            gray_bias=preprocessing_args.get("gray_bias", 0.0),
            image_scale=preprocessing_args.get("image_scale", 1.0),
        )

    preprocessing_args.clear()

    if len(image_output_names) > 0:
        for f in output_features:
            f_name = f[0]
            if f_name in image_output_names:
                is_bgr = deprocessing_args.get("is_bgr", False)
                _convert_multiarray_output_to_image(builder.spec,
                                                    f_name,
                                                    is_bgr=is_bgr)
    """
    Iterate through all the ONNX ops and translate them to CoreML layers, one by one.
    """
    """
    before proceeding to start the layer translation process,
    check whether there is an op in the ONNX graph, whose translation function is not yet
    implemented in the converter or which is not supported in the CoreML framework. If so,
    raise an error before starting the process.
    (if the user desires to add a custom layer then this check is not required)
    """
    if not add_custom_layers:
        _check_unsupported_ops(graph.nodes, disable_coreml_rank5_mapping)
    """
    ErrorHandling is a generic class, useful to store a variety of parameters during the conversion process
    """
    err = ErrorHandling(add_custom_layers, custom_conversion_functions)

    for i, node in enumerate(graph.nodes):
        print("%d/%d: Converting Node Type %s" %
              (i + 1, len(graph.nodes), node.op_type))
        if disable_coreml_rank5_mapping:
            _convert_node_nd(builder, node, graph, err)
        else:
            _add_const_inputs_if_required(builder, node, graph, err)
            _convert_node(builder, node, graph, err)

    if DEBUG:
        plot_graph(
            graph,
            graph_img_path="/tmp/after_conversion.pdf",
            show_coreml_mapped_shapes=not disable_coreml_rank5_mapping,
        )

    if add_deprocess:
        for f in output_features:
            output_name = f[0]
            if output_name not in image_output_names:
                continue
            output_shape = f[1].dimensions
            if len(output_shape) == 2 or output_shape[0] == 1:
                is_grayscale = True
            elif output_shape[0] == 3:
                is_grayscale = False
            else:
                raise ValueError("Output must be RGB image or Grayscale")
            _set_deprocessing(
                is_grayscale,
                builder,
                deprocessing_args,
                mapping[output_name],
                output_name,
            )

    if class_labels is not None:
        if isinstance(class_labels, Text):
            labels = [l.strip() for l in open(class_labels).readlines()
                      ]  # type: Sequence[Text]
        elif isinstance(class_labels, list):
            labels = class_labels
        else:
            raise TypeError("synset variable of unknown type. Type found: {}. \
                Expected either string or list of strings.".format(
                type(class_labels), ))

        builder.set_class_labels(class_labels=labels,
                                 predicted_feature_name=predicted_feature_name)

    def _add_informative_description(feature, raise_error=True):
        if feature.type.WhichOneof("Type") == "multiArrayType":
            if (feature.name in graph.onnx_coreml_shape_mapping
                    and feature.name in graph.shape_dict):
                mapp = graph.onnx_coreml_shape_mapping[feature.name]
                onnx_shape = graph.shape_dict[feature.name]
                if raise_error:
                    assert len(mapp) == len(
                        onnx_shape), "Something wrong in shape"
                if len(mapp) == len(onnx_shape):
                    shape = []
                    for i in range(5):
                        if i in mapp:
                            shape += [int(onnx_shape[mapp.index(i)])]
                        else:
                            shape += [1]
                    msg = "MultiArray of shape {}. The first and second dimensions correspond to sequence and batch size, respectively".format(
                        str(tuple(shape)))
                    feature.shortDescription += msg

    optional_input_names = []
    for tup in graph.optional_inputs:
        optional_input_names.append(tup[0])
    optional_output_names = []
    for tup in graph.optional_outputs:
        optional_output_names.append(tup[0])

    # add description for inputs and outputs shapes
    remove_input_id = []
    for i, input_ in enumerate(builder.spec.description.input):
        if input_.name not in optional_input_names:
            if not disable_coreml_rank5_mapping:
                _add_informative_description(input_)
        else:
            remove_input_id.append(i)
    remove_output_id = []
    for i, output_ in enumerate(builder.spec.description.output):
        if output_.name not in optional_output_names:
            if not disable_coreml_rank5_mapping:
                _add_informative_description(output_, raise_error=False)
        else:
            remove_output_id.append(i)

    for index in sorted(remove_input_id, reverse=True):
        del builder.spec.description.input[index]
    for index in sorted(remove_output_id, reverse=True):
        del builder.spec.description.output[index]

    if len(graph.optional_inputs) > 0 or len(graph.optional_outputs):
        builder.add_optionals(graph.optional_inputs, graph.optional_outputs)

    # Check for specification version and target ios compatibility
    if (minimum_ios_deployment_target == "11.2"
            and builder.spec.WhichOneof("Type") == "neuralNetwork"):
        nn_spec = builder.spec.neuralNetwork
        for layer in nn_spec.layers:
            if (layer.WhichOneof("layer") == "resizeBilinear"
                    or layer.WhichOneof("layer") == "cropResize"):
                raise TypeError(
                    "{} not supported with target iOS 11.2 please provide higher target iOS"
                    .format(layer.WhichOneof("layer")))

    # Optimize ML Model Spec
    ml_model_passes = [remove_disconnected_layers, transform_conv_crop]
    for opt in ml_model_passes:
        opt(builder.spec)

    print(
        "Translation to CoreML spec completed. Now compiling the CoreML model."
    )
    try:
        if DEBUG:
            import coremltools

            coremltools.models.utils.save_spec(
                builder.spec, "/tmp/node_model_raw_spec.mlmodel")
            from coremltools.models.neural_network.printer import print_network_spec

            print_network_spec(builder.spec, style="coding")
        mlmodel = MLModel(builder.spec)
    except RuntimeError as e:
        raise ValueError("Compilation failed: {}".format(str(e)))
    print("Model Compilation done.")

    # print information about all ops for which custom layers have been added
    if len(err.custom_layer_nodes) > 0:
        print("\n")
        print("Custom layers have been added to the CoreML model "
              "corresponding to the following ops in the onnx model: ")
        for i, node in enumerate(err.custom_layer_nodes):
            input_info = []
            for input_ in node.inputs:
                input_info.append((
                    str(input_),
                    graph.shape_dict.get(input_, str("Shape not available")),
                ))
            output_info = []
            for output_ in node.outputs:
                output_info.append((
                    str(output_),
                    graph.shape_dict.get(output_, str("Shape not available")),
                ))
            print(
                "{}/{}: op type: {}, op input names and shapes: {}, op output names and shapes: {}"
                .format(
                    i + 1,
                    len(err.custom_layer_nodes),
                    node.op_type,
                    str(input_info),
                    str(output_info),
                ))

    mlmodel.user_defined_metadata[_METADATA_VERSION] = ct_version
    mlmodel.user_defined_metadata[_METADATA_SOURCE] = "onnx=={0}".format(
        onnx.__version__)
    return mlmodel
Exemplo n.º 12
0
    def _test_keras_model_tf1(self, model, data_mode, decimal, use_cpu_only, has_variables, verbose):

        graph_def_file = os.path.join(self.saved_model_dir, 'graph.pb')
        frozen_model_file = os.path.join(self.saved_model_dir, 'frozen.pb')
        core_ml_model_file = os.path.join(self.saved_model_dir, 'model.mlmodel')

        input_shapes = {inp.op.name: inp.shape.as_list() for inp in model.inputs}
        for name, shape in input_shapes.items():
            input_shapes[name] = [dim if dim is not None else 1 for dim in shape]

        output_node_names = [output.op.name for output in model.outputs]

        tf_graph = _keras.get_session().graph
        tf.reset_default_graph()
        if has_variables:
            with tf_graph.as_default():
                saver = tf.train.Saver()

        # note: if Keras backend has_variable is False, we're not making variables constant
        with tf.Session(graph=tf_graph) as sess:
            sess.run(tf.global_variables_initializer())
            feed_dict = {}
            for name, shape in input_shapes.items():
                tensor_name = tf_graph.get_operation_by_name(name).outputs[0].name
                feed_dict[tensor_name] = generate_data(shape, data_mode)
            # run the result
            fetches = [
                tf_graph.get_operation_by_name(name).outputs[0] for name in output_node_names
            ]
            result = sess.run(fetches, feed_dict=feed_dict)
            # save graph definition somewhere
            tf.train.write_graph(sess.graph, self.saved_model_dir, graph_def_file, as_text=False)

            # freeze_graph() has been raising error with tf.keras models since no
            # later than TensorFlow 1.6, so we're not using freeze_graph() here.
            # See: https://github.com/tensorflow/models/issues/5387
            output_graph_def = tf.graph_util.convert_variables_to_constants(
                sess,  # The session is used to retrieve the weights
                tf_graph.as_graph_def(),  # The graph_def is used to retrieve the nodes
                output_node_names  # The output node names are used to select the useful nodes
            )
            with tf.gfile.GFile(frozen_model_file, 'wb') as f:
                f.write(output_graph_def.SerializeToString())

        _keras.clear_session()

        # convert to Core ML model format
        core_ml_model = coremltools.converters.tensorflow.convert(
            frozen_model_file,
            inputs=input_shapes,
            outputs=output_node_names,
            use_cpu_only=use_cpu_only)

        if verbose:
            print('\nFrozen model saved at {}'.format(frozen_model_file))
            print('\nCore ML model description:')
            from coremltools.models.neural_network.printer import print_network_spec
            print_network_spec(core_ml_model.get_spec(), style='coding')
            core_ml_model.save(core_ml_model_file)
            print('\nCore ML model saved at {}'.format(core_ml_model_file))

        # transpose input data as Core ML requires
        core_ml_inputs = {
            name: tf_transpose(feed_dict[self._get_tf_tensor_name(tf_graph, name)])
            for name in input_shapes
        }

        # run prediction in Core ML
        core_ml_output = core_ml_model.predict(core_ml_inputs, useCPUOnly=use_cpu_only)

        for idx, out_name in enumerate(output_node_names):
            tf_out = result[idx]
            if len(tf_out.shape) == 0:
                tf_out = np.array([tf_out])
            tp = tf_out.flatten()
            coreml_out = core_ml_output[out_name]
            cp = coreml_out.flatten()
            self.assertTrue(tf_out.shape == coreml_out.shape)
            for i in range(len(tp)):
                max_den = max(1.0, tp[i], cp[i])
                self.assertAlmostEqual(tp[i] / max_den, cp[i] / max_den, delta=10 ** -decimal)