CoreMLでハマったポイント

こんにちは。もう、寒すぎて冬だけ沖縄に帰りたい@kNagadouです。
この記事は、[筋肉エンジニアアドベントカレンダー2017](https://qiita.com/advent-calendar/2017/mdd)の17日目の記事です。
公開が遅れてしまい、申し訳ありません。**お詫びに、2018元旦までにいいねされた数だけ、腕立て伏せします。**

さて、今回はCaffeなどの機械学習ライブラリで作成した学習済みのモデルを、
coremltoolsでCoreML用にコンバートして、Xcodeプロジェクトにインポートするまでの手順、
そして実際にやってみてハマったポイントについて書いていきます。

# 学習済みモデルを用意
まずは、学習済みモデルを作成しなければいけません。

真面目に学習済みモデルを作ろうと思うと、物凄く手間と時間がかかりそうだったので、モデル作成は[Labellio](https://www.labell.io/)というサービスを利用しました。
なかなか便利なサービスで、

* データ収集
* ラベリング
* 学習
* 学習済みモデルの出力
    * Caffe形式のモデル

を全部やってくれます。すごいです。
（本当は、学習済みモデルも自力で作成する予定でしたが、「これは楽だなー」と手を抜いてしまいました。）

Labellioでモデルの出力が終わると、ZIPファイルで以下一式がDLできます。

```
caffemodel.binaryproto
deploy.prototxt
labellio.json
labels.json
```

使用するファイルは、`caffemodel.binaryproto, deploy.prototxt, labels.json`の3つです。

# coremltoolsをインストール（ハマりポイント①）
ここで、1つ目のハマりポイントです。

先ほど作成したモデルを、CoreML用にコンバートするためのツール「[coremltools](https://apple.github.io/coremltools/index.html)」をインストールします。
2017年12月時点で、[coremltoolsはPython2.7環境にしか対応していません。](https://pypi.python.org/pypi/coremltools)
そのため、Python2.7環境を用意する必要があります。
しかし、pyenvなどで構築したPython環境でcoremltoolsを実行すると、次のようなエラーが出ると思います。

```
Fatal Python error: PyThreadState_Get: no current thread
Abort trap: 6
```

これは恐らく、[こちらの記事](https://qiita.com/hatchinee/items/b16ee02b3c93603aee0c)の事象と同じことが起きています。
上記の記事を参考に、以下の手順でcoremltoolsの依存関係を修正することでこのエラーは解決できました。

```bash
# Python2.7インストール
$ PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 2.7.13
# coremltoolsのオブジェクトの依存関係を修正
$ cd ~/.pyenv/versions/2.7.13/lib/python2.7/site-packages/coremltools
$ install_name_tool -change @rpath/libpython2.7.dylib /Users/<username>/.pyenv/versions/2.7.13/lib/libpython2.7.dylib libcaffeconverter.so
$ install_name_tool -change @rpath/libpython2.7.dylib /Users/<username>/.pyenv/versions/2.7.13/lib/libpython2.7.dylib libcoremlpython.so
```

# モデルをCoreML用の形式にコンバート（ハマりポイント②）
coremltoolsでモデルをコンバートする際、ラベルファイルは改行区切りのテキストファイルとしなければいけないようなので、`labels.json`を以下のように修正して`labels.txt`にします。

例)

```json
{"heavy": 1, "olympia": 4, "medium": 3, "slim": 6, "physique": 5, "wrestler": 7, "macho": 2, "bodybuild": 0}
```
:point_down: 

```
bodybuild
heavy
macho
medium
olympia
physique
slim
wrestler
```

`labels.txt`への修正ができたら、CoreML用の形式にコンバートしましょう。
Labellioで作成されたモデルはCaffeの形式で出力されているので、`coremltools.converters.caffe.convert`を使ってコンバートできます。

```py
import coremltools

if __name__ == '__main__':
    coremlmodel = coremltools.converters.caffe.convert(
        ("caffemodel.binaryproto", "deploy.prototxt", "mean.binaryproto"),
        class_labels='labels.text',
        predicted_feature_name='classLabel',
        image_input_names='data',
        image_scale=1.0/255.0
    )

    coremlmodel.save("Muslce.mlmodel")
```

実は、ここのコンバートもハマりポイントでした。
コンバートしたファイルを、最後の`coremlmodel.save()`でファイル名を指定して保存していますが、**ここでファイル名を`model.mlmodel`としてはいけません。**
保存した後にファイル名をリネームすれば問題無いのですが、`model.mlmodel`のままで次の手順に行くと上手くいきません。
詳しい理由は次の章で。

# .mlmodelファイルをXcodeプロジェクトにインポートする
それでは、先ほど保存した.mlmodelファイルをXcodeにインポートしましょう。
.mlmodelファイルも他ファイルと同じように、「Add Files to」でプロジェクトにインポートできます。

![スクリーンショット 2017-12-19 5.08.51.png](https://qiita-image-store.s3.amazonaws.com/0/95133/04d07191-2923-f0e7-30bf-b2d153ae0a5e.png)

.mlmodelをインポートすると、XcodeがModel Classを自動生成してくれます。
![スクリーンショット 2017-12-19 5.12.25.png](https://qiita-image-store.s3.amazonaws.com/0/95133/d8affd5c-3f60-9efb-42e1-a2c7b52d2abc.png)
![スクリーンショット 2017-12-19 5.12.48.png](https://qiita-image-store.s3.amazonaws.com/0/95133/da2a3712-f39b-446d-b18f-c5345c97bc23.png)

自動生成されたコードを見てみると、70行目あたりにモデルのファイル名がそのままクラス名になっている部分があります。（この例では、`Muscle.mlmodel`をインポートしたので`Muscle`というクラスが生成されています。）
![スクリーンショット 2017-12-19 5.17.17.png](https://qiita-image-store.s3.amazonaws.com/0/95133/38653b5e-749e-f205-9606-8752f4c6b3f2.png)

**もしも、`model.mlmodel`というファイル名でXcodeにインポートしてしまうと、`model`というクラスが定義されてしまい、72行目の`var model: MLModel`と定義が被ってしまうことになります。**
もちろん、ビルドでコケます。

`model.mlmodel`なんて安直なファイル名にしてはいけません。（戒め）

---

以上です。
また時間がある時に、インポートしたモデルを使って画像認識アプリを作るところまで書きたいと思います。