DenseNet及torchvision中的实现

ResNets, Highway, Networks, Stochastic depth, DenseNet他们的共同的特点是:
They create short paths from early layers to later layers.

他们都在创造一些更短路径,使得前面的层的数据可以通过“捷径”传输到后面的层。

而论文作者也借鉴了这种思想,甚至把它发挥到极致:
在每一层都有连接其他不同层的前项传播路径。假设我们具有L层的传统卷积网络具有L个连接 - 每层与其后续层之间的连接 - 我们的网络具有 1 2 L ( L + 1 ) \frac{1}{2}L(L+1) 21L(L+1)个直接连接。

Dense Block具有如下图:
在这里插入图片描述
下面就是DenseNet的传播公式,可以看到,每一层的输入都包括之前所有层的输出。
x l = H l ( [ x 0 , x 1 , . . . , x l − 1 ] ) x_{l} = H_{l}([x_{0}, x_{1}, ..., x_{l-1}]) xl=Hl([x0,x1,...,xl1])

一个完整的DenseNet的网络结构如下:

DenseNet因此具有的优点是:

  1. 可以缓解梯度弥散问题
  2. 加强特征传播
  3. 加强特征重用
  4. 大幅减少参数数量

值得注意的是:与ResNet相比,DenseNet从未将features传递到图层之前通过求和来组合特征; 相反,是通过concatenating连接它们来组合features。简单点说,ResNet是直接相加,DenseNet是堆叠

在这里解释一下,为什么这样能大量减少参数量,明明连接的路径多了?
这种密集连接模式需要比传统卷积网络更少的参数,是因为不需要重新学习冗余特征映射。传统的前馈结构可以被视为具有状态的算法,该状态在层与层之间传递。每个层从其前一层读取状态并写入后续层。在更改状态的同时,还传递需要保留的信息。ResNets通过加性身份转换使这些信息,使它们保持明确。最近的变化表明,许多层贡献很少,实际上在训练期间可以随机丢弃。这使得ResNets的状态类似于(展开的)递归神经网络。但ResNets的参数数量要大得多,因为每个层都有自己的权重。而DenseNet架构明确区分了添加到网络的信息和保留的信息。

DenseNet图层非常窄(例如,每层12个filter),只为网络的“集体信息”添加一小组特征图,并保持其余的特征图不变 - 最终分类器根据feature-maps所有特征做出决策。

论文也给出了多个不同版本的实现,具体结构如下图:

上图中的k就是growth rate, k 0 k_{0} k0就是一开始input channels,然后之后的第i层channels就会变成 k ( i − 1 ) + k 0 k(i-1)+k_{0} k(i1)+k0

还有两个实现的要点:

  1. Bottleneck layers: 简单点说就是在3X3 conv 之前使用1X1 conv 来降低输入的feature map的数量。
  2. Compression:为了进一步提高模型的紧凑性,可以减少过渡层的特征图数量。利用压缩因子 θ ( 0 &lt; θ &lt; = 1 ) \theta (0&lt;\theta&lt;=1) θ(0<θ<=1)压缩feature map。当 θ = 1 \theta = 1 θ=1, 跨过层的特征映射的数量保持不变。

最后来看看结果:整体比ResNet强,参数更少,计算复杂度更小而且错误率降低了。
在这里插入图片描述
左边的图表示不同类型DenseNet的参数和error对比。中间的图表示DenseNet-BC和ResNet在参数和error的对比,相同error下,DenseNet-BC的参数复杂度要小很多。右边的图也是表达DenseNet-BC-100只需要很少的参数就能达到和ResNet-1001相同的结果。

在这里插入图片描述
总结一下,DenseNet于建立了不同层之间的连接关系,充分利用了feature,减轻了梯度消失问题。另外,利用bottleneck layer,Comparison以及较小的growth rate使得网络变窄,参数减少,有效抑制了过拟合,同时计算量也减少了。

Reference:
https://blog.csdn.net/sinat_33487968/article/details/83684453

torchvision中的实现

DenseNet由Dense Block组成,而Dense Block是由DenseLayer组成,下面就是DenseLayer类:

class _DenseLayer(nn.Sequential):
	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_out > 0:
		new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)
	return torch.cat([x, new_features], 1)

可以看到每一层卷积之前都是用了batchnorm和relu, 然后就是1x1的卷积和3x3的卷积。这个1x1的卷积就是bottleneck layer, 它能够降低输入的feature map的数量。bn_size参数是bottleneck_layer瓶颈层数的乘法因子。drop_rate是drop out的概率大小。注意最后的返回值,torch.cat([x, new_features], 1), 也就是包括自己本身和提取的feature层堆叠在一起。

有了DenseLayer之后就可以用它来构建DenseBlock:

class _DenseBlock(nn.Sequential):
	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)

解释一下一些参数的含义:growth_rate (int) - 代表每一层有多少个新的feature层添加进去 (paper里面是k),num_input features(int) - 输入的feature层数,num_layers(int)- 代表了Dense Block有多少层DenseLayers。
for循环就是根据num_layers逐层添加Dense Block注意每一层的输入num_input features不一样,是因为每一个DenseLayer之后feature的数目都会增加growth rate。直观一点理解,其实就是每一层的DenseLayer附带了自己学习到的feature还有自己本身一起传入下一个DenseLayer。所以后面的输出的结果会不断的增加,下图不能体现数据流动这一点,因为下图不是数据本身,而是代表卷积层

在DenseBlock之间,还添加了Transition层,这个层的作用是为了进一步提高模型的紧凑性,可以减少过渡层的特征图的数量。这里直接就把num_input_features压缩成num_output_features,使用一层卷积实现的。

class _Transition(nn.Sequential):
	def __init__(self, num_input_features, num_output_features):
		super(_Transition, self).__init__()
		self.add_module('norm', nn.BatchNorm2d(num_input_features))
		self.add_module('relu', nn.ReLU(inplace=True))
		self.add_module('conv', nn.Conv2d(num_input_features, num_output_features, kernel_size=1, stride=1, bias=False))
		self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))

接下来就可以组合成一个DenseNet:

class DenseNet(nn.Module):
	r"""Densenet-BC model class, based on
    `"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_
    Args:
        growth_rate (int) - how many filters to add each layer (`k` in paper)
        block_config (list of 4 ints) - how many layers in each pooling block
        num_init_features (int) - the number of filters to learn in the first convolution layer
        bn_size (int) - multiplicative factor for number of bottle neck layers
          (i.e. bn_size * k features in the bottleneck layer)
        drop_rate (float) - dropout rate after each dense layer
        num_classes (int) - number of classification classes
    """
	
	def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64, bn_size=4, drop_rate=0, num_classes=1000):
		super(DenseNet, self).__init__()
		#first convolution
		self.features = nn.Sequential(OrderDict([
			('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)),
			('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=1)),
		])) 

		# each DenseBlock
		num_features = num_init_features
		for i, num_layers in enumerate(block_config):
			block = _DenseBlock(num_layers=num_layers, num_input_features=num_features, bn_size=bn_size, growth_rate=growth_rate, drop_rate=drop_rate)
			self.features.add_module('denseblock%d' % (i+1), block)
			num_features = num_features + num_layers * growth_rate
			if i != len(block_config) -1 :
				trans = _Transition(num_input_features=num_features, num_output_features=num_features // 2)
				self.features.add_module('transition%d' % (i+1), trans)
				num_features = num_features // 2

		# final batch norm
		self.features.add_module('norm5', nn.BatchNorm2d(num_features))

		# linear layer
		self.classifier = nn.Linear(num_features, num_classes)
		
		# official init from torch repo
		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.weight, 1)
				nn.init.constant_(m.bias, 0)
			elif isinstance(m, nn.Linear):
				nn.init.constant_(m.bias, 0)

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

Reference:
https://blog.csdn.net/sinat_33487968/article/details/83687851

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值