- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
我的环境
- 语言环境:Python 3.12
- 编译器:Jupyter Lab
- 深度学习环境:Pytorch 2.4.1 Torchvision 0.19.1
- 数据集:鸟类分类数据库(和之前的几周一样)
📌 本周任务:
●2.了解并研究 DenseNet与ResNetV 的区别
●3.改进思路是否可以迁移到其他地方呢(自由探索,虽然不强求但是请认真对待这个哦)
一、前言
在计算机视觉领域,卷积神经网络(CNN)已经成为最主流的方法,比如GoogLenet,VGG-16,Incepetion等模型。CNN史上的一个里程碑事件是ResNet模型的出现,ResNet可以训练出更深的CNN模型,从而实现更高的准确度。ResNet模型的核心是通过建立前面层与后面层之间的“短路连接”(shortcuts,skip connection),进而训练出更深的CNN网络。
今天我们要介绍的是DenseNet模型,它的基本思路与ResNet一致,但是它建立的是前面所有层与后面层的密集连接(dense connection),它的名称也是由此而来。DenseNet的另一大特色是通过特征在channel上的连接来实现特征重用(feature reuse)。这些特点让DenseNet在参数和计算成本更少的情形下实现比ResNet更优的性能,DenseNet也因此斩获CVPR 2017的最佳论文奖。
DenseNet(Densely Connected Convolutional Network)是一种深度神经网络架构,于2017年由Gao Huang等人提出。它是深度学习中经典的卷积神经网络模型之一,在图像分类、目标检测等领域表现优异。DenseNet通过高效的信息流传递和参数复用,实现了更高的性能和更少的参数量。

以下是对DenseNet的核心思想、结构和优点的详细介绍:
核心思想
DenseNet的关键思想是通过密集连接(Dense Connectivity),实现特征的高效利用。具体来说:
- 在传统的卷积网络中,层与层之间是线性连接的,数据从上一层流向下一层。而在DenseNet中,每一层都与之前所有层直接相连。
- 每一层的输出不仅作为下一层的输入,还会被直接传递给所有后续层。这样可以更好地保留前面层提取的特征,并减轻梯度消失问题。
网络结构
DenseNet的基本构成可以分为以下几个部分:
-
Dense Block
- 在一个Dense Block中,每一层都接收所有前面层的特征图作为输入,并将自己的输出传递给后续所有层。
- 对于第( l )层,其输入是所有之前层的特征的级联,公式为:
x l = H l ( [ x 0 , x 1 , … , x l − 1 ] ) x_l = H_l([x_0, x_1, \ldots, x_{l-1}]) xl=Hl([x0,x1,…,xl−1])
其中, H l H_l Hl表示第 l l l层的非线性变换(如卷积、ReLU等), [ x 0 , x 1 , … , x l − 1 ] [x_0, x_1, \ldots, x_{l-1}] [x0,x1,…,xl−1]表示特征级联。
-
Transition Layer
- 在Dense Blocks之间插入Transition Layer,用于降维和控制模型复杂度。
- Transition Layer通常由一个 1 × 1 1\times1 1×1卷积层和一个平均池化层(Average Pooling)组成。
-
Growth Rate
- DenseNet的一个重要超参数是增长率(Growth Rate, ( k )),表示每一层新增的特征图数量。
- Growth Rate较小时,模型更轻量化;较大时,模型更有表达能力。
-
全局池化和分类层
- 最终通过全局平均池化层(Global Average Pooling)和全连接层实现分类。
优点
-
参数高效
- DenseNet通过特征复用显著减少了参数量,因为不需要重复计算冗余特征。
-
缓解梯度消失
- DenseNet的直接连接机制使得梯度可以从后层更轻松地传递到前面层,改善了深度网络的训练。
-
特征复用
- 每一层的特征都会被后续层反复利用,显著提高了模型的表达能力。
-
更深的网络
- DenseNet的架构支持更深的网络,而不会带来过高的计算开销。
DenseNet的变体
DenseNet有多个变体,适应不同的任务和场景:
-
DenseNet-121、DenseNet-169、DenseNet-201、DenseNet-264
- 基于层数的不同,适用于从轻量级到更复杂的任务。
-
DenseNet在语义分割、目标检测中的扩展
- 如在DenseASPP等模型中,与空洞卷积结合,用于语义分割任务。
应用场景
- 图像分类:如ImageNet、CIFAR-10等数据集上表现优异。
- 目标检测:DenseNet作为特征提取器,广泛应用于Faster R-CNN等模型中。
- 医学影像分析:DenseNet因其高效的特征表达能力,常用于CT图像分类、病灶检测等任务。
DenseNet与ResNet的比较
| 特性 | DenseNet | ResNet |
|---|---|---|
| 连接方式 | 每层与之前所有层连接 | 仅与前一层的输出相加 |
| 参数量 | 更少 | 较多 |
| 特征复用 | 高效 | 较低 |
| 梯度流动 | 更顺畅 | 较好 |
| 适用场景 | 更深、更复杂的网络 | 多种场景,适用性广泛 |
二、设计理念
相比ResNet,DenseNet提出了一种更为激进的密集链接机制:即互相连接所有的层,具体来说就是每个层都会接受前面所有层作为其额外的输入。
图1展示了ResNet的残差连接机制,作为对比,后面图2为DenseNet的密集连接机制。可以看到,ResNet是每个层与前面的某层短路连接在一起,连接方式是元素相加,而DenseNet中,每个层都会与前面所有层在channel维度上concat在一起,元素叠加,并作为下一层的输入。
图1
图2
三、网络结构
CNN网络一般要经过Pooling或者Stride > 1的Conv来降低特征图的大小,而DenseNet的密集连接方式需要特征图大小保持一致。为了解决这个问题,DenseNet网络中使用DenseBlock + Transition的结构,其中DenseBlock是包含很多层的模块,每个层的特征图大小相同,层与层之间采用密集连接方式,Transition层是链接两个相邻的DenseBlock,通过Pooling使特征图的大小降低。

四、网络构建
1、Denselayer的构建
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils import model_zoo
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_rate > 0:
new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)
return torch.cat([x, new_features],1)
2、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)
3、Transition的构建
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(2, stride=2))
4、完整网络构建
from collections import OrderedDict
class DenseNet(nn.Module):
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__()
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))
]))
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)
self.features.add_module('norm5', nn.BatchNorm2d(num_features))
self.features.add_module('relu5', nn.ReLU(inplace=True))
self.classifier = nn.Linear(num_features, num_classes)
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, kernel_size=features.size()[2:]).view(features.size(0), -1)
out = self.classifier(out)
return out
使用densenet121:
import re
from torch.utils import model_zoo
model_urls = {
'densenet121': 'https://download.pytorch.org/models/densenet121-a639ec97.pth',
}
def densenet121(pretrained=False, **kwargs):
model = DenseNet(num_init_features = 64, growth_rate = 32, block_config = (6, 12, 24, 16), **kwargs)
if pretrained:
pattern = re.compile(r'^(.*denselayer\d+\.(?:norm|relu|conv))\.((?:[12])\.(?:weight|bias|running_mean|running_var))$')
state_dict = model_zoo.load_url(model_urls['densenet121'])
for key in list(state_dict.keys()):
res = pattern.match(key)
if res:
new_key = res.group(1)+res.group(2)
state_dict[new_key] = state_dict[key]
del state_dict[key]
model.load_state_dict(state_dict)
return model
我们可以查看网络:
import torchsummary
model = densenet121(pretrained=True).cuda()
x = (3, 224, 224)
torchsummary.summary(model,x)
代码输出:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 64, 112, 112] 9,408
BatchNorm2d-2 [-1, 64, 112, 112] 128
ReLU-3 [-1, 64, 112, 112] 0
MaxPool2d-4 [-1, 64, 56, 56] 0
BatchNorm2d-5 [-1, 64, 56, 56] 128
ReLU-6 [-1, 64, 56, 56] 0
Conv2d-7 [-1, 128, 56, 56] 8,192
BatchNorm2d-8 [-1, 128, 56, 56] 256
ReLU-9 [-1, 128, 56, 56] 0
Conv2d-10 [-1, 32, 56, 56] 36,864
BatchNorm2d-11 [-1, 96, 56, 56] 192
ReLU-12 [-1, 96, 56, 56] 0
Conv2d-13 [-1, 128, 56, 56] 12,288
BatchNorm2d-14 [-1, 128, 56, 56] 256
ReLU-15 [-1, 128, 56, 56] 0
Conv2d-16 [-1, 32, 56, 56] 36,864
BatchNorm2d-17 [-1, 128, 56, 56] 256
ReLU-18 [-1, 128, 56, 56] 0
Conv2d-19 [-1, 128, 56, 56] 16,384
BatchNorm2d-20 [-1, 128, 56, 56] 256
ReLU-21 [-1, 128, 56, 56] 0
Conv2d-22 [-1, 32, 56, 56] 36,864
BatchNorm2d-23 [-1, 160, 56, 56] 320
ReLU-24 [-1, 160, 56, 56] 0
Conv2d-25 [-1, 128, 56, 56] 20,480
BatchNorm2d-26 [-1, 128, 56, 56] 256
ReLU-27 [-1, 128, 56, 56] 0
Conv2d-28 [-1, 32, 56, 56] 36,864
BatchNorm2d-29 [-1, 192, 56, 56] 384
ReLU-30 [-1, 192, 56, 56] 0
Conv2d-31 [-1, 128, 56, 56] 24,576
BatchNorm2d-32 [-1, 128, 56, 56] 256
ReLU-33 [-1, 128, 56, 56] 0
Conv2d-34 [-1, 32, 56, 56] 36,864
BatchNorm2d-35 [-1, 224, 56, 56] 448
ReLU-36 [-1, 224, 56, 56] 0
Conv2d-37 [-


2万+

被折叠的 条评论
为什么被折叠?



