TensorFlow Object Detectionチュートリアルのデータセットを変えて学習させたい

# これなに？

[object detectionのチュートリアル](https://github.com/tensorflow/models/tree/master/research/object_detection)をやってみて普通に物体検出できたが、データセットを変えて自分で検出したいものを学習しようとしたら、いろいろ分からないことが多かった。
チュートリアルの中身は読み解いて変更すべき場所はどこなのか調べた時のメモ。

# Step1. デモとチュートリアルの処理内容を眺める

## Object Detection Demo

Object Detectionを体験できる、[作成済みモデル](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)を使って動物の検出と分類を行うデモ。
どのようなことができるのかと共に、最終的に出力したモデルをどのようにすれば使えるのかが学べる。

* [models/object\_detection\_tutorial\.ipynb at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/object_detection_tutorial.ipynb)
    1. モデルのダウンロード
    1. モデルを読み込み `tf.Graph` に書き込む
    1. ラベルの読み込み（出力は数値として出るので単語に紐づける用）
    1. nparrayにした画像を`tf.Graph`に食わせる
    1. 出てきた矩形座標やスコア、ラベルIDなどを元画像に描画して表示する

## Distributed Training

`Oxford-IIIT Pets dataset` を使ってペットの顔検出モデルを作成するチュートリアル。
データセットからモデルの作成、COCO-pretrained Modelを例に転移学習の仕方から、gcloudを用いた学習と評価の仕方を学べる。

* [models/running\_pets\.md at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/running_pets.md)
    1. データセットのダウンロード
    1. データセットの画像を　`TFRecord format` に書き換える
    1. 学習用と評価用の `TFRecord` が出力される
    1. 転移学習用に `COCO-pretrained Model` をダウンロード
    1. Object Detection Pipelineのconfigに利用するアルゴリズムと設定値、TFRecordの場所などを設定
    1. 学習時に利用するインスタンスタイプと台数をymlに記載
    1. Google Cloud Storageにアップロード
        * TFRecord（学習、評価）
        * 転移学習用のモデル
        * Obejct Detection Pipilineのconfig
        * gcloudのyml
    1. gcloudで学習させる
    1. gcloudで評価させる
    1. TensorBoardで確認する
    1. Tensorflow Graph（モデル）をエクスポートする

# Step2. データセットでよくわからないところを調べる

## データセットの画像をTFRecordに書き換えるときになにをやっているのか

コマンドでいうと下記のもの。
データセットの画像から学習データである `pet_train.record` と評価データである `pet_eval.record` を出力する処理。

```bash
# From tensorflow/models/research/
python object_detection/create_pet_tf_record.py \
    --label_map_path=object_detection/data/pet_label_map.pbtxt \
    --data_dir=`pwd` \
    --output_dir=`pwd`
```

## データセットのフォーマットはどうなっているのか

* [models/create\_pascal\_tf\_record\.py at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/create_pascal_tf_record.py)
    * Oxford-IIIT Pets datasetの中身
        * 画像ファイルとxmlデータで作られている
    * データセットの画像とxmlを読み込んで処理後　`tf.train.Example` に入れ込んでいる
        * `image/key/sha256`
            * 画像を読み込んでhash化してkeyとして利用
        * `image/object/bbox/xmin`
            * xmin / width で割合データとして座標を変換
        * `image/object/class/label`
            * ファイル名とラベルデータからラベルIDを指定 
        * などなど
    * Exampleというフォーマットで出力される
        * [tensorflow/example\.proto at master · tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto)
        * これが学習用に使うものとなる
        * 最終的にはExampleフォーマットとして入れるので、XMLの形式にこだわらず
 `tf.train.Example` に直に入れる値で作っておいてもいいと思う


## データセットにある画像情報を記したXMLのフォーマットを調べる

検出したい物体がある位置を座標として与えている。

```xml:Abyssinian_100.xml
<annotation>
    <folder>OXIIIT</folder>　　　　#imageのpath指定のためだけに使ってる
    <filename>Abyssinian_100.jpg</filename> #画像名
    <source> #TFRecord作成には使ってない
        <database>OXFORD-IIIT Pet Dataset</database> 
        <annotation>OXIIIT</annotation>
        <image>flickr</image>
    </source>
    <size>
        <width>394</width>　　　　　　#画像の横幅
        <height>500</height>　　#画像の縦幅
        <depth>3</depth>　　　　　　　　　　#画像のチャンネル数
    </size>
    <segmented>0</segmented>　　　#TFRecord作成には使ってない
    <object>
        <name>cat</name>　　　　　　　　　　　#分類ラベル名
        <pose>Frontal</pose> 　#ペットの顔の向き？
        <truncated>0</truncated> #?
        <occluded>0</occluded> 　　　　#?
        <bndbox> #顔がある座標
            <xmin>151</xmin>
            <ymin>71</ymin>
            <xmax>335</xmax>
            <ymax>267</ymax>
        </bndbox>
        <difficult>0</difficult>　　#?
    </object>
</annotation>
```

データセットを別のものに変えるくらいであれば、XML情報を最小限に削ってもモデルの作成はできる。`create_pascal_tf_record.py` 側の修正はもちろん必要だけど。

```xml:custom_100.xml
<annotation>
    <filename>image_100.jpg</filename>
    <size>
        <width>394</width>
        <height>500</height>
        <depth>3</depth>
    </size>
    <object>
        <name>car</name>　
        <bndbox>
            <xmin>151</xmin>
            <ymin>71</ymin>
            <xmax>335</xmax>
            <ymax>267</ymax>
        </bndbox>
    </object>
</annotation>
```

```text:object_detection/data/custom_label_map.pbtxt
item {
  id: 1
  name: 'car'
}
```

# Step3. gcloudに投げている学習命令でわからないところを調べる

以下のコマンドでgcloudのmachine leaningに学習命令を送っている。

```bash
# From tensorflow/models/research/
gcloud ml-engine jobs submit training `whoami`_object_detection_`date +%s` \
    --job-dir=gs://${YOUR_GCS_BUCKET}/train \
    --packages dist/object_detection-0.1.tar.gz,slim/dist/slim-0.1.tar.gz \
    --module-name object_detection.train \
    --region us-central1 \
    --config object_detection/samples/cloud/cloud.yml \
    -- \
    --train_dir=gs://${YOUR_GCS_BUCKET}/train \
    --pipeline_config_path=gs://${YOUR_GCS_BUCKET}/data/faster_rcnn_resnet101_pets.config
```

## 学習させるときに指定したmoduleは何をやっているのか

* [models/train\.py at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/train.py)
    * configファイルをmodel_config, train_config, input_configにパース
    * 最終的にTFのtrainに投げる形に整える
    * [models/trainer\.py at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/trainer.py)
        * データセットを変えるだけであれば、ここまで見る必要はなさそう
        * 注目すべきはconfigのほう


```python
def train(create_tensor_dict_fn, create_model_fn, train_config, master, task,
          num_clones, worker_replicas, clone_on_cpu, ps_tasks, worker_job_name,
          is_chief, train_dir):
  """Training function for detection models.
  Args:
    create_tensor_dict_fn: a function to create a tensor input dictionary.
    create_model_fn: a function that creates a DetectionModel and generates
                     losses.
    train_config: a train_pb2.TrainConfig protobuf.
    master: BNS name of the TensorFlow master to use.
    task: The task id of this training instance.
    num_clones: The number of clones to run per machine.
    worker_replicas: The number of work replicas to train with.
    clone_on_cpu: True if clones should be forced to run on CPU.
    ps_tasks: Number of parameter server tasks.
    worker_job_name: Name of the worker job.
    is_chief: Whether this replica is the chief replica.
    train_dir: Directory to write checkpoints and training summaries to.
  """
```


## Object Detection Pipelineのconfigはなにを指定しているのか

### configに出てくる単語の意味がわからないのでFaster R-CNNを勉強

* [Faster R\-CNN による物体の検出と識別 Video \- MATLAB](https://jp.mathworks.com/videos/object-detection-and-recognition-using-faster-r-cnn-1494263953252.html)
    * ざっくりした仕組みをまずここで理解

### configの中身を眺める

configさえしっかり書いておけばよろしくやってくれそうなので、configを中身を見る。　　
以下はGoogle翻訳かましてコメントを日本語化ものです。


* [models/faster\_rcnn\_resnet101\_pets\.config at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/samples/configs/faster_rcnn_resnet101_pets.config)
    * 実際に利用したconfig

* [models/faster\_rcnn\.proto at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/protos/faster_rcnn.proto)

```json/faster_rcnn_resnet101_pets.config
model {
  #　利用するprotoを指定
  faster_rcnn {
　　　　　　　　# RPNのみを構築するかどうか
　　　　　　　　first_stage_only = false
    #　クラスの数
    num_classes: 37
    #　前処理として画像のリサイズ
    image_resizer {
      #　縦横日を維持してリサイズ
      keep_aspect_ratio_resizer {
　　　　　　　　　　　　　　　　#　短辺の長さの最小
        min_dimension: 600
        #　長辺の長さの最大
        max_dimension: 1024
      }
    }
    # 特徴抽出器
    feature_extractor {
      # Faster R-CNNのタイプ
      type: 'faster_rcnn_resnet101'
      # 抽出されたRPN特徴マップの出力ストライド（画像の横一列分のデータサイズ）
      first_stage_features_stride: 16
    }

    ## (First stage) region proposal network (RPN) parameters.
    # RPN anchorを計算するためのアンカージェネレータ
    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
      }
    }
    ＃畳み込みRPN box predictorのハイパーパラメータ
    first_stage_box_predictor_conv_hyperparams {
      op: CONV
      regularizer {
        l2_regularizer {
          weight: 0.0
        }
      }
      initializer {
        truncated_normal_initializer {
          stddev: 0.01
        }
      }
    }
    # 第1段階RPNの領域候補に適用されるNon-Maximum Suppression スコア閾値
    first_stage_nms_score_threshold: 0.0
    # 第1段階RPNの領域候補に適用されるNon-Maximum Suppression IOU閾値
    first_stage_nms_iou_threshold: 0.7
    # 第1段階処理後に保持されるRPNの領域候補の最大数。
    first_stage_max_proposals: 300
    # 第1段階RPN localization loss weight
    first_stage_localization_loss_weight: 2.0
    # 第1段階RPN objectness loss weight
    first_stage_objectness_loss_weight: 1.0
    # 初期値の出力サイズ（幅と高さが同じ）
    initial_crop_size: 14
    # ROIプーリング中にクロップされたフィーチャマップ上の最大プールオペレーションのカーネルサイズ
    maxpool_kernel_size: 2
    # ROIプーリング中にクロップされたフィーチャマップ上の最大プールオペレーションのストライド
    maxpool_stride: 2

    ## (Second stage) box classifier parameters
    # 第2段階のボックスプレディクタのハイパーパラメータ
    # ボックスプレディクタタイプがrfcn_box_predictorに設定されている場合、
    # R-FCNモデルが構築され、そうでなければ、Faster R-CNNモデルが構築される。
    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
            }
          }
        }
      }
    }
    # 第2段階ボックス分類器予測に適用するための後処理
    # 注：FasterRCNNMetaArchコンストラクタに提供される `score_converter`は、
    # ` second_stage_post_processing`プロトタイプから取得されます。
    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
    }
    # 第2段階で精緻化されたlocalization loss weight
    second_stage_localization_loss_weight: 2.0
    # 第2段階分類損失重量
    second_stage_classification_loss_weight: 1.0
  }
}
```
* Non\-Maximum Suppressionについて
    * [物体検出におけるNon\-Maximum Suppressionのアルゴリズム \| meideru blog](http://meideru.com/archives/3538)


* [models/train\.proto at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/protos/train.proto)

```json/faster_rcnn_resnet101_pets.config
train_config: {
  # バッチサイズ
  batch_size: 1
  # DetectionModelのトレーニングに使用されるオプティマイザ
  optimizer {
    # オプティマイザの種類を指定
    momentum_optimizer: {
      # RMSPropOptimizerの構成
      learning_rate: {
        # 手動で定義された学習率スケジュール
        # https://www.tensorflow.org/versions/master/api_guides/python/train#Decaying_the_learning_rate
        manual_step_learning_rate {
          initial_learning_rate: 0.0003
          schedule {
            step: 0
            learning_rate: .0003
          }
          schedule {
            step: 900000
            learning_rate: .00003
          }
          schedule {
            step: 1200000
            learning_rate: .000003
          }
        }
      }
      momentum_optimizer_value: 0.9
    }
    use_moving_average: false
  }
  # 0より大きい場合、クリップはこの値で勾配します
  gradient_clipping_by_norm: 10.0
  # 変数を復元するチェックポイント
  # 通常、オブジェクト検出の外でトレーニングされた特徴抽出変数をロードするために使用されます。
  fine_tune_checkpoint: "PATH_TO_BE_CONFIGURED/model.ckpt"
  # finetuneチェックポイントがオブジェクト検出モデルかどうかを指定します
  # オブジェクト検出モデルの場合、訓練されているモデルはnum_classesパラメータを除いて同じパラメータを持つ必要があります
  # falseの場合、チェックポイントはオブジェクト分類モデルであるとみなされます
  from_detection_checkpoint: true
  # 注：以下の行は、トレーニングプロセスを200Kステップに制限しています
  # これは、経験的にペットデータセットをトレーニングするのに十分であることがわかりました
  # これは効果的に学習率スケジュールをバイパスします（学習率は決して低下しません）
  # 以下の行を削除すると無制限に学習します
  num_steps: 200000
  # データ拡張オプション
  data_augmentation_options {
    random_horizontal_flip {
    }
  }
}
```

* [models/input\_reader\.proto at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/protos/input_reader.proto)

```json/faster_rcnn_resnet101_pets.config
train_input_reader: {
  tf_record_input_reader {
    # 作成したTFRecordのパスを指定
    input_path: "PATH_TO_BE_CONFIGURED/pet_train.record"
  }
  # 用意したラベルデータのパスを指定
  label_map_path: "PATH_TO_BE_CONFIGURED/pet_label_map.pbtxt"
}

eval_input_reader: {
  tf_record_input_reader {
    # 評価用TFRecordのパスを指定
    input_path: "PATH_TO_BE_CONFIGURED/pet_val.record"
  }
  # ラベルデータのパスを指定
  label_map_path: "PATH_TO_BE_CONFIGURED/pet_label_map.pbtxt"
  # データを読み込んだ順に処理するか、ランダムにシャッフルするか
  shuffle: false
  # 作成するリーダーインスタンスの数
  num_readers: 1
}
```

* [models/eval\.proto at master · tensorflow/models](https://github.com/tensorflow/models/blob/master/research/object_detection/protos/eval.proto)

```json/faster_rcnn_resnet101_pets.config
eval_config: {
  # 評価を処理するサンプル数
  num_examples: 2000
  # 注：以下の行は、評価プロセスを10評価に制限しています
  # 無期限に評価するには、次の行を削除します
  max_evals: 10
}
```

# まとめ

* 学習とテストに使えるだけの画像を用意する
* 検出したい矩形座標と分類を記したXMLを用意する
* 分類したい項目のjsonを用意する
* 学習とテストのパラメータを記したconfigを用意する
* GCPインスタンスをどのくらい使うか記したymlを用意する

基本的にこのあたりを変更するだけで別のデータセットで利用できると思う。
学習モデルのconfigでなにを指定したらよくなるのかが分からないので、そこが難しい。