当前位置: 首页 > article >正文

深度学习基础--CNN经典网络之“DenseNet”简介,源码研究与复现(pytorch)

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

前言

  • 如果说最经典的神经网络,ResNet肯定是一个,从ResNet发布后,很多人做了修改,denseNet网络无疑是最成功的一个,它采用密集型连接,将通道数连接在一起
  • 本文是复现DenseNet121模型,代码注释比较多,比较通俗易懂;
  • ResNet讲解: https://blog.csdn.net/weixin_74085818/article/details/145786990?spm=1001.2014.3001.5501
  • 欢迎收藏 + 关注,本人将会持续更新

文章目录

  • 简介
    • 设计理念
    • 减少维度
  • 模型搭建
    • 导入数据
    • 模型构建
    • 检测

简介

DenseNet是在ResNet之后发明出来的,从网络结构图来看,他类似采用残差的方式

在这里插入图片描述

设计理念

在ResNet网络中,提出残差连接,通过恒等映射实现。

而在DenseNet网络中,采用的是通道数连接,什么意思呢?

  • 在卷积神经网络中,图像数据维度是: [N, C, H, W],其中,N是批次的大小,C是通道数,H是图像高,W是图像宽像素,DenseNet网络就是通过在后面层的“C”通道数“加”前面“C”的通道数,达到后面层的效果不会比前面差(和ResNet网络一样)

特点

  • 在ResNet网络中,一层的残差连接,不会作用与后面的残差连接,但是在Denset网络中,通道数相加回一直叠加,如下图网络图示:

在这里插入图片描述

有点想数学的“组合”👀👀👀👀👀

减少维度

在Conv2d网络中,一般是通过Pooling和stride>1来降低维度,但是在DenseNet网络中提出Transition层降低维度,采用DenseBlock + Transition的结构:

Transition代码:

class _Transition(nn.Sequential):
    def __init__(self, num_init_features, num_out_features):
        super(_Transition, self).__init__()
        self.add_module("norm", nn.BatchNorm2d(num_init_features))
        self.add_module("relu", nn.ReLU(inplace=True))
        self.add_module("conv", nn.Conv2d(num_init_features, num_out_features, kernel_size=1, stride=1, bias=False))
        # 降维
        self.add_module("pool", nn.AvgPool2d(2, stride=2))

在这里插入图片描述

在DenseBlock中,采封装的DenseLayer层叠加而来,DenseNet-B结中,DenseLayer: BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv,如图一个DenseLayer结构:

在这里插入图片描述

模型搭建

导入数据

import torch  
import torch.nn as nn 
import torchvision
from collections import OrderedDict
import re
from torch.hub import load_state_dict_from_url

模型构建

在这里插入图片描述

DenseNet,首先实现DenseBlock结构,DenseBlock内部结构,这里采用:BN + ReLU + 1*1Conv + BN + ReLU + 3 * 3Conv, 大体结构如下:
在这里插入图片描述

这个代码很巧妙,值得研究,模仿,继承nn.Sequential类,将多层一样的,进行封装

👀 提示:注释过多,🤠🤠🤠🤠

import torch.nn.functional as F

# 实现DenseBlock中的部件:DenseLayer
'''  
1、BN + ReLU: 处理部分,首先进行归一化,然后在用激活函数ReLU
2、Bottlenck Layer:称为瓶颈层,这个层在yolov5中常用,但是yolov5中主要用于特征提取+维度降维,这里采用1 * 1卷积核 + 3 * 3的卷积核进行卷积操作,目的:减少输入输入特征维度
3、BN + ReLU:对 瓶颈层 数据进行归一化,ReLU激活函数,归一化可以确保梯度下降的时候较为平稳
4、3 * 3 生成新的特征图
'''
class _DenseLayer(nn.Sequential):
    def __init__(self, num_input_features, growth_rate, bn_size, drop_rate):
        '''  
        num_input_features: 输入特征数,也就是通道数,在DenseNet中,每一层都会接受之前层的输出作为输入,故,这个数值通常会随着网络深度增加而增加
        growth_rate: 增长率,这个是 DenseNet的核心概念,决定了每一层为全局状态贡献的特征数量,他的用处主要在于决定了中间瓶颈层的输出通道,需要结合代码去研究
        bn_size: 瓶颈层中输出通道大小,含义:在使用1 * 1卷积核去提取特征数时,目标通道需要扩展到growth_rate的多少倍倍数, bn_size * growth_rate(输出维度)
        drop_rate: 使用Dropout的参数
        '''
        super(_DenseLayer, self).__init__()
        self.add_module("norm1", nn.BatchNorm2d(num_input_features))
        self.add_module("relu1", nn.ReLU(inplace=True))
        # 输出维度: bn_size * growth_rate, 1 * 1卷积核,步伐为1,只起到特征提取作用
        self.add_module("conv1", nn.Conv2d(num_input_features, bn_size * growth_rate, stride=1, kernel_size=1, bias=False))
        
        self.add_module("norm2", nn.BatchNorm2d(bn_size * growth_rate))
        self.add_module("relu2", nn.ReLU(inplace=True))
        # 输出通道:growth_rate, 维度计算:不变
        self.add_module("conv2", nn.Conv2d(bn_size * growth_rate, growth_rate, stride=1, kernel_size=3, 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)  # self.training 继承nn.Sequential,是否训练模式
        # 模型融合,即,特征通道融合,形成新的特征图
        return torch.cat([x, new_features], dim=1)  # (N, C, H, W)  # 即 C1 + C2,通道上融合
    
'''  
DenseNet网络核心由DenseBlock模块组成,DenseBlock网络由DenseLayer组成,从 DenseLayer 可以看出,DenseBlock是
    密集连接,每一层的输入不仅包含前一层的输出,还包含网络中所有之前层的输出
'''
# 构建DenseBlock模块, 通过上图
class _DenseBlock(nn.Sequential):
    # num_layers 几层DenseLayer模块
    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)
    
 
 
 # Transition层,用于维度压缩
 # 组成:一个卷积层 + 一个池化层
class _Transition(nn.Sequential):
    def __init__(self, num_init_features, num_out_features):
        super(_Transition, self).__init__()
        self.add_module("norm", nn.BatchNorm2d(num_init_features))
        self.add_module("relu", nn.ReLU(inplace=True))
        self.add_module("conv", nn.Conv2d(num_init_features, num_out_features, kernel_size=1, stride=1, bias=False))
        # 降维
        self.add_module("pool", nn.AvgPool2d(2, stride=2))

         
# 搭建DenseNet网络
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.5, num_classes=1000):
        '''  
        growth_rate、num_init_features、num_init_features、drop_rate 和denselayer一样
        block_config : 参数在 DenseNet 架构中用于指定每个 Dense Block 中包含的层数, 如:
                DenseNet-121: block_config=(6, 12, 24, 16) 表示第一个 Dense Block 包含 6 层,第二个包含 12 层,第三个包含 24 层,第四个包含 16 层。
                DenseNet-169: block_config=(6, 12, 32, 32)
                DenseNet-201: block_config=(6, 12, 48, 32)
                DenseNet-264: block_config=(6, 12, 64, 48)
        compression_rate: 压缩维度, DenseNet 中用于 Transition Layer(过渡层)的一个重要参数,它控制了从一个 Dense Block 到下一个 Dense Block 之间特征维度的压缩程度
        '''
        super(DenseNet, self).__init__()
        # 第一层卷积
        # OrderedDict,让模型层有序排列
        self.features = nn.Sequential(OrderedDict([
            # 输出维度:((w - k + 2 * p) / s) + 1
            ("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
        # num_layers: 层数
        for i, num_layers in enumerate(block_config):
            block = _DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)
            # nn.Module 中features封装了nn.Sequential
            self.features.add_module("denseblock%d" % (i + 1), block)
            '''  
            # 这个计算反映了 DenseNet 中的一个关键特性:每一层输出的特征图(即新增加的通道数)由 growth_rate 决定,
            # 并且这些新生成的特征图会被传递给该 Dense Block 中的所有后续层以及下一个 Dense Block。
            '''
            num_features += num_layers * growth_rate  # 叠加,每一次叠加
            
            # 判断是否需要使用Transition层
            if i != len(block_config) - 1:
                transition = _Transition(num_features, int(num_features*compression_rate)) # 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)
        
        # params initialization         
        for m in self.modules():             
            if isinstance(m, nn.Conv2d):         
                '''
                如果当前模块是一个二维卷积层 (nn.Conv2d),那么它的权重 (m.weight) 将通过 Kaiming 正态分布 (kaiming_normal_) 进行初始化。
                这种初始化方式特别适合与ReLU激活函数一起使用,有助于缓解深度网络中的梯度消失问题,促进有效的训练。  
                '''       
                nn.init.kaiming_normal_(m.weight)             
            elif isinstance(m, nn.BatchNorm2d):      
                '''  
                对于二维批归一化层 (nn.BatchNorm2d),偏置项 (m.bias) 被初始化为0,而尺度因子 (m.weight) 被初始化为1。
                这意味着在没有数据经过的情况下,批归一化层不会对输入进行额外的缩放或偏移,保持输入不变。
                '''           
                nn.init.constant_(m.bias, 0)                 
                nn.init.constant_(m.weight, 1)             
            elif isinstance(m, nn.Linear):        
                '''  
                对于全连接层 (nn.Linear),只对其偏置项 (m.bias) 进行了初始化,设置为0'''         
                nn.init.constant_(m.bias, 0)
                
    def forward(self, x):
        features = self.features(x)
        out = F.avg_pool2d(features, 7, stride=1).view(x.size(0), -1)
        out = self.classifier(out)
        return out

搭建DenseNet-121网络,pytorch提供了参数

# 定义包含预训练模型URL的字典
model_urls = {
    'densenet121': 'https://download.pytorch.org/models/densenet121-a639ec97.pth',
}

def densent121(pretrained=False, **kwargs):
    model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 12, 16), **kwargs)
    
    # 否加载预训练的权重
    if pretrained:
        pattern = re.compile(
        r'^(.*denselayer\d+\.(?:norm|relu|conv))\.((?:[12])\.(?:weight|bias|running_mean|running_var))$')
    
        # 加载预训练模型的状态字典
        state_dict = load_state_dict_from_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

检测

model = densent121(False)

# 测试
print(model(torch.randn(32, 3, 224, 224)).shape)
torch.Size([32, 1000])

http://www.kler.cn/a/584582.html

相关文章:

  • RabbitMQ五种消息模型
  • 零成本搭建Calibre个人数字图书馆支持EPUB MOBI格式远程直读
  • 鸿蒙开发-一多开发之媒体查询功能
  • XSS跨站脚本攻击
  • Bash和Zsh的主要差异是?
  • 新鲜速递:OpenAI-Agents-Python:构建智能代理系统的轻量级框架
  • Docker子网冲突解决方案及配置说明
  • 第十五届蓝桥杯C/C++ C 组全部题目详细题解
  • 微信小程序使用的SSL证书在哪里申请?
  • 兼职招聘平台(源码+文档+讲解+演示)
  • tomcat负载均衡配置
  • leetcode0056. 合并区间 - medium
  • SQL Server性能优化实战:从瓶颈定位到高效调优
  • R+VIC模型融合实践技术应用及未来气候变化模型预测
  • Oracle获取SQL执行日志
  • 深度学习基础-onnxruntime推理模型
  • 如何通过自动化测试提升DevOps效率?
  • Axure PR 9 中继器 04 条件查询
  • MySQL与Redis的缓存一致性问题
  • 【linux】文件与目录命令 - stat