Android端末向けDeepLab用モデルのエクスポート

# 概要
Android端末上でDeepLabを利用しようとした場合、DeepLabのサンプルの凍結モデルはそのままでは読み込めても実行時にエラーを起こして動作しない。
チェックポイントからAndroid向けに凍結モデルをエクスポートしなおす必要がある。

本記事ではそのあたりのノウハウを記述する。
記事執筆時点(2018.7)では当記事のような作業が必要だが、将来的に不要になる可能性もあるので利用の際には各自確認されたい。

## 注意
記事作成者は機械学習とかPythonとかにはずぶの素人です。
おかしな記述があるかもしれませんので警戒しながら読んでください。

# 学習済みサンプルモデル
ここで開発元により複数種類のサンプルモデルが公開されている:

* [TensorFlow DeepLab Model Zoo](https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/model_zoo.md)

## 種類
4種類のデータセット(=学習用データ群)による学習結果が用意されている:

* [PASCAL VOC 2012](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/) + [MS-COCO](http://cocodataset.org) :"Visual Object Classes Challenge 2012" 用データセット + Microsoft COCOが提供しているデータセット
* [Cityscapes](https://www.cityscapes-dataset.com/) :ドイツのCityscapes datasetチームが提供しているデータセット
* [ADE20K](http://groups.csail.mit.edu/vision/datasets/ADE20K/) :MITが公開しているデータセット
* [ImageNet](http://www.image-net.org/) :スタンフォード大学が公開しているデータセット

このうちImageNetのモデルについては規模が大きいためAndroid端末上で動かないと予想して、本稿では試していない。

----
同じデータセットについて異なるネットワークバックボーンを用いた学習結果が用意されている:

* MobileNet-v2 :小さい、速い
* Xception_65 :大きい、結果が高品質

Xception_65が大きくて高品質である理由は、モデルの中に後処理を行うデコーダが組み込まれていることによると思われる。

----
PASCAL VOC 2012 + MS-COCOのデータセットには2種類のモデルが提供されている:

* train_arg
* trainval (train_aug+trainval)

が、現時点の自分の知識では差がよくわからない。学習を追加しようとする場合に違いがある模様。

### どれが使えるのか
ファイルのエクスポートという範疇においてはどれも使える。
Android端末上で利用するという目的においては、大きすぎるモデルは読み込み時にOutOfMemoryError例外が発生するので使えない。

Android端末上で利用可能なモデルは次の通り:

* deeplabv3_mnv2_pascal_train_aug
* deeplabv3_mnv2_pascal_trainval
* deeplabv3_mnv2_cityscapes_train

## ファイルの内容
各モデルはtar.gzにまとめられた複数のファイルにより提供されている:

* frozen_inference_graph.pb :結果を凍結したファイル。これを利用したいのだが、__そのままでは利用できない__
* model.ckpt*.* :チェックポイント(=学習経過)を出力したファイル。そのままでは使えない
* model.ckpt*.index :チェックポイントの副次情報ファイル

# Android向け学習済みモデルの生成
Android上で動くTensorFlow Moblie/Liteでは、サンプル提供されている学習済みモデルの凍結モデル"frozen_inference_graph.pb"は読み込みには成功するが実行時にエラーを起こして動作しない。
Android向けにチェックポイントから結果を凍結しなおす必要がある。

DeepLabのデータエクスポート用Pythonファイル "export_model.py" を改変して実行することによりこれを行う。
参考:https://github.com/tensorflow/models/issues/4278

## やるべきこと

* エクスポート用プログラムを動作させるためのTensorFlow動作環境の構築
* サンプルの学習済みモデルのダウンロード、解凍
* export_model.pyの改変、実行

## export_model.pyの改変内容
これを

```
    # Crop the valid regions from the predictions.
    semantic_predictions = tf.slice(
        predictions[common.OUTPUT_TYPE],
        [0, 0, 0],
        [1, resized_image_size[0], resized_image_size[1]])
```
こうする

```
    # Crop the valid regions from the predictions.
    semantic_predictions = tf.slice(
        tf.cast(predictions[common.OUTPUT_TYPE], tf.int32),
        [0, 0, 0],
        [1, resized_image_size[0], resized_image_size[1]])
```

記事執筆時点でのソースにおいて、131行目の1行のみを改変する。

## 手順
すべての処理をGoogleのDeepLaerning向けクラウド仮想環境 "Google Colaboratory"を利用して行なった。
無料で、Googleアカウントがあれば利用できる。
クラウド上にブラウザのみで操作可能なLinuxの仮想マシンが提供される。

すべてを行えるノートブックを用意した:

* https://colab.research.google.com/drive/1R93tD4Ay_OlF9jr9-h6yAZHMKQHz_yAj

Googleアカウントがあればブラウザ上から利用できる。
PLAYGROUNDモードに切り替えて実行ボタンを2つクリックすると変換された学習済みモデルがダウンロードされてくる。

----
ここで思料が必要なことと言えばどのモデルを利用するかの選択くらいの単純操作なのだが、中身は割と泥臭い処理になっているので次項で少し解説する。

モデルを利用するだけなら以降は読む必要はない。

# export_model.pyについての解説
エクスポート作業についての公式のドキュメントがこちら:
https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/export_model.md

export_model.pyにパラメータを渡して起動することでチェックポイントから凍結モデルへのエクスポートが行われる。
実行方法の基本は次の通り:

```
python deeplab/export_model.py \
    --checkpoint_path=${CHECKPOINT_PATH} \
    --export_path=${OUTPUT_DIR}/frozen_inference_graph.pb
```

これでヘルプが表示される:

```
python deeplab/export_model.py --help
```

しかし少々情報不足なのでここで解説をしておく。

## チェックポイントの指定
サンプルファイルを解凍すると次のような構成のファイルが得られる:

* frozen_inference_graph.pb
* model.ckpt-30000.data-00000-of-00001
* model.ckpt-30000.index

この場合、チェックポイントとして指定するのは拡張子を除外した"model.ckpt-30000"となる。
checkpoint_pathとして指定するパスは、これらのあるディレクトリのパス+"model.ckpt-30000" という架空のファイルへのパスとなる。

## 追加パラメータの指定
deeplabv3_mnv2_pascal_train_aug と deeplabv3_mnv2_pascal_trainval については追加のパラメータは必要ない。
それ以外についてはパラメータを追加する必要がある。

パラメータに与える値は[サンプル公開ページ](https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/model_zoo.md)から拾ってくる。

|パラメータ名|説明|例|
|---|---|---|
|model_variant|Network backboneが"Xception_65"の場合に必要|`--model_variant=xception_65`|
|atrous_rates|ASPPに書かれた値を複数列挙する<br>OS=8の方を適用する|`--atrous_rates 12 --atrous_rates 24 --atrous_rates 36`|
|inference_scales|Eval scalesに書かれた値を複数列挙する<br>OS=8の方を適用する|`--inference_scales 0.5 --inference_scales 0.25 --inference_scales 1.75`|
|num_classes |分類の種類数<br>エクスポートを実行してみてエラーが出たときのメッセージから拾う|`--num_classes 151`|

"OS=8の方を適用する"と記述しているが、OS(--output_stride)のデフォルト値が8なのでそのようにした。
OS=16の方は試していないので結果がどうなるかは未確認。

num_classesにはそのモデルが識別する物体の種類数を指定する。
モデル既定の数とパラメータで与えた値が一致しないと次のようなエラーが出る:
> tensorflow.python.framework.errors_impl.InvalidArgumentError: Assign requires shapes of both tensors to match. lhs shape= [1,1,256,21] rhs shape= [1,1,256,151]

rhs shapeの末尾の数値がnum_classesに相当する(この例では151)。