ResNet详解+PyTorch实现

article/2025/8/22 22:49:51

1.Resnet简介

深度残差网络(Deep residual network, ResNet)的提出是CNN图像史上的一件里程碑事件,由于其在公开数据上展现的优势,作者何凯明也因此摘得CVPR2016最佳论文奖。
Resnet是残差网络(Residual Network)的缩写,该系列网络广泛用于目标分类等领域以及作为计算机视觉任务主干经典神经网络的一部分,典型的网络有resnet50, resnet101等。Resnet网络证明网络能够向更深(包含更多隐藏层)的方向发展。

解决问题:网络退化

从经验来看,网络的深度对模型的性能至关重要,当增加网络层数后,网络可以进行更加复杂的特征模式的提取,所以当模型更深时理论上可以取得更好的结果,从图2中也可以看出网络越深而效果越好的一个实践证据。但是更深的网络其性能一定会更好吗?实验发现深度网络出现了退化问题(Degradation problem):网络深度增加时,网络准确度出现饱和,甚至出现下降。这个现象可以在图3中直观看出来:56层的网络比20层网络效果还要差。这不会是过拟合问题,因为56层网络的训练误差同样高。我们知道深层网络存在着梯度消失或者爆炸的问题,这使得深度学习模型很难训练。但是现在已经存在一些技术手段如BatchNorm来缓解这个问题。因此,出现深度网络的退化问题是非常令人诧异的。

在这里插入图片描述

2. RnsNet网络结构

ResNet网络是参考了VGG19网络,在其基础上进行了修改,并通过短路机制加入了残差单元,如图5所示。变化主要体现在ResNet直接使用stride=2的卷积做下采样,并且用global average pool层替换了全连接层。ResNet的一个重要设计原则是:当feature map大小降低一半时,feature map的数量增加一倍,这保持了网络层的复杂度。从图5中可以看到,ResNet相比普通网络每两层间增加了短路机制,这就形成了残差学习,其中虚线表示feature map数量发生了改变。图5展示的34-layer的ResNet,还可以构建更深的网络如表1所示。从表中可以看到,对于18-layer和34-layer的ResNet,其进行的两层间的残差学习,当网络更深时,其进行的是三层间的残差学习,三层卷积核分别是1x1,3x3和1x1,一个值得注意的是隐含层的feature map数量是比较小的,并且是输出feature map数量的1/4。

重点展示一下ResNet50网络,resnet50分为conv1、conv2_x、conv3_x、conv4_x、conv5_x 共5大层。1+1+3*3+4*3+6*3+3*3=50(前面一层卷积+一层池化+4组卷积 不考虑最后面的全连接、池化层)。
在这里插入图片描述
在这里插入图片描述

basic_block=identity_block,此结构保证了输入和输出相等,实现网络的串联。在这里插入图片描述

conv-blockidentity-block。右图可以看到,通过stride=2实现了图像下采样,并扩充了通道数。在这里插入图片描述

进一步,在下图可以多个模板中同时存在conv-blockidentity-block两种模式。本质区别就是有没有下采样downsample和通道转换
在这里插入图片描述

3. PyTorch模型

定义网络中的resnet18, resnet34, resnet50, resnet101网络架构

import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo# 这个文件内包括6中不同的网络架构
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101','resnet152']# 每一种架构下都有训练好的可以用的参数文件
model_urls = {'resnet18': 'https://s3.amazonaws.com/pytorch/models/resnet18-5c106cde.pth','resnet34': 'https://s3.amazonaws.com/pytorch/models/resnet34-333f7ec4.pth','resnet50': 'https://s3.amazonaws.com/pytorch/models/resnet50-19c8e357.pth','resnet101': 'https://s3.amazonaws.com/pytorch/models/resnet101-5d3b4d8f.pth','resnet152': 'https://s3.amazonaws.com/pytorch/models/resnet152-b121ed2d.pth',
}# 常见的3x3卷积
def conv3x3(in_planes, out_planes, stride=1):"3x3 convolution with padding"return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,padding=1, bias=False)# 这是残差网络中的basicblock,实现的功能如下方解释:
class BasicBlock(nn.Module):expansion = 1def __init__(self, inplanes, planes, stride=1, downsample=None): # inplanes代表输入通道数,planes代表输出通道数。super(BasicBlock, self).__init__()# Conv1self.conv1 = conv3x3(inplanes, planes, stride)self.bn1 = nn.BatchNorm2d(planes)self.relu = nn.ReLU(inplace=True)# Conv2self.conv2 = conv3x3(planes, planes)self.bn2 = nn.BatchNorm2d(planes)# 下采样self.downsample = downsampleself.stride = stridedef forward(self, x):residual = xout = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)if self.downsample is not None:residual = self.downsample(x)# F(x)+xout += residualout = self.relu(out)return out

1.BasicBlock类中的init()函数是先定义网络架构,forward()的函数是前向传播,实现的功能就是残差块,BasicBlock:
在这里插入图片描述

一位博主的手绘图:
在这里插入图片描述
Bottleneck:

class Bottleneck(nn.Module):expansion = 4      # 输出通道数的倍乘def __init__(self, inplanes, planes, stride=1, downsample=None):super(Bottleneck, self).__init__()# conv1   1x1self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)self.bn1 = nn.BatchNorm2d(planes)# conv2   3x3self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,padding=1, bias=False)self.bn2 = nn.BatchNorm2d(planes)# conv3   1x1  self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(planes * 4)self.relu = nn.ReLU(inplace=True)self.downsample = downsampleself.stride = stridedef forward(self, x):residual = xout = 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 += residualout = self.relu(out)return out

2.Bottleneck类是另一种blcok类型,同上,init()函数是预定义网络架构,forward函数是进行前向传播。该block中有三个卷积,分别是1x1,3x3,1x1,分别完成的功能就是维度压缩,卷积,恢复维度!故bottleneck实现的功能就是对通道数进行压缩,再放大。注意:这里的plane不再是输出的通道数,输出通道数应该就是plane*expansion,即4*plane。
在ss
附赠:
在这里插入图片描述
这两个class讲清楚的话,后面的网络主体架构就还蛮好理解的了,6中架构之间的不同在于basicblock和bottlenek之间的不同以及block的输入参数的不同。因为ResNet一般有4个stack,每一个stack里面都是block的堆叠,所以[3, 4, 6, 3]就是每一个stack里面堆叠block的个数,故而造就了不同深度的ResNet。

resnet18: ResNet(BasicBlock, [2, 2, 2, 2])

resnet34: ResNet(BasicBlock, [3, 4, 6, 3])

resnet50:ResNet(Bottleneck, [3, 4, 6, 3])

resnet101:ResNet(Bottleneck, [3, 4, 23, 3])

resnet152:ResNet(Bottleneck, [3, 8, 36, 3])

# resnet18
def resnet18(pretrained=False):"""Constructs a ResNet-18 model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = ResNet(BasicBlock, [2, 2, 2, 2])if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))return model# resnet34
def resnet34(pretrained=False):"""Constructs a ResNet-34 model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = ResNet(BasicBlock, [3, 4, 6, 3])if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet34']))return model# resnet50
def resnet50(pretrained=False):"""Constructs a ResNet-50 model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = ResNet(Bottleneck, [3, 4, 6, 3])if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet50']))return model# resnet101
def resnet101(pretrained=False):"""Constructs a ResNet-101 model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = ResNet(Bottleneck, [3, 4, 23, 3])if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet101']))return model# resnet152
def resnet152(pretrained=False):"""Constructs a ResNet-152 model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = ResNet(Bottleneck, [3, 8, 36, 3])if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet152']))return model

最后的ResNet类其实可以根据列表大小来构建不同深度的resnet网络架构。resnet一共有5个阶段,第一阶段是一个7x7的卷积,stride=2,然后再经过池化层,得到的特征图大小变为原图的1/4。_make_layer()函数用来产生4个layer,可以根据输入的layers列表来创建网络。

class ResNet(nn.Module):def __init__(self, block, layers, num_classes=1000):  # layers=参数列表 block选择不同的类self.inplanes = 64 super(ResNet, self).__init__()# 1.conv1self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)# 2.conv2_xself.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(block, 64, layers[0])# 3.conv3_xself.layer2 = self._make_layer(block, 128, layers[1], stride=2)# 4.conv4_xself.layer3 = self._make_layer(block, 256, layers[2], stride=2)# 5.conv5_xself.layer4 = self._make_layer(block, 512, layers[3], stride=2)self.avgpool = nn.AvgPool2d(7)self.fc = nn.Linear(512 * block.expansion, num_classes)# 初始化权重for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()def _make_layer(self, block, planes, blocks, stride=1):downsample = Noneif 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))# 每个blocks的第一个residual结构保存在layers列表中。self.inplanes = planes * block.expansionfor i in range(1, blocks):layers.append(block(self.inplanes, planes))   # 该部分是将每个blocks的剩下residual 结构保存在layers列表中,这样就完成了一个blocks的构造。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.avgpool(x)x = x.view(x.size(0), -1)   # 将输出结果展成一行x = self.fc(x)return x

ResNet50网络特征提取框架

import mathimport torch.nn as nn
from torchvision.models.utils import load_state_dict_from_urlclass Bottleneck(nn.Module):expansion = 4def __init__(self, inplanes, planes, stride=1, downsample=None):super(Bottleneck, self).__init__()self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, stride=stride, bias=False)self.bn1 = nn.BatchNorm2d(planes)self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(planes)self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(planes * 4)self.relu = nn.ReLU(inplace=True)self.downsample = downsampleself.stride = stridedef forward(self, x):residual = xout = 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 += residualout = self.relu(out)return outclass ResNet(nn.Module):def __init__(self, block, layers, num_classes=1000):#-----------------------------------##   假设输入进来的图片是600,600,3#-----------------------------------#self.inplanes = 64super(ResNet, self).__init__()# 600,600,3 -> 300,300,64self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)# 300,300,64 -> 150,150,64self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=0, ceil_mode=True)# 150,150,64 -> 150,150,256self.layer1 = self._make_layer(block, 64, layers[0])# 150,150,256 -> 75,75,512self.layer2 = self._make_layer(block, 128, layers[1], stride=2)# 75,75,512 -> 38,38,1024 到这里可以获得一个38,38,1024的共享特征层self.layer3 = self._make_layer(block, 256, layers[2], stride=2)# self.layer4被用在classifier模型中self.layer4 = self._make_layer(block, 512, layers[3], stride=2)self.avgpool = nn.AvgPool2d(7)self.fc = nn.Linear(512 * block.expansion, num_classes)for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()def _make_layer(self, block, planes, blocks, stride=1):downsample = None#-------------------------------------------------------------------##   当模型需要进行高和宽的压缩的时候,就需要用到残差边的downsample#-------------------------------------------------------------------#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.expansionfor 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.avgpool(x)x = x.view(x.size(0), -1)x = self.fc(x)return xdef resnet50(pretrained = False):model = ResNet(Bottleneck, [3, 4, 6, 3])if pretrained:state_dict = load_state_dict_from_url("https://download.pytorch.org/models/resnet50-19c8e357.pth", model_dir="./model_data")model.load_state_dict(state_dict)#----------------------------------------------------------------------------##   获取特征提取部分,从conv1到model.layer3,最终获得一个38,38,1024的特征层#----------------------------------------------------------------------------#features    = list([model.conv1, model.bn1, model.relu, model.maxpool, model.layer1, model.layer2, model.layer3])#----------------------------------------------------------------------------##   获取分类部分,从model.layer4到model.avgpool#----------------------------------------------------------------------------#classifier  = list([model.layer4, model.avgpool])features    = nn.Sequential(*features)classifier  = nn.Sequential(*classifier)return features, classifier

ResNet是一般性网络,后期也有很多学者进行调优,比如Highway Network,简单的说,就是将 x l = H ( x l − 1 ) + x l − 1 x_l = H(x_{l-1}) + x_{l-1} xl=H(xl1)+xl1变换为 x l = H ( x l − 1 ) × T ( x ) + x l − 1 × ( 1 − T ( x ) ) ) x_l = H(x_{l-1})\times T(x)+x_{l-1}\times (1-T(x))) xl=H(xl1)×T(x)+xl1×(1T(x))),就是给增加了两个门的开关呢。Facebook团中(包含何凯明大佬)再2017的CVPR论文中也对ResNet网络进行了进一步的提升,在不增加大量参数的情况下,通过分组卷积,提升了模型的检测精度,详细可参考ResNext文章呢。当然,还有一种改用堆叠的形式,而不是短路连接,相似但有一些区别,也得到了更深的网络,建议大家都可以看看呢

可参考:DenseNet详细内容学习


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

相关文章

点估计

1.说明: 设总体 X 的分布函数形式已知, 但它的一个或多个参数为未知, 借助于总体 X 的一个样本来估计总体未知参数的值的问题称为点估计问题. 在统计问题中往往先使用最大似然估计法, 在最大似然估计法使用不方便时, 再用矩估计法. 2.常用构造估计量的方法 1&#…

【应用统计学】参数统计-点估计与估计量的评价标准

一、点估计 参数的点估计就是根据样本构造一个统计量,作为总体未知参数的估计。这个统计量称为未知参数的估计量。 在统计中,经常使用的点估计量有: 二、估计量的评价标准 1、无偏性 无偏性即指估计量抽样分布的数学期望等于总体参数的真值。 2、有效…

功能点估算方法,如何让估算偏差更小?

1、何为软件功能点 ​ ​软件功能点是站在业务角度对软件规模的一种度量,功能点的多少代表软件规模的大小,这里说的功能点是标准的功能点,按照标准的估算方法,每个人对特定需求估算出的功能点数是一致的。 功能点估算方法&…

三点估算法

施工时间划分为乐观时间、最可能时间、悲观时间 乐观时间:也就是工作顺利情况下的时间为a 最可能时间:最可能时间,就是完成某道工序的最可能完成时间m 悲观时间:最悲观的时间就是工作进行不利所用时间b。 活动历时均值(或估计值)(乐观估计4最可能估计悲观估计)/6 …

点估计、区间估计(利用回归方程进行预测)

回归模型经过各种检验并标明符合预定的要求后,可利用它来预测因变量。预测(predict)是指通过自变量x的取值来预测因变量y的取值。 1、点估计 利用估计的方程,对于x的一个特定值 ,求出y的一个估计值就是点估计。点估计分为两种&…

数理统计中的点估计

• 统计推断的基本问题有二:估计问题,和假设检验问题. • 本章讨论总体参数的点估计和区间估计.理解这两种估计的思想,掌握求参数估计量的方法和评判估计量好坏的标准. 点估计 问题的提出 设灯泡寿命 T~N(μ,σ2) ,但参数 μ 和 σ2 未知. 现在要求通过对总体抽样得到的…

统计学-点估计和区间估计

点估计和区间估计 点估计 矩估计法 正态分布是一种统计量,目的是描述总体的某一性质。而矩则是描述这些样本值的分布情况,无论几阶矩,无外乎是描述整体的疏密情况。K阶矩分为原点矩和中心矩: 前者是绝对的:1阶就是平均…

点估计和区间估计——统计学概念

概念简介: 点估计和区间估计是通过样本统计量估计总体参数的两种方法。点估计是在抽样推断中不考虑抽样误差,直接以抽样指标代替全体指标的一种推断方法。因为个别样本的抽样指标不等于全体指标,所以,用抽样指标直接代替全体指标&…

【定量分析、量化金融与统计学】统计推断基础(3)---点估计、区间估计

一、前言 我发现很多人学了很久的统计学,仍然搞不清楚什么是点估计、区间估计,总是概念混淆,那今天我们来盘一盘统计推断基础的点估计、区间估计。这个系列统计推断基础5部分分别是: 总体、样本、标准差、标准误【定量分析、量化…

【数据统计】— 峰度、偏度、点估计、区间估计、矩估计、最小二乘估计

【数据统计】— 峰度、偏度、点估计、区间估计、矩估计、最小二乘估计 四分位差异众比率变异系数利用数据指标指导建模思路 形状变化数据分布形态峰度: 度量数据在中心聚集程度偏度 利用数据指标指导建模思路 参数估计点估计区间估计矩估计举例:黑白球(矩…

7.1 参数的点估计

小结: 点估计是一种统计推断方法,它用于通过样本数据估计总体参数的值。在统计学中,总体是指一个包含所有个体的集合,而样本是从总体中选出的一部分个体。总体参数是总体的某种特征,如平均值、标准差、比例等。 点估…

【数理统计】参数估计及相关(点估计、矩估计法、最大似然估计、原点矩中心距)

1 基础知识 1.1 常见分布的期望和方差 1.2 对数运算法则 log ⁡ a ( M N ) log ⁡ a M log ⁡ a N log ⁡ a ( M / N ) log ⁡ a M − log ⁡ a N log ⁡ a ( 1 / N ) − log ⁡ a N log ⁡ a M n n log ⁡ a M \log _{a}(M N)\log _{a} M\log _{a} N \\ \log _{a}(M / N…

二、机器学习基础11(点估计)

点估计:用实际样本的一个指标来估计总体的一个指标的一种估计方法。点估计举例:比如说,我们想要了解中国人的平均身高,那么在大街上随便找了一个人,通过测量这个人的身高来估计中国人的平均身高水平;或者在…

统计学之参数估计(点估计和参数估计)含例题和解答

统计学之参数估计 参数点估计矩估计法极大似然估计法点估计的评价准则(无偏性一致性有效性) 区间估计主要公式置信区间区间估计的内容总体均值的区间估计(大样本)总体均值的区间估计(小样本)单一总体均值的区间估计总结两个总体均值之差的区间估计(大样本…

点估计(矩估计法和最大似然估计法)

估计即是近似地求某个参数的值,需要区别理解样本、总体、量、值 大致的题型是已知某分布(其实包含未知参数),从中取样本并给出样本值 我只是一个初学者,可能有的步骤比较繁琐,请见谅~ 1、矩估计法 做题步骤…

概率论--点估计

首先我们来看下什么是参数估计 那么参数估计问题又是什么? 参数估计分为两大类,一类是点估计,还有一类是区间估计,点估计分为矩估计和最大似然估计,就比如说估计降雨量,预计今天的降雨量如果是550mm就…

点估计及矩估计的一些理解

点估计指的是用样本统计量来估计总体参数,因为样本统计量为数轴上某一点值,估计的结果也以一个点的数值表示,所以称为点估计。在这个定义中,总体参数也即是总体分布的参数,一般我们在讨论总体分布的时候,只有在简单随机样本(样本独立同分布)情况下才有明确的意义,总体…

参数估计(点估计和区间估计)

“参数估计是以抽样分布为中介,用样本的参数特征对总体的参数进行数值估计的过程。” 一、点估计 1.点估计就是用样本统计量来估计总体参数。 概念理解:当我们想知道某一总体的某个指标的情况时,测量整体该指标的数值 的工作量太大&#xff…

概率论:参数估计——点估计

首先,我们要知道点估计是什么: 简单来讲,点估计一般就是拿出很多样本来,拿他们的均值和方差之类的当成参数,或者是通过均值和方差计算出他的参数。 简单来说,参数空间就是这个分布的参数可以的取值。 先学习…

参数估计之点估计(矩估计,最大似然估计) 详解+例题

统计学 参数估计之点估计(矩估计,最大似然估计) 详解含推导 1.何为点估计 在了解点估计之前,我们先介绍一下估计量与估计值的概念 1.1估计量与估计值 参数估计 就是用样本统计量去估计总体的参数,如用样本均值 x ⃗ \vec x x 去估计总体均值 μ ,用样本比例 p 估计总体比…