Core ML与ONNX模型转换指南

随着深度学习技术的快速发展,现在即使是拥有普通GPU的个人也能轻松获得之前需要数百万美金和整个研究团队十年才能得到的结果。然而,深度神经网络也有其缺点,它们通常体积庞大且运行缓慢,这使得它们在移动设备上的表现并不总是那么理想。幸运的是,Core ML提供了解决方案:它允许创建能在iOS设备上良好运行的轻量级模型。

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

Core ML与ONNX

Core ML是苹果的框架,它允许将机器学习模型集成到应用程序中(不仅适用于移动设备和桌面,还适用于手表和Apple TV)。建议在考虑iOS设备上的机器学习时始终从Core ML开始。这个框架非常容易使用,并且支持充分利用苹果设备上可用的定制CPU、GPU和神经引擎。此外,可以将几乎所有的神经网络模型转换为Core ML的本地格式。

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

让看看所谓的“模型动物园”中可用的ONNX模型:

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

如所见,有很多模型可供选择。这些模型是使用众所周知的ImageNet分类数据集训练的,该数据集包含1000个对象类别(如“键盘”、“圆珠笔”、“蜡烛”、“狼蛛”、“大白鲨”等等)。

虽然这些模型在架构和训练框架上有所不同,但在模型动物园中,它们都被转换为ONNX格式。

在这里可用的最佳模型之一(错误率低至3.6%)是ResNet。这是将用于转换到Core ML的模型。

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

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

将模型从ONNX转换为Core ML

只要模型使用的是Core ML支持的操作和层(opset版本)(目前是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参数。

因为一个泛化良好的模型不应该明显受到图像色调小变化的影响,所以使用计算为指定stddev_vec值平均值的单个image_scale值是安全的:

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' with 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个"label":confidence对)。这里返回的置信度是原始神经网络输出,而不是概率。它可以很容易地转换为概率,如代码下载中包含的示例笔记本所示。

运行预测

有了转换后的模型,运行预测是一项直接的任务。让使用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')

查看代码下载中提供的笔记本,了解如何从模型输出中获得更多细节,例如概率和“前5”预测候选对象的标签。

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

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

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485