DeepLab v3+でオリジナルデータを学習してセグメンテーションできるようにする

Googleの実装コードである[こちら](https://github.com/tensorflow/models/tree/master/research/deeplab)を参考に、オリジナルのデータを学習させてセグメンテーションできるようにします。

なお、筆者はWindows環境でAnaconda Navigatorを使いながら確認しました。
（Jupyter Notebookで動きを確認しつつ、Terminalで実行しました）

# パスの設定
今回のモジュールはdeeplabとslim以下のモジュールを使用します。
そのため、パスに「master\research」「master\research\slim」を追加する必要があります。

WindowsのAnaconda環境であれば「PYTHONPATH」にパスを通す必要があります。

```
SET PYTHONPATH=master\research;master\research\slim
```
※本当はフルパスで指定

なお、コードに直接、

```python
# From tensorflow/models/research
import sys,os

cur_dir = os.getcwd()
sys.path.append(cur_dir + "/slim")
```
でもOKです。

## エラーメッセージ
うまくパスが通ってないと、以下のようなエラーメッセージが表示されます。

```
Traceback (most recent call last):
  File "deeplab/train.py", line 22, in <module>
    from deeplab import common
ModuleNotFoundError: No module named 'deeplab'
```

# 環境の準備
まずは学習データを用意するところから。
学習データは、以下の3種類が用意されています。

- PASCAL VOC 2012
- Cityscapes
- ADE20K

今回は「PASCAL VOC 2012」を使用します。
（これをまねして、オリジナルの学習データを用意すればよいということ）

※「tensorflow/models/research/deeplab/datasets」フォルダにある、「download_and_convert_voc2012.sh」を実行すればよいのですが、せっかくなので、その内容を記述していきます

## 学習データのダウンロード
[こちら](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar)からファイルをダウンロードします。
「tensorflow/models/research/deeplab/datasets」フォルダの下に「pascal_voc_seg」というフォルダを作成し、そこに保存します。
保存したら、同じところにTARファイルの中身を展開します。

すると、こんな階層のフォルダ構成になっているはずです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/130771/b98d72db-51cb-00ca-3a1a-6d7cae17cb2b.png)

この中の「JPEGImages」が入力データ、「SegmentationClass」がセグメンテーションされているデータになります。また「ImageSets/Segmentation」に使用するファイル名（拡張子なし）のリストが設定されています。

## データ変換
この「SegmentationClass」にある画像には、インデックスカラーでセグメンテーションされているので、グレースケール画像（RGBそれぞれに同じインデックス値を設定）に変換（つまり256クラスに分類）して「SegmentationClassRaw」というフォルダに保存しておきます。

※この処理は「remove_gt_colormap.py」のことになります

**これと同じことをして、「SegmentationClassRaw」にオリジナルデータのグレースケール画像を設定すればOKということになります。**
なお、色とインデックス値とクラスとの関係は[こちら](https://qiita.com/mine820/items/725fe55c095f28bffe87)を参考にしてください。

次に、学習時に読み込みやすいように、データセットをTFRecords形式に変換します。
保存されているデータは、画像ごとに以下の通りです。

- オリジナルの画像データ
- ファイル名
- 画像フォーマット（"jpg"とか）
- 画像高さ
- 画像幅
- チャンネル数（RGBなら3）
- セグメンテーション画像のデータ
- セグメンテーション画像のフォーマット（"png"とか）

これらを、例えばトレーニング用、評価用、トレーニング＋評価用の3種類用意します。
それぞれ4ファイルに分割されています。（4ファイルに分ける理由は不明）

※この処理は「build_voc2012_data.py」「build_data.py」のことになります
　（引数等の説明は省略）

### 注意事項
読み込む学習データ数が「segmentation_dataset.py」にハードコーディングされています。

```python
_PASCAL_VOC_SEG_INFORMATION = DatasetDescriptor(
    splits_to_sizes={
        'train': 1464,
        'train_aug': 10582,
        'trainval': 2913,
        'val': 1449,
    },
    num_classes=21,
    ignore_label=255,
)

_DATASETS_INFORMATION = {
    'cityscapes': _CITYSCAPES_INFORMATION,
    'pascal_voc_seg': _PASCAL_VOC_SEG_INFORMATION,
    'ade20k': _ADE20K_INFORMATION,
}
```
オリジナルのデータを使用する場合は、ここも修正する必要があります。


```python:修正例
_ORIGINAL_INFORMATION = DatasetDescriptor(
    splits_to_sizes={
        'train': 2000,
        'eval': 100,
        'vis': 10,
    },
    num_classes=4,
    ignore_label=255,
)

_DATASETS_INFORMATION = {
    'cityscapes': _CITYSCAPES_INFORMATION,
    'pascal_voc_seg': _PASCAL_VOC_SEG_INFORMATION,
    'ade20k': _ADE20K_INFORMATION,
    'original': _ORIGINAL_INFORMATION ,
}
```

【注意】クラス数を変えた場合、学習時に以前のチェックポイントが残っているとエラーになります。ちゃんとチェックポイントを消してから学習し直してください。

## 事前学習済みデータのダウンロード
さらに、初期値として事前学習済みのデータを使用しますので、[ここから](http://download.tensorflow.org/models/deeplabv3_pascal_train_aug_2018_01_04.tar.gz)ダウンロードしておきます。TAR+ZIPになってますので、解凍・展開しておきます。

# 学習
準備したデータを使用して学習を行います。
学習は、事前学習済みデータを初期値として、用意した学習データを使用して学習を進めていきます。

※この処理は「train.py」のことになります

```:実行例
python deeplab/train.py \
  --logtostderr \
  --train_split="train" \
  --model_variant="xception_65" \
  --atrous_rates=6 \
  --atrous_rates=12 \
  --atrous_rates=18 \
  --output_stride=16 \
  --decoder_output_stride=4 \
  --train_crop_size=513 \
  --train_crop_size=513 \
  --train_batch_size=64 \
  --fine_tune_batch_norm=false \
  --tf_initial_checkpoint="./data/init_models/deeplabv3_pascal_train_aug/model.ckpt" \
  --initialize_last_layer=false \
  --last_layers_contain_logits_only=true \
  --train_logdir="./data/log/train" \
  --dataset_dir="./data/tfrecord" \
  --dataset="pascal_voc_seg"
```

## 代表的な引数
| 引数名 | 意味 | 値（例） |
|---|---|---|
| logtostderr | ログ出力の有無 | － |
| training_number_of_steps | 学習回数 | 30000 |
| train_split | 使用データ | train, val, trailval |
| model_variant | 識別モデル種類 | xception_65, mobilenet_v2 |
| atrous_rates | Atrous畳み込みの比率<br/>※複数回設定可能 | 6 |
| output_stride | 出力ストライド（atrous_rateとの組み合わせ） | 16 |
| decoder_output_stride | 入出力の空間解像度の比率 | 4 |
| train_crop_size | 画像の切り出しサイズ<br/>※XYの2回指定 | 513 |
| train_batch_size | ミニバッチのサイズ | 64 |
| fine_tune_batch_norm | Batch Normalizationの実行 | true, false<br/>※GPUで学習するときはfalse |
| dataset | データセット名 | cityscapes, pascal_voc_seg, ade20k |
| tf_initial_checkpoint | 学習済みモデル名 | deeplab\datasets\pascal_voc_seg\init_models\deeplabv3_pascal_train_aug\model.ckpt |
| initialize_last_layer | 最後のレイヤーの初期化 | true, false<br/>クラス数を変えたときはfalse |
| last_layers_contain_logits_only | logitsを最後のレイヤーとしてのみ考慮 | true, false<br/>クラス数を変えたときはtrue |
| train_logdir | ログ出力フォルダ名 | deeplab\datasets\pascal_voc_seg\exp\train_on_trainval_set\train |
| dataset_dir | データセットフォルダ名 | deeplab\datasets\pascal_voc_seg\tfrecord |

「train_crop_size」は「output_stride*k+1 (k>=1)」の値を指定してください。

## エラーメッセージ
GPUメモリが足らないと、以下のようなエラーメッセージが表示されます。

```
ResourceExhaustedError (see above for traceback): OOM when allocating tensor wit
h shape[1,64,257,257] and type float on /job:localhost/replica:0/task:0/device:G
PU:0 by allocator GPU_0_bfc
```

## 実行結果
学習済みモデル（チェックポイント）がログ出力フォルダに作成されます。

```
checkpoint
model.ckpt.data-00000-of-00001
model.ckpt.index
model.ckpt.meta
```

# 評価
学習済みデータを使用して評価を行います。
識別率やloss値を確認します。
なお、評価用の画像データもTFRecords形式にしておく必要があります。

※この処理は「eval.py」のことになります

```:実行例
python deeplab/eval.py \
  --logtostderr \
  --eval_split="val" \
  --model_variant="xception_65" \
  --atrous_rates=6 \
  --atrous_rates=12 \
  --atrous_rates=18 \
  --output_stride=16 \
  --decoder_output_stride=4 \
  --eval_crop_size=513 \
  --eval_crop_size=513 \
  --max_resize_value=512 \
  --min_resize_value=128 \
  --checkpoint_dir="./data/log/train" \
  --eval_logdir="./data/log/eval" \
  --dataset_dir="./data/tfrecord" \
  --max_number_of_evaluations=1 \
  --eval_interval_secs=0 \
  --dataset="pascal_voc_seg"
```

## 代表的な引数
| 引数名 | 意味 | 値（例） |
|---|---|---|
| logtostderr | ログ出力の有無 | － |
| eval_split| 使用データ | train, val, trailval |
| model_variant | 識別モデル種類 | xception_65, mobilenet_v2 |
| atrous_rates | Atrous畳み込みの比率<br/>※複数回設定可能 | 6 |
| output_stride | 出力ストライド（atrous_rateとの組み合わせ） | 16 |
| decoder_output_stride | 入出力の空間解像度の比率 | 4 |
| eval_crop_size | 画像の切り出しサイズ<br/>※XYの2回指定 | 513 |
| max_resize_value | 画像の最大サイズ | 512 |
| min_resize_value | 画像の最小サイズ | 128 |
| dataset | データセット名 | cityscapes, pascal_voc_seg, ade20k |
| checkpoint_dir| チェックポイントフォルダ名 | deeplab\datasets\pascal_voc_seg\exp\train_on_trainval_set\train |
| eval_logdir | ログ出力フォルダ名 | deeplab\datasets\pascal_voc_seg\exp\train_on_trainval_set\eval |
| dataset_dir | データセットフォルダ名 | deeplab\datasets\pascal_voc_seg\tfrecord |
| max_number_of_evaluations | 繰り返し数 | 1 |
| eval_interval_secs | 繰り返し待ち時間（秒） | 0 |

「model_variant」「atrous_rates」「output_stride」「decoder_output_stride」は学習と同じにする必要があります。
「eval_crop_size」は「output_stride*k+1 (k>=1)」の値を指定してください。
「max_resize_value」は最大「vis_crop_size-1」を指定してください。
※「max_resize_value」を指定したら「min_resize_value」も指定してください

## エラーメッセージ
「max_resize_value」を指定せずに「eval_crop_size-1」より大きい値の画像を指定すると、以下のようなエラーになります。

```
INFO:tensorflow:Error reported to Coordinator: <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>, Shape mismatch in tuple component 1. Expected [513,513,3], got [3024,4032,3]
	 [[Node: batch/padding_fifo_queue_enqueue = QueueEnqueueV2[Tcomponents=[DT_INT64, DT_FLOAT, DT_STRING, DT_INT32, DT_UINT8, DT_INT64], timeout_ms=-1, _device="/job:localhost/replica:0/task:0/device:CPU:0"](batch/padding_fifo_queue, Reshape_3/_1607, add_2/_1609, ParseSingleExample/ParseSingleExample:1, add_3/_1611, batch/packed, Reshape_6/_1613)]]
```

## 実行結果
標準出力に、以下のように表示されます。

```
INFO:tensorflow:Evaluation [1/2]
INFO:tensorflow:Evaluation [2/2]
INFO:tensorflow:Finished evaluation at 2018-09-07-23:40:26
miou_1.0[0.194943964]
```

mIoU（mean Intersection over Union）とは、正しくセグメンテーションできた割合の評価画像全部の平均の値です。詳しくは[こちら](https://qiita.com/panchokyutech/items/2c972ede2bc883597a87)を参照してください。

# 可視化
学習済みデータを使用して、セグメンテーションした画像を取得します。
なお、可視化用の画像データもTFRecords形式にしておく必要があります。

※この処理は「vis.py」のことになります

```:実行例
python deeplab/vis.py \
  --logtostderr \
  --vis_split="val" \
  --model_variant="xception_65" \
  --atrous_rates=6 \
  --atrous_rates=12 \
  --atrous_rates=18 \
  --output_stride=16 \
  --decoder_output_stride=4 \
  --vis_crop_size=513 \
  --vis_crop_size=513 \
  --max_resize_value=512 \
  --min_resize_value=128 \
  --checkpoint_dir="./data/log/train" \
  --vis_logdir="./data/log/vis" \
  --dataset_dir="./data/tfrecord" \
  --max_number_of_iterations=1 \
  --eval_interval_secs=0 \
  --dataset="pascal_voc_seg"
```

## 代表的な引数
| 引数名 | 意味 | 値（例） |
|---|---|---|
| logtostderr | ログ出力の有無 | － |
| vis_split| 使用データ | train, val, trailval |
| model_variant | 識別モデル種類 | xception_65, mobilenet_v2 |
| atrous_rates | Atrous畳み込みの比率<br/>※複数回設定可能 | 6 |
| output_stride | 出力ストライド（atrous_rateとの組み合わせ） | 16 |
| decoder_output_stride | 入出力の空間解像度の比率 | 4 |
| vis_crop_size | 画像の切り出しサイズ<br/>※XYの2回指定 | 513 |
| max_resize_value | 画像の最大サイズ | 512 |
| min_resize_value | 画像の最小サイズ | 128 |
| dataset | データセット名 | cityscapes, pascal_voc_seg, ade20k |
| checkpoint_dir| チェックポイントフォルダ名 | deeplab\datasets\pascal_voc_seg\exp\train_on_trainval_set\train |
| vis_logdir | ログ出力フォルダ名 | deeplab\datasets\pascal_voc_seg\exp\train_on_trainval_set\vis |
| dataset_dir | データセットフォルダ名 | deeplab\datasets\pascal_voc_seg\tfrecord |
| max_number_of_iterations | 繰り返し数 | 1 |
| eval_interval_secs | 繰り返し待ち時間（秒） | 0 |

「model_variant」「atrous_rates」「output_stride」「decoder_output_stride」は学習と同じにする必要があります。
「vis_crop_size」は「output_stride*k+1 (k>=1)」の値を指定してください。
「max_resize_value」は最大「vis_crop_size-1」を指定してください。
※「max_resize_value」を指定したら「min_resize_value」も指定してください

## 実行結果
指定したログ出力フォルダの下の「segmentation_results」に、オリジナルの画像とセグメンテーションした画像のペアが保存されます。
ファイル名は順番に番号が振られ、オリジナル画像は「000000_image.png」、セグメンテーション画像は「000000_prediction.png」となります。