自作のソースコードをChainer v2 alphaに対応させてみた

# Chainer v2 alpha

Chainer v2 alphaがリリースされたので、自作のソースコードを対応させてみました。
以下のサイトを参考にしました。

* [公式ドキュメント](http://docs.chainer.org/en/v2.0.0a1/index.html)
* [Chainer Meetup #4の発表スライド](https://www.slideshare.net/beam2d/chainer-v2-alpha)

# 動作環境

* OS: Windows 10(64bit)
* Python: Python 2.7.11 :: Anaconda custom (64-bit)
* GPU: GTX 1080

# 使用したリポジトリ

[以前作成したCIFAR-10の画像認識](http://qiita.com/dsanno/items/ad84f078520f9c9c3ed1)のリポジトリにChainer v2用のブランチを作成しました。

https://github.com/dsanno/chainer-cifar/tree/chainer_v2

# インストール

Chainer Meetupのスライドにあるように、以下のコマンドでインストールできました。
念のため`--no-cache-dir`をつけました。

```
$ pip install chainer --pre --no-cache-dir
$ pip install cupy --no-cache-dir
```

# とりあえず実行してみる

Chainer v2の修正により後方互換性が壊れるので、動作しないことは予想できているのですがとりあえず実行してみます。

```
$ python src/train.py -g 0 -m vgg -p model\temp9 -b 100 --iter 200 --lr 0.1 --optimizer sgd --weight_decay 0.0001 --lr_decay_iter 100,150
```

以下のエラーが発生しました。

```
Traceback (most recent call last):
  File "src\train.py", line 143, in <module>
    cifar_trainer.fit(train_x, train_y, valid_x, valid_y, test_x, test_y, on_epoch_done)
  File "c:\project_2\chainer\chainer-cifar\src\trainer.py", line 26, in fit
    return self.__fit(x, y, valid_x, valid_y, test_x, test_y, callback)
  File "c:\project_2\chainer\chainer-cifar\src\trainer.py", line 40, in __fit
    loss, acc = self.__forward(x_batch, y[batch_index])
  File "c:\project_2\chainer\chainer-cifar\src\trainer.py", line 75, in __forward
    y = self.net(x, train=train)
  File "c:\project_2\chainer\chainer-cifar\src\net.py", line 360, in __call__
    h = self.bconv1_1(x, train)
  File "c:\project_2\chainer\chainer-cifar\src\net.py", line 28, in __call__
    h = self.bn(self.conv(x), test=not train)
TypeError: __call__() got an unexpected keyword argument 'test'
```

`chainer.links.BatchNormalization`の`__call__`の引数に`test`がないにもかかわらず渡しているというエラーです。

# Chainer v2で動作するように修正する

## chainer.functions.dropoutの呼び出し引数からtrainを削除

Chainer v2からは`dropout`の引数`train`が不要になるので削除します。

修正例:

```python
修正前:
h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
修正後:
h = F.dropout(F.max_pooling_2d(h, 2), 0.25)
```

## chainer.links.BatchNormalizationの呼び出し引数からtestを削除

`BatchNormalization`の引数`test`が不要になるので`dropout`の場合と同様に削除します。

修正前:

```python
class BatchConv2D(chainer.Chain):
    def __init__(self, ch_in, ch_out, ksize, stride=1, pad=0, activation=F.relu):
        super(BatchConv2D, self).__init__(
            conv=L.Convolution2D(ch_in, ch_out, ksize, stride, pad),
            bn=L.BatchNormalization(ch_out),
        )
        self.activation=activation

    def __call__(self, x, train):
        h = self.bn(self.conv(x), test=not train)
        if self.activation is None:
            return h
        return self.activation(h)
```

修正後:

```python
class BatchConv2D(chainer.Chain):
    def __init__(self, ch_in, ch_out, ksize, stride=1, pad=0, activation=F.relu):
        super(BatchConv2D, self).__init__(
            conv=L.Convolution2D(ch_in, ch_out, ksize, stride, pad),
            bn=L.BatchNormalization(ch_out),
        )
        self.activation=activation

    def __call__(self, x): # trainを削除
        h = self.bn(self.conv(x)) # testを削除
        if self.activation is None:
            return h
        return self.activation(h)
```

## 学習中でない場合の処理をchainer.using_config('train', False)で括る

`dropout`や`BatchNormalization`の呼び出しから引数`train`, `test`を削除しました。
このままだとこれらの関数が学習中のモードで動作してしまいます。
Chainer v2からは`with chainer.using_config('train', ):`を使って学習中かどうかを制御します。

```python
    with chainer.using_config('train', False):
        # 学習中でない場合の処理(テストデータの精度計算など)
```

## 学習中かどうかをchainer.config.trainで区別する

Chainer v2から`chainer.config`が追加され、学習中かどうか、back propagationが必要かどうかなどを`config`で判断できるようになりました。
私は今まで学習中かどうかを、以下のように自作関数の`train`引数で判定していたのですが、v2からは`train`引数は必要なく`configuration.config.train`で判定すればよいです。

修正前:

```python
def my_func(x, train=True):
    if train:
        # 学習中の処理
    else:
        # 学習中でない場合の処理
```

修正後:

```python
def my_func(x):
    if chainer.config.train:
        # 学習中の処理
    else:
        # 学習中でない場合の処理
```

## back propagationが不要な場合にchainer.using_config('train', False)で括る

back propagationが不要な処理を`chainer.using_config('train', False)`で括ります。
今まで`chainer.Variable`生成時に`volatile`フラグをONにしていたケースが該当します。

# Chainer v2 alphaでは必要ないが今後(beta以降で)必要になること

## chainer.Variableの引数volatileを削除

v2 alphaの段階では残っていますが今後`chainer.Variable`の`volatile`は削除される予定です。
`volatile`の代わりに`chainer.using_config('enable_backprop', )`で制御することになります。
`chainer.functions`、`chainer.links`の呼び出しに`Variable`ではなくNumpy配列、Cupy配列を渡せるようになっているので、`Variable`の生成処理も削除する選択肢もあると思います。

修正前:

```python
    x = Variable(xp.asarray(batch_x), volatile=Train)
```

修正後:

```python
    with chainer.using_config('enable_backprop', False):
        x = Variable(xp.asarray(batch_x))
```

# 修正後の実行

```
c:\project_2\chainer-cifar>python src\train.py -g 0 -m vgg -p model\temp -b 100 --iter 200 --lr 0.1 --optimizer sgd --weight_decay 0.0001 --lr_decay_iter 100,150
DEBUG: nvcc STDOUT mod.cu
   ライブラリ C:/Users/user_name/AppData/Local/Theano/compiledir_Windows-10-10.0.14393-Intel64_Family_6_Model_58_Stepping_9_GenuineIntel-2.7.11-64/tmpiwxtcf/265abc51f7c376c224983485238ff1a5.lib とオブジェクト C:/Users/user_name/AppData/Local/Theano/compiledir_Windows-10-10.0.14393-Intel64_Family_6_Model_58_Stepping_9_GenuineIntel-2.7.11-64/tmpiwxtcf/265abc51f7c376c224983485238ff1a5.exp を作 成中

Using gpu device 0: GeForce GTX 1080 (CNMeM is disabled, cuDNN 5105)
C:\Users\user_name\Anaconda\lib\site-packages\theano-0.8.2-py2.7.egg\theano\sandbox\cuda\__init__.py:600: UserWarning: Your cuDNN version is more recent than the one Theano officially supports. If you see any problems, try updating Theano or downgrading cuDNN to version 5.
  warnings.warn(warn)
loading dataset...
start training
epoch 0 done
train loss: 2.29680542204 error: 85.5222222221
valid loss: 1.95620539665 error: 81.3800000548
test  loss: 1.95627536774 error: 80.6099999845
test time: 1.04036228008s
elapsed time: 23.5432411172
epoch 1 done
train loss: 1.91133875476 error: 76.8000000185
valid loss: 1.83026596069 error: 73.6399999559
test  loss: 1.8381768012 error: 73.2900000066
test time: 0.993011643337s
```

Chainer v2にする前からTheano周りでWarningが出ているのですが、動作しているようです。

# 最後に

Chainer v2向けの修正は難しくはないのですが、`dropout`と`BatchNormalization`の使用箇所が多かったので、その分修正量としては多くなりました。
修正の結果として、いくつかの関数が持っていた引数`train`が不要になったのでコードが少しすっきりしました。
v1向けに実装した多くのコードがv2では動かなくなると思うので、v2が正式リリースされた直後は拾ってきたv1向けのコードを動かそうとしても動かないという事象が多く見られる気がします。