四畳半テクノポリス

コロナのストレスで気が狂い、D進した院生

やったー!PytorchでVGG11できたよー!

目的

大学院への進学に備えディープラーニングのお勉強をしていたのですが、Pytrochのチュートリアルに飽きてきたのでVGG11を実装してみました。VGG11というのは畳み込み8層と全結合3層からなるニューラルネットワークです。兄弟にVGG16とかも居ます。最初はVGG16を作ったが学習が遅すぎて飽きちゃったので無かったことにする。

やったこと

 今回はそのVGG11を使ってMNISTの分類をやってみました。MNISTというのはみんなご存知でしょうが手書き数字の分類を行うためのデータセットです。28x28の手書き文字が何万枚も含まれています。

 それに対しVGGは画像分類のためのネットワークです。こちらはImageNetのような写真の分類を目的としたニューラルネットワークです。このVGGはPytorchにはデフォルトで含まれていて転移学習で使うことが出来るのですが、今回は僕のGTX1070の火力を試してみたかったので自力でガバガバ実装しました。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.upsample = nn.Upsample(scale_factor=8, mode='nearest')
        self.conv1 = nn.Conv2d(1, 64, 3, padding=1)
        self.pool1 = nn.MaxPool2d(2, 2)

        self.conv2 = nn.Conv2d(64, 128, 3, padding=1)
        self.pool2 = nn.MaxPool2d(2, 2)

        self.conv3_1 = nn.Conv2d(128, 256, 3, padding=1)
        self.conv3_2 = nn.Conv2d(256, 256, 3, padding=1)
        self.pool3 = nn.MaxPool2d(2, 2)

        self.conv4_1 = nn.Conv2d(256, 512, 3, padding=1)
        self.conv4_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.pool4 = nn.MaxPool2d(2, 2)

        self.conv5_1 = nn.Conv2d(512, 512, 3, padding=1)
        self.conv5_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.pool5 = nn.MaxPool2d(2, 2)

        self.fc6 = nn.Linear(25088, 4096)
        self.drop6 = nn.Dropout2d()
        self.fc7 = nn.Linear(4096, 4096)
        self.drop7 = nn.Dropout2d()
        self.fc8 = nn.Linear(4096, 10)

    def forward(self, x):
        x = self.upsample(x)

        x = F.relu(self.conv1(x))
        x = self.pool1(x)

        x = F.relu(self.conv2(x))
        x = self.pool2(x)

        x = F.relu(self.conv3_1(x))
        x = F.relu(self.conv3_2(x))
        x = self.pool3(x)

        x = F.relu(self.conv4_1(x))
        x = F.relu(self.conv4_2(x))
        x = self.pool4(x)

        x = F.relu(self.conv5_1(x))
        x = F.relu(self.conv5_2(x))
        x = self.pool5(x)

        x = x.view(-1, 25088)

        x = F.relu(self.fc6(x))
        x = self.drop6(x)
        x = F.relu(self.fc7(x))
        x = self.drop7(x)
        x = self.fc8(x)
        return x

 本当はレイヤーごとに関数化してまとめたりするみたいなんですが初心者だしよくわかんないので全部まとめて一つの関数に書きました。しょうがないね。

 一応元のVGGと違うところがいくつかあるので書いておくが、まず入力の段で元のVGG11は224x224のサイズの画像を入力するように作ってあったのでMNISTを8倍にバイリニアで引き伸ばしてから入力している。また、元のVGG11は1000クラスのやっていたが今回はMNISTなので出力の段が10個なっている。

 ここではプログラム全体を載せ無いが、このコードをGPUで実行する場合バッチサイズはある程度大きくしたほうが良いと思われる(CPUでやると遅すぎて辛いくなる)。というのもGPUというのはCPUよりも並列性は高いがメモリ転送のオーバーヘッドが大きいという性質がある。よって、ある程度バッチサイズを大きくしないとメモリ転送のボトルネックが並列性の恩恵を上回ってしまうのである。ある程度がどんくらいかはNAISTの人が書いた論文に詳しく載ってたからそっち参照してほしい。

結果

f:id:toriten1024:20190222215108p:plain

学習経過

 上に示す図がVGG11を学習させた過程loss(損失関数の結果)の変化である。ある一定の学習回数を重ねたあと急激に学習が進む。じれったいので、ここまで待つのがすげーイライラする。全体としての学習には3時間ほどの時間を要した。PCのがうるさくてあんま寝れなかった。

 テストデータに対しては99%の推論制度を叩き出した。今まで勉強していた三層のニューラルネットワークに比べると破格に高い推論制度を叩き出している。最後の方では少しlossが増えて居るように見えるからこのまま行くと過学習ボロボロになるのかもしんない。

感想

 VGG11すごいと思う他に低速な計算機でここまで深いネットワークを学習させた先人たちの凄さを感じることができた。多分フルスクラッチでこのようなネットワークを考え実装することは私にはできない。巨人の肩美味しいです(^q^)

 今までごく浅いニューラルネットワークをいじってきたことから、このような11層もあるニューラルネットワークの学習を行えたことは今後に生きる良い経験であった。というかコレが僕の初ディープラーニングかもしれない。

 実装は行ったが未だVGGの論文を読んでないのでちゃんと読む必要がある。と言うか実装してるだけで理論的な理解が全くできてないのは大きな問題である。

 学生実験の糞みたいな実験の更にクソみたいな実験レポートみたいな記事だが別にいいのだ、俺は初心者なのだ。