zoukankan      html  css  js  c++  java
  • 神经网络-DenseNet 18

         上一节的ResNet通过前层与后层的短路连接Shortcuts) , 加强了前后层之间的信息流通, 在一定程度上缓解了梯度消失现象, 从而可以将神经网络搭建得很深。 更进一步, 本节的主角DenseNet最大化了这种前后层信息交流, 通过建立前面所有层与后面层的密集连接, 实现了特征在通道维度上的复用, 使其可以在参数与计算量更少的情况下实现比ResNet更优的性能, 提出DenseNet的《Densely ConnectedConvolutional Networks》 也一举拿下了2017CVPR的最佳论文。

         DenseNet的网络架构如图3.19所示, 网络由多个Dense Block与中间的卷积池化组成, 核心就在Dense Block中。 Dense Block中的黑点代表一个卷积层, 其中的多条黑线代表数据的流动, 每一层的输入由前面的所有卷积层的输出组成。 注意这里使用了通道拼接(Concatnate) 操作, 而非ResNet的逐元素相加操作。

     DenseNet的结构有如下两个特性:

    ·神经网络一般需要使用池化等操作缩小特征图尺寸来提取语义特征, 而Dense Block需要保持每一个Block内的特征图尺寸一致来直接进行Concatnate操作, 因此DenseNet被分成了多个BlockBlock的数量一般为4

    ·两个相邻的Dense Block之间的部分被称为Transition层, 具体包括BNReLU1×1卷积、 2×2平均池化操作。 1×1卷积的作用是降维, 起到压缩模型的作用, 而平均池化则是降低特征图的尺寸,

    具体的Block实现细节如图3.20所示, 每一个Block由若干个Bottleneck的卷积层组成, 对应图3.19中的黑点。 BottleneckBNReLU1×1卷积、 BNReLU3×3卷积的顺序构成。

     关于Block, 有以下4个细节需要注意:
    ·每一个Bottleneck输出的特征通道数是相同的, 例如这里的32。 同时可以看到, 经过Concatnate操作后的通道数是按32的增长量增加的,因此这个32也被称为GrowthRate
    ·这里1×1卷积的作用是固定输出通道数, 达到降维的作用。 当几十个Bottleneck相连接时, Concatnate后的通道数会增加到上千, 如果不增加1×1的卷积来降维, 后续3×3卷积所需的参数量会急剧增加。 1×1卷积的通道数通常是GrowthRate4倍。
    ·3.20中的特征传递方式是直接将前面所有层的特征Concatnate后传到下一层, 这种方式与具体代码实现的方式是一致的, 而不像图3.19中, 前面层都要有一个箭头指向后面的所有层。

    ·Block采用了激活函数在前、 卷积层在后的顺序, 这与一般的网络上是不同的。

    利用PyTorch来实现DenseNet的一个Block, 新建一个densenet_block.py文件, 代码如下:

     1 import torch
     2 from torch import nn
     3 import torch.nn.functional as F
     4 
     5 # 实现一个Bottleneck的类, 初始化需要输入通道数与GrowthRate这两个参数
     6 class Bottleneck(nn.Module):
     7 
     8     def __init__(self, nChannels, growthRate):
     9 
    10         super(Bottleneck, self).__init__()
    11         # 通常1×1卷积的通道数为GrowthRate的4倍
    12         interChannels = 4*growthRate
    13         self.bn1 = nn.BatchNorm2d(nChannels)
    14         self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1, bias=False)
    15 
    16         self.bn2 = nn.BatchNorm2d(interChannels)
    17         self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3, 
    18                                 padding=1, bias=1)
    19     
    20     def forward(self, x):
    21         out = self.conv1(F.relu(self.bn1(x)))
    22         out = self.conv2(F.relu(self.bn2(out)))
    23         # 将输入x同计算的结果out进行通道拼接
    24         out = torch.cat((x, out), 1)
    25         return out
    26 
    27 class Denseblock(nn.Module):
    28     
    29     def __init__(self, nChannels, growthRate, nDenseBlocks):
    30         super(Denseblock, self).__init__()
    31         layers = []
    32         # 将每一个Bottleneck利用nn.Sequential()整合起来, 输入通道数需要线性增长
    33         for i in range(int(nDenseBlocks)):
    34             layers.append(Bottleneck(nChannels, growthRate))
    35             nChannels += growthRate
    36         self.denseblock = nn.Sequential(*layers)
    37     
    38     def forward(self, x):
    39         return self.denseblock(x)
    View Code
     1 import torch
     2 from densenet_block import Denseblock
     3 
     4 # 实例化DenseBlock, 包含了6个Bottleneck
     5 denseblock = Denseblock(64, 32, 6).cuda()
     6 
     7 # 查看denseblock的网络结构, 由6个Bottleneck组成
     8 print(denseblock)
     9 >>  Denseblock(
    10   (denseblock): Sequential(
    11       # 第1个Bottleneck的输入通道数为64, 输出固定为32
    12     (0): Bottleneck(
    13       (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    14       (conv1): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    15       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    16       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    17     )
    18     #
    19     # 第2个Bottleneck的输入通道数为96, 输出固定为32
    20     (1): Bottleneck(
    21       (bn1): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    22       (conv1): Conv2d(96, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    23       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    24       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    25     )
    26     # 第3个Bottleneck的输入通道数为128, 输出固定为32
    27     (2): Bottleneck(
    28       (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    29       (conv1): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    30       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    31       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    32     )
    33     #第4个Bottleneck的输入通道数为160, 输出固定为32
    34     (3): Bottleneck(
    35       (bn1): BatchNorm2d(160, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    36       (conv1): Conv2d(160, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    37       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    38       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    39     )
    40     # 第5个Bottleneck的输入通道数为192, 输出固定为32
    41     (4): Bottleneck(
    42       (bn1): BatchNorm2d(192, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    43       (conv1): Conv2d(192, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    44       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    45       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    46     )
    47     # 第6个Bottleneck的输入通道数为224, 输出固定为32
    48     (5): Bottleneck(
    49       (bn1): BatchNorm2d(224, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    50       (conv1): Conv2d(224, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
    51       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    52       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    53     )
    54   )
    55 )
    56 
    57 input = torch.randn(1, 64, 256, 256).cuda()
    58 output = denseblock(input) # 将输入传入denseblock结构中
    59 print(input.shape)
    60 >> torch.Size([1, 64, 256, 256])
    61 
    62 # 输出的通道数为: 224+32=64+32×6=256
    63 print(output.shape)
    64 >> torch.Size([1, 256, 256, 256])
    View Code

     DenseNet网络的优势主要体现在以下两个方面:
    ·密集连接的特殊网络, 使得每一层都会接受其后所有层的梯度,而不是像普通卷积链式的反传, 因此一定程度上解决了梯度消失的问题。
    ·通过Concatnate操作使得大量的特征被复用, 每个层独有的特征图
    的通道是较少的, 因此相比ResNetDenseNet参数更少且计算更高效。DenseNet的不足在于由于需要进行多次Concatnate操作, 数据需要被复制多次, 显存容易增加得很快, 需要一定的显存优化技术。 另外,DenseNet是一种更为特殊的网络, ResNet则相对一般化一些, 因此ResNet的应用范围更广泛。

  • 相关阅读:
    fir.im Weekly
    【转】UITextView的使用详解
    UITextView textViewShouldEndEditing
    【转】 iOS 两种方法实现左右滑动出现侧边菜单栏 slide view
    【转】 UITableView 的indexPath
    【转】 iOS Provisioning Profile(Certificate)与Code Signing详解
    【原】AVAudio录制,播放 (解决真机播放音量太小)
    iOS开发知识点:理解assign,copy,retain变strong
    【转】 NSArray copy 问题
    UITableView中的visibleCells的用法(visibleCells帮上大忙了)
  • 原文地址:https://www.cnblogs.com/zhaopengpeng/p/13720986.html
Copyright © 2011-2022 走看看