【将棋AI】「将棋AIで学ぶディープラーニング」を読む♪～Chainerの基本II～Cifar10から学ぶ

<img width="200" style="float: left;" alt="将棋AIで学ぶディープラーニング" src="https://qiita-image-store.s3.amazonaws.com/0/233744/ded117b9-37c7-1b41-033d-960e63ca5fe1.jpeg">
第廿夜は、本書から逸脱するが、新たなNetworkモデルに拡張するために、基本に戻ってCifar１０のフィッティングについて学びなおす。
###やったこと
（１）そもそもCifar10のフィッティングについて
（２）そもそも今回のモデルでCifar１０はフィッティングできるのか
（３）VGGモデルを変えて、Cifar１０のフィッティング精度を見る
###（１）そもそもCifar10のフィッティングについて
いきなりですが、以下を参考にしました。
【参考】
①[ChainerによるCIFAR-10の一般物体認識 (2) ](http://aidiary.hatenablog.com/entry/20151114/1447469049)
②[mitmul/chainer-cifar10 ](https://github.com/mitmul/chainer-cifar10)
コードはほとんど②のコードを使わせていただきました。
ただし、OpenCV（Augmentation）は使わないのでコメントアウトします。
そして、Networkモデルもこのサイトに示されているように、VGGLikeのモデルのところで大きく精度が変わっており、モデルの複雑さとトレードオフだと考えると、やはりVGGLikeモデルが今回の学習には一番いいと考えました。
一方、現在使っているモデルは、ほぼ①のモデルと同じくしてネットワークを少し深くしたようなものと、そこにResnetを利用したものと考えられます。
###（２）そもそも今回のモデルでCifar１０はフィッティングできるのか
モデル的にはかなり似ているので、それではそもそもPolicyとかのNetworkモデルでCifar１０は学習できるのかをやってみました。
結論から言うと、なぜか失敗。
Policyだったからだと思うのとどうもパラメータの調整がうまくできませんでした。
ということで、一応動いたのは以下のモデル。

```py
ch = 16
class PolicyNetwork(Chain):
    def __init__(self,n_class=10):
        super(PolicyNetwork, self).__init__()
        with self.init_scope():
            self.l1=L.Convolution2D(in_channels = None, out_channels = ch, ksize = 3, pad = 1)
            self.l2=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l3=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l4=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l5=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l6=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l7=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l8=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l9=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l10=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l11=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l12=L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
            self.l13=L.Convolution2D(in_channels = ch, out_channels = 10, ksize = 3, pad = 1)
            self.fc1 = L.Linear(None, 512)
            self.fc2 = L.Linear(512, n_class)
            self.l13_bias=L.Bias(shape=(10))
    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = F.relu(self.l3(h2))
        h4 = F.relu(self.l4(h3))
        h5 = F.relu(self.l5(h4))
        h6 = F.relu(self.l6(h5))
        h7 = F.relu(self.l7(h6))
        h8 = F.relu(self.l8(h7))
        h9 = F.relu(self.l9(h8))
        h10 = F.relu(self.l10(h9))
        h11 = F.relu(self.l11(h10))
        h12 = F.relu(self.l12(h11))
        h = F.relu(self.l13(h12))
        h = F.spatial_pyramid_pooling_2d(h, 3, F.MaxPooling2D)
        h=self.fc1(h)
        h=self.fc2(h)
        return self.l13_bias(F.reshape(h, (-1, 10)))
```
動きはしたが、一向に収束に向かわない。。。ずっとaccuracy=0.1のままでした。
たぶん、パラメータ調整ができずに`h = F.spatial_pyramid_pooling_2d(h, 3, F.MaxPooling2D)`でごまかした所為なのかもしれませんが、いずれにしても失敗。
ここは仕方なく、今回はあきらめました。
###（３）VGGモデルを変えて、Cifar１０のフィッティング精度を見る
ということで、VGGLikeモデルを少し変更して以下の三種類をやってみました。

```VGG.py
import chainer
import chainer.functions as F
import chainer.links as L

class VGG(chainer.Chain):
    def __init__(self, n_class=10):
        super(VGG, self).__init__()
        with self.init_scope():
            self.conv1_1 = L.Convolution2D(None, 64, 3, pad=1)
            self.bn1_1 = L.BatchNormalization(64)
            self.conv1_2 = L.Convolution2D(64, 64, 3, pad=1)
            self.bn1_2 = L.BatchNormalization(64)
            self.conv2_1 = L.Convolution2D(64, 128, 3, pad=1)
            self.bn2_1 = L.BatchNormalization(128)
            self.conv2_2 = L.Convolution2D(128, 128, 3, pad=1)
            self.bn2_2 = L.BatchNormalization(128)
            self.conv3_1 = L.Convolution2D(128, 256, 3, pad=1)
            self.bn3_1 = L.BatchNormalization(256)
            self.conv3_2 = L.Convolution2D(256, 256, 3, pad=1)
            self.bn3_2 = L.BatchNormalization(256)
            self.conv3_3 = L.Convolution2D(256, 256, 3, pad=1)
            self.bn3_3 = L.BatchNormalization(256)
            self.conv3_4 = L.Convolution2D(256, 256, 3, pad=1)
            self.bn3_4 = L.BatchNormalization(256)
            self.fc4 = L.Linear(None, 1024)
            self.fc5 = L.Linear(1024, 1024)
            self.fc6 = L.Linear(1024, n_class)
    def __call__(self, x):
        h = F.relu(self.bn1_1(self.conv1_1(x)))
        h = F.relu(self.bn1_2(self.conv1_2(h)))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.relu(self.bn2_1(self.conv2_1(h)))
        h = F.relu(self.bn2_2(self.conv2_2(h)))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.relu(self.bn3_1(self.conv3_1(h)))
        h = F.relu(self.bn3_2(self.conv3_2(h)))
        h = F.relu(self.bn3_3(self.conv3_3(h)))
        h = F.relu(self.bn3_4(self.conv3_4(h)))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.dropout(F.relu(self.fc4(h)), ratio=0.5)
        h = F.dropout(F.relu(self.fc5(h)), ratio=0.5)
        h = self.fc6(h)
        return h
```
![loss.png](https://qiita-image-store.s3.amazonaws.com/0/233744/83a34206-ca9c-b02c-35d7-92dda3733712.png)
![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/233744/edea5d85-1420-e5d6-b810-c2aa40609153.png)
少しNetworkの層を減らした以下のモデルでやってみました。

```VGG_tiny.py
class VGG_tiny(chainer.Chain):
    def __init__(self, n_class=10):
        super(VGG_tiny, self).__init__()
        with self.init_scope():
            self.conv1_1 = L.Convolution2D(None, 64, 3, pad=1)
            self.bn1_1 = L.BatchNormalization(64)
            self.conv1_2 = L.Convolution2D(64, 64, 3, pad=1)
            self.bn1_2 = L.BatchNormalization(64)
            self.conv2_1 = L.Convolution2D(64, 128, 3, pad=1)
            self.bn2_1 = L.BatchNormalization(128)
            self.conv2_2 = L.Convolution2D(128, 128, 3, pad=1)
            self.bn2_2 = L.BatchNormalization(128)
            self.conv3_1 = L.Convolution2D(128, 256, 3, pad=1)
            self.bn3_1 = L.BatchNormalization(256)
            self.conv3_2 = L.Convolution2D(256, 256, 3, pad=1)
            self.bn3_2 = L.BatchNormalization(256)
            self.fc4 = L.Linear(None, 1024)
            self.fc5 = L.Linear(1024, 1024)
            self.fc6 = L.Linear(1024, n_class)
    def __call__(self, x):
        h = F.relu(self.bn1_1(self.conv1_1(x)))
        h = F.relu(self.bn1_2(self.conv1_2(h)))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.relu(self.bn2_1(self.conv2_1(h)))
        h = F.relu(self.bn2_2(self.conv2_2(h)))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.relu(self.bn3_1(self.conv3_1(h)))
        h = F.relu(self.bn3_2(self.conv3_2(h)))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.dropout(F.relu(self.fc4(h)), ratio=0.5)
        h = F.dropout(F.relu(self.fc5(h)), ratio=0.5)
        h = self.fc6(h)
        return h
```
![loss.png](https://qiita-image-store.s3.amazonaws.com/0/233744/79372e6e-f70b-57f9-d1e0-33fca25ded2f.png)
![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/233744/092e5218-6f06-64e9-03ef-b927ab1adb7a.png)
そして、やはりBatchNormalizationが気持ち悪いということで削除する。
※Dropoutは単純に利用するパラメータをランダムにその割合で利用するように調整して、過学習を避ける工夫だが、BNは入力を再規格化してより収束しやすくしているのである意味入力調整というのがウワンはあまり好きになれない手法です。

以下では、結合が必要な入力と出力でNoneを使い、入力に応じて適切な値が入るように、そして出力も同じようにしました。また中間層の層を調整できるように変数としました。

```VGG_tiny_nBn.py
ch=64
class VGG_tiny_nBn(chainer.Chain):
    def __init__(self, n_class=10):
        super(VGG_tiny_nBn, self).__init__()
        with self.init_scope():
            self.conv1_1 = L.Convolution2D(None, ch, 3, pad=1)
            self.conv1_2 = L.Convolution2D(ch, ch, 3, pad=1)
            self.conv2_1 = L.Convolution2D(ch, ch*2, 3, pad=1)
            self.conv2_2 = L.Convolution2D(ch*2, ch*2, 3, pad=1)
            self.conv3_1 = L.Convolution2D(ch*2, ch*4, 3, pad=1)
            self.conv3_2 = L.Convolution2D(ch*4, ch*4, 3,pad=1)
            self.fc4 = L.Linear(None, 1024)
            self.fc5 = L.Linear(1024, 1024)
            self.fc6 = L.Linear(1024, n_class)

    def __call__(self, x):
        h = F.relu(self.conv1_1(x))
        h = F.relu(self.conv1_2(h))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.relu(self.conv2_1(h))
        h = F.relu(self.conv2_2(h))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.relu(self.conv3_1(h))
        h = F.relu(self.conv3_2(h))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.dropout(h, ratio=0.25)
        h = F.dropout(F.relu(self.fc4(h)), ratio=0.5)
        h = F.dropout(F.relu(self.fc5(h)), ratio=0.5)
        h = self.fc6(h)
        return h
```
![loss.png](https://qiita-image-store.s3.amazonaws.com/0/233744/ff266d0d-9af2-4725-4333-65e7b52f93a0.png)
![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/233744/cf4da274-20a0-6f31-993c-6f18031f0814.png)
これら３つを比較するとほぼ似たような精度でよくフィッティング出来ていると思います。

|model|train/loss|train/accuracy|val/loss|val/accuracy|
|:-:|:-:|:-:|:-:|:-:|
|VGG| 0.023532|0.99244|0.31353|0.9296875|
|VGG_tiny|0.090028|0.96992|0.24947|0.93008|
|VGG_tiny_nBn|0.11527|0.96155|0.25098|0.925435|
やはりこのモデルはもともとBatchNormalizationが提案される前からのモデルであり、実際にも行った先（収束した状態）は、その有無であまり異ならないようだ。
###まとめ
・Chainerの基本としてCifar１０のフィッティングをおさらいした
・VGGLikeモデルについて、Networkモデルの違いによるフィッティング精度を見た
・将棋AIで使っているモデルでCifar１０のフィッティングができるかどうか試みたが失敗した

・次回は、いよいよVGGモデルで将棋AIを作ってみようと思う