機械学習で画像認識モデルを自作してAndroidアプリを実装する最短経路について

# この記事の目的
- 画像認識モデルを簡単に作って，簡単にアプリとして動かす方法を共有する

## 実行環境
- google colaboratory(ランタイム：GPU)(tensorflow 2.0)(Google Chrome)
- win 10
- Android Studio(3.5.2)
- android 9.0(Huawei mate 10 pro)

## この記事で書くこと
- [画像認識モデルのためのデータセット作成方法](#画像認識モデルのためのデータセット作成方法)
- [画像認識モデルの作成方法](#画像認識モデルの作成方法)
- [作成した画像認識モデルをAndroid Studioで読み込み動かす方法](#作成した画像認識モデルをandroid-studioで読み込み動かす方法)
- [筆者の失敗(?)談](#筆者の失敗談)

# 画像認識モデルのためのデータセット作成方法
以下のステップを踏む
1. 認識したいクラスを複数作る．（ここではクラスA,B,Cとする）
2. クラスA,B,Cの画像を集める
3. クラスA,B,Cの画像をトレーニング用，テスト用に分ける
## クラスA,B,Cの画像を集める
すでに画像を集めている場合はスキップ
まだ集めていない場合はgoogle_image_download[（参考URL）](https://co.bsnws.net/article/295)が便利．
キーワードや拡張子，サイズ，枚数などを指定して画像をダウンロードしてくれる．
※ちゃんと学習させたいなら，ダウンロードしてきた画像を認識したい部分だけ切り出す作業を行う．（かなり時間がかかりますが，必要な場合が多いと思います．いい方法あれば教えてください．．．）
## クラスA,B,Cの画像をトレーニング用，テスト用に分ける
トレーニング用，テスト用に画像をいい感じに分ける（トレーニング８割：テスト２割ぐらい？）
具体的には以下のような構成のフォルダを作る
images
├train
│└A
│　└a01.jpg
│　└aslfdjk.png
│　...
│└B
│　└ba.jpg
│　└dskrup.png
│　...
│└C
│　└ba.jpg
│　└sdddrrd.png
│　...
│
├validation
│└A
│　└fwwqd.jpg
│　└qiita.png
│　...
│└B
│　└sddd.jpg
│　└reag.png
│　...
│└C
│　└vtet.jpg
│　└fhyr.png
│　...


# 画像認識モデルの作成方法
モデルは以下のステップで作成する
1. tensorflowのtutorialを使用
2. １．でモデルをトレーニングするデータを自作のものに置き換える
3. Android Studioでの実装を考慮してモデルの形を微修正し，モデルをトレーニング
4. トレーニングしたモデルの保存方法


## tensorflowのtransfer learningのtutorialを使用
簡単さを重視し，tensorflowが提供しているtutorialを流用したコードを[（参考URL）※URLを押すといきなりgoogle colaboratoryが開きます](https://colab.research.google.com/drive/1p-MY8C_H238TWffWrRjC8cc20alZACSD)
google colaboratory の使い方については[公式HP](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)を参考．
## モデルをトレーニングするデータを自作のものに置き換える
ソースコードのこの部分

```
from google.colab import drive
drive.mount('/content/drive')
PATH = '/content/drive/'+'google drive上の自分のデータセットのある場所までのパス'
```
これを入力すると，google driveに対してのアクセスを許可するか？という旨の認証を求められる．個人的には気にならないので特に考えず認証したが，気になる人はgoogle colaboraotryで自分のデータセットを読み込ませるのはやめたほうが良い．

## Android Studioでの実装を考慮してモデルの形を微修正してモデルをトレーニング
ソースコードのこの部分

このあと以下のコードセルまでを順次実行するとモデルのトレーニングができる

```python

history = model.fit_generator(
    train_data_gen,
    steps_per_epoch=total_train // batch_size,
    epochs=epochs,
    validation_data=val_data_gen,
    validation_steps=total_val // batch_size
)
```
## トレーニングしたモデルの保存方法
これだけ

```python
saved_model_dir = base_dir+'保存したい場所'
tf.saved_model.save(model, saved_model_dir)
```
# 作成した画像認識モデルをAndroid Studioで読み込み動かす方法
以下のステップを踏む
1. 保存したモデルをTensorFlow Liteモデルに変換し，保存する
2. Android studioのプロジェクトを作る
3. Android studioで保存したモデルを読み込み，アプリを動かす

## 保存したモデルをTensorFlow Liteモデルに変換し，保存する
これだけ

```python
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_model = converter.convert()

with open(base_dir+'モデルの名前.tflite', 'wb') as f:
  f.write(tflite_model)
```
保存したモデルをダウンロードしてローカルに保存しておく．

## Android studioのプロジェクトを作る
まずAndroid studioをインストールし[（参考URL）](https://developer.android.com/studio/install?hl=ja)，
例によってtensorflowが提供するquick startを流用する．[参考URL](https://github.com/tensorflow/examples/tree/master/lite/examples/image_classification/android)
に従っていくと，ローカル環境にexampleという名前のフォルダが作成されるので，以下のパスにあるandroidプロジェクトを開く
\examples\lite\examples\image_classification\android

## Android studioで保存したモデルを読み込み，アプリを動かす
以下のステップで行う
1. ローカルに保存した.tfliteファイルをassetフォルダに置く
2. クラスを記載した.txtファイルをassetフォルダに置く
3. .jaファイルを一部書き換える

### ローカルに保存した.tfliteファイルをassetフォルダに置く
以下のフォルダに置く．
\examples\lite\examples\image_classification\android\app\src\main\assets
同じところに.tfliteがいくつか入っている．
### クラスを記載した.txtファイルをassetフォルダに置く
今回の場合，クラスA,B,Cを以下のように書き，.tfliteファイルと同じ場所に保存する．

```mylabel.text
A
B
C
```
### .javaファイルを一部書き換える
まず,
\examples\lite\examples\image_classification\android\app\src\main\java\org\tensorflow\lite\examples\classification\tflite\ClassifierFloatMobileNet.java
の56行目

```java
return "mobilenet_v1_1.0_224.tflite";
```
を以下のように書き換える


```java
return "モデルの名前.tflite";
```

次に，
\examples\lite\examples\image_classification\android\app\src\main\java\org\tensorflow\lite\examples\classification\tflite\Classifier.java
の110行目

```java
  public static Classifier create(Activity activity, Model model, Device device, int numThreads)
      throws IOException {
    if (model == Model.QUANTIZED) {
      return new ClassifierQuantizedMobileNet(activity, device, numThreads);
    } else {
      return new ClassifierFloatMobileNet(activity, device, numThreads);
    }
  }
```
を以下のように書き換える

```java
  public static Classifier create(Activity activity, Model model, Device device, int numThreads)
      throws IOException {
    //if (model == Model.QUANTIZED) {
      //return new ClassifierQuantizedMobileNet(activity, device, numThreads);
    //} else {
      return new ClassifierFloatMobileNet(activity, device, numThreads);
    //}
  }
```

最後にAndroid Studioでmake projectを行う．
その後スマホをPCにつなぎ，Run'app'を実行．するとスマホにTFL Classifyという名前のアプリがインストールされ，実行される．
画面の下のほうにA,B,Cのconfidence（どのくらいそのクラスらしいかを％で示したもの）が表示される．

# 筆者の失敗談
筆者が躓いた失敗（？）談のうち，参考になるかもしれないものを共有しておく．

## 失敗(？）１
pytorchモデルをtensorflowLITEモデルに変換することにトライし，結局挫折した．
主にこの２つのHPを参考にしたが，[URL１](https://qiita.com/lain21/items/9f9f9707ebad4bbc627d)，[URL２](https://heartbeat.fritz.ai/deploying-pytorch-and-keras-models-to-android-with-tensorflow-mobile-a16a1fb83f2)
どちらもよくわからないエラーが出てやめてしまった．今思うとkerasとtf.kerasがどちらも存在したことが原因だったかもしれない．今度検証してみようかな？

今回はとにかく早く自作モデルをandroidアプリで動かしたかったため，結局tensorflow tutorialを利用してモデルを作成し，上記のようにkerasモデルをtensorflowLITEモデルに変換した．

上の[URL１](https://qiita.com/lain21/items/9f9f9707ebad4bbc627d)にも記載されているように
>そんな中、TensorFlowが明らかにPyTorchに対して優位な点として、以下のようなものが挙げられます。

>TPUを使って学習できる
>TFLite や TensorFlow.js など、フロントエンドでのデプロイを容易にするAPIが充実している


アプリへの転用を容易にするサポートが現在tensorflowのほうが手厚いようです．

## 失敗（？）２
色んなコードを集めているうちにkerasとtf.kerasが混ざってしまっていた．途中からkeras絶対消すマンとしてtf.kerasに書き換えまくった．

## 失敗（？）３
モデルの出力がデフォルトのtutorialコードでは１つになっていることに気づかず，android studioで出力が足りないよ！というエラーを吐かせまくった．
エラーの原因がkerasからTFLiteConverterを使って.tfliteファイルを作るところにあると考え，そこで時間を使いまくった...
結局[このURL](https://www.tensorflow.org/lite/convert/python_api?hl=ja)で，自作モデルの評価をやってみるうちに「なんか出力のshapeおかしくね？」となり，モデルの形がおかしいことに気づいた

## 失敗（？）４
モデルを量子化してみたが，android studioで読み込めず断念．結局floatモデルを使用した．
量子化した方がモデルのサイズが小さくなるし処理が早くなりそうだったのでトライしたが，できなかった．
これはまだ原因がわかっていない．

## 失敗（？）５
そもそも当初は物体認識に何を使うか決めておらず，darknetでyoloを使おうと思っていたが，自作モデルを流用する方法がわからず，どうやればいいかわからず断念．知り合いからpytorchが簡単でいいよ！と教えられ，失敗１につながった．こう考えるとすぐ諦めてるな．．．

## 失敗（？）６
[クラスA,B,Cの画像を集める](#クラスA,B,Cの画像を集める)で簡単に画像をgoogle_image_downloadを使って集めればいいよ！と書いているが，結構これがしんどい．もちろん不適切な画像もダウンロードしてくるので，そこから捨てたりトリミングしたりがまず大変で，その後クラス分けの作業が待っている．
世の中でアノテーションが仕事になる理由が分かったが，リアルMPが減りそうなのであまりやりたくない．．．
関連ワードとしてactive learningなるものがあり，認識率が上がる画像を自動で選んでくれるという方法のようだ．できたら便利．かなぁ？

## 失敗（？）７
この記事を書き始めたのち，tensorflowのtutorialの内容が変わっていたことに気づく．
実は筆者はtransfer learningのtutorialを使っていたのだが，自分のコードを公開するのが面倒だったため，仕方なくimage classificationのtutorialを説明に使用．
自分でモデル作るより，少ないデータ数で精度が上がると言われているtransfer learningが便利だと思っています．
現在のtransfer learningのtutorialはデータの読み込みがtfのものから読み込むようになっており，そこを変更して書くのが煩雑なため，説明に使用することを断念．
自分のデータをtransfer learningで使用したい！という要望があれば頑張る気になるかもしれません．．．