zoukankan      html  css  js  c++  java
  • pytorch模型部署在MacOS或者IOS

    pytorch训练出.pth模型如何在MacOS上或者IOS部署,这是个问题。

    然而我们有了onnx,同样我们也有了coreML。

    ONNX:

    onnx是一种针对机器学习设计的开放式文件格式,用来存储训练好的模型,并进行多种框架模型间的转换。

    coreML:

    Apple在2017年 MacOS 10.13以及IOS11+系统上推出了coreML1.0,官网地址:https://developer.apple.com/documentation/coreml 。

    2018年又推出MacOS 10.14以及IOS12系统上的coreML2.0  https://www.appcoda.com/coreml2/

    coreML框架可以方便的进行深度学习模型的部署,利用模型进行预测,让深度学习可以在apple的移动设备上发光发热。而开发者需要做的仅仅是将model.mlModel拖进xcode工程,xcode工程会自动生成以模型名称命名的object-c类以及多种进行预测所需的类接口。

    pytorch -- ONNX -- coreML

    没错,就是这个流程。我们有训练好的.pth模型,通过pytorch.onnx.export() 转化为 .onnx模型,然后利用 onnx_coreml.convert()将 .onnx转换为 .mlModel。将.mlModel拖进xcode工程编写预测代码就可以了。

    1.  pytorch -- ONNX

    请先查看pytorch官网的onnx模块:https://pytorch.org/docs/stable/onnx.html  。 主要的代码就这一个API, 各个参数意义请查阅文档。

    torch.onnx.export(model, dummy_input, "alexnet.onnx", verbose=True, input_names=input_names, output_names=output_names);

    转换部分代码如下:
     batch_size=1
     onnx_model_path = "onnx_model.onnx"
     dummy_input = V(torch.randn(batch_size, 3, 224, 224), requires_grad=True)
     torch_out= torch.onnx.export(pytorch_model, dummy_input , onnx_model_path, verbose=True,input_names=['image'], output_names=['outTensor'], export_params=True, training=False )

     这里有一个需要注意的地方就是input_names和output_names的设置,如果不设置的情况,输入层和输出层pytorch会自动分配一个数字编号。比如下图(用netron工具查看,真是一个很好用的工具 https://pypi.org/project/netron/)。 自动分配的输入名称和输出名称是0 和 199。 这样转换成coreML模型后加载到xcode中会出现"initwith0"这样的编译错误,就是模型初始化的时候不能正确处理这个输入名称0。因此最好是在export的时候将其修改一个名称。

      

    修改之后的模型是这样的,可以看到模型的输入和输出名称都发生的修改:

     2. onnx -- mlModel

      这一部分需要安装onnx, github地址: https://github.com/onnx/onnx  以及安装一个转换工具onnx_coreML,github地址:https://github.com/onnx/onnx-coreml  。里面用到了一个coremltools : https://pypi.org/project/coremltools/,这个tool目前仅支持python2.7环境下使用。

      安装好后, import onnx , import onnx_coreML 就可以使用。转换代码如下:

    onnx_model = onnx.load("onnx_model.onnx")
    cml_model= onnx_coreml.convert(onnx_model)
    cml_model.save("coreML_model.mlmodel")

       当然, onnx_coreml.convert有很多参数,可以用来预处理,设置bgr顺序等,请参看github文档介绍。

      现在将coreML_model.mlModel拖进xcode工程里,会自动生成一个coreML_model类,这个类有初始化模型,输入 预测 输出等API,编写预测代码即可。

    3. 在最新的coreML2.0中,支持模型的量化. coreML1.0中处理模型是32位,而在coreML2.0中可以将模型量化为16bit, 8bit, 4bit甚至是2bit,并且可以设置量化的方法。 具体请看apple WWDC视频以及PPT。

      模型量化仍然是使用coreMLtool2.0工具,具体代码请查阅这篇博客,写的很详细:https://www.jianshu.com/p/b6e3cb7338bf。 两句代码即可完成量化转换,代码如下:

    import coremltools
    from coremltools.models.neural_network.quantization_utils import *
    
    model = coremltools.models.MLModel('Model.mlmodel')
    lin_quant_model = quantize_weights(model, 8, "linear")
    lin_quant_model.save('Model_8bit.mlmodel')

    时间仓促,写的粗糙,随后更新。

    ———————————今天来更新————————————————

    1.  将模型拖进xcode工程后,点击模型将在右侧页面看到这样的信息,包括模型的名称、尺寸、输入、输出等信息,并且会提示已经自动生成Objective-c的模型类文件:

      点击Model右侧的箭头可以跳转到model类的头文件model.h。里面包含了模型初始化以及预测等接口。如下:

    //
    // Model.h
    //
    // This file was automatically generated and should not be edited.
    //
    
    #import <Foundation/Foundation.h>
    #import <CoreML/CoreML.h>
    #include <stdint.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    
    /// Model Prediction Input Type
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
    @interface ModelInput : NSObject<MLFeatureProvider>
    
    /// image as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high
    @property (readwrite, nonatomic) CVPixelBufferRef image;
    - (instancetype)init NS_UNAVAILABLE;
    - (instancetype)initWithImage:(CVPixelBufferRef)image;
    @end
    
    
    /// Model Prediction Output Type
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
    @interface ModelOutput : NSObject<MLFeatureProvider>
    
    /// MultiArray of shape (1, 1, 10, 1, 1). The first and second dimensions correspond to sequence and batch size, respectively as multidimensional array of floats
    @property (readwrite, nonatomic, strong) MLMultiArray * outTensor;
    - (instancetype)init NS_UNAVAILABLE;
    - (instancetype)initWithOutTensor:(MLMultiArray *)outTensor;
    @end
    
    
    /// Class for model loading and prediction
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
    @interface Model : NSObject
    @property (readonly, nonatomic, nullable) MLModel * model;
    - (nullable instancetype)init;
    - (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError * _Nullable * _Nullable)error;
    - (nullable instancetype)initWithConfiguration:(MLModelConfiguration *)configuration error:(NSError * _Nullable * _Nullable)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) __attribute__((visibility("hidden")));
    - (nullable instancetype)initWithContentsOfURL:(NSURL *)url configuration:(MLModelConfiguration *)configuration error:(NSError * _Nullable * _Nullable)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) __attribute__((visibility("hidden")));
    
    /**
        Make a prediction using the standard interface
        @param input an instance of ModelInput to predict from
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the prediction as ModelOutput
    */
    - (nullable ModelOutput *)predictionFromFeatures:(ModelInput *)input error:(NSError * _Nullable * _Nullable)error;
    
    /**
        Make a prediction using the standard interface
        @param input an instance of ModelInput to predict from
        @param options prediction options
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the prediction as ModelOutput
    */
    - (nullable ModelOutput *)predictionFromFeatures:(ModelInput *)input options:(MLPredictionOptions *)options error:(NSError * _Nullable * _Nullable)error;
    
    /**
        Make a prediction using the convenience interface
        @param image as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high:
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the prediction as ModelOutput
    */
    - (nullable ModelOutput *)predictionFromImage:(CVPixelBufferRef)image error:(NSError * _Nullable * _Nullable)error;
    
    /**
        Batch prediction
        @param inputArray array of ModelInput instances to obtain predictions from
        @param options prediction options
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the predictions as NSArray<ModelOutput *>
    */
    - (nullable NSArray<ModelOutput *> *)predictionsFromInputs:(NSArray<ModelInput*> *)inputArray options:(MLPredictionOptions *)options error:(NSError * _Nullable * _Nullable)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) __attribute__((visibility("hidden")));
    @end
    
    NS_ASSUME_NONNULL_END
    View Code

    其中带有这样标志的表示是coreML1.0版本的接口以及对应的系统版本:

    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) 

    下面的是coreML2.0新增的接口以及对应的系统版本:

    API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0))

    2.   说一下几个常用API的使用:

    ① 首先是模型的初始化加载,初始化接口如下几个:

    - (nullable instancetype)init;
    - (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError * _Nullable * _Nullable)error;
    - (nullable instancetype)initWithConfiguration:(MLModelConfiguration *)configuration error:(NSError * _Nullable * _Nullable)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) __attribute__((visibility("hidden")));
    - (nullable instancetype)initWithContentsOfURL:(NSURL *)url configuration:(MLModelConfiguration *)configuration error:(NSError * _Nullable * _Nullable)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) __attribute__((visibility("hidden")));

    当然,最简单的初始化方法就是第一个:

    model = [ [Model alloc] init ];

    如果需要设置设备上计算单元的话,就去调用coreML2.0新增的initWithConfiguration接口,多的两个参数分别是(MLModelConfiguration *)configuration 以及 NSError *error。调用方法如下:

    MLModelConfiguration *config = [ [MLModelConfiguration alloc] init];
    config.computeUnits = MLComputeUnitsCpuOnly;//
        
    NSError *error;
    model = [ [nimaModel alloc] initWithConfiguration:config error:&error];

    其中config值有三种情况可以选择,分别是

    MLComputeUnitsCPUOnly  ----仅仅使用CPU计算;
    MLComputeUnitsCPUAndGPU ----使用CPU和GPU计算;
    MLComputeUnitsAll  -----使用所有计算单元进行计算(主要指A11以及A12仿生芯片中的netrual engine)

    具体见如下的MLModelConfiguration.h文件
    #import <Foundation/Foundation.h>
    #import <CoreML/MLExport.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef NS_ENUM(NSInteger, MLComputeUnits) {
        MLComputeUnitsCPUOnly = 0,
        MLComputeUnitsCPUAndGPU = 1
    
        ,
        MLComputeUnitsAll = 2
    
    } API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0));
    
    /*!
     * An object to hold options for loading a model.
     */
    API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0))
    ML_EXPORT
    @interface MLModelConfiguration : NSObject <NSCopying>
    
    @property (readwrite) MLComputeUnits computeUnits;
    
    @end
    
    NS_ASSUME_NONNULL_END
    View Code

    初始化的两个两个API用的不多,暂且按住不表。

    ②  模型的输入API。

    /// Model Prediction Input Type
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
    @interface ModelInput : NSObject<MLFeatureProvider>
    
    /// image as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high
    @property (readwrite, nonatomic) CVPixelBufferRef image;
    - (instancetype)init NS_UNAVAILABLE;
    - (instancetype)initWithImage:(CVPixelBufferRef)image;
    @end

    ModelInput类的API很简单,就用一个initWithImage就可以了,多做的一步就是需要将UIImage或者OpenCV加载的图像转换为CVPixelBuffer。然后使用API创建ModelInput:

    ModelInput *input = [[ModelInput alloc] initWithImage:buffer];

    ③ 模型的预测API。

    /**
        Make a prediction using the standard interface
        @param input an instance of ModelInput to predict from
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the prediction as ModelOutput
    */
    - (nullable ModelOutput *)predictionFromFeatures:(ModelInput *)input error:(NSError * _Nullable * _Nullable)error;
    
    /**
        Make a prediction using the standard interface
        @param input an instance of ModelInput to predict from
        @param options prediction options
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the prediction as ModelOutput
    */
    - (nullable ModelOutput *)predictionFromFeatures:(ModelInput *)input options:(MLPredictionOptions *)options error:(NSError * _Nullable * _Nullable)error;
    
    /**
        Make a prediction using the convenience interface
        @param image as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high:
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the prediction as ModelOutput
    */
    - (nullable ModelOutput *)predictionFromImage:(CVPixelBufferRef)image error:(NSError * _Nullable * _Nullable)error;
    
    /**
        Batch prediction
        @param inputArray array of ModelInput instances to obtain predictions from
        @param options prediction options
        @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
        @return the predictions as NSArray<ModelOutput *>
    */
    - (nullable NSArray<ModelOutput *> *)predictionsFromInputs:(NSArray<ModelInput*> *)inputArray options:(MLPredictionOptions *)options error:(NSError * _Nullable * _Nullable)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) __attribute__((visibility("hidden")));
    @end

    前面两个是标准的API。他俩的差异在于 Options: (MLPredictionOptions *)options 这个参数。第2个API中可以设置Options参数,这个是coreML1.0中就有的,具体见如下的MLPredictionOptions.h文件。

    //
    //  MLPredictionOptions.h
    //  CoreML
    //
    //  Copyright © 2017 Apple Inc. All rights reserved.
    //
    #import <Foundation/Foundation.h>
    #import <CoreML/MLExport.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    /*!
     * MLPredictionOptions
     *
     * An object to hold options / controls / parameters of how
     * model prediction is performed
     */
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0))
    ML_EXPORT
    @interface MLPredictionOptions : NSObject
    
    // Set to YES to force computation to be on the CPU only
    @property (readwrite, nonatomic) BOOL usesCPUOnly;
    
    @end
    
    NS_ASSUME_NONNULL_END
    View Code

    Option值默认不设置则为No,也就是说默认使用GPU,如果想要只使用CPU,调用时将其值设置为Yes即可。

     MLPredictionOptions *option = [ [MLPredictionOptions alloc] init];
     option.usesCPUOnly = yes;

      第三个API没有Options选项,但是可以直接将CVPixelBuffer作为输入,不用创建ModelInput。是一种更为简便的API接口。

      而第四个API是coreML2.0新增的batchPrediction接口。前面三个API一次只能输入一幅图像预测。而这个接口可以将多个输入组织为NSArray,作为一个Batch一次性传入。并得到NSArray形式的输出。更加快速,不用再去写for循环。

         以第三个形式的预测API为例,调用代码如下:

    ModelOutput *output = [model predictionFromFeatures:input options:option error:&error];

    ④ 模型输出API:

    /// Model Prediction Output Type
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
    @interface ModelOutput : NSObject<MLFeatureProvider>
    
    /// MultiArray of shape (1, 1, 10, 1, 1). The first and second dimensions correspond to sequence and batch size, respectively as multidimensional array of floats
    @property (readwrite, nonatomic, strong) MLMultiArray * outTensor;
    - (instancetype)init NS_UNAVAILABLE;
    - (instancetype)initWithOutTensor:(MLMultiArray *)outTensor;
    @end

    modelOutput默认是MLMultiArray类型,当模型有多个输出的时候,根据输出output.outTensor, outTensor.feature  .... 等等 依次获取。示例模型只有一个输出,因此只有一个outTensor。

    夜已深,就此打住。

  • 相关阅读:
    LeetCode Find Duplicate File in System
    LeetCode 681. Next Closest Time
    LeetCode 678. Valid Parenthesis String
    LeetCode 616. Add Bold Tag in String
    LeetCode 639. Decode Ways II
    LeetCode 536. Construct Binary Tree from String
    LeetCode 539. Minimum Time Difference
    LeetCode 635. Design Log Storage System
    LeetCode Split Concatenated Strings
    LeetCode 696. Count Binary Substrings
  • 原文地址:https://www.cnblogs.com/riddick/p/10434339.html
Copyright © 2011-2022 走看看