Python
DeepLearning
データ分析
コンピュータ将棋
Chainer

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

将棋AIで学ぶディープラーニング
第廿夜は、本書から逸脱するが、新たなNetworkモデルに拡張するために、基本に戻ってCifar10のフィッティングについて学びなおす。

やったこと

(1)そもそもCifar10のフィッティングについて
(2)そもそも今回のモデルでCifar10はフィッティングできるのか
(3)VGGモデルを変えて、Cifar10のフィッティング精度を見る

(1)そもそもCifar10のフィッティングについて

いきなりですが、以下を参考にしました。
【参考】
ChainerによるCIFAR-10の一般物体認識 (2)
mitmul/chainer-cifar10
コードはほとんど②のコードを使わせていただきました。
ただし、OpenCV(Augmentation)は使わないのでコメントアウトします。
そして、Networkモデルもこのサイトに示されているように、VGGLikeのモデルのところで大きく精度が変わっており、モデルの複雑さとトレードオフだと考えると、やはりVGGLikeモデルが今回の学習には一番いいと考えました。
一方、現在使っているモデルは、ほぼ①のモデルと同じくしてネットワークを少し深くしたようなものと、そこにResnetを利用したものと考えられます。

(2)そもそも今回のモデルでCifar10はフィッティングできるのか

モデル的にはかなり似ているので、それではそもそもPolicyとかのNetworkモデルでCifar10は学習できるのかをやってみました。
結論から言うと、なぜか失敗。
Policyだったからだと思うのとどうもパラメータの調整がうまくできませんでした。
ということで、一応動いたのは以下のモデル。

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)でごまかした所為なのかもしれませんが、いずれにしても失敗。
ここは仕方なく、今回はあきらめました。

(3)VGGモデルを変えて、Cifar10のフィッティング精度を見る

ということで、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
accuracy.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
accuracy.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
accuracy.png
これら3つを比較するとほぼ似たような精度でよくフィッティング出来ていると思います。

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の基本としてCifar10のフィッティングをおさらいした
・VGGLikeモデルについて、Networkモデルの違いによるフィッティング精度を見た
・将棋AIで使っているモデルでCifar10のフィッティングができるかどうか試みたが失敗した

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