前言
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就得到和輸入圖像分辨率相等像素預測值矩陣。