65.9K
CodeProject 正在变化。 阅读更多。
Home

将 ONNX 图像分类模型转换为 Core ML

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2020 年 8 月 27 日

CPOL

6分钟阅读

viewsIcon

13276

downloadIcon

94

在本文中,我们将一个 ResNet 模型转换为 Core ML 格式。

引言

深度神经网络在图像分类等任务上表现出色。十年前需要耗费数百万美元和整个研究团队才能完成的结果,现在任何拥有半成品 GPU 的人都可以轻松获得。然而,深度神经网络也有一个缺点。它们可能非常庞大且运行缓慢,因此在移动设备上并不总是运行良好。幸运的是,Core ML 提供了一个解决方案:它使您能够创建在 iOS 设备上运行良好的精简模型。

在本系列文章中,我们将向您展示两种使用 Core ML 的方法。首先,您将学习如何将预训练的图像分类器模型转换为 Core ML 格式,并在 iOS 应用程序中使用它。然后,您将训练自己的机器学习 (ML) 模型,并使用它来制作一个 Not Hotdog 应用——就像您可能在 HBO 的 《硅谷》 中看到的那样。

上一篇文章中,我们准备了开发环境。在这篇文章中,我们将把一个训练好的 ONNX 图像分类模型转换为 Core ML 格式。

Core ML 和 ONNX

Core ML 是苹果的框架,它允许您将 ML 模型集成到您的应用程序中(不仅限于移动设备和台式机,还包括手表和 Apple TV)。在考虑 iOS 设备上的 ML 时,我们建议始终从 Core ML 开始。该框架非常易于使用,并且支持完全利用 Apple 设备上提供的自定义 CPU、GPU 和神经网络引擎。此外,您可以将几乎任何神经网络模型转换为 Core ML 的原生格式。

如今,许多 ML 框架都得到了广泛使用,例如 TensorFlow、Keras 和 PyTorch。这些框架中的每一个都有自己的模型保存格式。有一些工具可以将大多数这些格式直接转换为 Core ML。我们将重点关注开放神经网络交换 (ONNX) 格式。ONNX 定义了一种通用的文件格式和操作,以便于在框架之间切换。

让我们来看看所谓的模型库可用的 ONNX 模型

点击视觉部分中的第一个链接,图像分类。这是它显示的页面

正如您所见,有相当多的模型可供选择。这些模型使用著名的 ImageNet 分类数据集进行训练,该数据集包含 1000 个对象类别(例如“键盘”、“圆珠笔”、“蜡烛”、“tarantula”、“大白鲨”……嗯,还有其他 995 个)。

虽然这些模型在架构和训练它们的框架方面有所不同,但在模型库中,它们都显示为已转换为 ONNX。

这里最好的模型之一(错误率低至 3.6%)是 ResNet。这将是我们用于转换为 Core ML 的模型。

要下载模型,请点击上面表格中的 ResNet 链接,然后向下滚动到所需模型版本。

为了向您展示 Core ML 可以让 iOS 设备处理“真实”模型,我们将选择最大(也是最好的)可用模型——具有 152 层的 ResNet V2。对于较旧的 iOS 设备,例如 iPhone 6 或 7,您可能希望尝试较小的模型之一,例如 ResNet18。

将模型从 ONNX 转换为 Core ML

只要模型使用的操作和层(opset 版本)受 Core ML 支持(目前 opset 版本为 10 及更低),就可以使用我们 Conda 环境中安装的 coremltoolsonnx 包将几乎任何模型转换为 Core ML。

两种类型的模型享有专用支持:分类回归。分类模型为输入(例如图像)分配一个标签。回归模型为给定输入计算一个数值。

我们将重点关注图像分类模型。

选定的 ResNet 模型需要什么作为输入?在相应的模型库页面上可以找到详细的描述。

正如您所见,ResNet 模型期望输入是具有以下维度的数组:批次、大小通道(对于红色、绿色和蓝色通道始终为 3)、高度和宽度。数组值应使用为每个颜色单独定义的均值和标准差缩放到 ~[0, 1] 的范围。

虽然 coremltools 库非常灵活,但其内置的图像分类选项不允许我们完全重现原始预处理步骤。让我们尽量接近

import coremltools as ct
import numpy as np

def resnet_norm_to_scale_bias(mean_rgb, stddev_rgb):
    image_scale = 1 / 255. / (sum(stddev_rgb) / 3)    
    bias_rgb = []
    for i in range(3):
        bias = -mean_rgb[i] / stddev_rgb[i]
        bias_rgb.append(bias)
    
    return image_scale, bias_rgb

# Preprocessing parameters specific for ResNet model 
# as defined at: https://github.com/onnx/models/tree/master/vision/
mean_vec = np.array([0.485, 0.456, 0.406])
stddev_vec = np.array([0.229, 0.224, 0.225])

image_scale, (bias_r, bias_g, bias_b) = resnet_norm_to_scale_bias(mean_vec, stddev_vec)

进行上述转换是必需的,因为标准的 ResNet 过程使用以下公式为图像中的每个像素计算一个归一化值

    norm_img_data = (img_data/255 - mean) / stddev = 
 	(img_data/255/stddev) - mean/stddev

Core ML 期望的是类似这样的

norm_img_data = (img_data * image_scale) + bias

ResNet 预处理对每个通道期望不同的 stddev(值缩放),但 Core ML 默认支持对应于 image_scale 参数的单个值。

由于一个泛化良好的模型不应明显受到图像色调微小变化的影响,因此可以使用单个 image_scale 值,该值计算为 specified stddev_vec 值之一的平均值。

image_scale = 1 / 255. / (sum(stddev_rgb) / 3)

接下来,我们为每个颜色通道计算 bias。我们最终得到一组预处理参数(image_scalebias_rbias_gbias_b),我们可以在 Core ML 转换中使用它们。

有了计算好的预处理参数,您就可以运行转换了

model = ct.converters.onnx.convert(
    model='./resnet152-v2-7.onnx',
    mode='classifier', 
    class_labels='./labels.txt',
    image_input_names=['data'],
    preprocessing_args={
        'image_scale': image_scale,
        'red_bias': bias_r,
        'green_bias': bias_g,
        'blue_bias': bias_b
    },
    minimum_ios_deployment_target='13'
)

让我们简要看一下其中的一些参数

  • mode=‘classifier’ 并使用 class_labels=‘./labels.txt’ 来确定分类模式并使用提供的标签。这将确保模型不仅输出数值,还输出最可能检测到的对象的标签。
  • image_input_names=[‘data’] 表示输入数据包含图像。它将允许您直接使用图像,而无需在 Swift 中转换为 MultiArray 或在 Python 中转换为 NumPy 数组。
  • preprocessing_args 指定先前计算的像素值归一化参数。
  • minimum_ios_deployment_target 设置为 13,以确保输入和输出结构比旧 iOS 版本所要求的更易于理解。

运行上述代码后,您可以打印模型摘要

在我们的例子中,模型接受 RGB 图像作为输入,大小为 224 x 224 像素,并生成两个输出

  • classLabel – 模型置信度最高的对象的标签。
  • resnetv27_dense0_fwd – 层输出字典(包含 1000 个“标签”:置信度对)。此处返回的置信度是原始神经网络输出,而不是概率。可以轻松地将其转换为概率,如代码下载中包含的示例 notebook 所示。

运行预测

使用转换后的模型,运行预测是一项简单的任务。让我们使用 PIL (pillow) 库来处理图像,以及包含在代码下载中的 ballpen.jpg 图像。

from PIL import Image
image = Image.open('ballpen.jpg')
image = image.resize((224,224))
pred = model.predict(data={"data": image})
print(pred['classLabel'])

预期结果是

ballpoint, ballpoint pen, ballpen, Biro

随时尝试其他图像。为避免重复转换过程,请保存模型

model.save('ResNet.mlmodel')

稍后您可以加载它

model = ct.models.MLModel('ResNet.mlmodel')

查看代码下载中提供的 notebook,了解如何从模型输出中获取更多详细信息,例如“top 5”预测候选的概率和标签。

摘要

您已将 ResNet 模型转换为 Core ML 格式并保存。

虽然不同的模型需要不同的预处理和转换参数,但总体方法保持不变。

模型还可以做很多其他事情,例如添加元数据(模型描述、作者等)、添加自定义层(如末尾的 softmax 以强制模型返回概率而不是原始网络输出)。我们只涵盖了基础知识,让您可以尝试自己的模型。

现在我们准备在 iOS 应用程序中使用转换后的模型——请参阅下一篇文章

© . All rights reserved.