pytorch unsqueeze_转换PyTorch模型到CoreML

ae4168ea1d748584b29647bbb89616f6.png

背景

Apple官方虽然不支持pytorch到coreml的直接转换。然而借助苹果的coremltools、pytorch的onnx、社区的onnx到coreml的转换工具这三大力量,这个转换过程还是很容易的。

本文以PyTorch 1.4为基础,以。将PyTorch模型转换为CoreML模型分为如下5个基本步骤:

  1. 使用PyTorch训练并保存一个模型(并对save的模型进行测试);
  2. PyTorch模型转换为ONNX模型;
  3. ONNX模型转换为CoreML模型;
  4. 在macOS上使用python脚本验证该模型;
  5. 集成到XCode上然后在iOS上验证该模型。

下面分步骤介绍下。

使用PyTorch训练并保存一个模型

这是PyTorch的基础了,在此不予赘述。你可以参考专栏文章:

Gemfield:详解Pytorch中的网络构造​zhuanlan.zhihu.com
d4ca86fc9526cad726e6b89a2ff8b5fe.png

PyTorch模型转换为ONNX模型

使用PyTorch中的onnx模块的export方法:

# -*- coding:utf-8 -*-

执行成功,输出syszux_scene.onnx模型。你可以使用Netron软件打开onnx模型来看它的网络结构(是不是预期中的)。

如果你手头没有自己的网络,则可以使用torchvision提供的mobilenet v2来试验下:

import torch
import torch.nn as nn
import torchvision

model = torchvision.models.mobilenet_v2(pretrained=True)

# torchvision的models中没有softmax层,我们添加一个
model = nn.Sequential(model, nn.Softmax())

dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, 'mobilenet_v2.onnx', verbose=True,
                  input_names=['image'], output_names=['gemfield_out'])

执行成功,输出mobilenet_v2.onnx模型。

转换为onnx模型后,可以使用onnx进行推理测试,以确保从pytorch转换到onnx的正确性:

import 

你也可以使用opencv来代替Pillow的Image来读取图片,并且使用numpy来代替pytorch的transform进行normalize计算,如下所示:

import cv2
import onnxruntime
import numpy as np
import sys
import torch

from PIL import Image
from torchvision import transforms

session = onnxruntime.InferenceSession("../syszux_scene.onnx")
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
input_shape = session.get_inputs()[0].shape
print("gemfield debug required input shape", input_shape)

img = cv2.imread(sys.argv[1])
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#INTER_NEAREST, INTER_LINEAR, INTER_AREA, INTER_CUBIC
img = cv2.resize(img, (224, 224),interpolation = cv2.INTER_LINEAR)

img = img.astype(np.float32) / 255.0

mean = np.array([0.485, 0.456, 0.406])
val = np.array([0.229, 0.224, 0.225])
img = (img - mean) / val
print(img)

print("gemfield debug img shape1: ",img.shape)
img= img.astype(np.float32)
img = img.transpose((2,0,1))
#img = img.transpose((2,1,0))
print("gemfield debug img shape2: ",img.shape)
img = np.expand_dims(img,axis=0)
print("gemfield debug img shape3: ",img.shape)

res = session.run([output_name], {input_name: img})
print(res)

ONNX模型转换为CoreML模型

借助下面的开源社区的项目,可以将syszux_scene.onnx转换为苹果的CoreML模型:syszux_scene.mlmodel。

onnx/onnx-coreml​github.com
aa628450288cc23a517b16342ce1c3f9.png

1,安装依赖

pip install --upgrade onnx-coreml

#安装上面的onnx-coreml的时候会自动安装coremltools
pip install --upgrade coremltools

2,转换成mlmodel

from onnx_coreml import convert

# Load the ONNX model as a CoreML model
model = convert(model='syszux_scene.onnx',minimum_ios_deployment_target='13')

# Save the CoreML model
model.save('syszux_scene.mlmodel')

这里面主要用到的就是convert函数。这个函数提供了相当多的参数,在这个例子中提供了其它参数使用的范例:

https://github.com/CivilNet/Gemfield/blob/master/src/python/coreml/onnx2coreml.py​github.com

注意这里的参数:minimum_ios_deployment_target='13',指定了该CoreML模型只能运行在iOS 13上,否则XCode就会给出如下告警:

'mobilenet_v2' is only available on iOS 13.0 or newer
'mobilenet_v2Input' is only available on iOS 13.0 or newer

如果在上面使用的是torchvision提供的mobilenet v2,则这里使用同样的脚本来进行coreml的转换:

import 

这个转换过程不会总是一帆风顺,事实上,除了几个经典的网络,或者极其简单的网络外,一般都会有各种各样的问题,这是不同软件生态之间磨合必然的代价。

在macOS上使用Python脚本验证CoreML模型

1,查看网络结构

使用Netron软件可以直接打开CoreML模型来查看网络结构和参数,除此之外,还可以使用下面的代码来查看.mlmodel模型文件网络结构(该代码可以运行在Linux/Mac/Win上):

import sys
import coremltools
from coremltools.models.neural_network import flexible_shape_utils
spec = coremltools.utils.load_spec('syszux_scene.mlmodel')
print(spec)

2,直接在macOS上使用CoreML模型进行推理测试

使用下面的代码可以在手机之外debug CoreML模型(该代码只能运行在Mac OS上):

from PIL import Image
import coremltools
import numpy as np
mlmodel = coremltools.models.MLModel('gemfield.mlmodel')
pil_img = Image.open('civilnet.jpg')
pil_img = pil_img.resize((768,1280))

#forward
out = mlmodel.predict({'gemfield': pil_img})

#visualize the model output
b = np.argmax(out['gemfieldout'],0)
im = np.where(b==1,255,0)
im = im.astype(np.uint8)
im=Image.fromarray(im)
im.show()

如果遇到错误:Required input feature not passed to neural network,则是因为输入输出的名字不匹配。

要想在Mac OS上执行上面的脚本,需要安装如下依赖:

sudo easy_install pip
pip install --user pillow
pip install --user coremltools

集成到XCode上然后在iOS上验证该模型

有了mlmodel文件后,便可将其加入到xcode工程中,最终将算法部署到iOS上。这个时候你需要一位熟练的iOS开发者。注意:如果你使用了vision框架配合CoreML处理图片的输入,切记VNCoreMLRequest这个类的实例上的imageCropAndScaleOption属性,因为默认的值会导致vision从中间将你的输入图片crop为正方形。 你可以更改该属性的值来改变这个状况:

request.imageCropAndScaleOption = .scaleFill

CoreML模型的输入

CoreML模型期待的输入是什么格式的图像呢?我们从3个方面来说:

1,RGB或者BGR?大多数模型训练的时候使用的是RGB像素顺序,而Caffe使用的是BGR。如果你的神经网络框架使用的是OpenCV来加载图像(又没有做其它处理的话),那么大概率使用的也是BGR像素顺序。

2,是否归一化?0~255还是0~1?

3,是否进行normalization?适用normalized = (data - mean) / std 对每个像素进行计算?

另外,有些模型自带layer用来进行图像的预处理,这个不在Gemfield本文的讨论范围之内,需要你仔细检查。

在iOS SDK中,CoreML模型期待的输入就是CVPixelBufferRef,CVPixelBuffer通常包含的像素是ARGB或者BGRA 格式,每个通道是8bits where each color channel ,值为0~255。比如在由mobilenet_v3.mlmodel生成出来的头文件中,你可以看到如下的代码:

@interface mobilenet_v3Input : 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

因此这里的问题就变成了——CoreML输入的CVPixelBufferRef究竟是kCVPixelFormatType_32BGRA还是kCVPixelFormatType_32ARGB?究竟是0~255的取值还是0~1还是-1~1?究竟要不要适用mean还是std?

1,RGB还是BGR

关于是RGB还是BGR,你在使用coremltools进行转换的时候,通过is_bgr参数来指明:

args = dict(is_bgr=False, ......)

2,是否归一化

使用image_scale参数来指明:

scale = 1.0 / 255.0
args = dict(is_bgr=False, image_scale = scale,......)

3,是否做normalization

标准的normalization的公式为:normalized = (data - mean) / std,像素的值减去均值,再除以方差。CoreML使用的是类似的公式:normalized = data*image_scale + bias。如此换算下来,大致相当于:

red_bias = -red_mean
green_bias = -green_mean
blue_bias = -blue_mean

image_scale参数又要扮演方差的角色了:

image_scale = 1 / std
//bias也要除以std
red_bias /= std
green_bias /= std
blue_bias /= std

Gemfield来举几个例子:

1,输入是0-255

image_scale = 1
red_bias = 0
green_bias = 0
blue_bias = 0

2,输入是0 - 1

image_scale = 1/255.0
red_bias = 0
green_bias = 0
blue_bias = 0

3,输入是-1 ~ 1

image_scale = 2/255.0
red_bias = -1
green_bias = -1
blue_bias = -1

4,Caffe模型

red_bias = -123.68
green_bias = -116.779
blue_bias = -103.939
is_bgr = True

//需要normalize的话,方差是58.8, 1/58.8 = 0.017
image_scale = 0.017
red_bias = -123.68 * 0.017
green_bias = -116.779 * 0.017
blue_bias = -103.939 * 0.017
is_bgr = True

5,PyTorch模型

PyTorch训练的时候,一般会对图像进行如下预处理:

def preprocess_input(x):
    x /= 255.0
    mean = [0.485, 0.456, 0.406]
    std = [0.229, 0.224, 0.225]
    return (x - mean) / std

这个就麻烦了,因为每个通道的方差不一样,而我们只有一个image_scale参数来扮演这个角色!只能大致加权出来一个了方差了:

image_scale = 1.0 / (255.0 * 0.226)
red_bias = -0.485 / 0.226
green_bias = -0.456 / 0.226
blue_bias = -0.406 / 0.226

0.226就是这样估计出来的。要想更精确,只能手写代码来对每个像素进行计算了:

//R
normalizedBuffer[i] = (Float32(dstData.load(fromByteOffset: i * 4 + 2, as: UInt8.self)) / 255.0 - 0.485) / 0.229
//G
normalizedBuffer[width * height + i] = (Float32(dstData.load(fromByteOffset: i * 4 + 1, as: UInt8.self)) / 255.0 - 0.456) / 0.224
//B
normalizedBuffer[width * height * 2 + i] = (Float32(dstData.load(fromByteOffset: i * 4 + 0, as: UInt8.self)) / 255.0 - 0.406) / 0.225

顺便的把BGR转换成了RGB三个平面(从hwc到chw)。

总结

总结下来,这个转换过程就是从PyTorch到ONNX,再从ONNX到CoreML,这个转换过程不会总是一帆风顺。前者从PyTorch到ONNX转换所用到的函数都属于PyTorch项目,因此出现问题的概率相对比较小;而从ONNX到CoreML转换的过程中,除了几个经典的网络,或者极其简单的网络外,一般都会有各种各样的问题,这是不同软件生态之间磨合必然的代价。比如下面的一些错误:

NotImplementedError: Unsupported ONNX ops of type: Shape,Gather,Expand

再比如错误:

TypeError: Error while converting op of type: Conv. Error message: provided number axes -1 not supported

遇到类似错误,可以评论留言。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值