転移学習モデルを利用して画像分類 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は初めて使ったので書き方等ご教授いただければ幸いです。
この記事が気に入ったらサポートをしてみませんか?