Fully Convolutional NetworksにVGGの学習済みパラメータを使ってみたときのメモ

GPUを買ったので，DNNを試してみる．

CNN＋fine-tuneによる画像分類は[以前やった](https://github.com/eisoku9618/kuroiwa_demos/tree/master/python/classification/chainer-fine-tune)ので，今回はFCNを試してみる．

1. 論文を理解する
2. 自分で実装する

くらいをやってFCNについてある程度把握しておく．

ソースは https://github.com/eisoku9618/kuroiwa_demos/tree/master/python/semantic_segmentation/fcn


# 参考サイト

- 元論文： https://arxiv.org/pdf/1411.4038.pdf
- 学習データ：
 http://host.robots.ox.ac.uk/pascal/VOC/voc2012

## 解説サイト
- https://github.com/wkentaro/fcn
- http://www.mathgram.xyz/entry/chainer/fcn
- http://seiya-kumada.blogspot.jp/2016/03/fully-convolutional-networks-chainer.html
- http://www.mathgram.xyz/entry/keras/fcn

基本的にはこれらのプログラムをガッチャンコしたもの

# まず論文を読む

https://arxiv.org/pdf/1411.4038.pdf

Fully Convolutional Networks for Semantic Segmentation

UC Berkeleyの人がCVPR2015に出した論文．

## Abstract
AlexNet / VGG net / GoogLeNetを使ってfine-tuneができるらしい．

## Introduction
Sementic Segmentationはsemanticとlocationのどっちを取るか的な課題を持つ．大域的な情報は何があるかを教えてくれて，局所的な情報はそれがどこにあるかを教えてくれる．

## Related work
レイヤーにまたがって特徴量をフュージョンすることで，非線形な局所・大局表現を行う．

## Segmentation Architecture
### From classifier to dense FCN

- VGG 16-layer netを採用
- GoogLeNetは最後のloss層だけ使う．average pooling層は使わない
- それぞれのnetの最後のFully Connected層をConvolution層にすげ替える

FCN-AlexNet vs FCN-VGG16 vs FCN-GoogLeNetを比較して，FCN-VGG16がbsetらしい．

### Combining what and where
FCN-32sよりもFCN-8sの方が詳細に表現できている．32とか8とかはstrideの値らしいので．

### Experimental framework
Optimization: SGD with momentum
minibatch size: 20 images
learning rate: $10^{-4}$ for FCN-VGG16


# 分かったこと
FCN32s -> FCN16s -> FCN8sの順番に学習させていくらしい．
FCN32sだけ試してみた．

また，世の中に出ている日本語解説記事はfc層を省きがちなことに注意．ここでは省いていない．

## VGG16はchainerに入っている
https://github.com/eisoku9618/kuroiwa_demos/pull/110/files#diff-05a35d5dd788d339bede949d5f16091dR31 でダウンロードできて便利

```python
from chainer.links import VGG16Layers
vgg16_model = VGG16Layers()
```

でok

## fcnのconv1_2のpadを100にするとメモリをめっちゃ食う
original実装の https://github.com/shelhamer/fcn.berkeleyvision.org/blob/master/voc-fcn32s/net.py#L28 で100になっていて，その理由は https://github.com/shelhamer/fcn.berkeleyvision.org#frequently-asked-questions に書いてあるが，これだと画像サイズが大きくなってしまって，メモリを10GBとか食う．
これだと手元のGPUは4GBなので，メモリが足りずに実行できない．

padを100にしているのは任意のサイズの画像を受け付けられるように簡単にしたまでで，入力前にVGGの入力である224にresizeするようにして，解決．

## batch_size / iteration / epoch

1000個の学習データがあったとして，batch_sizeを500個にすると，1epochで2iterationが回る．
1000個の学習データがあったとして，batch_sizeを100個にすると，1epochで10iterationが回る．

iterationの度に重み行列が更新されるので，batch_sizeが小さくなると重み行列が更新されがちになる．これをiterationをたくさん回せる，と言うこともある．
batch_sizeが大きすぎると，iterationが回しづらくなる．
batch_sizeが小さすぎると，重みの更新があっちゃこっちゃ行きがち．

というバランスを取る必要があるみたい．
今回はmemoryが4GBなので，batch_sizeは否応なく1になった．

## chainerが抽象化されていた
おしゃれになっていた．
ただ，学習させたmodelを使って，適当な画像を入力して，その結果を見る，というやり方が分からず，めんどくなって諦めた．
ここはちゃんと調べたいところ．

まず，``extensions.snapshot()``でtrainerは保存されているが，これだけではmodelを呼び出せないのかどうかを知りたい．分からなかったので，仕方なく，``extensions.snapshot_object``でmodelを直接saveしてみた．

![b.png](https://qiita-image-store.s3.amazonaws.com/0/25325/68239c63-2de0-f0bf-9a82-1a6d968819ed.png)

```python
if __name__ == "__main__":
    model = FCN32s(num_output=21)
    model = L.Classifier(model)

    chainer.serializers.load_npz("./result2/model_iter_102480", model)
    import fcn.utils
    import scipy.misc
    import numpy as np
    img = PIL.Image.open("/tmp/2007_000129.jpg")
    img = np.array(img, dtype=np.uint8)
    img = scipy.misc.imresize(img, (224, 224))
    datum = img.astype(np.float32)
    datum = datum[:,:,::-1]
    datum -= VOC2012ClassSeg.mean_bgr
    datum = datum.transpose((2, 0, 1))

    lbl_pred = chainer.functions.argmax(model.predictor(datum.reshape(1, 3, 224, 224)), axis=1).data[0]

    print fcn.datasets.VOC2012ClassSeg.class_names
    viz = fcn.utils.visualize_segmentation(
        lbl_pred=lbl_pred, img=img, n_class=21,
        label_names=VOC2012ClassSeg.label_names)
    import skimage
    skimage.io.imsave("/tmp/aaaaa.png", viz)
```

こんな感じで無理やり可視化すると上のようになる．``pip install fcn``が必要．
70回くらいepochを回した結果で，動いている気はする．


## 怪しい部分

1. 学習データを読み込む部分
   - https://github.com/eisoku9618/kuroiwa_demos/blob/master/python/semantic_segmentation/fcn/dataset/voc.py#L53-L72
   - 怪しいのは2箇所
        - RGBの入れ替えあってる？ PILとscipy.misc.imreadの違いとか分かっていない．
        - 縮小している部分微妙．RGBの方は良いけど，label画像を1/3にresizeするとして，周囲9マスの平均ではダメで，周囲9マスの多数決にならないといけないはず．labelだから平均ではダメという意味．

2. fcnの最後のdeconvolutionのところ
   - https://github.com/eisoku9618/kuroiwa_demos/blob/master/python/semantic_segmentation/fcn/model/fcn_model.py#L44
   - ここは，入力画像の縦横サイズ・出力画像の縦横サイズのみ決まっていて，stride / ksize / padに指定がないから，この3個中2個は任意に決まるけど，どうやって決めればいいか不明．
      - https://github.com/shelhamer/fcn.berkeleyvision.org/blob/master/voc-fcn32s/net.py#L28 とか　http://seiya-kumada.blogspot.jp/2016/07/fully-convolutional-networks-chainer-1.html　では ksizeが64，strideが32になっているが．．．どうやって決めている？

式としては，わかりやすさのために，$IN=deconvの入力$，$OUT=deconvの出力$として，deconvはconvの逆であることから，

$$
IN = \frac{OUT - ksize + pad * 2}{stride} + 1
$$

となる．
例えば，resizeしないとして，入力画像が224だとしたら，$OUT=224*224$で，$IN=1*1$となるので，右辺の分数は0になればよくなる．
ksizeが64で，strideが32だとすると，padは80になって，これでいいのかなぁという気持ちになる．

ので，今回はresizeすることにして，$IN=1*1$，$OUT=224*224$で，strideは不定なのでとりあえず32にして，padは小さいほうが良い気もするので0にして，その結果，ksizeが224になった．

とても怪しい．