【iOS11】mlmodelのコンパイル【CoreML】

# はじめに

iOS11から登場した「**CoreML**フレームワーク」。
そしてCoreMLフレームワークで　使用するための機械学習のモデルファイル「**.mlmodel**」。

当記事では、この「**.mlmodel**」ファイルのコンパイルについて取り上げていきます。

また、こちらの記事は[AKIBA.swift 第11?回](https://classmethod.connpass.com/event/70228/)にて発表させていただいた内容になるので、下記スライド資料もご参考ください。
https://speakerdeck.com/tajitaji/mlmodel-falsekonpairu

# 「**mlmodel**」とは？

まず、mlmodelとは何かについてざっくり説明します。

- Keras, Caffe, scikit-learn といった機械学習フレームワークにて作成した学習済みモデルをCoreMLフレームワークで使用するフォーマットに変換したもの
    - 対応している機械学習フレームワークは[こちら](https://developer.apple.com/documentation/coreml/converting_trained_models_to_core_ml)
- ファイル拡張子として「.mlmodel」を使う


![c35ebf2d-ee94-4448-8fae-16420e7cc4ed.png](https://qiita-image-store.s3.amazonaws.com/0/11914/0a87805d-d51f-aec0-531e-8f85c90a757f.png)

※画像は https://developer.apple.com/documentation/coreml より

このように、CoreMLフレームワークを使用してmlmodelを使うことによって、機械学習をアプリにintegrateできます。

# mlmodelのコンパイル

それでは、本題のmlmodelのコンパイルについて述べていきます。

前述の通り、mlmodelは事前に作成した学習済みモデルをCoreMLフレームワークで使用できるフォーマットに変換したファイルですが、
実際にCoreMLフレームワークで使用する際には、**「.mlmodel」ファイルをコンパイルしたものを使用**します。

コンパイル方法を3種類紹介します。

## 1. Xcodeでコンパイル

Xcode9以降、mlmodelファイルを扱うことができるようになりました。

### ▼ コンパイル方法

Xcodeを使ってmlmodelファイルをコンパイルする方法は下記の通りになります。

1. Xcodeのプロジェクトにmlmodelファイルを追加する
2. 追加したmlmodelファイルをプロジェクトのターゲットに追加する
3. 2の時点でXcodeが自動コンパイルしてくれる

この様子はWWDCの講演でもデモがされていましたね。
https://developer.apple.com/videos/play/wwdc2017/703/ (22分を過ぎたあたり)

さて、Xcodeにmlmodelを追加すると、Xcodeが自動でSwiftのコードを生成します。
自動生成されるコードは、mlmodelの学習済みモデルをSwiftで使うためのクラスなどが定義されています。

さて、ここで、Xcodeで自動作成されたSwiftコードの一部を掲載します。

```swift:fruits_classifier.swift
/// Construct a model that automatically loads the model from the app's bundle
convenience init() {
    let bundle = Bundle(for: fruits_classifier.self)
    let assetPath = bundle.url(forResource: "fruits_classifier", withExtension:"mlmodelc")
    try! self.init(contentsOf: assetPath!)
}
```

これは、機械学習モデルを使用するクラスのinitializerの部分になります。（`fruits_classifier`という名前は、元の.mlmodelファイルに由来するところなので自分の環境で読み替えてください。）

### ▼ mlmodelc

さて、上記コードの中に**`mlmodelc`**という拡張子のファイルが呼び出されていることが分かるかと思います。

```swift
let assetPath = bundle.url(forResource: "fruits_classifier", withExtension:"mlmodelc")
```

これは、mlmodelをコンパイルするとできるもので、Swiftのコードからはこのmlmodelcを使用します。

Xcodeでのコンパイルに関わらず、**mlmodelをコンパイルすると、mlmodelcができ、Swiftのコードからはmlmodelcを呼びます。**

さて、それでは、このmlmodelcはどこにあるのでしょうか？

### ▼ mlmodelc の場所

initializerのドキュメントコメントに`Construct a model that automatically loads the model from the app's bundle`とありますが、

前述の方法によってXcodeでmlmodelが自動コンパイルされた場合、アプリのバンドルの中にmlmodelcが作られます。

下のキャプチャが実際にアプリの中をみてみたものです。
(アプリを仮にMyAppとすると、MyApp.appの中身)

<img width="420" alt="スクリーンショット 2017-10-27 8.52.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/11914/c82e1353-2c13-b8b7-6ce3-5ae254dabec7.png">

このように、mlmodelcがディレクトリとして作成されていることが分かります。

### ▼ Xcodeでmlmodelをコンパイルするデメリット

正確には、mlmodel, mlmodelcをアプリのバンドルに組み込むデメリットになりますが、
下記のようなことが考えられます。

- 機械学習のモデルを更新したいときにアプリごとアップデートを行う必要がある
- mlmodelのサイズに伴って、アプリの容量が大きくなる

上記のようなことを回避する案として、mlmodelを最初からアプリに入れておくのではなく、アプリの機能としてネットワークを通じてmlmodelをダウンロードして使用するという方法が考えられます。

そうなったときに必要なのが、Swiftコードでmlmodelをコンパイルする方法です。

## 2. Swiftコードでコンパイル

### ▼ コンパイル方法

まず、事前にアプリからmlmodelファイルをダウンロードしておきます。
そのmlmodelファイルのローカルファイルパスを指定してコンパイルを行います。

```swift
// mldeolUrlはダウンロードした.mlmodelファイルのパス
let compiledUrl = try MLModel.compileModel(at: modelUrl)
let model = try MLModel(contentsOf: compiledUrl)
```

CoreMLの`MLModel`クラスのタイプメソッドとして`compileModel(at:)`というものがあるので、それを使用します。
https://developer.apple.com/documentation/coreml/mlmodel/2921516-compilemodel

返り値としてコンパイルされた**mlmodelc**のパスが返ってくるので、それを使用してモデルクラスをインスタンス化することができます。

### ▼ 注意点

- メインスレッドでは行なわない
    - コンパイル処理が重い処理になりがちなのでメインスレッドは避ける
- コンパイルされた`mlmodelc`はtemporaryなディレクトリに保存される
    - 返り値として返ってくる`mlmodelc`のパスも当然temporaryなディレクトリ
    - 永続的に使用する場合はapplicationSupportDirectoryなどにコピーする

機械学習モデルファイルの更新などを考えると、このようにSwiftでのコンパイルを実行する機能をアプリに実装しておいたほうがいいかもしれません。

## 3. コマンドラインでコンパイル（おまけ）

使いどころが不明なのですが、こういう方法もありますというのをご紹介します。
情報お持ちの方、ご教授いただければ幸いです。

### ▼ 方法

下記のコマンドを使用してmlmodelファイルをコンパイルすることができます。

```shell-session
$ /Applications/Xcode.app/Contents/Developer/usr/bin/coremlc compile [path to mlmodel] [output path]
```

※`/Applications/Xcode.app/Contents/Developer`の部分は`xcode-select -p`にて出力されるパスです

# まとめ

- mlmodelをコンパイルすると**mlmodelc**というディレクトリが作成される
- CoreMLからはmlmodelcを呼ぶ
- Xcodeでの自動コンパイルはアプリのバンドルにmlmodelcが作成される
- Swiftコードでのコンパイルには`MLModel.compileModel(at:)`を使用する
- Xcodeのツールの中に`coremlc`というコマンドがあり、そのコマンドでコンパイルすることも可能

# おわりに

以上、mlmodelのコンパイル方法をいくつか紹介しました。
まだ情報が少ないので、今回紹介した内容は公式ドキュメントに書いてあることも多いですが、間違いなどあればご指摘いただければ幸いです。

また、mlmodelについて調べていると、Protocol buffersが関連していることがわかってきたので、そのあたりも深掘りしていきたいと思います。

# 参考

- https://developer.apple.com/documentation/coreml
- https://developer.apple.com/videos/play/wwdc2017/703/
- https://developer.apple.com/documentation/coreml/mlmodel/2921516-compilemodel