通过和resnet18和resnet50理解PyTorch的ResNet模块

通过和resnet18和resnet50理解PyTorch的ResNet模块文章目录模型介绍resnet18模型流程总结resnet50总结resnet和resnext的框架基本相同的,这里先学习下resnet的构建,感觉高度模块化,很方便。本文算是对PyTorch源码解读之torchvision.modelsResNet代码的详细理解,另外,强烈推荐这位大神的PyTorch的教程!模型介绍resnet的模型可以直接通过torchvision导入,可以通过pretr…

大家好,又见面了,我是你们的朋友全栈君。

resnet和resnext的框架基本相同的,这里先学习下resnet的构建,感觉高度模块化,很方便。本文算是对
PyTorch源码解读之torchvision.modelsResNet代码的详细理解,另外,强烈推荐这位大神的PyTorch的教程!

模型介绍

resnet的模型可以直接通过torchvision导入,可以通过pretrained设定是否导入预训练的参数。

import torchvision
model = torchvision.models.resnet50(pretrained=False)

如果选择导入,resnet50、resnet101和resnet18等的模型函数十分简洁并且只有ResNet的参数不同,只是需要导入预训练参数时,调用load_state_dict加载model_zoo.load_url下载的参数,这里model_urls是一个维护不同模型参数下载地址的字典。

def resnet18(pretrained=False, **kwargs):
    """Constructs a ResNet-18 model. Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
    return model
def resnet50(pretrained=False, **kwargs):
    """Constructs a ResNet-50 model. Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """
    model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet50']))
    return model
model_urls = { 
   
    'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
    'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
    'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
    'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
    'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
}

接下来我们看下重点,也就是ResNet,ResNet的组成是:基础模块Bottleneck/Basicblock,通过_make_layer生成四个的大的layer,然后在forward中排序。
__init__的两个重要参数,block和layers,block有两种(Bottleneck/Basicblock),不同模型调用的类不同在resnet50、resnet101、resnet152中调用的是Bottleneck类,而在resnet18和resnet34中调用的是BasicBlock类,在后面我们详细理解。layers是包含四个元素的列表,每个元素分别是_make_layer生成四个的大的layer的包含的resdual子结构的个数,在resnet50可以看到列表是 [3, 4, 6, 3]。
_make_layer包含四个参数,第一个参数是block的类型,第二个参数planes是输出的channel数,第三个参数blocks每个blocks中包含多少个residual子结构,也就是上述列表layers所存储的数字,第四个参数为步长。

class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000):
self.inplanes = 64
super(ResNet, self).__init__()
self.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)
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=2)
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.AvgPool2d(7, stride=1)
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_channels
m.weight.data.normal_(0, math.sqrt(2. / n)) # 卷积参数变量初始化
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1) # BN参数初始化
m.bias.data.zero_()
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.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x

接下来我们看下两种block:Bottleneck/Basicblock,他们最重要的是resdual的结构。所有的模型都继承torch.nn.Module,bottleneck改写了__init__和forward(),forward()中的out += residual就是element-wise add的操作。Bottleneck需要理解的有两处:expansion=4和downsample(下采样)。关于下采样的理论我也不清楚,我们后面直接通过代码来理解吧。

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 * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
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)
return out

Basicblock的resdual包含两个卷积层,第一层卷积层的kernel=3。

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)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.downsample = downsample
self.stride = stride
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)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out

resnet18模型流程

resnet调用的Resnet参数是model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
Resnet – init()
self.layer1之前的变量初始化不难理解,self.layer1=self._make_layer(block, 64, layers[0])这里block=Basicblock,layer[0]=2
执行_make_layer
downsample = None——if条件不满足,downsample=None
下面构建blocks层Basicblock:
layers=[]——layers.append(Basicblock(64,64,1,downsample=None))
赋值输入channel self.inplanes = planesblock.expansion = 641 = 64
for循环构建剩下的blocks-1个residual,不传downsample.
self.layer2 执行self._make_layer(block, 128, layers[1], stride=2)
downsample=None
显然if条件满足 downsample=nn.Sequential(nn.Conv2d(64,128, kernel_size=1, stride=2, bias=False), nn.BatchNorm2d(128),
)
layers=[]——layers.append(Basicblock(64,128,2,downsample))
self.inplanes = 128*1=128
for循环构建剩下的blocks-1个residual,不传dowmsample.
可以看出接下来layer3和layer4与layer2相似,最终构成resnet18.

总结

从layer2到layer4,每个layer第一个输入会增加一倍channel,所以resdual会采用下采样,而对于每一层而言,channel都是相同的,basicblock.expansion都为1,所以我们看不出其发挥的作用,我们将在resnet50研究下。如下图,这里没找到resnet18,图中的虚线就是downsample,其产生于channel变化的resdual。
在这里插入图片描述

resnet50

model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs),可以看出,resnet50采用Bottleneck模块,并且每个大的layer的blocks数量也不同。
layer1=self._make_layer(Bottleneck, 64, 3)
if条件满足,downsample = nn.Sequential(
nn.Conv2d(self.inplanes=64, 64 * 4,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(644),)
layers.append(Bottleneck(64,64,1,dowmsample)),bottleneck内经过三个卷积层Conv2d(64,64) Conv2d(64,64) Conv2d(64,64
4)保证每个block的输出channel是planesexpansion,通过self.inplanes = planesblock.expansion赋值后面block的输入channel也是planes*expansion。
在这里插入图片描述

总结

1.resnet18和resnet50所采用的基础block不同,两种block的卷积层分别为两层和三层。
2.每个大层layer中第二到最后一个block的输入输出是相同的
3.每个layer前会采用下采样,因为这个layer的输入和输出不同,所以resdual需要采用下采样。
4.self.inplanes会在layer第一个block后赋值保证后续的block输入和输出相同。
5.关于resnet和下采样的理论我不太清楚,所以以上就是以来来理解一下操作,欢迎指正。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141133.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • java 常量池和运行时常量池_java运行时常量池

    java 常量池和运行时常量池_java运行时常量池简介:这几天在看Java虚拟机方面的知识时,看到了有几种不同常量池的说法,然后我就去CSDN、博客园等上找资料,里面说的内容真是百花齐放,各自争艳,因此,我好好整理了一下,将我自认为对的理解写下来与大家共同探讨:在Java的内存分配中,总共3种常量池:1.字符串常量池(StringConstantPool):1.1:字符串常量池在Java内存区域的哪个位置?在JDK6.0及之前版本,字符串

  • 网站挂马检测_域名拦截检测网站

    网站挂马检测_域名拦截检测网站网站挂马检测工具,网站挂马如何检测,如何检测网站挂马在最近的一次网站优化项目中,我负责的一个网站出现了网站被挂马问题,所以网站的关键词排名很快就从首页消失了。把网站挂马问题解决后,花费了很多的精力才让关键词排名恢复。在seo公司工作期间,经常听到同事说起网站挂马的危害,因为没有切身体会,所以并没有太多感触。突然网站挂马的问题落到了自己负责的项目上,这才意识到网站挂马问题真的很不简单,在平时…

  • 一文解释清卷积神经网络中池化层的作用「建议收藏」

    池化层:池化层夹在连续的卷积层中间,用于压缩数据和参数的量,减小过拟合。简而言之,如果输入是图像的话,那么池化层的最主要作用就是压缩图像。池化层分为3类,平均池化,最大池化和随机池化。拿最大池化举个例子:上图的例子是按步幅2进行2X2的最大池化时的处理顺序。最大池化是获得最大值的运算,“2X2”表示目标区域的大小。如上图所示,从2X2的窗口的移动间隔为2个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如3X3的窗口的步幅会设为3,4X4的窗口的步幅会设为4等。而最大池化的优点是:

  • 离散 单射 满射 双射

    离散 单射 满射 双射单射双射满射阐述一下什么是单射,双射,满射1.单射:对于每一个不同的x都有不同的y,即x1!=x2–>y1!+y2条件:|X|<=|Y|2.满射:对于每一个y都有x与之对应条件:|Y|<=|X|3.双射:既是单射又是满射条件:|X|=|Y|代码实现通过map函数建立映射1.单射:map<int,int>BuildInjection(vector<int>src,vector<int>dst){map&l

  • python最新激活码2021 4月底【在线注册码/序列号/破解码】

    python最新激活码2021 4月底【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • javaScript — touch事件详解(touchstart、touchmove和touchend)

    HTML5中新添加了很多事件,但是由于他们的兼容问题不是很理想,应用实战性不是太强,所以在这里基本省略,咱们只分享应用广泛兼容不错的事件,日后随着兼容情况提升以后再陆续添加分享。今天为大家介绍的事件主要是触摸事件:touchstart、touchmove和touchend。一开始触摸事件touchstart、touchmove和touchend是iOS版Safari浏览器为了向开发人员传达一些信息新

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号