深度学习之图像分类(七)--ResNet网络结构

article/2025/9/16 6:54:14

深度学习之图像分类(七)ResNet 网络结构

目录

    • 深度学习之图像分类(七)ResNet 网络结构
      • 1. 前言
      • 2. Residual
      • 3. 网络配置
      • 4. 代码

本节学习ResNet网络结构,以及迁移学习入门,学习视频源于 Bilibili,感谢霹雳吧啦Wz,建议大家去看视频学习哦。

请添加图片描述

1. 前言

ResNet 是在 2015 年由微软实验室提出来的,斩获当年 ImageNet 竞赛中分类任务第一名,目标检测任务第一名,获得 COCO 数据集中目标检测第一名,图像分割第一名。总结而言就是 NB!更 NB 的是,这是中国人提出来的!原始论文为:Deep Residual Learning for Image Recognition。

网络越深,咱们能获取的信息越多,而且特征也越丰富。按理说,网络越来越深,至少能达到浅网络的性能,为什么呢?因为后面全部学习恒等映射就可以了呀。网络加深,不是想加就加的;恒等映射,不是说学就能学的。

但是根据实验表明,随着网络的加深,优化效果反而越差,测试数据和训练数据的准确率反而降低了。**这是由于网络的加深会造成梯度爆炸和梯度消失的问题。**目前针对这种现象已经有了解决的方法:通常是通过对数据进行标准化处理,权重初始化,以及 Batch Normalization 来处理的,这种方法可以保证网络在反向传播中采用随机梯度下降(SGD),从而让网络达到收敛。但是,这个方法仅对几十层的网络有用,当网络再往深处走的时候,这种方法就无用武之地了。并且退化问题(degradation problem,层数深的效果没有浅的好,下图左边部分 )严重。下图中实线是验证集,虚线是训练集。右图中 ResNet 层数越深,错误率越低,确实解决了退化问题。

请添加图片描述

网络亮点在于:

  • 超级深的网络结构(突破了 1000 层)
  • 提出 Residual 模块(搭建深网络的基础,学恒等不如学习0变换)
  • 使用 Batch Normalization 加速训练,丢弃了 Dropout

ResNet34 网络结构如下所示(conv_3, conv_4, conv_5 的第一个残差结构,虚线的残差结构需要注意):

请添加图片描述

2. Residual

Residual 可以把它理解为一个子网络,这个子网络经过堆叠可以构成一个很深的网络。下图给出了两种残差结构。左边的主要针对网络层数较少的网络;右边主要针对网络层数较多的网络。左边结构中,主线是将输入特征矩阵经过两个 3 × 3 3 \times 3 3×3 的卷积层,右边有一个弧线直接从输入连接到输出,与卷积后的特征矩阵按元素相加得到最终特征矩阵结果。右边的结构的主分支则是在输入与输出都加上了 1 × 1 1 \times 1 1×1 的卷积层,用来实现降维和升维。右边的参数节省了 94%。

注意:加了之后再经过 ReLU 激活函数。

注意:主分支与 shortcut 分支的输出特征矩阵的 shape 必须一致才能进行元素加法。回顾,GoogleNet 是在深度方向进行拼接。

请添加图片描述

a [ l + 2 ] = R e l u ( W [ l + 2 ] ( R e l u ( W [ l + 1 ] a [ l ] + b [ l + 1 ] ) ) + b [ l + 2 ] + a [ l ] ) a^{[l+2]} = Relu(W^{[l+2]}(Relu(W^{[l+1]}a^{[l]} + b^{[l+1]})) + b^{[l+2]} + a^{[l]}) a[l+2]=Relu(W[l+2](Relu(W[l+1]a[l]+b[l+1]))+b[l+2]+a[l])
其中 b b b 为卷积层偏置 bias。

如何理解 Residual 呢?

假设我们要求解的映射为: H ( x ) H(x) H(x)。现在咱们将这个问题转换为求解网络的残差映射函数,也就是 F ( x ) F(x) F(x),其中 F ( x ) = H ( x ) − x F(x) = H(x)-x F(x)=H(x)x。残差即观测值与估计值之间的差。这里 H ( x ) H(x) H(x) 就是观测值, x x x 就是估计值(也就是上一层Residual 输出的特征映射)。我们一般称 x x x 为 Identity Function(恒等变换),它是一个跳跃连接;称 F ( x ) F(x) F(x) 为 Residual Function。

那么我们要求解的问题变成了 H ( x ) = F ( x ) + x H(x) = F(x)+x H(x)=F(x)+x。有小伙伴可能会疑惑,我们干嘛非要经过 F ( x ) F(x) F(x) 之后在求解 H ( x ) H(x) H(x) 啊!整这么麻烦干嘛,直接搞不好吗,神经网络那么强(三层全连接可以拟合任何函数)!我们来分析分析:如果是采用一般的卷积神经网络的话,原先我们要求解的是 H ( x ) = F ( x ) H(x) = F(x) H(x)=F(x) 这个值对不?那么,我们现在假设,在我的网络达到某一个深度的时候,咱们的网络已经达到最优状态了,也就是说,此时的错误率是最低的时候,再往下加深网络的化就会出现退化问题(错误率上升的问题)。我们现在要更新下一层网络的权值就会变得很麻烦,权值得是一个让下一层网络同样也是最优状态才行,对吧?我们假设输入输出特征尺寸不变,那么下一层最优的状态就是学习一个恒等映射,不改变输入特征是最好的,这样后续的计算会保持,错误了就和浅一层一样了。但是这是很难的,试想一下,给你个 3 × 3 3 \times 3 3×3 卷积,数学上推导出来恒等映射的卷积核参数有一个,那就是中间为 1,其余为 0。但是它不是想学就能学出来的,特别是初始化权重离得远时。

但是采用残差网络就能很好的解决这个问题。还是假设当前网络的深度能够使得错误率最低,如果继续增加咱们的 ResNet,为了保证下一层的网络状态仍然是最优状态,咱们只需要把令 F ( x ) = 0 F(x)=0 F(x)=0 就好啦!因为 x x x 是当前输出的最优解,为了让它成为下一层的最优解也就是希望咱们的输出 H ( x ) = x H(x)=x H(x)=x 的话,是不是只要让 F ( x ) = 0 F(x)=0 F(x)=0 就行了?这个太方便了,只要卷积核参数都足够小,乘法加法之后就是 0 呀。当然上面提到的只是理想情况,咱们在真实测试的时候 x x x 肯定是很难达到最优的,但是总会有那么一个时刻它能够无限接近最优解。采用 Residual 的话,也只用小小的更新 F ( x ) F(x) F(x) 部分的权重值就行啦!不用像一般的卷积层一样大动干戈!

注意:如果残差映射( F ( x ) F(x) F(x))的结果的维度与跳跃连接( x x x)的维度不同,那咱们是没有办法对它们两个进行相加操作的,必须对 x x x进行升维操作,让他俩的维度相同时才能计算。升维的方法有两种:

  • 全 0 填充
  • 采用 1 × 1 1 \times 1 1×1 卷积

3. 网络配置

下图给出了不同 ResNet 的配置图。

请添加图片描述

对于需要进行下采样的残差结构(conv_3, conv_4, conv_5 的第一个残差结构),论文使用如下形式(原论文有多个形式,我们这里说的是最后作者选择的形式),主线部分 3 × 3 3 \times 3 3×3 的卷积层使用 stride = 2,实现下采样;虚线部分的 1 × 1 1 \times 1 1×1 卷积且 stride = 2:

请添加图片描述

请添加图片描述

值得注意的是,pytorch 官方实现和原始论文有些许不同:

请添加图片描述

注意一些细节:对于 conv_2 一开始,在 Resnet18 和 ResNet34 得到的就是期望的 [56, 56, 64] 的输入;但是对于 ResNet50,ResNet101 和 ResNet152 而言,通过最大池化后得到的输出 shape 是 [56, 56, 64] ,而实线残差结构所需要的输入 shape 是 [56, 56, 256] ,所以 conv_2 对应的第一个也是虚线残差层,仅旁路只调整特征矩阵的深度,高和宽都不变。

通常我们都使用 ImageNet 的预训练参数(迁移学习),能够快速训练出一个理想的模型,尽管在小数据集下也能训练一个好的模型。注意使用别人预训练模型参数时,要注意别人的预处理方式,例如 ImageNet 的常见正则化为:

transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])

迁移学习大白文理解如下所示,假设网络训练完成之后,网络第一个卷积层可能学到了角点信息,第二个卷积层可能学会了分析纹理,再到后来的卷积层学到了如何识别部件。全连接层基于这些信息学会如何分类目标类别。通常浅层的卷积操作是通用的,所以可以直接使用用于初始化。

请添加图片描述

常见的迁移学习方式包括:

  • 载入权重后训练所有参数
  • 载入权重后只训练最后几层参数
  • 在数权重后在原有网络基础上再加一层全连接,仅训练最后一个全连接层

4. 代码

ResNet 实现代码如下所示:

import torch.nn as nn
import torchclass BasicBlock(nn.Module):expansion = 1def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):super(BasicBlock, self).__init__()self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,kernel_size=3, stride=stride, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(out_channel)self.relu = nn.ReLU()self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,kernel_size=3, stride=1, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channel)self.downsample = downsampledef forward(self, x):identity = xif self.downsample is not None:identity = self.downsample(x)out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out += identityout = self.relu(out)return outclass Bottleneck(nn.Module):"""注意:原论文中,在虚线残差结构的主分支上,第一个1x1卷积层的步距是2,第二个3x3卷积层步距是1。但在pytorch官方实现过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,这么做的好处是能够在top1上提升大概0.5%的准确率。可参考Resnet v1.5 https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch"""expansion = 4def __init__(self, in_channel, out_channel, stride=1, downsample=None,groups=1, width_per_group=64):super(Bottleneck, self).__init__()width = int(out_channel * (width_per_group / 64.)) * groupsself.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,kernel_size=1, stride=1, bias=False)  # squeeze channelsself.bn1 = nn.BatchNorm2d(width)# -----------------------------------------self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,kernel_size=3, stride=stride, bias=False, padding=1)self.bn2 = nn.BatchNorm2d(width)# -----------------------------------------self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion,kernel_size=1, stride=1, bias=False)  # unsqueeze channelsself.bn3 = nn.BatchNorm2d(out_channel*self.expansion)self.relu = nn.ReLU(inplace=True)self.downsample = downsampledef forward(self, x):identity = xif self.downsample is not None:identity = self.downsample(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)out += identityout = self.relu(out)return outclass ResNet(nn.Module):def __init__(self,block,blocks_num,num_classes=1000,include_top=True,groups=1,width_per_group=64):super(ResNet, self).__init__()self.include_top = include_topself.in_channel = 64self.groups = groupsself.width_per_group = width_per_groupself.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,padding=3, bias=False)self.bn1 = nn.BatchNorm2d(self.in_channel)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(block, 64, blocks_num[0])self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)if self.include_top:self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # output size = (1, 1)self.fc = nn.Linear(512 * block.expansion, num_classes)for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')def _make_layer(self, block, channel, block_num, stride=1):downsample = Noneif stride != 1 or self.in_channel != channel * block.expansion:downsample = nn.Sequential(nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(channel * block.expansion))layers = []layers.append(block(self.in_channel,channel,downsample=downsample,stride=stride,groups=self.groups,width_per_group=self.width_per_group))self.in_channel = channel * block.expansionfor _ in range(1, block_num):layers.append(block(self.in_channel,channel,groups=self.groups,width_per_group=self.width_per_group))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)if self.include_top:x = self.avgpool(x)x = torch.flatten(x, 1)x = self.fc(x)return xdef resnet34(num_classes=1000, include_top=True):# https://download.pytorch.org/models/resnet34-333f7ec4.pthreturn ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)def resnet50(num_classes=1000, include_top=True):# https://download.pytorch.org/models/resnet50-19c8e357.pthreturn ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)def resnet101(num_classes=1000, include_top=True):# https://download.pytorch.org/models/resnet101-5d3b4d8f.pthreturn ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)def resnext50_32x4d(num_classes=1000, include_top=True):# https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pthgroups = 32width_per_group = 4return ResNet(Bottleneck, [3, 4, 6, 3],num_classes=num_classes,include_top=include_top,groups=groups,width_per_group=width_per_group)def resnext101_32x8d(num_classes=1000, include_top=True):# https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pthgroups = 32width_per_group = 8return ResNet(Bottleneck, [3, 4, 23, 3],num_classes=num_classes,include_top=include_top,groups=groups,width_per_group=width_per_group)# load ImageNet pretrain parameter
def main():device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")my_output_channel = 5# load pretrain weights# download url: https://download.pytorch.org/models/resnet34-333f7ec4.pthmodel_weight_path = "./resnet34-pre.pth"assert os.path.exists(model_weight_path), "file {} does not exist.".format(model_weight_path)# option1net = resnet34()net.load_state_dict(torch.load(model_weight_path, map_location=device))# change fc layer structurein_channel = net.fc.in_featuresnet.fc = nn.Linear(in_channel, my_output_channel)# option2# net = resnet34(num_classes=my_output_channel)# pre_weights = torch.load(model_weight_path, map_location=device)# del_key = []# for key, _ in pre_weights.items():#     if "fc" in key:#         del_key.append(key)## for key in del_key:#     del pre_weights[key]## missing_keys, unexpected_keys = net.load_state_dict(pre_weights, strict=False)# print("[missing_keys]:", *missing_keys, sep="\n")# print("[unexpected_keys]:", *unexpected_keys, sep="\n")

http://chatgpt.dhexx.cn/article/gl3vO8nJ.shtml

相关文章

[NCTF2019]SQLi 1regexp注入

[NCTF2019]SQLi&regexp注入 知识点: 无引号闭合,可以利用转义符 \ %00作为截断字符可以代替注释符#和-- - regexp注入可以配合 ^ 来爆破字符 解题: 打开题目得到: 可以看做插入语句: select * from users where…

buuctf-[NCTF2019]Keyboard

题目是这样子的: 下载完压缩包并解压后打开txt文件,发现是这样一串密文: 其实答案就在问题题目中,keyboard是键盘的意思,那结果显而易见了,o y i w u e u y u w u i i e不难发现,这些字母在键盘…

NCTF Crypto WriteUp

http://ctf.nuptzj.cn/challenges Crypto篇: 第一题、第二题、第七题和CG-CTF一样,不写了… 第三题: 说了全家桶,那就python跑吧… Flag:nctf{base64_base32_and_base16} 150分到手 第四题: 鬼知道进行…

[NCTF2019]Fake XML cookbook

BUUCTF 刷题 [NCTF2019]Fake XML cookbook BUUCTF 刷题前言一、[NCTF2019]Fake XML cookbook总结 前言 通过刷题拓宽自己的知识面 一、[NCTF2019]Fake XML cookbook (1)打开后如图所示 (2)尝试了一下万能密码,没啥用…

[NCTF2019]True XML cookbook

打开环境,看到熟悉的页面,和前面的[NCTF2019]Fake XML cookbook页面一样,应该也是XXE漏洞,这里再介绍一下XXE漏洞: XXE(XML External Entity Injection)全称为XML外部实体注入。 XML是什么? XML指可扩展…

[NCTF2019]Sore

[NCTF2019]Sore 附件: ciphertext.txt: nsfAIHFrMuLynuCApeEstxJOzniQuyBVfAChDEznppfAiEIDcyNFBsCjsLvGlDtqztuaHvHbCmuyGNsIMhGlDtbotCoDzDjhyBzHGfHGfoHsuhlssIMvwlixBHHGfDRjoCKrapNIwqNyuxIBACQhtMwCmMCfEBpsrzEuiLGBoMipTkxrznoHfAkqwzvxuzCzDbL…

[NCTF2019]babyRSA1

1.题目代码: # from Crypto.Util.number import * # from flag import flag # # def nextPrime(n): # n 2 if n & 1 else 1 # while not isPrime(n): # n 2 # return n # # p getPrime(1024) # q nextPrime(p) # n p * q # e 0x1000…

[NCTF 2018]Easy_Audit

1.打开环境,发现就是php套娃。 $_REQUEST:PHP的内置变量,是一个数组,保存传递的参数,它的特性是如果get,post一起传参,则会优先post传参,可以由此进行变量覆盖。 $_SERVER:PHP的内置变量,是一个…

第十届南京邮电大学网络攻防大赛(NCTF 2021)writeup

Web X1cT34m_API_System Author:wh1sper 题目描述:在API安全的新时代,安全圈迎来风云变幻。掀起巨浪的你?只手遮天的你?选择保护还是放弃你的曾经的伙伴?target: http://129.211.173.64:58082/ 附件链接&am…

[NCTF 2018]签到题

这题呢 看题目说签到题目 我们可以看出来 我们用F12看源代码 去搜索一下flag值在哪里 但是我们看了没有 我

NCTF2021——wp

文章目录 一、REHello せかいShadowbringer鲨鲨的秘密 二、MISC做题做累了来玩玩游戏吧Hex酱的秘密花园Hello File Format 一、RE Hello せかい ida反编译,flag明文给出 Shadowbringer 那两个函数是2次base64变异码表的编码,函数反过来换两次码表解码…

NCTF2022 calc题目复现

calc(环境变量注入getshell) 经典计算器题目,看着有点眼熟,没错,就是buu三月赛的一道题目。由于那时候web可能都算不上入门,所以也就没有复现。比赛时就网上看了看三月赛的wp,但是没有什么用&a…

[NCTF2019]SQLi

[NCTF2019]SQLi 进入页面,尝试万能密码登录: 觉得有waf,爆破一下 发现过滤了相当多的可用字符串 另外在robots.txt文件中有提示 再进入hint.txt 看到了黑名单,并且说要让查找admin的密码, 但是admin在黑名单中&am…

BUUCTF:[NCTF2019]phar matches everything

题目地址&#xff1a;https://buuoj.cn/challenges#[NCTF2019]phar%20matches%20everything 源码地址&#xff1a;https://github.com/swfangzhang/My-2019NCTF/tree/master/phar matches everything catchmime.php //catchmime.php <?php class Easytest{protected $tes…

2022NCTF的部分wp及复现

Misc Signin 程序设计实验作业,但是签到题(听zys说建议把终端字体调小一点并且只需要输入一串来自35年前的神秘秘籍 35年前的秘籍&#xff0c;就是魂斗罗的秘籍 上、上、下、下、左、右、左、右、B、A、B、A 就可以得到flag NCTF{VVe1c0m3_T0_NCTF_2022!!!} Web calc 命令…

NCTF2021 Ezsql

好兄弟&#x1f466;发来的题目&#xff0c;&#x1f474;当时没做出来&#xff0c;幸好环境还在 这道题测试一遍后&#xff0c;没什么收获&#xff0c;马后炮先扫描网站目录 发现源码&#xff0c;共有三个文件&#xff1a;config.php、DB.php、login.php 代码审计 config是…

2022 NCTF

MISC 炉边聚会 卡组代码是 Base64 编码的字节串&#xff0c;exp <?php $deckstring "AAEDAZoFKIwGngXIBrwFzgnQBfIHygf0CIgJkAiBogJ1gjMCPIHtgeeBeAD6AfyB7YHvgbgAAD4AO2B7wFkgnMCMwIga2B/QImgi6BJAIiAn2BOIJAAA"; #这是⼀个⾮常有趣的萨满卡组 $binary bas…

NCTF2022-复现

文章目录 NCTF2022-复现比赛题目比赛总结MiscSignin只因因炉边聚会zystegoqrssssssssqrssssssss_revenge 参考&#xff1a; NCTF2022-复现 比赛题目 NCTF2022 比赛总结 本次比赛&#xff0c;web还是一无所获&#xff0c;自己还是差的很多。反观misc&#xff0c;这次比赛六道…

NCTF2022 Web Writeup

1.calc 题目地址&#xff1a;http://116.205.139.166:8001/ 右键 /source 源码 app.route("/calc",methods[GET]) def calc():ip request.remote_addrnum request.values.get("num")log "echo {0}{1}{2}> ./tmp/log.txt".format(time.strf…

NCTF web总结与复现

前言 打完NCTF休息了一下&#xff0c;总体感觉还行&#xff0c;学到了很多。 calc 这一题也卡了我很久&#xff0c;因为复现过DASCTF三月赛&#xff0c;一直在想着有没有可以替代反引号或绕过的方法&#xff0c;搞了好久都没出&#xff0c;在学长的提示下学到了一个方法&…