FCN

  1. FCN网络架构
    1. 优点
    2. 缺点
  2. 训练过程
    1. FCN 问题小贴士
    2. FCN8S代码

FCN网络架构

优点

  1. 整体的网络框架基于正常的CNN,将最后的全连接网络改为了卷积。
    这样做的效果是:
    1)网络得以接受任意维度的输入,不需要变成224x224了
    2)
  2. 在上述结构的基础上,将1000维变成21维(20种分类+背景类),再接一个反卷积层。
    这样做的效果就是:
    1)输出了21张热点图,然后通过反卷积网络变成了21张大小等于原图的Mask
    2)21张Mask出来之后就可以和真实数据做逐像素的损失函数了,进行训练
  3. 接下来是Skip结构,是将pool3和pool4层的特征引到输出。具体来说,pool3和pool4层特征图的大小是pool5层的4、2倍,所以最后的热点图要先反卷积扩大为2倍、4倍再相加,然后再反卷积扩大为16倍、8倍。得到原图尺寸。这样做是为了让Mask更加精细,之前说过这个方法或许可以被替代。

缺点

1)先得到的热点图的尺寸和最后一层卷积的特征图尺寸是相当的,在作者采用的VGG16中,尺寸为原图的1/32,这就让一层反卷积网络实现32倍的放大,结果可想而知是粗糙(coarse)的。作者后来采用了skip的方法做了精细化,但或许直接采用多层的反卷积就可以有不错的效果。
2)这一段写的过程中被删掉了,因为写着写着发现了问题,我把删掉的放到后面,可以作为思路记录。训练的时候每个点的预测值就是一个21维的向量,做交叉熵损失函数。最后预测结果采用max算法。嗯这是一个提醒自己训练和测试不同的好例子。
其实21张Mask出来之后他有采用一个最大值算法,使得最后输出的MAsk就一张,包括了每个点最可能的预测。这种方式有个缺陷就是它相当于把每个点的非极大预测值变成了0。之后loss具体是交叉熵还是单纯的3-2(这个不太可能)不太清楚,就以交叉熵来说,他的损失函数就是不合理的,因为可能本身该点像素预测第二名就是对的,但这下又要推倒重来。

训练过程

FCN 问题小贴士

  1. FCN 转置卷积,调用API的时候,kernel=2n, stride=n 来实现n被上采样,为什么?
    通常我们训练的时候可以随机初始化权重,但是在 fcn 的网络中,使用随机初始化的权重将会需要大量的时间进行训练,所以我们卷积层可以使用在 imagenet 上预训练的权重,那么转置卷积我们使用什么样的初始权重呢?这里就要用到 bilinear kernel。
  2. FCN 实现的时候先实现了VGG, 特点是小 kernel 3x3 代替大 kernel 5x5 或 7x7, 为什么这么代替
  3. FCN 实现的第一层Padding=100,为什么?有个弊端是什么?
  4. 通过怎样的变化可以让FC实现任意输入?
  5. FCN 三大贡献

FCN8S代码

#!/usr/bin/env python
# coding: utf-8
import torch.nn as nn
# In[ ]:
class FCN8s(nn.Module):
     def __init__(self, n_class = 21):
        super(FCN8s, self).__init__()
        #conv1
        self.conv1_1 = nn.Conv2d(3, 64, 3, padding=100)
        self.relu1_1 = nn.Relu(inplace=True)
        self.conv1_2 = nn.Conv2d(64, 64, 3, padding=1)
        self.relu1_2 = nn.Relu(inplace=True)
        self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/2

        #conv2
        self.conv2_1 = nn.Conv2d(64, 128, 3, padding=1)
        self.relu2_1 = nn.Relu(inplace=True)
        self.conv2_2 = nn.Conv2d(128, 128, 3, padding=1)
        self.relu2_2 = nn.Relu(inplace=True)
        self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/4

        #conv3
        self.conv3_1 = nn.Conv2d(128, 256, 3, padding=1)
        self.relu3_1 = nn.Relu(inplace=True)
        self.conv3_2 = nn.Conv2d(256, 256, 3, padding=1)
        self.relu3_2 = nn.Relu(inplace=True)
        self.conv3_3 = nn.Conv2d(256, 256, 3, padding=1)
        self.relu3_3 = nn.Relu(inplace=True)
        self.pool3 = nn.MaxPool2d(2, stride=1, ceil_mode=True) # 1/8

        #conv4
        self.conv4_1 = nn.Conv2d(256, 512, 3, padding=1)
        self.relu4_1 = nn.Relu(inplace=True)
        self.conv4_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu4_2 = nn.Relu(inplace=True)
        self.conv4_3 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu4_3 = nn.Relu(inplace=True)
        self.pool4 = nn.MaxPool2d(2, stride=1, ceil_mode=True) # 1/16

        #conv5
        self.conv5_1 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu5_1 = nn.Relu(inplace=True)
        self.conv5_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu5_2 = nn.Relu(inplace=True)
        self.conv5_3 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu5_3 = nn.Relu(inplace=True)
        self.pool5 = nn.MaxPool2d(2, stride=1, ceil_mode=True) # 1/32

        #fc6
        self.fc6 = nn.Conv2d(512, 4096, 7)
        self.relu6 = nn.Relu(inplace=True)
        self.drop6 = nn.Dropout2d()

        #fc7
        self.fc7 = nn.Conv2d(4096, 4096, 1)
        self.relu7 = nn.Relu(inplace=True)
        self.drop7 = nn.Dropout2d()

        self.score_fr = nn.Conv2d(4096, n_class, 1)
        self.score_pool3 = nn.Conv2d(256, n_class, 1)
        self.score_pool4 = nn.Conv2d(512, n_class, 1)

        self.upscore2 = nn.Convtranspose2d(n_class, n_class, 4, stride=2, bias=False)
        self.upscore8 = nn.Convtranspose2d(n_class, n_class, 16, stride=8, bias=False)
        self.upscore_pool4 = nn.Convtranspose2d(n_class, n_class, 4, stride=2 ,bias=False)

    def forward(self, x):
        h = x
        h = self.relu1_2(self.conv1_2(self.relu1_1(self.conv1_1(h))))
        h = self.pool1(h)

        h = self.relu2_2(self.conv2_2(self.relu2_1(self.conv2_1(h))))
        h = self.pool2(h)

        h = self.relu3_3(self.conv3_3(self.relu3_2(self.conv3_2(self.relu3_1(self.conv3_1(h))))))
        h = self.pool3(h)
        pool3 = h # 1/8

        h = self.relu4_3(self.conv4_3(self.relu4_2(self.conv4_2(self.relu4_1(self.conv4_1(h))))))
        h = self.pool4(h)
        pool4 = h #  1/16

        h = self.relu5_3(self.conv5_3(self.relu5_2(self.conv5_2(self.relu5_1(self.conv5_1(h))))))
        h = self.pool5(h)

        h = self.drop6(self.relu6(self.fc6(h)))

        h = self.drop7(self.relu7(self.fc7(h)))

        #第一次卷积之后 : o1 = i+198
        #第三次卷积之后 : o2 = (i+198)/8
        #第四次卷积之后 : o3 = (i+198)/16
        #第五次卷积之后 : o4 = (i+198)/32
        #经过fc6 : o5 = (o4-7+0)/1+1=(i+198)/32 - 6 =(i + 6)/32
        #经过fc7 : 不变

        #先扩大两倍 :o6 = (i+198) / 16 - 10
        #(i-1)*stride-2padding+kernel <---{(i+2p-k)/2 + 1}
        #转置卷积的padding默认为0
        h = self.upscore2(self.score_fr(h))
        upscore2 = h

        #预测pool4,大小不变
        h = self.score_pool4(pool4)
        #剪切转置卷积2倍后的大小
        h = h[:, :, 5:5 + upscore2.size([2]), 5:5 + upscore2.size()[3]]
        score_pool4c = h # 1/16
        #特征融合
        h = upscore2 + score_pool4c # 1/16

        #再扩大两倍 :o7 = (i+198) / 8 - 18
        h = self.upscore_pool4(h)
        upsorce_pool4 = h # 1/8

        #预测pool3
        h = self.score_pool3(pool3)
        #再次剪切转置卷积2倍后的大小
        h = h[:, :, 9:9 + upscore_pool4.size()[2], 9:9 + upscore_pool4.size()[3]]
        score_pool3c = h # 1/8

        #再次进行特征融合
        h = upscore_pool4 +score_pool3c # 1/8

        #最后直接转置卷积8倍:o8 = i+ 62
        h = self.upscore8(h)
        h = h[:, :, 31:31 + x.size()[2], 31:31 + x.size()[3]].contiguous()

        return(h)

请多多指教。

文章标题:FCN

本文作者:顺强

发布时间:2019-12-24, 23:59:00

原始链接:http://shunqiang.ml/paper-fcn/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏