pytorch模型部署在MacOS或者IOS

2022-10-26,,,,

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

其中带有这样标志的表示是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 = ,
MLComputeUnitsCPUAndGPU = ,
MLComputeUnitsAll = } 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

初始化的两个两个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

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。

夜已深,就此打住。

<!--
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Menlo; color: #000000; background-color: #ffffff}
span.s1 {color: #1c00cf}
-->

pytorch模型部署在MacOS或者IOS的相关教程结束。

《pytorch模型部署在MacOS或者IOS.doc》

下载本文的Word格式文档,以方便收藏与打印。