Chainer で、C3D を実装してみた


Facebookが論文を書いている、C3Dというモデルがあります。これは、動画の分類器です。通常、写真の分類器ですと、2次元のConvolutionを使いますが、C3Dでは時間方向に次元を拡張し、3次元のConvolutionをしています。

* 元コード: https://github.com/facebook/C3D
* 論文: https://arxiv.org/abs/1412.0767

これの、[Tensorflow版](https://github.com/frankgu/C3D-tensorflow)、[Keras版](https://github.com/axon-research/c3d-keras)、[PyTorch版](https://github.com/DavideA/c3d-pytorch)そしてオリジナルの[Caffe版](https://github.com/facebook/C3D)（但しConvolution3Dに対応するためCaffeを改造）はあるのですが、Chainer版が見当たらなかったので、移植してみました。

* c3d-chainer: https://github.com/ikeyasu/c3d-chainer

※ 一応動いていますが、元のものと同じ精度が出ているかは確認していません。

Chainer用に変換した学習済みモデルもこちらで配布しています。

https://github.com/ikeyasu/c3d-chainer/releases/

# Pretrained モデルを使ってみる

[README.md](https://github.com/ikeyasu/c3d-chainer)のとおり、モデルをダウンロードして、必要なものをインストールすれば以下のように実行できます。
以下、 https://www.youtube.com/watch?v=dM06AMFLsrc のバスケットボールの動画を予測をしています。

```bash
$ python predict.py -a c3d --model caffe_model/conv3d_deepnetA_sport1m_iter_1900000_chainer.model --mean caffe_model/train01_16_128_171_mean.npy --video dM06AMFLsrc.mp4 --labels caffe_model/labels.txt
Loaded 487 labels.
Loaded caffe_model/conv3d_deepnetA_sport1m_iter_1900000_chainer.model.
/home/ikeyasu/anaconda3/envs/chainer3/lib/python3.6/site-packages/chainer/utils/experimental.py:104: FutureWarning: chainer.functions.pooling.MaxPoolingND is experimental. The interface can change in the future.
  FutureWarning)
Position of maximum probability: 367
Maximum probability: 10.43649
Corresponding label: basketball

Top 5 probabilities and labels:
10.43649 basketball
8.61597 volleyball
8.46861 streetball
7.04241 roller derby
6.63952 freestyle wrestling
```

## コンバート

本来、Chainerでしたら、`CaffeFunction`を使って、Caffeモデルのインポートができます。が、C3Dは、Caffe自体を改造して作られているのでインポートができません。
そのため、重みのコピーを行いました。これは、[C3D Model for Keras](https://gist.github.com/albertomontesg/d8b21a179c1e6cca0480ebdf292c34d2)を参考にしています。

https://github.com/ikeyasu/c3d-chainer/blob/master/convert_caffe_model.py

を見てもらえると分かりますが、各レイヤーの重みをコピーしているだけです。

# スクラッチからTraining

また、元論文は、[Sports-1M](https://github.com/gtoderici/sports-1m-dataset)を使っているのですが、スクラッチから学習を試すには規模が大きかったので、[UCF-11](http://crcv.ucf.edu/data/UCF_YouTube_Action.php)という小ぶりなデータセットを使っています。（個人的には、Video classificationのCIFAR-10の位置づけと認識しています）

これで学習すると、以下のように、8割ちょっとの結果がでました。学習時間は、[GTX 1080 搭載の手元のPC](https://qiita.com/ikeyasu/items/db16a5d9fc68fa21b3d5)で、3時間ぐらいです。

![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/38555/1330021f-ee21-4ccb-52f4-fabea47b47f0.png "accuracy.png")

![Kobito.tlv0jt.png](https://qiita-image-store.s3.amazonaws.com/0/38555/5b68db8c-06d1-bc41-2fa5-9d22b8516d95.png "Kobito.tlv0jt.png")

実行の仕方は、こちらの[README.md](https://github.com/ikeyasu/c3d-chainer/blob/master/README.md)を見てもらえれば分かるので、ここでは実装時に工夫した点を書きたいと思います。

# データの用意

試行錯誤の時間を早めるため、ffmpeg で静止画に展開して、それを動画として読み込むためのDatasetクラスを作りました。https://github.com/ikeyasu/c3d-chainer/blob/master/datasets/UCF11.py

また、最初は、CIFAR-10と合わせて、32px の大きさで試していました。が、イマイチ結果が出ず。これは、人間の目で見ても、解像度が低すぎて何か分からないので、仕方ないかなと思います。

# Data augumentation

その他パラメータを論文に合わせて実行すると、すぐに main/accuracy が1になって、validation/accuracy が良くならない、そして学習が進むとvalidation/lossが増えるという、過学習の状態になりました。当初は、さぼって Data augumentation をしてなかったので、それが悪かったんだろうという事で、以下3つのaugumentationをしてみました。

* Random Fliping (水平方向反転)
* Random Crop (少し大きめの画像から領域をランダムにトリミング)
* Random Erase (一部をマスク)

比較のため、全てのData augmentationをOffにしたグラフを以下に掲載します。

過学習しているグラフ（Data augmentationなし）：
![Kobito.qKfO26.png](https://qiita-image-store.s3.amazonaws.com/0/38555/fdaf6ce5-23f5-f942-8dcb-9e3dd395cb9b.png "Kobito.qKfO26.png")

![Kobito.9NRrbU.png](https://qiita-image-store.s3.amazonaws.com/0/38555/508da818-eba0-1956-89d8-9ba9a693d174.png "Kobito.9NRrbU.png")

先ほどのグラフと比べて、完全に過学習していて、精度が上がっていない事が分かります。もっとも、単純にaccuracy(Classificationなので、softmax_cross_entropy)を比較しても、Data augmentationをしていないときと、そんなに変わらないようです。ただ、Lossの方は、向上しており、過学習がある程度解消している事は見て取れます。