Densely Connected Convolutional Networks

论文链接:https://arxiv.org/abs/1608.06993
代码:https://github.com/liuzhuang13/DenseNet

一、论文介绍:

Densely Connected Convolutional Networks_第1张图片

在本文中,作者提出了一种架构:为了确保网络中各层之间的最大信息流,将所有层(具有匹配的特征图大小)直接相互连接。为了保持前馈特性,每一层从前面的所有层获得额外的输入,并将自己的特征映射传递给随后的所有层。这种布局如上图所示。最重要的是,与ResNets相比,本结构在特性被传递到一个层之前,不通过来summation组合它们;相反,作者通过concatenating来组合它们。因此,“第 l h l^h lh层的输入l是由所有前面的卷积块的特征映射组成。它自己的特征图被传递到所有 L − l L-l Ll的后续层。这在L层网络中引入了 L ( L + 1 ) / 2 L(L+1)/ 2 L(L+1)/2个连接,而不是像传统架构中那样只引入L个连接。由于其密集的连接模式,我们将该方法称为密集卷积网络(DenseNet)。
这种密集连接模式需要的参数比传统卷积网络更少,因为不需要重新学习冗余的特征映射。传统的前馈体系结构可以看作是具有state的算法,state在层与层之间传递。每一层从它的前一层读取state,并写入后一层。这种体系结构会改变state,但也传递需要保存的信息。ResNets通过添加身份映射来使信息保持更清晰。但ResNets最近的变种表明,许多层的贡献非常小,实际上可以在训练过程中随机删除。这使得ResNets的状态类似于(展开的)循环神经网络,但ResNets的参数数量要大得多,因为每一层都有自己的权值。本文提出的DenseNet架构明确区分了添加到网络中的信息和保存的信息。DenseNet的层非常窄(例如,每层12个卷积核),只向网络的“collective
knowledge”中添加一小组特征图,并保持剩余的特征图不变,最终的分类器基于网络中的所有特征图做出决策。
除了更好的参数使用功效,DenseNet的一大优势是改进了整个网络的信息流和梯度,这使得它们更容易训练。每一层都可以直接获取损失函数和原始输入信号的梯度,从而得到隐式的深度监督。这有助于训练更深层的网络架构。此外,密集连接具有正则化效应,可以减少训练集较小的任务的过拟合。
与其他网络结构相比,DenseNets不是从极深或极宽的架构中提取表征能力,而是通过特征重用来开发网络的潜力,产生易于训练和高效的参数压缩模型。将不同层学习的feature-map串联起来,增加了后续层输入的变化,提高了效率。这构成了DenseNets和ResNets之间的主要区别。与Inception网络相比,Inception也将不同层的特征连接起来,但是DenseNets更简单、更高效。

二、DenseNets架构详解:

该网络由L层组成,每一层实现一个非线性变换 H l ( ⋅ ) H _l(·) Hl()的功能,其中 l l l表示该层。 H l ( ⋅ ) H _l(·) Hl()是批量归一化(BN)、整流线性单元(ReLU)、池化(Pooling)或卷积(Conv)等操作的复合函数。我们将第 l h l^h lh层的输出表示为 X l X_l Xl

(一)、Dense connectivity

为了进一步改善层间的信息流,本文提出了一种连接模式:将任何层直接连接到所有后续层。文章开头展示了DenseNet的布局示意图。因此,第 l h l^h lh层接收到前面所有层的特征图, X 0 X_0 X0,…, X l − 1 X_l-1 Xl1作为输入。具体公式如下:
在这里插入图片描述
X 0 X_0 X0,…, X l − 1 X_l-1 Xl1表示在0,… l − 1 l-1 l1层中生成的特征图的串联。由于其密集的连接性,我们将这种网络结构称为密集卷积网络(DenseNet)。为了便于实现,我们将上式中 H l ( ⋅ ) H _l(·) Hl()的多个输入串接成一个张量。

(二)、Composite function

这里定义 H l ( ⋅ ) H _l(·) Hl() 作为三个连续运算的复合函数: 批处理归一化(BN),然后整流线性单元(ReLU)和一个3 × 3 (Conv)。

(三)、Pooling layers

当feature-map的大小改变时,上式中中使用的串联操作是不可行的。然而,卷积网络的一个重要部分是改变特征图大小的下采样层。为了便于在本架构中进行下采样,作者将网络划分为多个紧密连接的密集块。把块之间的层称为过渡层,它做卷积和池化。过渡层包括一个批处理归一化层和一个1×1卷积层和一个2×2平均池化层。其结构如下图所示
Densely Connected Convolutional Networks_第2张图片

(四)、Growth rate

如果每个函数 H l H_l Hl产生k个feature-map,则第 l h l^h lh层有 k 0 + k ∗ ( l − 1 ) k_0 +k*(l-1) k0+k(l1)个输入feature-map,其中 k 0 k_0 k0是输入层的通道数。DenseNet和现有网络架构之间的一个重要区别是,DenseNet可以有非常狭窄的层,例如,k = 12。超参数k为网络的增长率。一个相对较小的增长率足以在测试的数据集中获得最先进的结果。对此的一种解释是,每一层都可以访问其块中的所有前面的特征图,因此,可以访问网络的集体知识。人们可以将特征图看作是网络的全局状态。每一层都为这个状态添加了k个自己的feature-map。增长率调节了每一层对全局状态的贡献有多少新信息。与传统的网络架构不同,全局状态一旦写入,就可以从网络中的任何地方访问,不需要从一层复制到另一层。

(五)、Bottleneck layers

虽然每一层只输出k个特征图,但它通常有更多的输入。在每个33卷积之前,可以引入1×1卷积作为瓶颈层,以减少输入feature-map的数量,从而提高计算效率。这种设计对DenseNet特别有效,作者将具有这种瓶颈层的网络,即BN-ReLU-Conv(1×1)-BN-ReLU-Conv(3×3)版本的 H l H_l Hl,称为DenseNet- B。实验中,作者让每个1×1次卷积产生4k个特征图。

(六)、Compression

为了进一步提高模型的紧凑性,可以减少过渡层特征图的数量。如果一个密集块包含m个特征图,则让下面的过渡层输出 [ θ m ] [θm] [θm]个特征图,其中 0 < θ < = 1 0 <θ <=1 0<θ<=1称为压缩因子。θ = 1时,过渡层间特征图数量不变;当 θ < 1 θ<1 θ<1的DenseNet称为DenseNet- C,实验中设θ = 0.5。当瓶颈层和过渡层均为 θ < 1 θ<1 θ<1时,将模型称为DenseNet-BC。

(七)Implementation Details

在除ImageNet之外的所有数据集上,实验中使用的DenseNet有三个dense block,每一个都有相同数量的层。在进入第一个dense block之前,对输入图像进行16(或DenseNet-BC增长速度的两倍)输出通道的卷积。对于核大小为3×3的卷积层,输入的每一边用一个像素补零,以保持特征地图大小固定。使用1×1个卷积和2个平均池作为两个dense block之间的过渡层。在最后一个dense block的末尾,执行一个全局平均池,然后附加一个softmax分类器。三个dense block的特征图大小分别为32×32、16×16和8×8。在实验中采用了基本的DenseNet结构,配置分别为{L = 40, k = 12}, {L = 100, k = 12}和{L = 100, k = 24}。对于DenseNet-BC,配置分别为{L= 100, k= 12}, {L= 250, k= 24}和{L= 190, k= 40}。
在ImageNet上的实验中,使用了一个DenseNet-BC结构,对224×224的输入图像使用了4个dense block。初始卷积层包括2k个大小为7×7,stride为2的卷积;所有其他层的feature-map的数量也从设置k开始。在ImageNet上使用的网络配置如下表所示。
Densely Connected Convolutional Networks_第3张图片

三、实验细节

Densely Connected Convolutional Networks_第4张图片
CIFAR:CIFAR的两个数据集由32×32个像素的彩色自然图像组成。CIFAR-10 (C10)由来自10个类的图像和来自100个类的图像组成。训练集和测试集分别包含50,000张和10,000张图像,用5,000张训练图像作为验证集。并采用了一种标准的数据增强方案(镜像/移位)。在数据集名称(例如,C10+)的末尾用+标记来表示这个数据扩展方案。为了进行预处理,使用通道均值和标准偏差对数据进行归一化。对于最后的运行,使用所有的50,000个训练图像,并在训练结束时报告最终的测试错误。
SVHN:Street View House Numbers (SVHN)数据集包含32×32德彩色数字图像。训练集中有73,257幅图像,测试集中有26,032幅图像,附加训练集中有531,131幅图像。使用所有的训练数据,而不进行任何数据增强,并从训练集中分离出一个包含6000张图像的验证集。在训练过程中,选择验证误差最小的模型,并报告测试误差。
ImageNet:ILSVRC 2012分类数据集包含120万张用于训练的图像和5万张用于验证的图像,它们来自1000个类。对训练图像采用与之前相同的数据增强方案,并在测试时采用尺寸为224×224的单目标或10目标。

(一)、训练方式

所有的网络使用随机梯度下降(SGD)训练。在CIFAR和SVHN上,分别为300和40个epoch使用64批进行训练。初始学习速率设为0.1,以总训轮次的50%和75%除以10进行调整。在ImageNet上,以256个批处理大小训练90个轮次的模型。学习速率最初设置为0.1,并在第30和60轮次时分别降低10倍。使用的权值衰减为 1 0 − 4 10^{-4} 104和没有衰减的Nesterov动量为0.9。对于没有进行数据扩充的三个数据集,即C10, C100和SVHN,在每个卷积层(除第一个卷积层外)后增加一个dropout层,设置dropout率为0.2。设置每个任务和模型只评估一次测试错误率。

(二)、Classification Results on CIFAR and SVHN

作者训练不同深度、L和生长速率k的DenseNets。CIFAR和SVHN的主要结果如下表所示。为了突出总体趋势,用黑体字标出所有表现优于现有最先进水平的结果,用蓝色标出总体最好的结果。
Densely Connected Convolutional Networks_第5张图片

(1)Accuracy

上表的最后一行,在CIFAR的所有数据集上,当L = 190和k = 40的DenseNet-BC始终优于现有的最先进的技术。它在C10+上的错误率为3.46%,在C100+上的错误率为17.18%,显著低于宽ResNet架构的错误率。在C10和C100(没有数据增强)上的最佳结果甚至更好:两者都比采用下降路径正则化的FractalNet的错误率低近30%。在SVHN上,在dropout情况下,L = 100和k = 24的DenseNet也超过了目前宽ResNet所取得的最佳结果。然而,250层DenseNet-BC并没有比更短的DenseNet-BC进一步提高性能。这可能是因为SVHN是一项相对容易的任务,而且非常深入的模型可能会对训练集过拟合。

(2)Capacity(容量)

在没有压缩或bottleneck layers的情况下,随着L和k的增加,DenseNet的性能会变的更好。这主要归因于模型容量的增长。这在C10+和C100+列中得到了最好的证明。在C10+上,随着参数数量的增加(从1.0M、7.0M以上到27.2M),误差从5.24%下降到4.10%,最后下降到3.74%。在C100+上,也有类似的趋势。这表明DenseNets可以利用更大更深入的模型增加的表现能力。这也表明它们不存在残差网络的过拟合和优化困难。

(3)Parameter Efficiency(参数设置)

上表中的结果表明,DenseNet比其他架构(特别是ResNets)更有效地利用参数。具有过渡层瓶颈结构和降维特性的DenseNet-BC其参数效率更高。例如,DenseNet250层模型只有15.3M参数,但它始终优于其他模型,如FractalNet和Wide ResNets有超过30M参数。与使用少于90%参数的1001-layer pre-activation ResNet相比,L = 100和k = 12的DenseNet-BC实现了类似的性能(例如,C10+上的4.51% vs 4.62%错误,C100+上的22.27% vs 22.71%错误)。下图显示了这两个网络在C10+上的训练损耗和测试误差。1001层深层ResNet收敛到较低的training loss,但test errors 相似。
Densely Connected Convolutional Networks_第6张图片

(4)、Overfitting(过拟合)

更有效地使用参数的可以使DenseNet趋向于不太容易过度拟合。在没有数据扩展的数据集上,DenseNet架构比之前的工作的改进特别明显。在C10上,误差相对减少29%,从7.33%下降到5.19%。在C100上,从28.20%下降到19.64%,降幅约为30%。在实验中,我们观察到了在同一设置下的潜在的过拟合:在C10上,通过调整k = 12到k = 24产生的参数增加了4倍,导致误差从5.77%增加到5.83%。DenseNet-BC的瓶颈层和压缩层似乎是对抗这种趋势的有效方法。

(三)、Classification Results on ImageNet

在ImageNet分类任务中以不同的深度和增长率评估DenseNet-BC,并将其与最先进的ResNet架构进行比较。为了确保两个架构之间的公平比较,通过采用ResNet提供的公开的Torch实现,消除了所有其他因素,比如数据预处理和优化设置的差异。只需将ResNet模型替换为DenseNet-BC网络,并保持所有的实验设置与ResNet使用的完全相同。
Densely Connected Convolutional Networks_第7张图片

上表为DenseNet在ImageNet上的single-crop和10-crop验证错误率。下图显示了DenseNets和ResNets的single-crop top-1验证错误率(左)和FLOPs(右)。图中显示的结果表明,DenseNet的性能与最先进的ResNets相当,同时需要更少的参数和计算就达到了类似的性能。例如,具有20M参数模型的DenseNet-201与具有超过40M参数的101层ResNet产生了类似的验证误差。从右边的图中可以观察到类似的趋势,与ResNet-50的计算量相似的DenseNet与需要两倍计算量的ResNet-101的计算量相当。
Densely Connected Convolutional Networks_第8张图片
值得注意的是,实验设置意味着使用了经过优化的超参数设置 ResNets,但不适合DenseNets。可以想象,更广泛的超参数搜索可能会进一步提高DenseNet在ImageNet上的性能。

四、实验总结

从表面上看,DenseNet与ResNets非常相似不同之处只是将 H l ( ⋅ ) H _l(·) Hl()的输入串接而不是求和。然而,这个看似很小的修改会导致两个网络体系结构的本质不同的行为。

(一)Model compactness

作为输入串联接的直接影响:DenseNet的任何一层学习到的特征图可以被随后的所有层访问。这鼓励了整个网络的特性重用,并会产生更紧凑的模型。
Densely Connected Convolutional Networks_第9张图片

上图图显示了实验的结果,该实验旨在比较DenseNets所有变体的参数效率(左)和ResNet体系结构(中)。在C10+上训练多个不同深度的小型网络,并将它们的测试精度绘制为网络参数的曲线。与其他流行的网络架构,如AlexNet或VGG-net相比,具有预激活功能的ResNets使用更少的参数,而通常获得更好的结果。因此,将DenseNet (k = 12)与这种架构进行比较。DenseNet的训练设置与前一节相同。
由图可知,DenseNet- BC始终是DenseNet中参数效率最高的变体。此外,为了达到同样的精度水平,DenseNet-BC只需要ResNets大约1/3的参数。上图的右图显示,只有0.8M可训练参数的DenseNet-BC能够达到与具有10.2M参数的1001层(预激活)ResNet相媲美的精度。

(二)、Implicit Deep Supervision

密集卷积网络精度提高的一种解释可能是,通过更短的连接,各层从损失函数获得额外的监督。可以将DenseNets解读为一种深度监督。深度监督的好处已经在深度监督网络(DSN)体现出来,即在每个隐藏层上都附加了分类器,强制中间层学习判别特征。
DenseNet以一种隐式的方式执行类似的深度监督:网络顶部的单个分类器最多通过两个或三个过渡层提供对所有层的直接监督。然而,DenseNet的损失函数和梯度本质上不那么复杂,因为相同的损失函数在所有层之间共享。

(三)、Stochastic vs. deterministic connection(随机与确定性联系)

稠密卷积网络与残差网络的随机深度正则化之间存在着有趣的联系。在随机深度下,残差网络中的layers被随机dropped,从而使周围各层之间产生直接连接。由于池化层不会被dropped,因此网络的连接模式与DenseNet相似:如果所有的中间层都被随机dropped,同一池化层之间的任何两层直接连接的概率都很小。尽管这些方法非常不同,但DenseNet对随机深度的解释可能会为这种正则化器的成功提供见解。

(四)、Feature Reuse(特征重用)

根据设计,DenseNets允许各层访问之前所有层的特征图(尽管有时通过过渡层)。作者进行了一个实验来调查一个训练有素的网络是否利用了这个条件。首先用L = 40和k = 12在C10+上训练DenseNet。对于一个块中的每个卷积层,计算分配给与层 l l l连接的平均(绝对)权重。下图显示了三个 dense blocks的热图。平均绝对权值作为卷积层对其前一层依赖关系的替代。位置(l,s)上的红点表示l层平均而言,最大限度使用了s-layers之前生成的特征图。
Densely Connected Convolutional Networks_第10张图片
1、所有层都将它们的权重分散到同一block中的许多输入上。这表明,由非常早期提取的特征图,实际上是由整个同一dense block的深层直接使用的。
2、过渡层的权值也将它们的权值分散到前一个dense block中的所有层上,表明信息从DenseNet的第一层到最后一层通过很少的间接流动。
3、第二个和第三个dense block内的层一致将最小权值分配给过渡层的输出(上图三角形的最上面一排),这表明过渡层输出了很多冗余特征(平均权值较低)。这与DenseNet-BC的强结果是一致的,在DenseNet-BC中,这些输出被压缩了。
4、尽管最后的分类层,如上图右图所示,也在整个密集的区块中使用了权重,但似乎在最终的特征地图上有一个集中,这表明可能在网络的后期产生了一些更高级的特征。

五、文章总结

作者提出了一种新的卷积网络架构,称之为密集卷积网络(DenseNet)。它引入了具有相同特征图大小的任意两个层之间的直接连接。DenseNets可以自然地扩展到数百个层,而没有显示出任何优化困难的问题。在我们的实验,DenseNets趋向于随着参数数量的增加而在准确性上不断提高,而没有任何性能退化或过拟合的迹象。在多个设置下,它在几个高度竞争的数据集上实现了最先进的结果。此外,DenseNetst需要更少的参数和更少的计算来实现最先进的性能。由于在研究中采用了针对残差网络优化的超参数设置,可以通过对超参数和学习速率调度进行更详细的调整,可以进一步提高DenseNets的精度。
在遵循简单的连接规则的同时,DenseNets自然地集成了身份映射、深度监督和多样化深度的属性。它们允许特征在整个网络中重用,因此可以学习更紧凑的模型以及更精确的模型。由于其紧凑的内部表示和减少的特征冗余,DenseNets可能是各种基于卷积特征的计算机视觉任务的良好特征提取器。

六、部分源码

import torch
import torch.nn as nn
import torchvision

print("PyTorch Version: ",torch.__version__)
print("Torchvision Version: ",torchvision.__version__)

__all__ = ['DenseNet121', 'DenseNet169','DenseNet201','DenseNet264']

def Conv1(in_planes, places, stride=2):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_planes,out_channels=places,kernel_size=7,stride=stride,padding=3, bias=False),
        nn.BatchNorm2d(places),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
    )

class _TransitionLayer(nn.Module):
    def __init__(self, inplace, plance):
        super(_TransitionLayer, self).__init__()
        self.transition_layer = nn.Sequential(
            nn.BatchNorm2d(inplace),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=inplace,out_channels=plance,kernel_size=1,stride=1,padding=0,bias=False),
            nn.AvgPool2d(kernel_size=2,stride=2),
        )

    def forward(self, x):
        return self.transition_layer(x)


class _DenseLayer(nn.Module):
    def __init__(self, inplace, growth_rate, bn_size, drop_rate=0):
        super(_DenseLayer, self).__init__()
        self.drop_rate = drop_rate
        self.dense_layer = nn.Sequential(
            nn.BatchNorm2d(inplace),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=inplace, out_channels=bn_size * growth_rate, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(bn_size * growth_rate),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=bn_size * growth_rate, out_channels=growth_rate, kernel_size=3, stride=1, padding=1, bias=False),
        )
        self.dropout = nn.Dropout(p=self.drop_rate)

    def forward(self, x):
        y = self.dense_layer(x)
        if self.drop_rate > 0:
            y = self.dropout(y)
        return torch.cat([x, y], 1)


class DenseBlock(nn.Module):
    def __init__(self, num_layers, inplances, growth_rate, bn_size , drop_rate=0):
        super(DenseBlock, self).__init__()
        layers = []
        for i in range(num_layers):
            layers.append(_DenseLayer(inplances + i * growth_rate, growth_rate, bn_size, drop_rate))
        self.layers = nn.Sequential(*layers)

    def forward(self, x):
        return self.layers(x)


class DenseNet(nn.Module):
    def __init__(self, init_channels=64, growth_rate=32, blocks=[6, 12, 24, 16],num_classes=1000):
        super(DenseNet, self).__init__()
        bn_size = 4
        drop_rate = 0
        self.conv1 = Conv1(in_planes=3, places=init_channels)

        blocks*4

        num_features = init_channels
        self.layer1 = DenseBlock(num_layers=blocks[0], inplances=num_features, growth_rate=growth_rate, bn_size=bn_size, drop_rate=drop_rate)
        num_features = num_features + blocks[0] * growth_rate
        self.transition1 = _TransitionLayer(inplace=num_features, plance=num_features // 2)
        num_features = num_features // 2
        self.layer2 = DenseBlock(num_layers=blocks[1], inplances=num_features, growth_rate=growth_rate, bn_size=bn_size, drop_rate=drop_rate)
        num_features = num_features + blocks[1] * growth_rate
        self.transition2 = _TransitionLayer(inplace=num_features, plance=num_features // 2)
        num_features = num_features // 2
        self.layer3 = DenseBlock(num_layers=blocks[2], inplances=num_features, growth_rate=growth_rate, bn_size=bn_size, drop_rate=drop_rate)
        num_features = num_features + blocks[2] * growth_rate
        self.transition3 = _TransitionLayer(inplace=num_features, plance=num_features // 2)
        num_features = num_features // 2
        self.layer4 = DenseBlock(num_layers=blocks[3], inplances=num_features, growth_rate=growth_rate, bn_size=bn_size, drop_rate=drop_rate)
        num_features = num_features + blocks[3] * growth_rate

        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(num_features, num_classes)

    def forward(self, x):
        x = self.conv1(x)

        x = self.layer1(x)
        x = self.transition1(x)
        x = self.layer2(x)
        x = self.transition2(x)
        x = self.layer3(x)
        x = self.transition3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

def DenseNet121():
    return DenseNet(init_channels=64, growth_rate=32, blocks=[6, 12, 24, 16])

def DenseNet169():
    return DenseNet(init_channels=64, growth_rate=32, blocks=[6, 12, 32, 32])

def DenseNet201():
    return DenseNet(init_channels=64, growth_rate=32, blocks=[6, 12, 48, 32])

def DenseNet264():
    return DenseNet(init_channels=64, growth_rate=32, blocks=[6, 12, 64, 48])

if __name__=='__main__':
    # model = torchvision.models.densenet121()
    model = DenseNet121()
    print(model)

    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)

你可能感兴趣的:(yolo,深度学习,机器学习,计算机视觉)