Get models on device using Core ML Converters
Machine Learning & AI 进阶 24m

使用 Core ML Converters 将模型部署到设备端

Get models on device using Core ML Converters

2020年6月24日

在 Apple 官方观看视频

一句话判断

无论你用 TensorFlow、PyTorch 还是其他框架训练模型,Core ML Converters 都能帮你无痛迁移到 Apple 设备——但转换过程中的坑比你想象的多,这场 Session 帮你提前避开。

这场 Session 讲了什么

Core ML Tools(coremltools)是 Python 生态中的模型转换工具链,负责将各种训练框架产出的模型转换为 Apple 的 .mlmodel 格式。Session 全面介绍了 2020 年更新后的转换器能力,包括对 TensorFlow 2/Keras、PyTorch、ONNX 的支持改进,以及新增的对更多算子(operators)的覆盖。

Session 的核心内容分为三部分。首先,概述了当前支持的转换路径:从 TensorFlow 可以通过 tf.kerastf.contrib 转换;从 PyTorch 需要先导出为 ONNX 格式再转换。其次,详细讨论了转换过程中常见的算子兼容性问题,以及如何通过自定义算子(custom layers)处理不支持的算子。最后,演示了一个端到端的转换流程,从训练好的图像分类模型到能在 iOS 上运行的 Core ML 模型。

特别值得注意的是,Session 介绍了 MIL(Model Intermediate Language)这个新的中间表示层。MIL 作为统一的前端,让所有框架的转换都经过同一套优化和验证流程,大幅减少了转换器本身的维护负担,也提高了转换成功率。

值得深挖的点

MIL 统一中间表示的意义

以前 Core ML Tools 为每个训练框架维护独立的转换管道,导致功能碎片化和 bug 修复缓慢。MIL 的引入意味着所有模型(无论来自哪个框架)都先被转换为 MIL 图,再经过统一的优化和验证阶段编译为 Core ML 格式。这个架构变更让 Apple 可以集中精力优化一条管道,同时方便社区贡献新的前端转换器。

自定义层的实现策略

当你的模型包含 Core ML 不原生支持的算子时,需要实现自定义层。这涉及两部分:Python 端的转换器扩展(告诉 coremltools 如何处理这个算子)和 Swift/C 端的运行时实现(告诉 Core ML 如何执行这个算子)。Session 建议尽量通过等价替换的方式避免自定义层——比如把不支持的激活函数替换为数学上等价的 sigmoid/ReLU 组合。

代码片段

# 将 TensorFlow 2 Keras 模型转换为 Core ML 格式
import coremltools as ct
import tensorflow as tf

# 加载或训练一个 Keras 模型
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)),
    tf.keras.layers.MaxPooling2D((2, 2)),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# 转换为 Core ML 模型
coreml_model = ct.convert(
    model,
    inputs=[ct.ImageType(name="image", shape=(1, 224, 224, 3),
                         scale=1/255.0)],
    classifier_config=ct.ClassifierConfig(
        class_labels=["猫", "狗", "鸟", "鱼", "花", "树", "车", "船", "飞机", "房子"]
    )
)

# 添加元数据
coreml_model.author = "我的团队"
coreml_model.short_description = "10类物体分类器"
coreml_model.version = "1.0"

# 保存模型文件
coreml_model.save("ImageClassifier.mlmodel")
# 将 PyTorch 模型转换为 Core ML 格式(通过 ONNX)
import torch
import coremltools as ct

class MyModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = torch.nn.Conv2d(3, 16, kernel_size=3, padding=1)
        self.fc = torch.nn.Linear(16 * 112 * 112, 5)
    
    def forward(self, x):
        x = torch.relu(self.conv1(x))
        x = torch.max_pool2d(x, 2)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

# 导出为 ONNX
pytorch_model = MyModel()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
    pytorch_model,
    dummy_input,
    "model.onnx",
    input_names=["input"],
    output_names=["output"],
    dynamic_axes=None
)

# 从 ONNX 转换为 Core ML
coreml_model = ct.converters.onnx.convert(
    model="model.onnx",
    minimum_ios_deployment_target="14",
    image_input_names=["input"]
)
coreml_model.save("MyPyTorchModel.mlmodel")
# 转换后验证模型的正确性
import numpy as np
import coremltools as ct

# 加载转换后的模型
model = ct.models.MLModel("ImageClassifier.mlmodel")

# 生成随机测试输入
test_input = np.random.rand(1, 224, 224, 3).astype(np.float32)

# 用 Core ML 模型预测
coreml_prediction = model.predict({"image": test_input})

# 对比原始框架的输出
# keras_prediction = tf_model.predict(test_input)
# 比较两者的差异
# assert np.allclose(coreml_prediction, keras_prediction, atol=1e-4)
print("预测结果:", coreml_prediction)

最佳实践

  • 转换前先用 coremltools.utils.evaluate_classifier 验证模型输出的数值一致性,确保转换没有引入精度损失
  • 使用 ct.compute_unit. 设置指定计算后端,在转换时就明确模型的目标硬件
  • PyTorch 模型转换时注意 dynamic_axes 的处理,Core ML 目前对动态维度支持有限
  • 如果转换失败,先用 ONNX Runtime 验证 ONNX 模型本身的正确性,排除训练框架的问题
  • 保存转换日志,记录每次转换的 coremltools 版本和参数,方便问题追溯

还有什么值得关注

  • coremltools 4.0 开始支持直接从 PyTorch 转换(不需要经过 ONNX 中间步骤),但部分复杂算子仍需 ONNX 路径
  • Session 提到了模型量化(quantization)工具,可以在转换后对模型做 8-bit 或 16-bit 量化,大幅减小模型体积
  • 对于 NLP 模型,新的转换器支持将 word embedding 和 BERT 类模型直接转换,这是之前版本的痛点
WWDC 2020