计算机系统应用教程网站

网站首页 > 技术文章 正文

深度学习之重读经典(八)DenseNet

btikc 2024-09-03 11:19:46 技术文章 12 ℃ 0 评论

背 景

在本系列第五篇文章ResNet中,我们提到在网络层间加入“跳接”(shortcut connection)来连接不同层级的feature map,从而缓解深层网络训练时会出现的梯度消失问题。DenseNet延续了这个思想,在不同层间建立密集连接,将特征重用最大化,同时一定程度上减少了参数量,并获得了CVPR 2017的最佳论文奖。


01 DenseNet与ResNet

DenseNet一定程度上来说是对ResNet的延伸与改进,那么就不免对二者展开比较,它们的结构对比如下图所示。

DenseNet与ResNet的不同点在于,ResNet每一层的输出会加上来自上一层的输入,公式表达即

Xl=Hl(Xl-1)+Xl-1

其中Xl为第l层的输出,Xl-1为第l-1层的输出,也即第l层的输入。而DenseNet每一层会对前面所有特征层的输出进行叠加再传到下一层,即

Xl=Hl([X0,X1,...,Xl-1])

其中特征层堆叠采用Concat方法,相比ResNet求和的做法,Concat操作也更有利于梯度反向传播过程中信息流的传递。整体网络递进过程如下图所示。

然而大量的特征图叠加,会使得后续层的输入通道持续增多,从而导致模型参数量过大。因此DenseNet使用了Bottleneck层作为主体,通过增加1×1的卷积层来调整内部的通道数,减少输出特征图数量。此思想在ResNet50/101等深层残差网络中也有过类似的表达。


02 DenseNet网络架构

上文描述的DensNet网络结构通常被当作一个DenseBlock,通过若干个不同维度的DenseBlock相连即可组成不同层数的DenseNet。表格中列举了四类在ImageNet数据集上使用的网络层配置。均由初始的7×7卷积,3×3池化,主体部分四个Denseblock以及最后的全局平均池化与全连接层组成。

其中,每两个DenseBlock之间由一个Transition层相连,Transition层包含一个1×1的卷积和2×2的池化层,同样起到降低通道数、压缩模型的作用。DenseNet整体网络结构如下图所示。

作者在ImageNet数据集上与不同层级的ResNet进行对比,如下图所示,在相同的错误率水平(y轴)下,DenseNet所需的参数量(左图)与浮点计算量(右图)均少于ResNet,进一步验证了上述结构的有效性。


03 代码实现

使用pytorch框架实现DenseNet中DenseBlock、Transition等各个模块以及最终的121层DenseNet网络,代码如下:

class _DenseLayer(nn.Sequential):
    """Basic unit of DenseBlock (using bottleneck layer) """
    def __init__(self, num_input_features, growth_rate, bn_size, drop_rate):
        super(_DenseLayer, self).__init__()
        self.add_module("norm1", nn.BatchNorm2d(num_input_features))
        self.add_module("relu1", nn.ReLU(inplace=True))
        self.add_module("conv1", nn.Conv2d(num_input_features, bn_size*growth_rate,
                                           kernel_size=1, stride=1, bias=False))
        self.add_module("norm2", nn.BatchNorm2d(bn_size*growth_rate))
        self.add_module("relu2", nn.ReLU(inplace=True))
        self.add_module("conv2", nn.Conv2d(bn_size*growth_rate, growth_rate,
                                           kernel_size=3, stride=1, padding=1, bias=False))
        self.drop_rate = drop_rate

    def forward(self, x):
        new_features = super(_DenseLayer, self).forward(x)
        if self.drop_rate > 0:
            new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)
        return torch.cat([x, new_features], 1)
class _DenseBlock(nn.Sequential):
    """DenseBlock"""
    def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):
        super(_DenseBlock, self).__init__()
        for i in range(num_layers):
            layer = _DenseLayer(num_input_features+i*growth_rate, growth_rate, bn_size,
                                drop_rate)
            self.add_module("denselayer%d" % (i+1,), layer)
class _Transition(nn.Sequential):
    """Transition layer between two adjacent DenseBlock"""
    def __init__(self, num_input_feature, num_output_features):
        super(_Transition, self).__init__()
        self.add_module("norm", nn.BatchNorm2d(num_input_feature))
        self.add_module("relu", nn.ReLU(inplace=True))
        self.add_module("conv", nn.Conv2d(num_input_feature, num_output_features,
                                          kernel_size=1, stride=1, bias=False))
        self.add_module("pool", nn.AvgPool2d(2, stride=2))
class DenseNet(nn.Module):
    "DenseNet-BC model"
    def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64,
                 bn_size=4, compression_rate=0.5, drop_rate=0, num_classes=1000):
        super(DenseNet, self).__init__()
        # first Conv2d
        self.features = nn.Sequential(OrderedDict([
            ("conv0", nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)),
            ("norm0", nn.BatchNorm2d(num_init_features)),
            ("relu0", nn.ReLU(inplace=True)),
            ("pool0", nn.MaxPool2d(3, stride=2, padding=1))
        ]))

        # DenseBlock
        num_features = num_init_features
        for i, num_layers in enumerate(block_config):
            block = _DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)
            self.features.add_module("denseblock%d" % (i + 1), block)
            num_features += num_layers*growth_rate
            if i != len(block_config) - 1:
                transition = _Transition(num_features, int(num_features*compression_rate))
                self.features.add_module("transition%d" % (i + 1), transition)
                num_features = int(num_features * compression_rate)

        # final bn+ReLU
        self.features.add_module("norm5", nn.BatchNorm2d(num_features))
        self.features.add_module("relu5", nn.ReLU(inplace=True))

        # classification layer
        self.classifier = nn.Linear(num_features, num_classes)

        # params initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.bias, 0)
                nn.init.constant_(m.weight, 1)
            elif isinstance(m, nn.Linear):
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        features = self.features(x)
        out = F.avg_pool2d(features, 7, stride=1).view(features.size(0), -1)
        out = self.classifier(out)
        return out

完整训练流程见:

https://github.com/PopSmartTech/deeplearning


04 总 结

DenseNet充分发挥特征重用优势,融合多尺度特征提升分类精度,密集连接方式利于梯度反向传播,进一步减轻梯度消失问题,降低网络训练难度。同时,利用DenseBlock和Transition等方法有效减少参数量与计算复杂度。但是由于显存占用高、结构相对复杂等原因,在应用层面并不如ResNet来的广泛,不过其理论贡献无疑给后续的研究工作带来深刻启发。


参考资料:

[1] Densely Connected Convolutional Networks

https://arxiv.org/pdf/1608.06993.pdf

[2] DenseNet:比ResNet更优的CNN模型 https://zhuanlan.zhihu.com/p/37189203


本文所有文字版权均属“宝略科技”,更多内容请搜索关注【宝略科技】微信公众号~

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表