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





5.00/5 (3投票s)
在本文中,我们将一个 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 环境中安装的 coremltools
和 onnx
包将几乎任何模型转换为 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_scale
、bias_r
、bias_g
和 bias_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 应用程序中使用转换后的模型——请参阅下一篇文章。