PyTorch 0.4.1 examples (コード解説) : 画像分類 – Oxford 花 17 種 (VGG)

PyTorch 0.4.1 examples (コード解説) : 画像分類 – Oxford 花 17 種 (VGG)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 08/07/2018 (0.4.1)

* 本ページは、github 上の以下の pytorch/examples と keras/examples レポジトリのサンプル・コードを参考にしています:

* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

VGG

PyTorch 0.4.1 の自作のサンプルをコードの簡単な解説とともに提供しています。
初級チュートリアル程度の知識は仮定しています。

MNIST / Fashion-MNIST / CIFAR-10 & CIFAR-100 について一通りウォークスルーした後、
17 Category Flower Dataset を題材にして AlexNet を試してみました。

今回は VGG で試します。

 

University of Oxford: 17 種フラワー・データセット

ここでは University of Oxford が提供している古典的な題材を利用します。
データセットの詳細は 17 Category Flower Dataset を参照してください。

 

VGG 11 モデル定義

VGG の実装は torchvision に含まれていますが、まずは自前で実装してみます。

※ VGG の詳細は Very Deep Convolutional Networks for Large-Scale Visual Recognition (by Karen Simonyan & Andrew Zisserman) を参照してください。

VGG は一般には VGG 16 と VGG 19 を指しますが、最初に見当をつけるためにもう少し単純化した VGG 11 を定義しました 。
この場合、抽出器の層スタックは次のようなものになります :

* ブロック 1 *
Conv2d (out_channels=64, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 2 *
Conv2d (out_channels=128, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 3 *
Conv2d (out_channels=256, kernel_size=3)
Conv2d (out_channels=256, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 4 *
Conv2d (out_channels=512, kernel_size=3)
Conv2d (out_channels=512, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 5 *
Conv2d (out_channels=512, kernel_size=3)
Conv2d (out_channels=512, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

 
そしてストレートにコーディングすれば以下のようになります。
ブロックに分けたのは便宜的なもので、nn.Sequential でまとめてかまいません :

class VGG11(nn.Module):
    def __init__(self, num_classes):
        super(VGG11, self).__init__()

        self.block1_output = nn.Sequential (
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block2_output = nn.Sequential (
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block3_output = nn.Sequential (
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block4_output = nn.Sequential (
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block5_output = nn.Sequential (
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, num_classes),
        )


    def forward(self, x):
        x = self.block1_output(x)
        x = self.block2_output(x)
        x = self.block3_output(x)
        x = self.block4_output(x)
        x = self.block5_output(x)

        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

 

VGG11 モデルを訓練 / 評価する

学習率 0.008, 0.006 と 0.004 で 50 エポック訓練してみます :

結果をまとめると :

  • 0.008 : 69.29 %
  • 0.006 : 81.89 %
  • 0.004 : 81.89 %

5.0e-3 前後であれば問題なさそうです。

 

VGG 13 (with バッチ正規化層) モデル定義

VGG 11 が一応機能しているようなので、次に VGG 13 を定義します :

* ブロック 1 *
Conv2d (out_channels=64, kernel_size=3)
Conv2d (out_channels=64, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 2 *
Conv2d (out_channels=128, kernel_size=3)
Conv2d (out_channels=128, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 3 *
Conv2d (out_channels=256, kernel_size=3)
Conv2d (out_channels=256, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 4 *
Conv2d (out_channels=512, kernel_size=3)
Conv2d (out_channels=512, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

* ブロック 5 *
Conv2d (out_channels=512, kernel_size=3)
Conv2d (out_channels=512, kernel_size=3)
MaxPool2d(kernel_size=2, stride=2)

 
◆ そのコーディングですが、バッチ正規化層と初期化を付け加えました :

class VGG13(nn.Module):
    def __init__(self, num_classes):
        super(VGG13, self).__init__()

        self.block1_output = nn.Sequential (
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block2_output = nn.Sequential (
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block3_output = nn.Sequential (
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block4_output = nn.Sequential (
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.block5_output = nn.Sequential (
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, num_classes),
        )

        self._initialize_weights()


    def forward(self, x):
        x = self.block1_output(x)
        x = self.block2_output(x)
        x = self.block3_output(x)
        x = self.block4_output(x)
        x = self.block5_output(x)
        #print(x.size())

        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                #init.orthogonal_(m.weight.data, gain=init.calculate_gain('relu'))
                init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                init.constant_(m.weight, 1)
                init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                init.normal_(m.weight, 0, 0.01)
                init.constant_(m.bias, 0)

 

VGG 13 (with バッチ正規化層) モデルを訓練 / 評価する

まとめると :

  • 0.001 : 72.44 %
  • 0.0005 : 83.46 %
  • 0.00025 : 81.10 %
  • 0.0001 : 82.68 %
 

以上