転移学習モデルを利用して画像分類 with Pytorch

はじめに

プログラム全体はgithub下記リンクにアップロードしています。

記事内で触れない部分についてはこちらのリンクを参照にしてください。

https://github.com/naru-byte/transfer_learning/tree/master/code_pytorch

前回記事と同様ImageNet学習済みモデルを利用してCaltech101のクラス分類を行います。
ディレクトリやフォルダ名等も前回の記事と同じであることを前提としています。

Pytorch

Pytorchは深層学習のモデルを構築するためのライブラリの1つです。

近年研究における使用率がかなり伸びており、開発の主体に用いられることも増えている印象です。

MONOistさん
PFNがChainerの開発を終了しPyTorchへ移行、西川社長「非常に大きな決断」
OpenAIさん
OpenAI→PyTorch

今回はtorchvisionのmodelsに用意されているvggを使用します。

torchvision vgg

呼び出すとVGGのモデル構造が返ってきます。

特徴抽出部(下出力結果の"features")とクラス分類部("classifier")に分けられています。
引数にはpretrainedとprogressがあり、pretrainedをTrueにすることで学習済みパラメータが利用できます。今回は転移学習をしたいのでpretrainedにはTrueを渡しましょう。

import torch
from torchvision import models

print(models.vgg16())
--以下print結果--

VGG(
 (features): Sequential(    
   (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (1): ReLU(inplace=True)
   (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (3): ReLU(inplace=True)
   (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
   (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (6): ReLU(inplace=True)
   (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (8): ReLU(inplace=True)
   (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
   (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (11): ReLU(inplace=True)
   (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (13): ReLU(inplace=True)
   (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (15): ReLU(inplace=True)
   (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
   (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (18): ReLU(inplace=True)
   (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (20): ReLU(inplace=True)
   (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (22): ReLU(inplace=True)
   (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
   (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (25): ReLU(inplace=True)
   (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (27): ReLU(inplace=True)
   (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
   (29): ReLU(inplace=True)
   (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
 )
 (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
 (classifier): Sequential(
   (0): Linear(in_features=25088, out_features=4096, bias=True)
   (1): ReLU(inplace=True)
   (2): Dropout(p=0.5, inplace=False)
   (3): Linear(in_features=4096, out_features=4096, bias=True)
   (4): ReLU(inplace=True)
   (5): Dropout(p=0.5, inplace=False)
   (6): Linear(in_features=4096, out_features=1000, bias=True)
 )
)

vggは11,13,16,19とそれぞれにbatch normalizationを適用したモデルvggxx_bnがあります。

今回はvgg16を使用します。

参考 : https://pytorch.org/docs/stable/_modules/torchvision/models/vgg.html

転移学習モデルの構築

コードは以下のものになります。以降このコードの解説を行います。

(torch_models.py)
import torch
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision import models

#transfer model
def transfer(block, pretrained):
   #load model
   base_model = models.vgg16(pretrained=pretrained)
   #redefine model 
   num2block = [4,9,16,23,30][block-1]
   base_vgg = base_model.features[:(num2block+1)]
   return base_vgg

#classification model
def fcnet(n_size, fc_shapes, n_classes):
   layers = []
   in_shape = n_size
   for out_shape in fc_shapes:
       layers += [torch.nn.Linear(in_features=in_shape, out_features=out_shape),
                  torch.nn.ReLU(),
                  torch.nn.Dropout()
                  ]
       in_shape = out_shape
   classification = torch.nn.Linear(in_features=in_shape, out_features=n_classes)
   return torch.nn.ModuleList(layers), classification

class Network(torch.nn.Module):

   def __init__(self, block, input_shape, fc_shapes=[], n_classes=1, pretrained=True):
       super(Network, self).__init__()
       #vgg model
       self.base_vgg = transfer(block=block, pretrained=pretrained)
       #vgg model output shape
       self.n_size = self._get_conv_output(shape=input_shape)
       #classification model
       self.fc, self.disc = fcnet(n_size=self.n_size, fc_shapes=fc_shapes, n_classes=n_classes)
   
   # generate input sample and forward to get shape
   def _get_conv_output(self, shape):
       bs = 1
       input_ = Variable(torch.rand(bs, *shape))
       output_feat = self._forward_features(input_)
       n_size = output_feat.data.size(1)
       return n_size

   def _forward_features(self, x):
       x = self.base_vgg(x)
       return x

   def forward(self, x):
       x = self._forward_features(x)
       x = F.avg_pool2d(x, kernel_size=x.size()[2:])
       x = x.view(-1, self.n_size)
       for i in range(len(self.fc)):
           x = self.fc[i](x)
       x = self.disc(x)
       return x

vgg部分のコード(transfer関数)

vggのモデルは主にコード内の"transfer"関数で定義されます。

#transfer model
def transfer(block, pretrained):
   #load model
   base_model = models.vgg16(pretrained=pretrained)
   #redefine model 
   num2block = [4,9,16,23,30][block-1]
   base_vgg = base_model.features[:(num2block+1)]
   return base_vgg

1行目

   base_model = models.vgg16(pretrained=pretrained)

modelsによるvgg呼び出しです。

2,3行目

   num2block = [4,9,16,23,30][block-1]
   base_vgg = base_model.features[:(num2block+1)]

2,3回の畳み込みの後にプーリング操作を行う一連の演算はブロックと呼ばれることが多いです。

通常のvgg16の畳み込み部は5ブロックですが、入力画像サイズが小さく5回のプーリングが行えないなど、全てを使えない場合もあります。

この2行は1行目で呼び出したvggモデルの頭の数ブロックのみを利用するためのコードです。

何ブロック目まで使うかはgithub内のtorch_transfer_main.py(以下main)の27行目で設定します。(下記例は2ブロックまで)

(torch_transfer_main.py)
27 | block = [1,2,3,4,5][1]

2ブロックまでを用いるように再定義したbase_vggをprintで表示すると下のよう出力されます。
「torchvision vgg」の項に載せたvggモデル表示結果の2回目MaxPool2dまでと一致します。

Sequential(
 (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
 (1): ReLU(inplace=True)
 (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
 (3): ReLU(inplace=True)
 (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
 (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
 (6): ReLU(inplace=True)
 (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
 (8): ReLU(inplace=True)
 (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)

vgg部分のコード(_get_conv_output関数)

transferで定義したbase_vggは何ブロック目まで利用するかで出力の形が異なります。
その度にブロックに合わせて設定し直すのは手間がかかってしまうため、サイズを取得する関数を定義して利用します。

    def _get_conv_output(self, shape):
       bs = 1
       input_ = Variable(torch.rand(bs, *shape))
       output_feat = self._forward_features(input_)
       n_size = output_feat.data.size(1)
       return n_size

https://discuss.pytorch.org/t/inferring-shape-via-flatten-operator/138/4
このサイトからもってきた関数です。

classifier部分のコード

クラス分類のための全結合層です。

#classificatioin model
def fcnet(n_size, fc_shapes, n_classes):
   layers = []
   in_shape = n_size
   for out_shape in fc_shapes:
       layers += [torch.nn.Linear(in_features=in_shape, out_features=out_shape),
                  torch.nn.ReLU(),
                  torch.nn.Dropout()
                  ]
       in_shape = out_shape
   classification = torch.nn.Linear(in_features=in_shape, out_features=n_classes)
   return torch.nn.ModuleList(layers), classification

main28行目のリスト"fc_shape"の要素で次元数、要素数で層数を定義します。
他引数のn_sizeは"_get_conv_output関数"の出力、n_classesは識別するクラスの数(Caltech101では101物体+背景の102)です。

(torch_transfer_main.py)
28 | fc_shapes = [256]

for文

   for out_shape in fc_shapes:
       layers += [torch.nn.Linear(in_features=in_shape, out_features=out_shape),
                  torch.nn.ReLU(),
                  torch.nn.Dropout()
                  ]
       in_shape = out_shape

最終の識別結果を出す以前の層をfc_shapeリストを基にlayerを積み重ねてModuleListで定義します。

   classification = torch.nn.Linear(in_features=in_shape, out_features=n_classes)
   return torch.nn.ModuleList(layers), classification

最終の識別結果を出す層を定義して、それ以前の層とそれぞれ返り値とします。

定義したそれぞれvggとclassifierをtorch.nn.Moduleクラス"Network"内の"forward"関数でつなげればモデルの完成です。

おわりに

tensorflow(grpah)でも書きます。

Pytorchは初めて使ったので書き方等ご教授いただければ幸いです。

この記事が気に入ったらサポートをしてみませんか?