PSPNet

 

ResNet 主干特征提取

残差网络根据堆叠的层数不同,采用两个不同的单元。

ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)     //18
ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)    // 34
ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)     //50
ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)     //101
ResNet(Bottleneck, [3, 8, 36, 3], **kwargs)     //152
 
为了保证可以相加,如果经过卷积后输出的h、w发生改变 ,在分支上会加上一个下采样的单元
    if stride != 1 or self.inplanes != planes * block.expansion:
      # 1x1卷积,保证维度相同,可以相加
      downsample = nn.Sequential(
          nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False),
          norm_layer(planes * block.expansion)
      )

相比于图像分类采用的残差网络,在PSPnet中后两个layer中strides=1,即不对图像进行下采样,最终得到 /8的feature_map

    self.layer1 = self._make_layer(block, 64, layers[0], norm_layer=norm_layer)
    self.layer2 = self._make_layer(block, 128, layers[1], stride=2, norm_layer=norm_layer)
 
 self.layer3 = self._make_layer(block, 256, layers[2], stride=1, dilation=2, norm_layer=norm_layer)
 self.layer4 = self._make_layer(block, 512, layers[3], stride=1, dilation=4, norm_layer=norm_layer)
 
为什么只进行了8倍的下采样呢?因为下采样越多对于图像的细节信息损失的就越多,为了避免太多细节信息的损失
所以相对于以前的分类问题,只进行了8倍的下采样。但这会带来一个新的问题,卷积操作的视野域减小
输出的feature_map上损失了全局信息。如何解决视野域的问题呢?答案就是空洞卷积。即在3*3卷积核中间填充0。
空洞卷积有一个参数可以设置dilation rate,具体含义就是在卷积核中填充dilation rate-1个0,
因此,当设置不同dilation rate时,感受野就会不一样,也即获取了多尺度信息。其计算输出的方式与普通卷积一样
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,  padding=dilation, dilation=dilation, bias=False)
当kernel_size = 3 , striides=1时,设置dilation=2, kernel_szie 被扩充为5 , 当padding=2 时,输出的分辨率和输入保持一致 
而视野域相当于kernel_size = 5 的视野域。
还有一个好处,相比于分类采用的下采样32倍的残差网络,参数量并没有增加,所以可以采用迁移学习,直接用预训练网络的权重
 

Pyramid parsing module(PPM)

  在feature map上做不同尺度的自适应平均池化(AdaptiveAvgPool2d)

    prior = nn.AdaptiveAvgPool2d(output_size=bin_sz)
  通过指定输出的大小,自适应调节kernel_size 和 strides 来进行平均池化
  在PSPNet 中分别进行了四种不同尺度的均值池化,分别是(1, 2, 3, 6)
  
  最后对这四个特征层进行上采样到与原feature_map同样大小,并于feature_map进行堆叠
    pyramids = [features]
    pyramids.extend([F.interpolate(stage(features), size=(h,w), mode='bilinear', align_corners=True)
               for stage in self.stages])
    torch.cat(pyramids, dim=1)
 
class _PSPModule(nn.Module):
  def __init__(self, in_channels, bin_sizes, norm_layer):
    super(_PSPModule, self).__init__()
    out_channels = in_channels // len(bin_sizes)

    # 不同大小均值池化,获得不同大小视野域
    self.stages = nn.ModuleList([self._make_stages(in_channels, out_channels, b_s, norm_layer)
                      for b_s in bin_sizes])
    
    self.bottleneck = nn.Sequential(nn.Conv2d(in_channels+out_channels*len(bin_sizes), out_channels, kernel_size=3, padding=1, bias=False), 
                      norm_layer(out_channels),
                      nn.ReLU(inplace=True), 
                      nn.Dropout2d(0.1)) 
    
  def _make_stages(self, in_channels, out_channels, bin_sz, norm_layer):
    # 采用池化下采样
    prior = nn.AdaptiveAvgPool2d(output_size=bin_sz)
    # 1X1 压缩维度
    conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
    bn = norm_layer(out_channels)
    relu = nn.ReLU(inplace=True)

    return nn.Sequential(prior, conv, bn, relu)

  def forward(self, features):
    h, w = features.size(2), features.size(3)
    pyramids = [features]
    # 组合不同视野域信息和浅层信息
    # 双线性插值 上采样
    pyramids.extend([F.interpolate(stage(features), size=(h,w), mode='bilinear', align_corners=True)
             for stage in self.stages
            ])
    
    output = self.bottleneck(torch.cat(pyramids, dim=1))

    return output

 PSPNet总体结构

class PSPNet(nn.Module):
  def __init__(self, num_classes, backbone=resnet152, in_channels=3, pretrained=True, use_aux=True, freeze_bn=False,
          freeze_backbone=False):
    super(PSPNet, self).__init__()
    norm_layer = nn.BatchNorm2d
    model = backbone(pretrained, norm_layer=norm_layer)

    # 全连接输入单元数 :  512 * block.expansion
    m_out_sz = model.fc.in_features
    print("全连接层输入", m_out_sz)

    self.use_aux = use_aux

    # conv1, bn1, relu, maxpool
    self.initial = nn.Sequential(*list(model.children())[:4])

    if in_channels != 3:
      self.initial[0] = nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)
    self.initial = nn.Sequential(*self.initial)

    self.layer1 = model.layer1
    self.layer2 = model.layer2
    self.layer3 = model.layer3
    self.layer4 = model.layer4

    # 2048*n*n->2048*1*1->512*1*1->512*n*n
    # 2048*n*n->2048*2*2->512*2*2->512*n*n
    # 2048*n*n->2048*3*3->512*3*3->512*n*n
    # 2048*n*n->2048*6*6->512*6*6->512*n*n

    # (2048+512*4)*n*n -> 512*n*n  -> num_classes*n*n
    self.master_branch = nn.Sequential(
      _PSPModule(m_out_sz, bin_sizes=[1, 2, 3, 6], norm_layer=norm_layer),
      nn.Conv2d(m_out_sz // 4, num_classes, kernel_size=1)
    )

    # 1024*2n*2n -> 512*2n*2n -> num_classes*2n*2n
    self.auxiliary_branch = nn.Sequential(
      nn.Conv2d(m_out_sz // 2, m_out_sz // 4, kernel_size=3, padding=1, bias=False),
      norm_layer(m_out_sz // 4),
      nn.ReLU(inplace=True),
      nn.Dropout2d(0.1),
      nn.Conv2d(m_out_sz // 4, num_classes, kernel_size=1)
      )

    #参数初始化
    initialize_weights(self.master_branch, self.auxiliary_branch)

        
    if freeze_bn:
      self.freeze_bn()
        
    if freeze_backbone:
      set_trainable([self.initial, self.layer1, self.layer2, self.layer3, self.layer4], False)

  def forward(self, x):
    input_size = (x.size(2), x.size(3))
    x = self.initial(x)    #/4
    x = self.layer1(x)     #/4
    x = self.layer2(x)     #/8
    x = self.layer3(x)     #/8  dilated = True
    x_aux = x
    x = self.layer4(x)     #/8

    output = self.master_branch(x)   # num_classes  /8  /8
     # num_classes /1  /1
    output = F.interpolate(output, size=input_size, mode='bilinear')

    if self.training and self.use_aux:
      # num_classes  /8  /8
      aux = self.auxiliary_branch(x_aux)
      # num_classes /1  /1
      aux = F.interpolate(aux, size=input_size, mode='bilinear')

      return output, aux

    return output

 

 
 
  
posted @ 2020-12-25 23:55  learningcaiji  阅读(1429)  评论(0编辑  收藏  举报