2019年1月6日日曜日

ML Kit Custom Model その5 : Inception_V1_quant を使う

前回の 「ML Kit Custom Model その4 : Mobilenet_V2_1.0_224_quant を使う」 と同じように今回は Inception_V1_quant モデルを使ってみます。



Inception_V1_quant

まずは https://www.tensorflow.org/lite/models からモデルをダウンロードしましょう。



中には tflite ファイルの他に checkpoint(ckpt.*)も入っています。
$ ls inception_v1_224_quant_20181026 graph.pbtxt inception_v1_224_quant.ckpt.data-00000-of-00001 inception_v1_224_quant.ckpt.index inception_v1_224_quant.ckpt.meta inception_v1_224_quant.tflite inception_v1_224_quant_frozen.pb inception_v1_224_quant_info.txt
inception_v1_224_quant.tflite も入力の shape が [ 1, 224, 224, 3]、出力の shape が [ 1, 1001] です。
使い方は その2, その3 と同じなので割愛します。


左: Mobilenet V1, 右: Mobilenet V2

Inception V1


Inception V1 の確率は Mobilenet V1 と V2 の間くらい

左: Mobilenet V1, 右: V2

Inception V1


Inception V1 と Mobilenet V2ではミーアキャットと認識されているが、Inception V1 の確率は Mobilenet V2 より低い

左: Mobilenet V1, 右: V2

Inception V1


Inception V1 も Mobilenet V2と同じく tusker と認識されているが、Inception V1 の確率は Mobilenet V2 より低い



ML Kit Custom Model その4 : Mobilenet_V2_1.0_224_quant を使う

「ML Kit Custom Model その1 : TensorFlow Lite Hosted Models を利用する」
「ML Kit Custom Model その2 : Mobilenet_V1_1.0_224_quant を LocalModel として使う」
「ML Kit Custom Model その3 : Mobilenet_V1_1.0_224_quant を CloudModel として使う」
で Mobilenet_V1_1.0_224_quant.tflite を使いました。

ここでは Mobilenet V2 のモデルを使ってみます。



Mobilenet_V2_1.0_224_quant

まずは https://www.tensorflow.org/lite/models からモデルをダウンロードしましょう。



中には tflite ファイルの他に checkpoint(ckpt.*)も入っています。
$ ls mobilenet_v2_1.0_224_quant mobilenet_v2_1.0_224_quant.ckpt.data-00000-of-00001 mobilenet_v2_1.0_224_quant.ckpt.index mobilenet_v2_1.0_224_quant.ckpt.meta mobilenet_v2_1.0_224_quant.tflite mobilenet_v2_1.0_224_quant_eval.pbtxt mobilenet_v2_1.0_224_quant_frozen.pb mobilenet_v2_1.0_224_quant_info.txt
使い方は その2, その3 と同じなので割愛します。


左: V1, 右: V2


サイなのだがトリケラトプスと出るのは変わらず、確率は上がっている

左: V1, 右: V2


V2ではミーアキャットになった(猫はないのか?)

左: V1, 右: V2


V2 だと tusker と認識され確率も上がっている



2019年1月4日金曜日

ML Kit Custom Model その3 : Mobilenet_V1_1.0_224_quant を CloudModel として使う

前回の「ML Kit Custom Model その2 : Mobilenet_V1_1.0_224_quant を LocalModel として使う」では、assets に tflite ファイルをバンドルして LocalModel として利用しました。

今回は firebase 上に tflite ファイルをホストして CloudModel として利用します。

まず firebase console から tflite ファイルをアップロードします。



モデル名は mobilenet_v1_quant にしました。この名前はアプリ側からモデルを指定するときに使います。


firebase にホストしてある model を使うには FirebaseCloudModelSource を用意します。 FirebaseCloudModelSource.Builder() の引数には firebase console で設定したモデル名(ここでは "mobilenet_v1_quant")を指定します。

モデルが更新されたときにダウンロードするようにするには enableModelUpdates() で true を指定します。

setInitialDownloadConditions() で初回ダウンロード時の、setUpdatesDownloadConditions() で更新モデルダウンロード時の設定をすることができます。設定の FirebaseModelDownloadConditions はFirebaseModelDownloadConditions.Builder を使って生成します。設定できる項目は requireCharging(), requireDeviceIdle(), requireWifi() です。

作成した FirebaseCloudModelSource は FirebaseModelManager.registerCloudModelSource() で登録しておきます。

FirebaseModelOptions.Builder の setCloudModelName() には FirebaseCloudModelSource.Builder() の引数に指定した名前(ここでは "mobilenet_v1_quant")を渡します。

最後に FirebaseModelOptions を渡して FirebaseModelInterpreter のインスタンスを取得します。

private val interpreter: FirebaseModelInterpreter by lazy { val cloudModelSource = FirebaseCloudModelSource.Builder("mobilenet_v1_quant") .enableModelUpdates(true) .setUpdatesDownloadConditions( FirebaseModelDownloadConditions.Builder() .requireWifi() .build() ) .build() FirebaseModelManager.getInstance().registerCloudModelSource(cloudModelSource) val modelOptions = FirebaseModelOptions.Builder() .setCloudModelName("mobilenet_v1_quant") .build() FirebaseModelInterpreter.getInstance(modelOptions)!! }

interpreter を使って推論を行う部分は前回の「ML Kit Custom Model その2 : Mobilenet_V1_1.0_224_quant を LocalModel として使う」と同じです。



2019年1月1日火曜日

ML Kit Custom Model その2 : Mobilenet_V1_1.0_224_quant を LocalModel として使う

「ML Kit Custom Model その1 : TensorFlow Lite Hosted Models を利用する」で mobilenet_v1_1.0_224_quant.tflite をダウンロードし、ラベル情報を labels.txt として用意しました。

いよいよ ML Kit Custom Model に組み込みます。


ベースとして MLKitSample ( https://github.com/yanzm/MLKitSample/tree/start ) の start ブランチを利用します。

Firebase でプロジェクトを作ってアプリを登録し、google-services.json を app モジュール直下に配置します。詳しくは上記 MLKitSample の README.md 課題1,2 を参考にしてください。

アプリの dependencies に以下を追加します。 dependencies { ... implementation "com.google.firebase:firebase-ml-model-interpreter:16.2.4" }

今回は mobilenet_v1_1.0_224_quant.tflite をあらかじめアプリにバンドルして LocalModel として利用します。
そのため app/src/main/assets に mobilenet_v1_1.0_224_quant.tflite を配置します。また labels.txt も assets に配置します。





推論を実行する際には、入力データの他に入出力の形式も指定しないといけません。そのために用意するのが FirebaseModelInputOutputOptions です。

前回調べた入出力の shape は以下の通りでした。

入力の shape は [ 1, 224, 224, 3] の int32 です。1番目の 1 はバッチサイズ、2番目の 224 は画像の width、3番目の 224 は画像の height、4番目の 3 は色情報(R,G,B)です。入力の dtype は numpy.uint8 です。

出力の shape は [ 1, 1001] の int32 です。1番目の 1 はバッチサイズ、2番目の 1001 は class の個数です。出力の dtype は numpy.uint8 です。

入力の shape は [ 1, 224, 224, 3] の int32 なので inputDims として intArrayOf(1, 224, 224, 3) を用意します。
出力の shape は [ 1, 1001] の int32 なので outputDims として intArrayOf(1, 1001) を用意します。

入出力いずれも dtype は numpy.uint8 なので、dataType には FirebaseModelDataType.BYTE を指定します。 private val dataOptions: FirebaseModelInputOutputOptions by lazy { val inputDims = intArrayOf(1, 224, 224, 3) val outputDims = intArrayOf(1, 1001) FirebaseModelInputOutputOptions.Builder() .setInputFormat(0, FirebaseModelDataType.BYTE, inputDims) .setOutputFormat(0, FirebaseModelDataType.BYTE, outputDims) .build() }

assets にある model を LocalModel として使うには FirebaseLocalModelSource を用意します。 FirebaseLocalModelSource.Builder() の引数に LocalModelSource を識別するための名前(ここでは "asset")を指定し、setAssetFilePath() で assets/ に配置したモデルファイルを指定します。

作成した FirebaseLocalModelSource は FirebaseModelManager.registerLocalModelSource() で登録しておきます。

FirebaseModelOptions.Builder の setLocalModelName() には FirebaseLocalModelSource.Builder() の引数に指定した名前(ここでは "asset")を渡します。

最後に FirebaseModelOptions を渡して FirebaseModelInterpreter のインスタンスを取得します。 private val interpreter: FirebaseModelInterpreter by lazy { val localModelSource = FirebaseLocalModelSource.Builder("asset") .setAssetFilePath("mobilenet_v1_1.0_224_quant.tflite") .build() FirebaseModelManager.getInstance().registerLocalModelSource(localModelSource) val modelOptions = FirebaseModelOptions.Builder() .setLocalModelName("asset") .build() FirebaseModelInterpreter.getInstance(modelOptions)!! }

次に検出部分の実装をしていきます。

FirebaseModelInterpreter の入力データは ByteBuffer か配列か多次元配列でなければなりません。 ここでは ByteBuffer にしてみます。

モデルに渡す画像は 224 x 224 なので Bitmap.createScaledBitmap() で画像をスケールし、ピクセル情報のうち R,G,B のデータを ByteBuffer に入れます。 /** * Bitmap を ByteBuffer に変換 */ private fun convertToByteBuffer(bitmap: Bitmap): ByteBuffer { val intValues = IntArray(224 * 224).apply { // bitmap を 224 x 224 に変換 val scaled = Bitmap.createScaledBitmap(bitmap, 224, 224, true) // ピクセル情報を IntArray に取り出し scaled.getPixels(this, 0, 224, 0, 0, 224, 224) } // ピクセル情報のうち R,G,B を ByteBuffer に入れる return ByteBuffer.allocateDirect(1 * 224 * 224 * 3).apply { order(ByteOrder.nativeOrder()) rewind() for (value in intValues) { put((value shr 16 and 0xFF).toByte()) put((value shr 8 and 0xFF).toByte()) put((value and 0xFF).toByte()) } } } private fun detect(bitmap: Bitmap) { overlay.clear() // Bitmap を ByteBuffer に変換 val imageByteBuffer = convertToByteBuffer(bitmap) } FirebaseModelInputs.Builder で入力データを作成し、FirebaseModelInterpreter の run() で推論を実行します。 private fun detect(bitmap: Bitmap) { overlay.clear() // Bitmap を ByteBuffer に変換 val imageByteBuffer = convertToByteBuffer(bitmap) val inputs = FirebaseModelInputs.Builder() .add(imageByteBuffer) .build() interpreter .run(inputs, dataOptions) .addOnSuccessListener { outputs -> val output = outputs!!.getOutput<Array<ByteArray>>(0) // output.size : 1 val labelProbabilities: ByteArray = output[0] // labelProbabilities.size : 1001 // labelProbabilities の各 Byte を 255f で割ると確率値になる // 確率の高い上位3つを取得 val topLabels = getTopLabels(labelProbabilities, 3) overlay.add(TextsData(topLabels)) } .addOnFailureListener { e -> e.printStackTrace() detectButton.isEnabled = true progressBar.visibility = View.GONE Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show() } } assets の labels.txt を読んでラベルのリストを用意しておき、labelProbabilities の各確率に対応するラベルを割り出します。 getTopLabels() では PriorityQueue を使って確率の高いラベルだけ残すようにします。 private val labelList: List<String> by lazy { assets.open("labels.txt").reader().use { it.readLines() } } @Synchronized private fun getTopLabels(labelProbabilities: ByteArray, maxSize: Int): List<String> { val sortedLabels = PriorityQueue<Map.Entry<String, Float>>( maxSize, Comparator<Map.Entry<String, Float>> { o1, o2 -> o1.value.compareTo(o2.value) } ) labelList.forEachIndexed { index, label -> sortedLabels.add( AbstractMap.SimpleEntry<String, Float>( label, (labelProbabilities[index].toInt() and 0xff) / 255f ) ) if (sortedLabels.size > maxSize) { sortedLabels.poll() } } return sortedLabels.map { "${it.key} : ${it.value}" } .reversed() }



サイなのだが、トリケラトプスって出てる...



猫がでない...



像は認識できた



次は「ML Kit Custom Model その3 : Mobilenet_V1_1.0_224_quant を CloudModel として使う」 です。



ML Kit Custom Model その1 : TensorFlow Lite Hosted Models を利用する

ML Kit Custom Model を使ってみるには TensorFlow Lite 形式のモデルファイルが必要です。
TensorFlow Lite のサイトにはホストされているモデルの一覧があり、ここからダウンロードすることができます。

https://www.tensorflow.org/lite/models




現在ここには以下のモデルがあります。
  • AutoML mobile image classification models (Float Models)
  • Image classification (Float Models)
  • Image classification (Quantized Models)
  • Other models
    • Smart reply
Image classification は画像識別を行うモデルです。入力として画像のピクセルデータを渡すと、画像に写っているものを識別し、各ラベルの確率が出力されます。

モデルがたくさんあってどれを選べばいいのかわからない...となりますよね。size, accuracy, performance などを見てユースケースにあったものを選びましょう。

モバイルアプリに組み込むとなると size はできれば10Mb以下に抑えたいですし、カメラのプレビューに繋いでリアルタイムに推論するなら performance は30ms以下にしたいところです。一方静止画像で推論するなら perfomance が多少遅くなっても accuracy が高いものを選ぶことができます。



MobileNet

MobileNet は on-device や組み込みアプリケーションなどの制限されたリソースを考慮しながら正確さを最大限に高められるように設計されたモデルです。サイズが小さく低遅延で低消費電力という特徴があります。

MobileNet の Pre-trained Models は ImageNet Large Scale Visual Recognition Challenge 2012 (ILSVRC2012) の image classification dataset で学習されています。これの学習用データは 1000 classes の計 1.2 million の画像(class ごとに約700〜1300枚の画像)です。



Mobilenet_V1_1.0_224_quant

静止画像を Mobilenet_V1_1.0_224_quant で推論してみましょう。

Mobilenet_V1_1.0_224_quant は MobileNet V1の Post-training quantization が施されたモデルです。

まずはモデルをダウンロードしましょう。



中には tflite ファイルの他に checkpoint(ckpt.*)も入っています。
$ ls mobilenet_v1_1.0_224_quant mobilenet_v1_1.0_224_quant.ckpt.data-00000-of-00001 mobilenet_v1_1.0_224_quant.ckpt.index mobilenet_v1_1.0_224_quant.ckpt.meta mobilenet_v1_1.0_224_quant.tflite mobilenet_v1_1.0_224_quant_eval.pbtxt mobilenet_v1_1.0_224_quant_frozen.pb mobilenet_v1_1.0_224_quant_info.txt モデルを使うにあたって、どんな入力を渡せばいいのか、どんな出力が得られるのかを知らなければいけません。そこでまず入力と出力の形式を調べます。

https://www.tensorflow.org/install/ に従って tensorflow をインストールし、以下の python コードを実行して入力と出力の情報を print します。

dump_input_and_output.py import tensorflow as tf interpreter = tf.contrib.lite.Interpreter(model_path="mobilenet_v1_1.0_224_quant.tflite") interpreter.allocate_tensors() print("input") print(interpreter.get_input_details()[0]) print("output") print(interpreter.get_output_details()[0]) $ python dump_input_and_output.py input {'name': 'input', 'index': 87, 'shape': array([ 1, 224, 224, 3], dtype=int32), 'dtype': <class 'numpy.uint8'>, 'quantization': (0.007843137718737125, 128)} output {'name': 'MobilenetV1/Predictions/Reshape_1', 'index': 86, 'shape': array([ 1, 1001], dtype=int32), 'dtype': <class 'numpy.uint8'>, 'quantization': (0.00390625, 0)} 入力の shape は [ 1, 224, 224, 3] の int32 です。1番目の 1 はバッチサイズ、2番目の 224 は画像の width、3番目の 224 は画像の height、4番目の 3 は色情報(R,G,B)です。入力の dtype は numpy.uint8 です。

出力の shape は [ 1, 1001] の int32 です。1番目の 1 はバッチサイズ、2番目の 1001 は class の個数です。1000個より1つ多いのは index 0 が 'background' class として予約されているからです。出力の dtype は numpy.uint8 です。


1001個 の class それぞれの確率が出力されるわけですが、index 0 が 'background' として index 1 〜 1000 の class は何でしょうか。

それを調べるために tensorflow の datasets の中にある imagenet.py の create_readable_names_for_imagenet_labels() を利用します。 このメソッドは index と class 名(人が読めるラベル)のマップを返します。

LSVRC の synsets 一覧(index 1 〜 1000 の class の id 一覧)
https://github.com/tensorflow/models/blob/master/research/inception/inception/data/imagenet_lsvrc_2015_synsets.txt n01440764 n01443537 n01484850 ... と、id とラベル名の一覧
https://github.com/tensorflow/models/blob/master/research/inception/inception/data/imagenet_metadata.txt n00004475 organism, being n00005787 benthos n00006024 heterotroph ... からマップを作っています。 n01440764 → tench, Tinca tinca n01443537 → goldfish, Carassius auratus n01484850 → great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias ... { 0: 'background', 1: 'tench, Tinca tinca', 2: 'goldfish, Carassius auratus', 3: 'great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias', ... 1000: 'toilet tissue, toilet paper, bathroom tissue' } これを参考にして、ラベルだけ出力する python コードを実行して labels.txt として保存しておきます。

dump_labels.py from six.moves import urllib synset_url = 'https://raw.githubusercontent.com/tensorflow/models/master/research/inception/inception/data/imagenet_lsvrc_2015_synsets.txt' synset_to_label_url = 'https://raw.githubusercontent.com/tensorflow/models/master/research/inception/inception/data/imagenet_metadata.txt' filename, _ = urllib.request.urlretrieve(synset_url) synset_list = [s.strip() for s in open(filename).readlines()] assert len(synset_list) == 1000 filename, _ = urllib.request.urlretrieve(synset_to_label_url) synset_to_label_list = open(filename).readlines() assert len(synset_to_label_list) == 21842 synset_to_label_map = {} for s in synset_to_label_list: parts = s.strip().split('\t') assert len(parts) == 2 synset_to_label_map[parts[0]] = parts[1] print("background") for synset in synset_list: print(synset_to_label_map[synset]) $ python dump_labels.py > labels.txt $ cat labels.txt background tench, Tinca tinca goldfish, Carassius auratus great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias ... toilet tissue, toilet paper, bathroom tissue


これで事前準備が完了です。その2で ML Kit に組み込んでいきます。

「ML Kit Custom Model その2 : Mobilenet_V1_1.0_224_quant を LocalModel として使う」