ABEJA Tech Blog

中の人の興味のある情報を発信していきます

iOSアプリ開発者のための Core ML フォーマット比較と最適運用法

こんにちは、ABEJA 新卒エンジニアの和田です。

PyTorchで学習したResNet18ベースの年齢・性別推定モデルをCore MLへ変換する際に遭遇した「形式エラー」と、その回避策として、Core MLを使ってiOSアプリを作成している人や .mlmodel.mlpackage の使い分けがわからない人に向けて、実際のコードの解読まで深掘りしていきます!

背景:なぜCore MLなのか

近年、モバイル端末上で機械学習推論を完結させるニーズが高まっています。外部サーバに送らずにデータを処理することで、レイテンシ削減とプライバシー保護の両立が可能です。AppleのCore MLは、まさにこの“オンデバイス推論”を容易にするフレームワークです。Neural EngineやGPUを自動活用し、フレームワーク側で最適化までやってくれるのが魅力です。

次は、私が実際にぶつかった“形式エラー”について、その原因と発見までの経緯を振り返ります。

私が直面した“モデル形式エラー”

ResNet18ベースのモデルをcoremltoolsで変換し、Xcodeにドラッグすると──

Model format too old to load: Expected modern .mlmodel format.

というエラーが出ました。Xcode 12 リリースノートをチェックし、Core ML Model Formats を読み、各フォーマットの対応OS/Xcodeバージョンを確認し、.mlpackageの正式サポートはXcode13以降であることを把握しました。そして、原因は、私が変換時に.mlpackageを指定していたためです。当時は「フォルダ型なら新しいから大丈夫だろう」と思い込んでいたのですが、実際にはターゲットのiOSバージョンとXcodeの組み合わせ次第で読み込みの可否が変わります。

エラーの原因が「フォーマットの選択ミス」にあるとわかったところで、改めてCore MLモデルがどのようにアプリに組み込まれるか、その内部パイプラインを押さえておきます。

1. Core MLフォーマットの内部構造を覗く

Core MLのモデルは最終的に.mlmodelcというバイトコード形式でアプリに組み込まれます。

  • 変換前.mlmodel(単一ファイル) または .mlpackage(フォルダ型)
  • ビルド時:Xcodeが自動で.mlmodelcディレクトリを生成し、最適化されたバイナリを配置
  • ランタイム:Core MLフレームワークが.mlmodelc内のメタデータと演算グラフを解釈し、Neural Engine/GPU/CPUを使い分けて推論

このパイプラインを理解すると、なぜフォーマットの選択が実行時パフォーマンスやメンテナンス性に影響するのかが見えてきます。内部構造がわかったところで、まずは従来型の .mlmodel がどう定義されているのかを深掘りします。

2. .mlmodel 深掘り:Protocol Buffers仕様

.mlmodelはProtocol Buffers(protobuf)で定義されたスキーマに従う単一ファイルです。中身をバイナリとして分解すると、以下の要素が含まれます。

  1. Model Specification
    • Model メッセージ:モデルタイプ(neuralNetwork、pipeline、treeEnsembleなど)
    • 入出力の名前/型情報(FeatureDescription
  2. Neural Network Graph
    • 各レイヤー(畳み込み、全結合、バッチ正規化など)の定義と繋がり
    • 層ごとのパラメータ(重み・バイアス)は同じprotobuf内に直埋め
  3. Metadata
    • モデルのクラスラベル、description、author、licenseなどのカスタムプロパティ

メリット/デメリット

  • メリット:ファイル数が1つなので扱いがシンプル。Git管理やファイル転送が容易。
  • デメリット:一部だけ書き換えたいときには、protobufをパース→編集→再シリアライズの手順が必要で、coremltools APIへの依存度が高い。

実際、手元でprotocを使って.mlmodelをデコードしようとすると、Appleが公開している.proto定義を手元に用意しなければならず手間がかかります。日常的な修正はほぼcoremltools一択になります。続いて、より新しいフォルダ型.mlpackage がどのように構成されているかを見ていきます。

3. .mlpackage 深掘り:フォルダコンテナの仕組み

一方、.mlpackageは内部がフォルダ構造で、以下のように分割されています。

MyModel.mlpackage/
├── specification.json      # モデル定義のJSON版
├── weights/
│   ├── 00_model_weights.bin
│   └── 01_layer0_weights.bin
└── metadata.plist          # Xcode用メタデータ
  • specification.json

    protobuf定義と同等のモデル構造をJSONで表現。条件分岐やループを含むML Programでは、ここにプログラム的な記述が追記される。

  • weights/

    重みが複数ファイルに分割されている。大容量モデルでは数十〜数百に分割され、必要なパーツだけを読み込むことが可能です。

  • metadata.plist

    XcodeがUI上で表示するモデル名やサンプル入力・出力の設定を保持。直接GUIで編集できるのも大きな魅力です。

拡張性

  • ML Programのカスタムオペレーションを追加するときは、specification.jsonに新しいOpタイプを記述し、weightsフォルダに該当バイナリを配置すれば完結します。

この分割管理のおかげで、重みの差し替えだけをCIジョブで行ったり、JSON部分だけを設計者がテキストエディタで微調整したり、といった柔軟な運用が可能です。ここまでで両フォーマットの内部構造がわかったので、最後に「実際にはどちらを選ぶべきか」を改めて整理します。

比較表

項目 .mlmodel .mlpackage
形式 単一ファイル(Protocol Buffers) フォルダ型(JSON+バイナリ重みファイル)
動的モデル対応 ×(静的モデルのみ) ○(条件分岐・ループ等のML Program対応)
互換性 iOS 13+/macOS 10.15+ 静的モデル:iOS 11+/macOS 10.13+動的モデル:iOS 15+/macOS 12+
管理のしやすさ Git/CIで簡単 メタデータと重みが分離され、差分運用が可能
ロード・メモリ効率化 一括ロード 必要な部分のみオンデマンドロード

私のおすすめフロー

  1. まずは.mlmodelでプロトタイプ

    互換性と手軽さを活かして素早く動かし、動作検証とUI組み込みに注力する。

  2. 要件が固まったら.mlpackageへ移行

    動的な前処理/後処理が必要になったら、あるいは量子化や大規模モデル対応が視野に入ったら、パッケージ型に切り替える。

  3. 必要がなければそのまま.mlmodel運用

    小〜中規模の静的モデルであれば、わざわざ移行せずとも運用コストは最小です。

まとめ

以上で、.mlmodel.mlpackage の内部構造から、実際のエラー原因、そして調査手順まで一通りご紹介しました。フォーマットの違いがなぜ生まれ、どのように動作し、影響を与えるのかを体験を通じて理解いただけたのではないでしょうか。私自身、この経験を機に「公式ドキュメントを読む→実験する→検証する」というサイクルの重要性を再認識しました。この記事が、Core ML フォーマット選びで迷っている皆さんの一助となり、さらなるオンデバイスML開発へのモチベーションにつながれば幸いです。読んでいただき、ありがとうございました!

We Are Hiring!

ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)

careers.abejainc.com