网络编程
位置:首页>> 网络编程>> Python编程>> pytorch教程resnet.py的实现文件源码分析

pytorch教程resnet.py的实现文件源码分析

作者:xz1308579340  发布时间:2023-11-07 21:18:47 

标签:pytorch,resnet,源码分析

调用pytorch内置的模型的方法


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

这样就导入了resnet50的预训练模型了。如果只需要网络结构,不需要用预训练模型的参数来初始化

那么就是:


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

如果要导入densenet模型也是同样的道理

比如导入densenet169,且不需要是预训练的模型:


model = torchvision.models.densenet169(pretrained=False)

由于pretrained参数默认是False,所以等价于:


model = torchvision.models.densenet169()

不过为了代码清晰,最好还是加上参数赋值。

解读模型源码Resnet.py

包含的库文件


import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo

该库定义了6种Resnet的网络结构

包括


__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50',  'resnet101',  'resnet152']

每种网络都有训练好的可以直接用的.pth参数文件


__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50',  'resnet101',  'resnet152']

Resnet中大多使用3*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)

该函数继承自nn网络中的2维卷积,这样做主要是为了方便,少写参数参数由原来的6个变成了3个

输出图与输入图长宽保持一致

如何定义不同大小的Resnet网络

Resnet类是一个基类,
所谓的"Resnet18", ‘resnet34', ‘resnet50', ‘resnet101', 'resnet152'只是Resnet类初始化的时候使用了不同的参数,理论上我们可以根据Resnet类定义任意大小的Resnet网络
下面先看看这些不同大小的Resnet网络是如何定义的

定义Resnet18


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

定义Resnet34


def resnet34(pretrained=False, **kwargs):    
"""Constructs a ResNet-34 model.  
Args:        pretrained (bool): If True, returns a model pre-trained on ImageNet    """  
model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)  
if pretrained:        
   model.load_state_dict(model_zoo.load_url(model_urls['resnet34']))    
return model

我们发现Resnet18和Resnet34的定义几乎是一样的,下面我们把Resnet18,Resnet34,Resnet50,Resnet101,Resnet152,不一样的部分写在一块进行对比


model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)    #Resnet18
model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)    #Resnet34
model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)    #Eesnt50
model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)  #Resnet101
model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs)  #Resnet152

代码看起来非常的简洁工整,

其他resnet18、resnet101等函数和resnet18基本类似,差别主要是在:

1、构建网络结构的时候block的参数不一样,比如resnet18中是[2, 2, 2, 2],resnet101中是[3, 4, 23, 3]。

2、调用的block类不一样,比如在resnet50、resnet101、resnet152中调用的是Bottleneck类,而在resnet18和resnet34中调用的是BasicBlock类,这两个类的区别主要是在residual结果中卷积层的数量不同,这个是和网络结构相关的,后面会详细介绍。

3、如果下载预训练模型的话,model_urls字典的键不一样,对应不同的预训练模型。因此接下来分别看看如何构建网络结构和如何导入预训练模型。

Resnet类

构建ResNet网络是通过ResNet这个类进行的。ResNet类是继承PyTorch中网络的基类:torch.nn.Module。

构建Resnet类主要在于重写 init() forward() 方法。

我们构建的所有网络比如:VGGAlexnet等都需要重写这两个方法,这两个方法很重要

看起来Resne类是整个文档的核心

下面我们就要研究一下Resnet基类是如何实现的

Resnet类采用了pytorch定义网络模型的标准结构,包含

iinit()方法: 定义了网络的各个层
forward()方法: 定义了前向传播过程

这两个方法的用法,这个可以查看pytorch的官方文档就可以明白

在Resnet类中,还包含一个自定义的方法make_layer()方法

是用来构建ResNet网络中的4个blocks

_make_layer方法的第一个输入block是BottleneckBasicBlock

第二个输入是该blocks的输出channel

第三个输入是每个blocks中包含多少个residual子结构,因此layers这个列表就是前面resnet50的[3, 4, 6, 3]。

_make_layer方法中比较重要的两行代码是:


layers.append(block(self.inplanes, planes, stride, downsample))

该部分是将每个blocks的第一个residual结构保存在layers列表中。


for i in range(1, blocks): layers.append(block(self.inplanes, planes))

该部分是将每个blocks的剩下residual 结构保存在layers列表中,这样就完成了一个blocks的构造。这两行代码中都是通过Bottleneck这个类来完成每个residual的构建

接下来介绍Bottleneck类


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)
               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

下面我们分别看看这两个过程:

网络的forward过程


def forward(self, x):                                #x代表输入
       x = self.conv1(x)                             #进过卷积层1
       x = self.bn1(x)                                #bn1层
       x = self.relu(x)                                #relu激活
       x = self.maxpool(x)                         #最大池化
       x = self.layer1(x)                            #卷积块1
       x = self.layer2(x)                           #卷积块2
       x = self.layer3(x)                          #卷积块3
       x = self.layer4(x)                          #卷积块4
       x = self.avgpool(x)                     #平均池化
       x = x.view(x.size(0), -1)               #二维变成变成一维向量
       x = self.fc(x)                             #全连接层
       return x

里面的大部分我们都可以理解,只有layer1-layer4是Resnet网络自己定义的,
它也是Resnet残差连接的精髓所在,我们来分析一下layer层是怎么实现的

残差Block连接是如何实现的

从前面的ResNet类可以看出,在构造ResNet网络的时候,最重要的是 BasicBlock这个类,因为ResNet是由residual结构组成的,而 BasicBlock类就是完成residual结构的构建。同样 BasicBlock还是继承了torch.nn.Module类,且重写了__init__()和forward()方法。从forward方法可以看出,bottleneck就是我们熟悉的3个主要的卷积层、BN层和激活层,最后的out += residual就是element-wise add的操作。

这部分在 BasicBlock类中实现,我们看看这层是如何前向传播的


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

我画个流程图来表示一下

pytorch教程resnet.py的实现文件源码分析

画的比较丑,不过基本意思在里面了,

根据论文的描述,x是否需要下采样由x与out是否大小一样决定,

假如进过conv2和bn2后的结果我们称之为 P

假设x的大小为wHchannel1

如果P的大小也是wHchannel1

则无需下采样
out = relu(P + X)
out的大小为W * H *(channel1+channel2),

如果P的大小是W/2 * H/2 * channel

则X需要下采样后才能与P相加,
out = relu(P+ X下采样)
out的大小为W/2 * H/2 * (channel1+channel2)

BasicBlock类和Bottleneck类类似,前者主要是用来构建ResNet18和ResNet34网络,因为这两个网络的residual结构只包含两个卷积层,没有Bottleneck类中的bottleneck概念。因此在该类中,第一个卷积层采用的是kernel_size=3的卷积,就是我们之前提到的conv3x3函数。

下面是BasicBlock类的完整代码


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

以上就是pytorch教程resnet.py的实现文件源码解读的详细内容,更多关于pytorch源码解读的资料请关注脚本之家其它相关文章!

来源:https://blog.csdn.net/xz1308579340/article/details/86527971

0
投稿

猜你喜欢

  • 六、XML展望 任何一项新技术的产生都是有其需求背景的,XML的诞生是在HTML遇到不可克服的困难之后。近年来HTML在许多复杂的Web应用
  • 本文实例讲述了Python实现随机创建电话号码的方法。分享给大家供大家参考,具体如下:当需要随机的生成一些电话号码的时候,可以使用以下脚本,
  • 由于计算机软件的非法复制,通信的泄密、数据安全受到威胁。一般为了安全,会要求将数据库名称、密码等信息进行加密。所以加密在开发过程中是经常使用
  • 介绍百度aip模块是用于实现百度云与用户接口,简单来说就是使用百度云所拥有的人工智能模块。模块使用pip install baidu-aip
  • 下面都是我收集的一些比较常用的正则表达式,因为平常可能在表单验证的时候,用到的比较多。特发出来,让各位朋友共同使用。呵呵。匹配中文字符的正则
  • numpy的log和ln函数每次当我想用python实现ln函数时,下意识的就会输入错误的函数代码,这里特来记录一下关于numpy中的ln和
  • PHP fprintf() 函数实例把一些文本写入到名为 "test.txt" 的文本文件:<?php $numb
  • 相关代码:JavaScript写的日期时间控件,很好用 13个超酷的js显示时间效果 <html><head><
  • 一提起Google的产品,大多数人可能都会想到用一个词来形容,“简洁”。简单得来又实用,这就是Google的产品设计方针了。Jon Wile
  • 本文实例讲述了python中尾递归用法。分享给大家供大家参考。具体分析如下:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递
  • 我就废话不多说了,大家还是直接看代码吧~# 两个依赖包: sasl&thriftThe easier way I find to i
  • 上一篇:微软建议的ASP性能优化28条守则(8)技巧 28:阅读资源链接下面是一些与性能有关的出色的资源链接。如果您想了解有关信息,请阅读
  •  几个月来好像就现在暂时无需求,稍微轻松一下,然后在Q群中发现有人提问,怎么用CSS实现数学公式“四又二分之一”。对于这个公式个人
  • 今天发现sympy依赖的库mpmath里也有很多数学函数,其中也有在复平面绘制二维图的函数cplot,具体例子如下from mpmath i
  • 基于web的技术中,分页是一个老的不能再老的,但大家津津乐道的问题,随着xml技术的日渐应用,把xml应用到分页当中,也是一种可能,当然网上
  • 一、模块模块可以看成是一堆函数的集合体。一个py文件内部就可以放一堆函数,因此一个py文件就可以看成一个模块。如果这个py文件的文件名为mo
  •  有时候我们在设计表单的时候不希望用户输入其它字符,只想他在input中输入数字,那么我们就可以使用下面的代码,当然这个比较是客户
  • 最近看到一个内部项目的插件加载机制,非常赞。当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载
  • 目的:JS+ASP打造无刷新新闻列表,下图所示的新闻列表相信大家并不少见,包括新闻的分页功能,本文要介绍的就是各分页间的切换方式。传统的方法
  • 安装依赖1)下载安装opencv-2.4.9,并将cv2.pyd拷贝到python安装目录的site-package下2)pip insta
手机版 网络编程 asp之家 www.aspxhome.com