語義分割學習系列(八)結合代碼分析FCN模型結構

前言

FCN模型結構往簡答說,就是先用VGG類似的卷積網絡進行特徵提取,然後再對特徵圖進行反捲積(deconvolution)來將其投影到像素空間從而實現逐個逐個像素的分類。 

結合代碼分析模型結構

說起來簡單,不過還是需要結合代碼把其細節部分講清楚。

在train.py中,只用了兩行代碼就創建了FCN模型。

vgg_model = models.VGGNet(requires_grad=True)
fcn_model = models.FCN8s(pretrained_net=vgg_model, n_class=n_class)

這兩行代碼的實現部分在models.py中,它是先創建vgg模型,然後基於它再創建FCN模型。

我們進去models.py先看看class VGGNet的構造函數。

    def __init__(self, pretrained=True, model='vgg16', requires_grad=True, remove_fc=True, show_params=False):
        super().__init__(make_layers(cfg[model]))
        self.ranges = ranges[model]

        if pretrained:
            vgg16 = models.vgg16(pretrained=False)
            vgg16.load_state_dict(torch.load('/home/xxx/models/vgg16-397923af.pth'))
            # exec("self.load_state_dict(models.%s(pretrained=True).state_dict())" % model)

        if not requires_grad:
            for param in super().parameters():
                param.requires_grad = False

        if remove_fc: # delete redundant fully-connected layer params, can save memory
            del self.classifier

        if show_params:
            for name, param in self.named_parameters():
                print(name, param.size())

這段代碼有幾個注意點:

1) 參數model='vgg16'  因爲 VGG模型有VGG11, VGG13, VGG16以及VGG19等類型,這裏使用model缺省參數VGG16。

2)通過cfg[model]就可以獲得對應VGG模型配置文件,然後通過make_layers()就把VG

3)因爲pretrained參數爲True,所以需要加載一個預訓練好的模型放到本地目錄。因爲該模型比較大,所以最好先下載好。

4)remove_fc爲True,因爲FCN是全卷積網絡模型,所以需要把VGG最後的全連接層去掉。

VGG提取特徵後,FCN模型就開始對特徵圖使用deconvolution進行upsample,按不同的upsample方式,FCN可以分爲FCN8s,FCN16s以及FCN32s。 如下圖所示。

首先要搞清楚的一點,就是VGG16一共有5個block,每個block的最後一層就是MaxPooling,對應到上圖就是pool1,pool2,pool3,pool4以及pool5。

FCN32s就是直接對pool5進行32倍的放大(相當於stride爲32的反捲積)變回輸入圖像分辨率大小的像素預測空間;FCN16s則是pool5的2倍放大+pool4,然後再16倍放大到輸入圖像大小的空間;FCN8s則是pool3加上(pool4+pool5的2倍upsample)的2倍upsample,再8倍upsample到輸入圖像大小。 從這裏可以看出,FCN32s最爲簡單粗暴,所以準確度相對最低,而FCN8s考慮了3個pool層並融合,所以相對較爲精細。

下面是FCN8s的forward函數,它在train和predict時被調用:

    def forward(self, x):
        output = self.pretrained_net.forward(x)
        x5 = output['x5']  # size=[n, 512, x.h/32, x.w/32]
        x4 = output['x4']  # size=[n, 512, x.h/16, x.w/16]
        x3 = output['x3']  # size=[n, 512, x.h/8, x.w/8]

        score = self.relu(self.deconv1(x5))                  # size=[n, 512, x.h/16, x.w/16]
        score = self.bn1(score + x4)                         # element-wise add, size=[n, 512, x.h/16, x.w/16]
        score = self.relu(self.deconv2(score))               # size=[n, 256, x.h/8, x.w/8]
        score = self.bn2(score+x3)
        score = self.bn3(self.relu(self.deconv3(score)))     # size=[n, 128, x.h/4, x.w/4]
        score = self.bn4(self.relu(self.deconv4(score)))     # size=[n, 64, x.h/2, x.w/2]
        score = self.bn5(self.relu(self.deconv5(score)))     # size=[n, 32, x.h, x.w]
        score = self.classifier(score)                       # size=[n, n_class, x.h, x.w]

        return score

 上面代碼中,x3,x4和x5分別是pool3,pool4以及pool5的動態輸出。然後對x5進行2倍upsample,並和x4進行逐像素相加,其和再做一個2倍upsample,最後和pool3逐像素相加,其和連續做3次2倍upsample就得到和輸入圖像分辨率相等像素預測值矩陣。

 

 


 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章