pytorchでPSPNet(その1 ネットワークを作る[Feature Map])

書いてる理由

  • pytorchを基礎からもう一回

参考

pytorchによる発展ディープラーニング

https://arxiv.org/pdf/1612.01105.pdf

詳細

pytorch_work/network.py at master · ys201810/pytorch_work · GitHub

PSPNetでセマンティックセグメンテーションする。
今回からネットワークの部分に入る。

f:id:raishi12:20200312221551p:plain

PSPNetは、上の画像の通り、inputの画像(a)をFeature Map(b)で特徴抽出して、Pyramid Pooling Moudle(c)で4つの異なるサイズのFeature Mapを元のFeature MAPの大きさにUpsamplingした上でconcatしてから予測する。
これをパーツごとに作成していく。
まずはFeature Map(b)を作成するところ。

全部説明はきついので、部分部分。

# Feature Map作成部分
class PSPNet(nn.Module):
    def __init__(self, n_classes):
        super(PSPNet, self).__init__()

        block_config = [3, 4, 6, 3]  # resnet50用
        img_size = 475
        img_size_8 = 60

        self.feature_conv = FeatureMapConvolution()

        self.feature_res1 = ResidualBlockPSP(n_blocks=block_config[0], in_channels=128, mid_channels=64,
                                             out_channels=256, stride=1, dilation=1)

        self.feature_res2 = ResidualBlockPSP(n_blocks=block_config[1], in_channels=256, mid_channels=128,
                                             out_channels=512, stride=2, dilation=1)

        self.feature_dilated_res1 = ResidualBlockPSP(n_blocks=block_config[2], in_channels=512, mid_channels=256,
                                                     out_channels=1024, stride=1, dilation=2)

        self.feature_dilated_res2 = ResidualBlockPSP(n_blocks=block_config[3], in_channels=1024, mid_channels=512,
                                                     out_channels=2048, stride=1, dilation=4)

Feature Map作成部分は、(conv -> batchnorm -> relu) * 3 + maxpoolingの箇所と、ResNetの箇所からなる。
FeatureMapConvolutionが(conv -> batchnorm -> relu) * 3 + maxpoolingの箇所
ResidualBlockPSPがResNetの箇所

FeatureMapConvolutionはそのままで、以下の定義
Conv2DBatchNormReluはConv2d -> BatchNorm -> Reluをひとまとめにしたもの。
これを単純に3回繰り返して最後にmaxpoolして終了。
入力は画像で、475[width] * 475[height] * 3[channel(RGB)]
出力は、119[width] * 119[height] * 128[channel]

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

        # conv1
        conv1_conf = [3, 64, 3, 2, 1, 1, False]
        in_channels, out_channels, kernel_size, stride, padding, dilation, bias = conv1_conf
        self.cbnr1 = Conv2DBatchNormRelu(in_channels, out_channels, kernel_size, stride, padding, dilation, bias)

        # conv2
        conv2_conf = [64, 64, 3, 1, 1, 1, False]
        in_channels, out_channels, kernel_size, stride, padding, dilation, bias = conv2_conf
        self.cbnr2 = Conv2DBatchNormRelu(in_channels, out_channels, kernel_size, stride, padding, dilation, bias)

        # conv3
        conv3_conf = [64, 128, 3, 1, 1, 1, False]
        in_channels, out_channels, kernel_size, stride, padding, dilation, bias = conv3_conf
        self.cbnr3 = Conv2DBatchNormRelu(in_channels, out_channels, kernel_size, stride, padding, dilation, bias)

        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

    def forward(self, x):
        x = self.cbnr1(x)
        x = self.cbnr2(x)
        x = self.cbnr3(x)
        outputs = self.maxpool(x)

        return outputs

ResNetのところは、BottleNeckPSPとBottleNeckIdentifyPSPを通って作る。
Identifyは通常のResidual Blockの通り出力yが、y = x + F(x)で作られて
Identifyじゃない方は出力yが、y = Conv(x) + F(x)のように、xにConvとBatchNormを通してサイズを一緒にして差を取って次の層に伝播させる感じで作られている。

class BottleNeckIdentifyPSP(nn.Module):
    """ 入力と同じサイズの出力。 出力はresidualで特徴抽出したもの + 入力 """
    def __init__(self, in_channels, mid_channels, stride, dilation):
        super(BottleNeckIdentifyPSP).__init__()

        self.cbr1 = Conv2DBatchNormRelu(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, dilation=1,
                                        bias=False)
        self.cbr2 = Conv2DBatchNormRelu(mid_channels, mid_channels, kernel_size=3, stride=1, padding=dilation,
                                        dilation=dilation, bias=False)
        self.cb3 = Conv2DBatchNorm(mid_channels, in_channels, kernel_size=1, stride=1, padding=0, dilation=1, bias=False)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        conv = self.cb3(self.cbr2(self.cbr1(x)))
        residual = x
        return self.relu(conv + residual)  # y = x + F(x)


class BottleNeckPSP(nn.Module):
    """ residualだが、convを挟んでresidulaをするかしないかがIdentifyとの違い """
    def __init__(self, in_channels, mid_channels, out_channels, stride, dilation):
        super(BottleNeckPSP).__init__()

        self.cbr1 = Conv2DBatchNormRelu(in_channels, mid_channels, kernel_size=1, stride=1, padding=0,
                                        dilation=1, bias=False)
        self.cbr2 = Conv2DBatchNormRelu(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=dilation,
                                        dilation=dilation, bias=False)
        self.cb3 = Conv2DBatchNorm(mid_channels, out_channels, kernel_size=1, stride=1, padding=0,
                                   dilation=1, bias=False)

        self.cb_residual = Conv2DBatchNorm(in_channels, out_channels, kernel_size=1, stride=stride, padding=0,
                                           dilation=1, bias=False)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        conv = self.cb3(self.cbr2(self.cbr1(x)))
        residual = self.cb_residual(x)
        return self.relu(conv + residual)  # y = Conv(x) + F(x)  ConvとBatchNormを通った奴を足してる。

今日はここまで。