Object Detection APIを使って堀川君を検知

[TensorFlow Object Detection API](https://github.com/tensorflow/models/tree/master/research/object_detection)を使って[Google Cloud ML Engine](https://cloud.google.com/ml-engine/docs/technical-overview?hl=ja)で訓練をしました。
適当にGoogle画像検索で堀川君の画像を使ったのですが、結果いまいちです。教師データの画像も約40枚と少なく、色々な点で手を抜いています。
![20.ResultImage02.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/12962f74-5ae7-41b4-303d-235334e3b134.jpeg)

※Google画像検索すると変な画像も多かったです。評価画像を6枚しか用意していなかったため、一番わかりやすかったのが↑なのですが、ファンの方すいません・・・
モデルのエクスポートは[「Object Detection APIのsaved_model出力」](https://qiita.com/FukuharaYohei/items/d404bacd45afbf0f068e)を参照ください。

# 概要

- 教師画像：訓練用30枚、評価用6枚
- Epoch：1000回
- バッチサイズ：1
- アルゴリズム: Faster R-CNN with Resnet-101 (v1)
- 転移学習：Tensorflow detection model zooのCOCO-trained modelsを使用

# 参考
- [Google Cloud ML EngineでTensorFlow機械学習訓練実行](https://qiita.com/FukuharaYohei/items/2a435ef83ecf1702395c)
- [TensorFlow Object Detection APIをUbuntuにインストール](https://qiita.com/FukuharaYohei/items/5e4081ba8f7f9255a8f6)
- [Object Detectionのためのアノテーション(ラベリング)](https://qiita.com/FukuharaYohei/items/9bd1be8ba04430c1ebea)
- [Object Detection APIのsaved_model出力](https://qiita.com/FukuharaYohei/items/d404bacd45afbf0f068e)
- [Access Tokenを取得してGoogle Cloud MLでオンライン予測](https://qiita.com/FukuharaYohei/items/5face08fa7d07bbe11fe)


#環境
|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|Windows8.1からOracle VirtualBoxを使って動かしています|
|Python|3.5.6|Python3.5.6をpyenvで動かしています。Google Cloud MLのランタイム1.10を使うのでPythonは最新の3.7を使いません(2019/1/5時点)|
|TensorFlow|1.10|Google Cloud MLのランタイム1.10を使うのでTensorFlowは最新の1.12を使いません(2019/1/5時点)|
※後で気になって調べたのですが、2019/1/5時点でもTensorFlow1.12が使えました。日本語の情報だと1.10までしかなかったのですが、英語で見ると1.12も使えました。

# 前提
- pyenvはインストール済
- GCPのアカウントを持っていること
- Google Cloud SDKインストール済
- TensorFlow Object Detection API インストール済
- 訓練用データ作成済


# 手順
##1. 訓練事前準備
###1.1. パッケージング
Google Cloud ML Engineに送るプログラムをパッケージングします。

```bash
# From tensorflow/models/research/
bash object_detection/dataset_tools/create_pycocotools_package.sh /tmp/pycocotools
python setup.py sdist
```

以下のコマンドでファイル生成を確認します。

```bash
$ ls /tmp/pycocotools/
pycocotools-2.0.tar.gz

$ ls dist
object_detection-0.1.tar.gz
```

カレントディレクトリをslimにして、setup.py実行。

```bash
(cd slim && python setup.py sdist)
```

同様にファイル生成を確認。

```bash
$ ls slim/dist
slim-0.1.tar.gz
```

###1.2. 訓練ジョブYAMLファイル作成
訓練ジョブのYAMLファイルを作成します。ランタイムのバージョンを1.10にして、サーバ構成をBASIC_GPUというGPUを使用したシンプルな構成にしています。
[TrainingInput](https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs?hl=ja#traininginput)に各パラメータの説明があります。

```yaml:config.yaml
trainingInput:
  runtimeVersion: "1.10"
  scaleTier: BASIC_GPU
```

###1.3. 転移学習元モデルのダウンロード
[Tensorflow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)から[faster_rcnn_resnet101_coco](http://download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_2018_01_28.tar.gz)をダウンロードして解凍。**今回訓練するモデルが転移学習元と関係なくても、少ないEpochで精度が高くなる**ので転移学習は非常におすすめです。

###1.4. Configファイル作成
["models/research/object_detection/samples/configs/"フォルダ](https://github.com/tensorflow/models/tree/master/research/object_detection/samples/configs)にある[ファイル"faster_rcnn_resnet101_coco.config"](https://github.com/tensorflow/models/blob/master/research/object_detection/samples/configs/faster_rcnn_resnet101_coco.config)をコピーして一部、変更します。
ここでは、次の手順「1.5. Google Cloud Storageへのアップロード」でファイルアップロードすることを見越してファイルパスの設定もしています。

```json:faster_rcnn_resnet101_coco.config
# Faster R-CNN with Resnet-101 (v1), configuration for MSCOCO Dataset.
# Users should configure the fine_tune_checkpoint field in the train config as
# well as the label_map_path and input_path fields in the train_input_reader and
# eval_input_reader. Search for "PATH_TO_BE_CONFIGURED" to find the fields that
# should be configured.

model {
  faster_rcnn {
    num_classes: 1
    image_resizer {
      keep_aspect_ratio_resizer {
        min_dimension: 600
        max_dimension: 1024
      }
    }
    feature_extractor {
      type: 'faster_rcnn_resnet101'
      first_stage_features_stride: 16
    }
    first_stage_anchor_generator {
      grid_anchor_generator {
        scales: [0.25, 0.5, 1.0, 2.0]
        aspect_ratios: [0.5, 1.0, 2.0]
        height_stride: 16
        width_stride: 16
      }
    }
    first_stage_box_predictor_conv_hyperparams {
      op: CONV
      regularizer {
        l2_regularizer {
          weight: 0.0
        }
      }
      initializer {
        truncated_normal_initializer {
          stddev: 0.01
        }
      }
    }
    first_stage_nms_score_threshold: 0.0
    first_stage_nms_iou_threshold: 0.7
    first_stage_max_proposals: 300
    first_stage_localization_loss_weight: 2.0
    first_stage_objectness_loss_weight: 1.0
    initial_crop_size: 14
    maxpool_kernel_size: 2
    maxpool_stride: 2
    second_stage_box_predictor {
      mask_rcnn_box_predictor {
        use_dropout: false
        dropout_keep_probability: 1.0
        fc_hyperparams {
          op: FC
          regularizer {
            l2_regularizer {
              weight: 0.0
            }
          }
          initializer {
            variance_scaling_initializer {
              factor: 1.0
              uniform: true
              mode: FAN_AVG
            }
          }
        }
      }
    }
    second_stage_post_processing {
      batch_non_max_suppression {
        score_threshold: 0.0
        iou_threshold: 0.6
        max_detections_per_class: 100
        max_total_detections: 300
      }
      score_converter: SOFTMAX
    }
    second_stage_localization_loss_weight: 2.0
    second_stage_classification_loss_weight: 1.0
  }
}

train_config: {
  batch_size: 1
  optimizer {
    momentum_optimizer: {
      learning_rate: {
        manual_step_learning_rate {
          initial_learning_rate: 0.0003
          schedule {
            step: 900000
            learning_rate: .00003
          }
          schedule {
            step: 1200000
            learning_rate: .000003
          }
        }
      }
      momentum_optimizer_value: 0.9
    }
    use_moving_average: false
  }
  gradient_clipping_by_norm: 10.0
  fine_tune_checkpoint: "gs://cl-ml01/train_input/transfer/model.ckpt"
  from_detection_checkpoint: true
  num_steps: 1000
  data_augmentation_options {
    random_horizontal_flip {
    }
  }
  data_augmentation_options {
    random_crop_pad_image {
    }
  }
  data_augmentation_options {
    random_black_patches {
      max_black_patches: 5
    }
  }
}

train_input_reader: {
  tf_record_input_reader {
    input_path: "gs://cl-ml01/train_input/train.record"
  }
  label_map_path: "gs://cl-ml01/train_input/label_map.pbtxt"
}

eval_config: {
  num_examples: 6
  num_visualizations: 6
  save_graph: true

  # Note: The below line limits the evaluation process to 10 evaluations.
  # Remove the below line to evaluate indefinitely.
  max_evals: 10
}

eval_input_reader: {
  tf_record_input_reader {
    input_path: "gs://cl-ml01/train_input/eval.record"
  }
  label_map_path: "gs://cl-ml01/train_input/label_map.pbtxt"
  shuffle: false
  num_readers: 1
}
```

意識したパラメータは以下の点です。詳しくないですが[Configuring the Object Detection Training Pipeline](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/configuring_jobs.md)に情報があります。

|パス|変更点|
|:--|:--|
|model -> faster_rcnn -> num_classes|分類クラスの値を3から1(堀川君のみ)に変更|
|train_config -> fine_tune_checkpoint|転移学習のためのモデルパスを記載|
|train_config -> num_steps|訓練するEpoch数として1000を設定|
|train_config -> data_augmentation_options|データ水増しのオプションとして以下を追加<br>random_crop_pad_image<br>random_black_patches|
|eval_config -> num_examples|評価に使う画像枚数|
|eval_config -> num_visualizations|TensorBoardに表示する評価の画像枚数|

###1.5. Google Cloud Storageへのアップロード
"gsutil cp"コマンドを使うのが王道かと思いますが、面倒なのでブラウザを使ってGoogle Cloud Storageへ各ファイルをアップロードしました。バケット名をcl-ml01にしています。プロジェクト等の設定は別記事[「Google Cloud ML EngineでTensorFlow機械学習訓練実行」](https://qiita.com/FukuharaYohei/items/2a435ef83ecf1702395c)を参考にしてください。

- faster_rcnn_resnet101_coco.config: 「1.4. Configファイル作成」で作成したConfigファイル
- eval.record: 評価用教師データセット(作成方法は[別記事](https://qiita.com/FukuharaYohei/items/9bd1be8ba04430c1ebea#25-tfrecord%E5%A4%89%E6%8F%9B)参照)
- label_map.pbtxt: ラベルデータ(作成方法は[別記事](https://qiita.com/FukuharaYohei/items/9bd1be8ba04430c1ebea#23-%E3%83%A9%E3%83%99%E3%83%AB%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E4%BD%9C%E6%88%90)参照)
- train.record: 訓練用教師データセット(作成方法は[別記事](https://qiita.com/FukuharaYohei/items/9bd1be8ba04430c1ebea#25-tfrecord%E5%A4%89%E6%8F%9B)参照)
- transferフォルダの中身: 「1.3. 転移学習元モデルのダウンロード」で取得・解凍したファイル

```
.
└── cl-ml01
    ├── train_input
    │   ├── config
    │   │   └── faster_rcnn_resnet101_coco.config
    │   ├── eval.record
    │   ├── label_map.pbtxt
    │   ├── train.record
    │   └── transfer
    │       ├── checkpoint
    │       ├── frozen_inference_graph.pb
    │       ├── model.ckpt.data-00000-of-00001
    │       ├── model.ckpt.index
    │       ├── model.ckpt.meta
    │       ├── pipeline.config
    │       └── saved_model
    │           ├── saved_model.pb
    │           └── variables
    └── train_output
        ├── job
        └── model
```

###1.6. 訓練実行
環境変数を設定し、venv仮想環境を有効化します。

```bash
export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
source ~/Documents/python/venv/clml-od/bin/activate
```

訓練実行開始です。

```bash
# From tensorflow/models/research/
gcloud ml-engine jobs submit training object_detection_`date +%m_%d_%Y_%H_%M_%S` \
    --runtime-version 1.10 \
    --job-dir=gs://cl-ml01/train_output/job \
    --packages dist/object_detection-0.1.tar.gz,slim/dist/slim-0.1.tar.gz,/tmp/pycocotools/pycocotools-2.0.tar.gz \
    --module-name object_detection.model_main \
    --config ~/Documents/od/test01/train_input/config/config.yml \
    --region us-central1 \
    -- \
    --model_dir=gs://cl-ml01/train_output/model \
    --pipeline_config_path=gs://cl-ml01/train_input/config/faster_rcnn_resnet101_coco.config
```

指定したパラメータは以下の通りです。

|パラメータ|内容|
|:--|:--|
|job-dir|パッケージングしたファイルが保存される場所(クラウド)|
|packages|パッケージングしたファイル(ローカル)|
|module-name|実行するモジュール|
|config|YAMLファイル(ローカル)|
|model_dir|訓練結果のモデル？が保存される場所|
|pipeline_config_path|パイプラインのコンフィグファイル|

##2. 結果確認
###2.1. ジョブ確認
[GCPのコンソール](https://console.cloud.google.com/mlengine/jobs)からジョブを確認します。正常終了してるのがわかります。30分もかからず終わっています。さすがGPU！
![40.ResultJob01.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/c299e532-5236-fd84-fbec-179f0275ab01.jpeg)

「ログを表示」をクリックして詳細ログを確認します。最後にPrecisionやRecallの数値を出してくれています。
![40.ResultJob02.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/42d3a53c-47d4-0fd5-f345-3a8cb6afe1f1.jpeg)

###2.2. TensorBoard確認
TensorBoard確認の前にGCPにログインします。

```bash
gcloud auth application-default login
```

「許可」をクリック。これでTensorBoardから結果を参照できます。
![10.TensorBoardAuth01.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/5fa57926-4599-33b5-dd3a-7555c68a270b.jpeg)


TensorBoardで直接、Google Cloud Storageにつないで確認します。Windowsだと一回、ローカルPCにダウンロードしないとTensorBoardで見られません。

```bash
tensorboard --logdir=gs://cl-ml01/train_output/model
```

結果はTensorBoardで見るとこんな感じです。

訓練途中(529Epoch時)と最終結果(1000Epoch)で並べると学習していることがわかります。波平や変な衣装は識別できましたが、ワカメちゃんは識別できていないですね。
そもそも画像サイズを統一していなかったのですが、TensorFlow Object Detection APIが統一しないでもOKか調べてません。本当はだめかもしれません。

|529Epoch|1000Epoch|
|:--|:--|
|![20.ResultImage01.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/7c2a542f-e3c2-2c3b-7614-6e45ab859b48.jpeg)|![20.ResultImage02.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/12962f74-5ae7-41b4-303d-235334e3b134.jpeg)|

吹き出しまで検出しているのはひどい・・・
![20.ResultImage03.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/b65e8d79-e61b-53ff-c0fb-5403f546f6e4.jpeg)

っていうか、他の人は機械学習的にはすごい似ているのか？
![20.ResultImage04.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/72a28618-cad8-6481-59bb-41adecca38ef.jpeg)

その他Scalar系ものせておきます。評価時の情報しかTensorBoardに出ませんでした。多分、何かやり方が間違っているのですが、今回はそこまで調べません。

![30.ResultScalar.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/c6272b79-0852-6cad-da37-0d8c057fb0cc.jpeg)
![30.ResultScalar02.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/0e4f2cda-6d97-8eb8-a273-764e96deb1f0.jpeg)
![30.ResultScalar03.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/0ea1f54f-7dd3-0983-f0fe-fe2a91bf6d95.jpeg)

# 感想
昔やったときより、すんなり動きました。ジョブの失敗が1回もありません！昔はいろいろとエラーが多く、プログラムを書き換えたりしていました。
以下は調査不足な点です。

- データ水増しオプション：3つ使っていますが、もっと使えるはず。
- 画像サイズ：統一しないとうまく動かない。少なくてもTensorBoardで確認時には少し変に表示されていました。今までは統一した画像サイズでやっていたので気づかなかった。
- TensorBoardのScalar：訓練時のデータが表示されない。なんでだろう・・・