Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

【研究開発部の技術】第4回 coremltoolsを用いたCore MLモデルへの変換

研究開発部Architectグループの堤です。最近は研究開発部の技術や成果物について紹介する記事をいくつか書いてきたのですが、 今回は、下記記事で紹介した"Smart Captured"(略してスマキャプ)の開発の中で行った「Core ML化」について深堀りしたいと思います。

buildersbox.corp-sansan.com

今回のテーマ

上に載せた記事内で、スマキャプでは以下の機械学習モデルの推論処理をオンデバイスで行っている、と書きました。

  • 名刺検出(名刺の矩形を検出)
  • 名刺切り出し(セグメンテーション)

それぞれのモデルはTensorFlowで学習しています。

さらにiOSでは、モデルをCore MLに変換することで、大幅なパフォーマンス向上に成功しています 1

  • 矩形検出は300%高速化(18 fps → 55 fps)
  • セグメンテーションも推論時間は0.01〜0.02[s]

さらに、機械学習モデルの推論処理のためのCPU負荷が下がることで、UIの描画やユーザーインタラクションのレスポンスも改善されます。下図は、変換前・後のモデルを切り替えてCPU使用率を可視化したものになります。

山が旧モデル(TensorFlowから出力したモデルそのまま)使用時、谷が新モデル(Core ML)使用時。CPU負荷についても顕著な違いがあることがわかる。

本記事ではこのCore ML化の具体的な手順や勘所について、詳しく解説します。

なぜCore ML化すると速くなるのか

そもそもなぜCore ML化すると速くなるのか、について解説しておきます。

Core MLは、機械学習モデルをiOS, macOS, watchOS, …に組み込むためのApple製のフレームワーク, モデルフォーマットなのですが、

iOSで機械学習モデルの推論処理を行うための選択肢はCore ML以外にもいくつかあります。

  • TensorFlow for iOS (TensorFlow Mobile) 2
  • TensorFlow Lite
  • PyTorch Mobile (LibTorch)

ただし、TensorFlow for iOS (TensorFlow Mobile)PyTorch Mobile (LibTorch) では、すべてがCPUで処理されます。また TensorFlow LiteGPU (Metal) をサポートしていますが、Neural EngineはそもそもAPIが公開されていないため、Apple公式で提供されているモデルフォーマットであるCore MLを利用する以外にNeural Engineで処理させる手段がありません。

まとめると、以下のようになります:

  • TensorFlow for iOS (TensorFlow Mobile) → CPU
  • TensorFlow Lite → CPU, GPU
  • PyTorch Mobile (LibTorch) → CPU
  • Core ML → CPU, GPU, Neural Engine

Core MLがiOSデバイスの性能を最も活かせる(ので、Core ML化すると速くなる)というわけです。

Neural Engineについて

Neural Engineは、ニューラルネットワークの演算を高速処理するプロセッサです。ANE (Apple Neural Engine) とも呼ばれます。

ANEはNPU (Neural Processing Unit) の一種で、ANE以外でNPUで有名なものとしてはGoogleのTPUがあります。 3

ANEは2017発売のiPhone 8, iPhone X以降のiPhone / iPadに搭載されていますが、Core MLによって利用されるANEは2018年発売の(iPhone XSやXRに搭載されている)A12 Bionic以降となります。

ANE自体の性能も毎年上がっています。 4

  • The A14 has a 16-core Neural Engine that is twice as fast as the previous generation, and can perform 11 trillion operations per second.

  • The A15 has a 16-core Neural Engine, with the same amount of cores it can perform 15.8 trillion operations per second (43% faster than the previous generation).

すなわち、ここ数年に発売されたiOSデバイスの多くでは、機械学習モデルのCore ML化による高速化が期待でき、最新デバイスほどさらにその恩恵は大きくなる、ということになります。

Core MLモデルへの変換

さて、本記事の本題であるCore MLモデルへの変換について解説していきます。

変換には、coremltools というApple製OSSツールを利用します。

https://github.com/apple/coremltools

coremltoolsのバージョンについて

2022年11月22日現在の最新バージョンは6.1となります。

github.com

スマキャプでのCore MLモデル変換に当時利用したバージョンは3系でした。

coremltools 4.0からUnified Converterが登場して、TensorFlow, PyTorchのモデルが同じAPIで変換できるようになったのですが、3系ではTensorFlowモデルからCore MLモデルへの変換には tf-coreml というツールを利用する必要がありました。

https://github.com/tf-coreml/tf-coreml

本記事では当時のバージョン(tf-coreml)をベースに説明しますが、ツールの使い方、エラーと対峙する際の考え方は最新版でもほぼ同じです。

変換してみる

tfcoremlを利用してTensorFlowモデルを変換してみましょう。引数には入力モデルのパス、出力パス、入出力ノード名やshapeを与えます。

import tfcoreml

mlmodel = tfcoreml.convert(
        tf_model_path = tf_model_path,    # .pbモデルのパス
        mlmodel_path = mlmodel_path,      # .mlmodel出力パス
        input_name_shape_dict = in_dict,  # 入力ノード名とshape
        output_feature_names = out_names, # 出力ノード名
        ...)

これを名刺検出モデルに対して実行してみると・・・

ValueError: Graph has cycles.

このようなエラーが出ました。

調べてみると、次のように説明されています。

CoreML does not support graphs with cycles at the moment. However, it does support recurrent nodes (RNN, LSTM, GRU etc). When these cells are implemented in TF, they get broken down into cyclic graphs in the frozen .pb file. tfcoreml cannot handle such graphs at the moment. This is a known issue currently. Handling such graphs would require going from the broken down graph structure to the higher level structure and mapping the nodes to an operation that CoreML supports (RNN, LSTM etc).

TFで出力したモデルは、Core MLがサポートしていない循環のあるグラフを生成することがあるようです。

実は、同様のエラーは、(メッセージこそ違いますが)coremltoolsの新しいバージョンでも、変換元がPyTorchモデルである場合でも発生します。 5

こういった場合に、どのように解決していくかを解説します。

coremltoolsの変換エラーを解決する

どこが代用可能か?

今回は "Graph has cycles." エラーということで、循環を構成している部分を切除し、coremltoolsで変換可能な計算グラフにします。

まずはモデルのグラフを見て、切り出す部分のあたりをつけます。

名刺検出モデルをTensorboardで可視化

ポイントは、グラフの当該部分を別の手段で代用できるのであれば、そこは切除可能、ということです。

  • Preprocessor部分は変換コマンドのオプションで付与
  • MultiGridAnchorGeneratorは静的な値で代替可能
  • Postprocessor は同様の処理をSwiftで実装可能

赤で囲んだ部分が切り出し可能(他で代用可能)な部分

以上の部分を切り出せば、循環のないグラフにできそうです。

サブグラフを切り出す

coremltoolsを使ってサブグラフを切り出します。

from tensorflow.python.tools import strip_unused_lib

in_names = ['Preprocessor/sub']     # 入力ノード名の配列
out_names = ['Squeeze', 'concat_1'] # 出力ノード名の配列

gdef = strip_unused_lib.strip_unused(
        input_graph_def = original_gdef,
        input_node_names = in_names,
        output_node_names = out_names,
        placeholder_type_enum = dtypes.float32.as_datatype_enum)

切り出し後のグラフはこのようになり、

切り出し後のグラフをTensorboardで可視化

coremltools / tfcoremlのconvert関数も成功するようになりました。

切除したサブグラフを補填していく

元のモデルから切除した部分を別の手段で補填していきます。

まず、Preprocessor 部分の補填ですが、次のように coremltools (tf-coreml) の convert 関数のオプションで代替できます。

mlmodel = tfcoreml.convert(
        tf_model_path = tf_model_path,
        mlmodel_path = ml_model_path,
        input_name_shape_dict = in_shapes,
        output_feature_names = out_names,
        image_input_names = image_in_names,
        red_bias   = -1.0,
        green_bias = -1.0,
        blue_bias  = -1.0,
        image_scale = 2./255)

次に MultiGridAnchorGenerator 部分を補填します。

名刺検出のモデルはSSD(Single Shot MultiBox Detector)という有名なアーキテクチャを利用しているのですが、このSSDの MultiGridAnchorGenerator 部分は、入力画像が何であれ、出力されるアンカーボックスは同じなので、静的な値で代替可能です。

そこで、オリジナルモデルを使用して事前にアンカーボックスの座標群を出力し、そのデータをSwiftで読み込むように実装することでこの部分を代替しました。

最後に、Postprocessor 部分の補填ですが、ここは同様の処理をSwiftで実装しました。6

まとめ: Core MLモデル変換の基本的な考え方

今回はスマキャプの名刺検出モデル(種類としては物体検出、アーキテクチャはSSD)を具体例として挙げましたが、どういうモデルであれ、変換エラーと対峙する際の「考え方」は共通しています。

エラーを起こしている部分を「どうにかする」という考え方です。

どうにかする手段としては以下があります。

  • coremltoolsで切除する
    • 切除した部分の補填
      • coremltoolsの変換オプションで指定
      • iOS側で同等の機能を実装
  • coremltoolsで置き換える
  • PyTorch / TensorFlowコード内で置き換える / 削除する
    • 同等のOpで置き換える
    • iOS用途では必要ない処理であれば削除する

筆者の経験上、最新のcoremltoolsバージョンを利用しても、残念ながらすんなり変換できるケースはほとんどなく、多くの場合はエラーに遭遇します。 大事なのは、エラーが出ても諦めず、上記のようにそのエラーを「どうにかする」手段を持っておくことかと思います。


  1. 3年前の端末、iPhone 11 Proで計測。
  2. スマキャプのCore ML化以前のバージョンはこの方式を採用していました。TensorFlowから出力した .pb モデルをそのまま組み込む方式です。
  3. 参考: https://github.com/hollance/neural-engine/blob/master/README.md
  4. 参考ページ: https://github.com/hollance/neural-engine/blob/master/docs/supported-devices.md
  5. "Graph is not a DAG!" など。
  6. アーキテクチャはSSDなので、Non-Maximum Suppression (NMS) といったアルゴリズムをSwiftで実装しました。

© Sansan, Inc.