Twitterに書き足して

Twitterでつぶやいていることをまとめて書きたいときや詳細検証などで使ったり使わなかったり

Microsoft Custom VisionのモデルデータをTensorFlow Liteで扱えるようにする

最近は機械学習が世界的に大流行しているようで、何かにつけて機械学習ディープラーニングだと言っています。

で、この記事ではそういう愚痴とかが書きたいのではなく、その機械学習を使う割に比較的簡単に分類器を作ることができるMicrosoft Custom VisionのモデルデータをAndroid端末でもバリバリ動かそうという話です。

 

  

 

TL;DR

Microsoftのお手軽機械学習サービスCustom Visionで学習したモデルをAndroidの軽量機械学習API TensorFlow Liteで実行するには

  • TensorBoardで入力層と出力層のラベル、入力数を調べる
  • 調べた入力層と出力層のラベル、入力数を元にTensorFlow Lite Converterでモデルを変換する
  • 変換したモデルをデモアプリに実装してコードを少しだけ修正する

これだけです。

 

はじめに

Microsoft Custom Visionをご存じない方は、きのこの山たけのこの里を判別するモデルを作った方の記事をご覧ください。

azure-recipe.kc-cloud.jp

 

ツイートにもあるとおり、Custom Visionで学習したモデルデータをAndroid端末で利用しようと思うと、MicrosoftのリファレンスではTensorFlow Mobileでの使用方法しか解説がありません。*1

TensorFlow Mobileはお世辞にも高速とは言えず、デモアプリをNexus 7で動かしてみましたが16秒に1回ぐらいしか画面更新がされない状態*2になってしまいました。

  

そこでTensorFlow Liteの出番です。

詳細は省きますが、簡単に言えばモデルデータをtflite形式に変換して、その際に量子化をすることでモデルデータの小型化&高速化を図るというものらしいです。

詳しくは以下の記事にまとめられています。

qiita.com

実際のところ、先程のNexus 7でTensorFlow Liteのアプリを動作させてみましたが、判別にかかる処理時間はおよそ160~180ms程度でした。*3

 

 

「じゃあCustom Visionのモデルデータをtflite形式に変換すればいいだけじゃないか」

 

そうなんですが、その変換がややこしかったんです。

てっきり私はこう思い込んでいました。というのも、Custom Visionはできるだけ簡単に機械学習をしてもらうために、階層をすべて隠しています。

TensorFlow Liteのコンバーターに通すためには、変換したいモデルの入力層と出力層のラベル、そして入力数を明確にしておかなければいけません。

ここから説明していきます。

 

注意 

モデルデータの変換には成功していますが、変換後のモデルデータが元のモデルデータと同じ処理が可能であるかは確認中です。

万が一、変換したモデルデータが元のモデルデータと違う場合(精度が非常に低下するなど)は、責任を負いかねます。

 

最終目標

TensorFlow LiteのデモアプリでCustom Visionのモデルデータを使った判定を行うこととします。

 

用意するもの

変換作業にはTensorFlowが必要になるので以下の用意をしておいてください。

  • Python 3
  • TensorFlow(ビルドしたものとpip用どちらも)
  • Custom Visionで生成したモデル(Android向け)

 

作業開始

1. モデルデータをTensorBoardで読み込めるようにする

 はじめに、モデルデータの入力層と出力層のラベルと入力数がわからないといけません。

それを調べるのが、TensorFlowの機能であるTensorBoardです。

TensorBoardはDebian系ではaptで、RedHat系ではyumでtensorflowをインストールすればついて(くると思い)ます。Windowsインストーラーがありますね。

そのTensorBoardで読み込むためにはログにしておく必要があるので、モデルデータを一度Pythonで読み込んでログにしておきます。

ソースコードは次のとおりです。

view.py

import tensorflow as tf
from tensorflow.python.platform import gfile

with tf.Session() as sess:
    model_filename = 'model.pb'
    with gfile.FastGFile(model_filename, 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
        g_in = tf.import_graph_def(graph_def)

    LOGDIR='LOGDIR'
    train_writer = tf.summary.FileWriter(LOGDIR)
    train_writer.add_graph(sess.graph)

 これを、Custom VisionからダウンロードしたAndroid用TensorFlowモデルデータの同じ階層に置き、view.pyを実行します。

すると、同じ階層にLOGDIRというディレクトリが生成されます。

 

2. モデルデータをTensorBoardで表示する

ここで、view.pyを置いた同じ階層でターミナルを開き、TensorBoardを起動します。

$ tensorboard --logdir=./LOGDIR

しばらくするとWebサーバがローカル上に立ち上がるので、ターミナルの指示に従ってWebページへアクセスしてください。

すると、Main Graphが表示されると思います。

その中のimportをダブルクリックしてください。

f:id:uberded:20181209013329p:plain

すると、importがグワッと開いて中に色々表示されると思います。

それが、Custom Visionのモデルデータの中身です。

 

3.入力層と出力層のラベル、入力数を確認する

ここで重要なのが、一番上と一番下です。

一番上が出力層のラベル、一番下が入力層のラベルです。

まずこの2つを記録しておきます。

おそらく出力側はmodel_outputs、入力側はPlaceholderになっていると思います。

そして、入力数を確認します。

入力数は、一番下の層をクリックすると右上の方にいろいろ表示される情報のうち、shapeと書いてあるものが該当します。

f:id:uberded:20181209014209p:plain

ここをとりあえずまるっとコピーしておきましょう。

 

4. モデルを変換する

さて、ここで3つのものが揃ったか確認します。

・入力層ラベル : Placeholder

・出力層ラベル : model_outpus

・入力数 : {"shape":{"dim":[{"size":-1},{"size":227},{"size":227},{"size":3}]}}

ではここで次のプログラムをview.pyと同じディレクトリに置きましょう。

convert.py

import tensorflow as tf

input_arrays = ["Placeholder"]
output_arrays = ["model_outputs"]
input_shapes = {"dim": [-1, 227, 227, 3]}
converter = tf.contrib.lite.TFLiteConverter.from_frozen_graph('model.pb', input_arrays, output_arrays, input_shapes)
tflite_model = converter.convert()
open('model.tflite', 'wb').write(tflite_model)

このコードのうち、input_arraysに代入している場所に入力層ラベルを、output_arraysに代入している場所に出力層ラベルを、input_shapesに代入している場所に入力数として持ってきたものを同じフォーマットになるように指定します。

おそらく同じじゃないでしょうか?違ったら書き換えてください。

書き換え終わったら、convert.pyを実行してみます。

しばらくすると、同じディレクトリ内にmodel.tfliteというファイルができていると思います。

それが、今回作りたかったTensorFlow Lite形式のモデルデータです。

 

5.試してみる

モデルデータの結果が実際に使えるかどうかは、TensorFlow Liteのデモアプリで確認することができます。

デモアプリのソースコードGitHub上で公開されているので、それを自分でビルドする必要があります。

github.com

また、ビルドするときにモデルデータを読み込むため、次のことを行ってください。

5-1. モデルデータとラベルをアセットディレクトリに移動させる

先程生成されたmodel.tfliteと、そのラベルデータであるlabel.txtソースコード内のapp/src/main/assets/に置きます。

5-2. ソースコードを編集する

そのままではアプリに予め入っているデモ用のモデルデータが参照されてしまうため、今入れたモデルデータを読み込むように変更します。

また、入力数(今回の場合は画素数)も違うので、その部分も変更します。

変更するファイルはCamera2BasicFragment.javaImageClassifierFloatInception.javaの2つです。

for TensorFlow Lite demo app

gist6f036fcf0d1b461d52976460c538c377

1つ目のCamera2BasicFragment.javaではFloatモデルのモデルデータを読み込めるようにリストなどを追加しています。

2つ目のImageClassifierFloatInception.javaではモデルデータとラベルデータの指定、画素数の指定を行っています。

2つ目のImageClassifierFloatInception.java54行目59行目、そして64行目69行目はそれぞれモデルデータとラベルデータのファイル名、画像の画素数を指定するところなので必ず自分で設定してください。(おそらくそのままでは動きません。)

また、TensorFlow Liteの開発が活発なのもあってかこのデモアプリも非常に変化します。

ソースコードが全く異なる場合もあるのでその場合は適宜読み替えてください(重要)。*4

5-3. ビルドする

 あとはビルドするだけです。GitHubからクローンしたコードはいろいろと設定する必要があると思うのでそのあたりは頑張ってください。

Android用の設定をAndroid Studioなどに設定してこのプロジェクトを開くようにしておけばおそらくビルドできると思います。

アプリをビルドすると、黄色いデモアプリが実行されますので、そこでCustomized Modelを選択してあなたのモデルを試してみてください。

 

 終わりに

冒頭の注意にも書いたとおり、変換したモデルが正しいかどうかはまだ確認していません。

おそらく変換の途中で設定をミスするとぜんぜん違うモデルデータが完成してしまうと思います。

ここまでたどり着くのに3週間かかりました。私を褒めて。*5

*1:iOSに関してはビルド環境(mac)がないので試せていません。もし試してみた方がいらっしゃればぜひ教えていただきたいです。

*2:判別処理を別スレッドで実行すればカメラ映像が止まることはなくなりますが、判別処理が高速化されるわけではないので判定は16秒ごとに1回ぐらいのペースになると思います。

*3:この結果はCustom Visionで学習させた同じモデルデータをtflite形式に変換した上で計測したものです。

*4:私は今、思いっきり変わったコードを見て書き直したものをここに書いています。

*5:ちなみにこの方法は今後使うことは無いかもしれないということを知ってショックを受けました。