雑多なノート

プログラミング初心者がメモとかを書きます。基礎的なこともメモとして。偏食系のアニオタ。

TensorFlowの画像分類を利用したAndroidアプリを作ってみた

TensorFlowのAndroid実装を利用したアプリを作ってみたので流れをまとめる.
ちなみちディープラーニングのコードには一切触れてない入門書読んだだけのディープラーニングにわかなのでご了承を.
何か頓珍漢なこと言ってたら教えてください.

環境

モデルの準備

モデルが無ければ何も始まらないのでまずはモデルを用意する.
用意するといっても自分で1から作るのはしんどいといので既存のモデルを転移学習することにした.
自分でモデル設計するスキルも1から学習させられるようなスペックのパソコンも持ってないし.

既存のモデルと学習済みの重みを元に新しいモデルを作ることをFine-tuning(転移学習)というらしく,これにより少ない学習データと学習時間でそれなりの結果が得られる.
Fine-tuningは以下のチュートリアルに従うことによって簡単にできる.

How to Retrain Inception's Final Layer for New Categories  |  TensorFlow

まずはTensorflow本体をGitから持ってくる.

$ git clone https://github.com/tensorflow/tensorflow

次にビルドを行う.
初回のビルドはそれなりに時間がかかる.

$ cd tensorflow/
$ ./configure
$ bazel build tensorflow/examples/image_retraining:retrain

途中でライブラリが足りないだの何だの言われるので適宜持ってくる.
ノーパソで1時間ちょいかかった.

これでFine-tuningを行う準備は完了.
正直拍子抜けするほど簡単であるがこれだけでFine-tuningを行うことができるらしい.
任意のモデル使ったり細かいカスタマイズとかは出来ないけど.

データセットはラベル名をディレクトリ名として適当な場所に置く.
自分は大体以下の感じになった.

.
├── ajiazou
│   ├── 000000274001L.jpg
│   ├── 001(2).jpg
│   ├── 004.jpg
│   ├── 008.jpg
│   ├── ...
│ 
├── amerikabaku
│   ├── 000000274001L.jpg
│   ├── 001(2).jpg
│   ├── 004.jpg
│   ├── 008.jpg
│   ├── ...
│  

今回は画像検索からもってきた動物画像を学習データとして使った.
動物の種類は46種.
それぞれ100-200枚程度集められた.

そしてスクリプトを実行する.
image_dirに学習データを置いてあるディレクトリを指定する.

$ bazel-bin/tensorflow/examples/image_retraining/retrain --image_dir=~/dataset/ueno/

ここでもライブラリがないだの何だの言われるので適当にpipでインストールする.
後は待っているだけで自動的にモデルをダウンロードしてきてFine-tuningが行われる.
画像でないファイルが含まれると容赦なく落ちるので事前に確認しておく.
デフォルトでダウンロードされるモデルはGoogleのInception v3となってる.
オプションで指定することによりMobileNetという軽量モデルを使うことも可能である.
ただ試してみた感じ汎化性が微妙っぽい.
あまりに結果が悪すぎるので使い方が間違ってる可能性もあるけど.
モデルの指定の他にトレーニング回数(デフォルトで4000)や学習時の画像加工(反転,拡大,明るさ)の指定もできる.
ちなみにモデルのライセンスはApache2.0なのでいろいろと安心.
ちなみにTensoBordも使えるらしいので手軽にディープラーニングやってる感を味わえる.

途中経過はコンソールにも表示されるので見てて楽しい.
すぐに飽きたけど.

f:id:vayacico:20171021013959p:plain

学習されたモデルは特に指定してなければ/tmp/output_graph.pbとしてラベルが書かれたoutput_label.txtとペアで保存される.

PC上で使う分にはこれで終了なのだがAndroidで使うためにはもう一手間必要となる.
AndroidのTensorflowで扱うためには無駄のないモデルである必要があるらしく,つまりは学習のためのみに使われるドロップアウト層や結果に関与しない層を取り除く必要があるとのこと.
聞くと面倒そうだがこれもコマンド一発で行うことができる.

$ bazel build tensorflow/python/tools:optimize_for_inference 
$ bazel-bin/tensorflow/python/tools/optimize_for_inference --input="/tmp/output_graph.pb" --input_names="Mul" --output_names="final_result"

input_namesとoutput_namesはそれぞれ入力層と出力層の名前らしい.
正直よくわかってないけどinception v3とMobileNetは共通してこれでできるので問題ない.

これでAndroidで使うためのモデルの準備が完了した.
出力されたモデルファイルとラベルファイルをAndroidアプリのassetに持っていく.

Androidアプリの作成

続いてAndroidサイドの実装を行う.
tensorflow/tensorflow/examples/android at master · tensorflow/tensorflow · GitHub
ここに書いてある通り,gradleに書くだけで自動的にダウンロードしてきてくれて使える状態になる.

allprojects {
    repositories {
        jcenter()
    }
}

dependencies {
    compile 'org.tensorflow:tensorflow-android:+'
}

ただnightly-android [Jenkins]から最新のビルドを持ってきて使った方が早かったので自分はこっちを使った.
数時間おきに安定ビルドが行われているちょっとよくわからない世界である.

これでAndroidでTensorflowを使えるようになったわけだけど,自分で画像の形式変換や結果の解析のコードを書くのは面倒なのでTensorflowのデモアプリに含まれるクラスを持ってきて便利に使わせてもらう.
使わせてもらうクラスは以下の2つ.


簡単に言うとTensorflowを簡単に使うためのクラスと画像分類を簡単に使うためのクラス.
ここまでくると後は簡単.

まずは各変数の定義

int INPUT_SIZE = 299;
int IMAGE_MEAN = 128;
float IMAGE_STD = 128;
String INPUT_NAME = "Mul";
String OUTPUT_NAME = "final_result";
String MODEL_FILE = "file:///android_asset/optimized_graph.pb";
String LABEL_FILE = "file:///android_asset/output_labels.txt";

MODEL_FILEとLABEL_FILEでassetsに置いたモデルファイルとラベルファイルのパスを指定.
INPUT_SIZEは入力サイズでinception_v3を使う場合は299となる.
MobileNetを使う場合は指定したモデル名に書いてあるサイズを指定(MobileNet_v1_1.0_???)する.

次に初期化.

Classifier classifier = TensorFlowImageClassifier.create(getAssets(), MODEL_FILE, LABEL_FILE, INPUT_SIZE, IMAGE_MEAN, IMAGE_STD, INPUT_NAME, OUTPUT_NAME);

そしてこれで識別の実行.
それなりに時間がかかるので自分はAsyncTaskで実行させた.
スレッドで実行させると速度がガクッと下がるのが謎.
軽く調べた感じネイティブ実装でCPUに割り振るときのうんたらかんたらとか書いてあったがよく分からなかった.

List<Classifier.Recognition> results = classifier.recognizeImage(bitmap);

与えるBitmapはARGB形式(Bitmap.Config.ARGB_8888)でサイズがINPUT_SIZE x INPUT_SIZEである必要がある.
デモアプリに含まれるクラスでサイズの変換できる.
tensorflow/ImageUtils.java at master · tensorflow/tensorflow · GitHub

これでresultにラベル名と信頼度のリストが格納され無事分類が完了する.

実装したアプリ

以上の流れで実装してみたアプリを動物園で試してみた結果がこちら.
UI自体はWebViewで作った.


転移学習でラッキーさんに動物を分類してもらった


うまくいく動物相手だとわりと安定して正しく識別してくれる.
動画だとそれなりにいい感じに動いてるけど実際はダメダメ.
失敗動画はそんなに録画してないだけ.
撮影環境にかなりシビアである.
原因としては画像検索で得られる画像が良くも悪くも綺麗すぎるのが考えられる.
やはりプロが一眼で撮ったであろう野生生物とスマホのカメラで撮った動物園の動物とでは条件が違いすぎたのかもしれない.

ここでも述べられている通りデータの特徴の違いが大きく影響したのだと考えられる.
まぁ当たり前ではあるが.
https://qiita.com/nonbiri15/items/b29fe079d359d531bf85qiita.com

動物園でデータ集めればもうちょいいい結果になるのかもしれない.

まとめ

最終的なアプリとしてはちょっと悔しい結果ではあったが,専門知識を持ち合わせていない自分がここまで簡単にできるようになるとはディープラーニングもずいぶん裾野が広がってきたように感じる.
最先端っぽいことが簡単に実装できちゃうとやっぱり楽しい.
最先端の人たちにとっては画像分類なんかもう古いみたいになってるのかもしれないけど.
ググるとなんか凄いことばっかりやってるし.
まぁ順調に自分が扱えるところまで下りてきてると考える夢が広がる.