将DenseNet换成Resnet——更换深度学习骨干网络

news2024/11/28 12:32:47

最近我在学习一个手写公式识别的网络,这个网络的backbone使用的是DenseNet,我想将其换成ResNet
至于为什么要换呢,因为我还没换过骨干网络,就像单纯拿来练练手,增加我对网络的熟悉程度,至于会不会对模型的性能有所提升,这我不知道。废话不多说,直接开干

这个网络中使用的是DenseNet-100,这里的100是这么来的
100=(16+16+16)2+1(77的卷积)+3(transition layer)

论文中给出的DenseNet代码如下

import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from thop import profile


# DenseNet-B
#Bottleneck又称为瓶颈层,因为其长得像一个瓶子,两头大中间细,其主要目的是为了减少计算量
class Bottleneck(nn.Module):
    def __init__(self, nChannels, growthRate, use_dropout):
        super(Bottleneck, self).__init__()
        interChannels = 4 * growthRate
        self.bn1 = nn.BatchNorm2d(interChannels)#对输入的数据进行批量标准化
        self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1, bias=False)#一层1*1的卷积层,nChannels为输入通道数,interChannels为输出通道数
        self.bn2 = nn.BatchNorm2d(growthRate)#归一化
        self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3, padding=1, bias=False)#一层3*3的卷积层
        self.use_dropout = use_dropout#每次训练时随机丢掉一些神经元防止过拟合
        self.dropout = nn.Dropout(p=0.2)#20%的神经元被丢弃

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)), inplace=True)#对第一层的卷积进行relu操作
        if self.use_dropout:
            out = self.dropout(out)#20%不更新
        out = F.relu(self.bn2(self.conv2(out)), inplace=True)#对第二层的卷积进行relu操作
        if self.use_dropout:
            out = self.dropout(out)##20%不更新
        out = torch.cat((x, out), 1)#将输入的初始量和经过特征提取的量相加
#        print(out.shape)
        return out


# single layer 进行了一层3*3的特征提取,并与初始量进行相加
#这段代码没用上-----------------------------------------------------------------------
class SingleLayer(nn.Module):
    def __init__(self, nChannels, growthRate, use_dropout):
        super(SingleLayer, self).__init__()
        self.bn1 = nn.BatchNorm2d(nChannels)
        self.conv1 = nn.Conv2d(nChannels, growthRate, kernel_size=3, padding=1, bias=False)
        self.use_dropout = use_dropout
        self.dropout = nn.Dropout(p=0.2)

    def forward(self, x):
        out = self.conv1(F.relu(x, inplace=True))
        if self.use_dropout:
            out = self.dropout(out)
        out = torch.cat((x, out), 1)
        return out
# ----------------------------------------------------------------------------------


# transition layer
class Transition(nn.Module):
    def __init__(self, nChannels, nOutChannels, use_dropout):
        super(Transition, self).__init__()
        self.bn1 = nn.BatchNorm2d(nOutChannels)#标准化
        self.conv1 = nn.Conv2d(nChannels, nOutChannels, kernel_size=1, bias=False)#1*1卷积,调整维度
        self.use_dropout = use_dropout
        self.dropout = nn.Dropout(p=0.2)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)), inplace=True)
        # print('relu:')
        # print(out.shape)
        if self.use_dropout:
            out = self.dropout(out)
        out = F.avg_pool2d(out, 2, ceil_mode=True)#平局池化,使用2*2的核图像变小一般,奇数进一
        # print('pool')
        # print(out.shape)
        return out


class DenseNet(nn.Module):
    def __init__(self, params):
        super(DenseNet, self).__init__()
        growthRate = params['densenet']['growthRate']#获取字典中densenet-growthRate的值(论文配置为24)
        reduction = params['densenet']['reduction']#论文为0.5
        bottleneck = params['densenet']['bottleneck']#使用瓶颈层
        use_dropout = params['densenet']['use_dropout']#使用正则化

        nDenseBlocks = 16
        nChannels = 2 * growthRate
        self.conv1 = nn.Conv2d(params['encoder']['input_channel'], nChannels, kernel_size=7, padding=3, stride=2, bias=False)#densenet的第一层*7*7的卷积核,输入通道数为1,输出通道数为48
        self.dense1 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout)#相当于使用了48个7*7的卷积核来学习特征,那么自然有48个通道
        nChannels += nDenseBlocks * growthRate#densenet输入通道等于块数乘每个块的输入通道数
        nOutChannels = int(math.floor(nChannels * reduction))#输出通道数每次是输入通道数的一半
        self.trans1 = Transition(nChannels, nOutChannels, use_dropout)
        nChannels = nOutChannels
        self.dense2 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout)#第二层densenet

        nChannels += nDenseBlocks * growthRate
        nOutChannels = int(math.floor(nChannels * reduction))
        self.trans2 = Transition(nChannels, nOutChannels, use_dropout)

        nChannels = nOutChannels
        self.dense3 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout)

    def _make_dense(self, nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout):
        layers = []
        for i in range(int(nDenseBlocks)):#这是一个包含16个块的densenet网络
            if bottleneck:#使用的均是瓶颈层
                layers.append(Bottleneck(nChannels, growthRate, use_dropout))
            else:
                layers.append(SingleLayer(nChannels, growthRate, use_dropout))

            nChannels += growthRate#输出通道数增加
        return nn.Sequential(*layers)#将多个层按顺序连起来

    def forward(self, x):
        # print('原始')#torch.Size([4, 1, 128, 316])
        # print(x.shape)
        out = self.conv1(x)#经过一个7*7的卷积核,输出从1通道变为48通道,图片大小减半
        # print('经过7*7卷积')#torch.Size([4, 48, 64, 158])
        # print(out.shape)
        out = F.relu(out, inplace=True)
        # print('RELU')#torch.Size([4, 48, 64, 158])
        # print(out.shape)
        out = F.max_pool2d(out, 2, ceil_mode=True)#维数不变,尺寸减半
        # print('池化')#torch.Size([4, 48, 32, 79])
        # print(out.shape)
        out = self.dense1(out)#维数增加,尺寸减半
        # print('dense1层')#torch.Size([4, 432, 32, 79])
        # print(out.shape)
        out = self.trans1(out)#通道数、feature map均减半
        # print('tramns1层')#torch.Size([4, 216, 16, 40])
        # print(out.shape)
        out = self.dense2(out)
        # print('dense2层')#torch.Size([4, 600, 16, 40])
        # print(out.shape)
        out = self.trans2(out)
        # print('tramns2层')#torch.Size([4, 300, 8, 20])
        # print(out.shape)
        out = self.dense3(out)
        # print('dense3层')#torch.Size([4, 684, 8, 20])
        # print(out.shape)
        return out

这里我就在想将其换成resnet看看效果如何,这里我首先将其换成resnet50

这里更换非常值得一提,更换骨干网络,说简单非常简单,只要把resnet和densenet想象成两个黑盒子,只要输入和输出的维数一样就算更换成功,但是必须对两个网络有一个大致的了解才能进行更换

这里resnet个densenet的输入是相同的(因为就是直接输入图片,不需要进行其他处理),那就需要将输出的维度弄相同即可,这里我将我的代码densenet输入和输出维度打印在终端上,可以看出来,这里这四个维度分别是:[batch_size,通道数,图片宽度,图片高度]

在这里插入图片描述
由此可以看出,输入张量经过densenet网络后,通道数从1变为684,图片的宽高变缩小了16倍(向上取整)

那么接下来要做的事情就简单了,我只需将resnet对输入张量的操作也变为通道数从1变为684,图片的宽高变缩小了16倍即可

首先我运行resnet50的代码将张量通过resnet50的输入输出信息打印到终端上,看一下张量进入resnet后维度进行了怎样的变化
在这里插入图片描述

这里发现resnet-50将张量的通道数从1变为2048,图片的宽高变缩小了32倍

那么只需要将通道数从2048变为684,让图片的宽高缩小为16倍即可达到目的

这里我对resnet的代码做了如下修改

1.首先修改输出通道数

我在resnet输出的最后,加入了一个1*1的卷积层,将通道数从2048降到了684

        self.layer5 =  nn.Conv2d(2048, 684, kernel_size=1, bias=False)
        self.mybn1 = nn.BatchNorm2d(684)  # 标准化
2.修改特征图宽高

将第二个卷积层的步长从2变为1,这样feature map的宽高就少进行了一次缩小操作,即可将缩小32倍修改为缩小16倍

#self.layer2 = self._make_layer(block, 128, layers[1], stride=2)修改前
self.layer2 = self._make_layer(block, 128, layers[1], stride=1)#修改后

再次运行代码,即可变为将通道数从1变为684,图片的宽高变缩小了16倍的操作了,也就是成功将骨干网络将Densenet换成resnet-50,这里我的的手写公式的代码就可以正常训练了

在这里插入图片描述

最后给出完整版的修改后的resnet-50的代码

import torch.nn as nn

class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride
        self.dropout = nn.Dropout(p=0.2)

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)
        out = self.dropout(out)  ##20%不更新
        return out

class ResNet50(nn.Module):

    def __init__(self, block, layers, num_classes=1000):
        super(ResNet50, self).__init__()
        self.inplanes = 64
        self.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=1)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)
        self.layer5 =  nn.Conv2d(2048, 684, kernel_size=1, bias=False)
        self.mybn1 = nn.BatchNorm2d(684)  # 标准化

    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(planes * block.expansion),
            )
        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))
        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x= self.layer5(x)
        x=self.mybn1(x)
        # print('###############')
        # print(x.shape)
        return x

总结:

更换骨干网络其实挺简单的,说白了就是将两个网络的输入维数和输出维数调成一样的即可,但是想要调成一样的,要求对两个骨干网络的代码和原理都比较熟悉才行,我这里更换骨干网络花了接近2整天才完成,前一天半主要是学习resnet和densnet的代码和原理,最后半天进行代码的修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【时间序列数据挖掘】ARIMA模型

目录 0、前言 一、移动平均模型MA 二、自回归模型AR 三、自回归移动平均模型ARMA 四、自回归移动平均模型ARIMA 【总结】 0、前言 传统时间序列分析模型: ARIMA模型是一个非常灵活的模型,对于时间序列的好多特征都能够进行描述,比如说平…

5.11黄金最新行情走势分析及多空交易策略

近期有哪些消息面影响黄金走势?本周黄金多空该如何研判? ​黄金消息面解析:北京时间周三(5月10日)20:30,美国劳工部公布4月通胀报告,整体与核心CPI年率都走低,支持美联储6月份保持利率不变。数据显示&…

RabbitMQ详解(一):Linux安装

消息队列概念 消息队列是在消息的传输过程中保存消息的容器。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。 常见的消息队列 RabbitMQ 基于AMQP(高级消息队列协议)基础上…

不要轻易放弃丢失的U盘文件夹数据,这里有按文件夹恢复数据的技巧

U盘,全名叫USB闪存盘,是一种便携式的存储设备,是一种可以插入到电脑等电子设备上进行数据传输和存储的硬件设备。U盘的使用方便、速度高、存储容量大、稳定性高,因此被广泛用于数据备份、文档传输、音频视频存储等方面。但是使用过…

easyrecovery免费版2023最新电脑数据恢复软件

通常,许多人会将工作或生活中的数据存储在我们的计算机上。很多时候,由于我们的误操作或其他一些问题,很容易错误地删除一些文件和数据。特别是,一些计算机故障总是会导致数据丢失,这是非常麻烦的。当需要重新安装系统…

【TA100】5 纹理的秘密

1 是什么? 2 为什么使用纹理 3 纹理管线 纹理投影 展开UV到UV坐标系 模型坐标> uv坐标 > 乘分辨率(256 256) > 颜色采样 4 纹理模式 重复,镜像重复,边界拉伸,填充颜色 5 采样模式 它决定了当纹理由于变换而产生拉伸时&a…

go小技巧(易错点)集锦

目录 len的魔力评论区大佬解答答案详解 结构体是否相等答案解析:结构体比较规则举例 常量的编译我的答案标准答案内存四区概念: new关键字答案 iota的魔力结果解析可跳过的值定义在一行中间插队 小结iota详解iota 原理iota 规则依赖 const按行计数多个io…

云数据库技术沙龙|多云多源下的数据复制技术解读-NineData

摘要:随着数据智能时代的到来,多云多源架构下的数据管理是企业必备的基础设施,我们认为数据存取、数据集成与分发、数据安全与数据质量是基础,也是走向多云多源架构的起点。本议题介绍云原生的多云多源数据管理NineData&#xff0…

PlSql存储过程基础

目录儿 常用指令1. 什么是PLSQL语言2. PLSQL程序结构2.1 第一个程序 HelloWord:2.2 执行程序2.2.1 在工具中执行2.2.2 在sqlplus客户端中执行(命令行) 3. 变量3.1 普通变量3.2 引用型变量3.3 记录型变量 4. 流程控制4.1 条件分支4.2 循环 5. 游标5.1 定义5.2 语法5.3 游标的属性…

或许你需要这套uni-app打包android与ios流程

1、hbuilder每个账户的每日云打包有上限次数限制,超出次数要么换账户要么换成本地打包(uni-app提供了足够多云端的打包次数) 2、android打包,也就是apk包 优先搞明白两个需求: 、android包名是否为默认值,如果是默认值&#xf…

基于轻量化深度学习网络的工业环境小目标缺陷检测

源自:控制与决策 作者:叶卓勋 刘妹琴 张森林 摘 要 工业环境下表面缺陷检测是质量管理的重要一环, 具有重要的研究价值.通用检测网络(如YOLOv4)已被证实在多种数据集检测方面是有效的, 但是在工业环境的缺陷检测仍需要解决两个问题: 一是缺陷实例在…

【接口测试】

【接口测试】 1、分层测试理论: UI测试(测试成本最高,发现BUG时间最晚) 接口测试 单元测试 2、协议分析工具 1)网络监听 TcpDump Wireshark 2)协议客户端工具 curl、postman 3)代理Pr…

phpstorm+phpstusy配置xdebug

真心觉得配置好麻烦呀 phpstorm版本: php版本用的7.3.4nts phpstudy版本:8.1.3 先下载xdebug,我用的是php_xdebug-3.1.6-7.3-vc15-nts-x86_64,phpstudy自带的那个xdebug我配置完不起作用 把下完的xdebug放到php下etc目录里,并改名为php_x…

C语言小项目之三子棋

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C语言学习分享⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习更多C语言知识   🔝🔝 三子棋 1. 前言📕2. 思…

【英】考虑多能负荷不确定性的区域综合能源系统鲁棒规划(MatlabPython代码)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

寅家科技完成近亿元B1轮融资,加速高阶智能驾驶布局

近日,寅家科技宣布完成近亿元人民币B1轮融资,本轮融资由东方富海、深创投、深圳高新投联合投资,所募资金主要用于公司高阶智能驾驶技术产品的研发迭代,以及智能驾驶产品量产、海外市场开拓,从而进一步提升核心产品的市…

Solow模型推导模拟

Solow模型推导模拟 文章目录 Solow模型推导模拟[toc]1 Solow模型推导2 Solow模型模拟 1 Solow模型推导 在存在资本折旧、技术进步和人口增长条件下,有效劳动人均资本为 k ˙ ( t ) K ˙ ( t ) A ( t ) L ( t ) − K ( t ) [ A ( t ) L ( t ) ] 2 [ A ( t ) L ˙ …

腾讯云网站备案流程步骤、备案审核通过时间详细说明

腾讯云网站备案流程先填写基础信息、主体信息和网站信息,然后提交备案后等待腾讯云初审,初审通过后进行短信核验,最后等待各省管局审核,前面腾讯云初审时间1到2天左右,最长时间是等待管局审核时间,网站备案…

css新手引导实现方式总结

新手引导功能一般都是用一个半透明的黑色进行遮罩,蒙层上方对界面进行高亮,旁边配以弹窗进行讲解,样式如下 但是由于交互不同,实现方案也不一样,下面就针对不用的交互,总结了不同的实现方法(下文…

路由和寻址的区别

如果说传输层协议,除了 TCP/UDP,我们还可以有其他选择,比如 Google 开发的 QUIC 协议,帮助在传输层支持 HTTP 3.0 传输。但是在网络层,IP 协议几乎一统天下。IP 协议目前主要有两个版本 IPv4 和 IPv6。 根据 Google 统…