Resnet代码详解

news2024/11/16 3:23:47

这篇文章是用来讲解Resnet(残差网络)代码的,结合代码理解残差网络结构。

目录

Bottleneck类

Conv3×3

Conv1×1 

BasicBlock 

ResNet

 _make_layer代码解析

完整的ResNet代码:


可以直接调用torch内置的resnet官方代码。

from torchvision.models import resnet50
model = resnet50()
print("model:", model)

不论是调用resnet50还是resnet101,这些模型都是调用的Resnet模型。因此我们仅需要看这个类就可以。

在ResNet这个类中又由Bottleneck(瓶颈层)、3×3卷积层1×1卷积层BasicBlock组成。接下来将逐步解释。

Bottleneck类

拼劲层这个类在resnet50及之后的系列用这个,resnet18、resnet34BasicBlock

参数说明:

        expansion=4:Bottleneck的输出通道数是输入通道数的4背

        inplanes:输入通道数

        planes:输出通道数

        stride:步长

        downsample:下采样

        groups:分组卷积

        base_width:卷积块宽度

        dilation:空洞卷积

        nor_layer:是否传入norm_layer

class Bottleneck(nn.Module):
    # Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
    # while original implementation places the stride at the first 1x1 convolution(self.conv1)
    # according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
    # This variant is also known as ResNet V1.5 and improves accuracy according to
    # https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.

    expansion = 4
    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None):
        super(Bottleneck, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        width = int(planes * (base_width / 64.)) * groups
        # Both self.conv2 and self.downsample layers downsample the input when stride != 1
        self.conv1 = conv1x1(inplanes, width)
        self.bn1 = norm_layer(width)
        self.conv2 = conv3x3(width, width, stride, groups, dilation)
        self.bn2 = norm_layer(width)
        self.conv3 = conv1x1(width, planes * self.expansion)
        self.bn3 = norm_layer(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        identity = 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)
        
        # 表示如果输入和输出通道数不等,那就通过1x1卷积进行升维后的相加操作,否则可以可以直接相加
        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)
        '''
        if downsample:
            x -->conv_1x1-->bn-->relu-->conv_3x3-->bn-->relu-->conv_1x1-->bn--add-->relu-->out
            |___________downsample____________________________________________|
        else:
            x -->conv_1x1-->bn-->relu-->conv_3x3-->bn-->relu-->conv_1x1-->bn--add-->relu-->out
            |__________________________________________________________________|
        '''

        return out

下面这张图是一个Bottleneck结构图,残差边为一个1x1的卷积。 

Conv3×3

传入参数:

in_planes:输入通道

out_planes:输出通道

stride:步长

groups:卷积分组数

dilation:可以控制空洞卷积

可以看到这个conv3×3中的kernel_size为3,bias为False,padding的大小和dilation一样。

def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=dilation, groups=groups, bias=False, dilation=dilation)

Conv1×1 

in_planes:输入通道数

out_planes:输出通道数

可以看到kernel_size为1,bias为False

def conv1x1(in_planes, out_planes, stride=1):
    """1x1 convolution"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)

BasicBlock 

这个BasicBlock当在resnet18、resnet34用这个

传入参数:

inplanes:输入通道数

planes:输出通道数

stride:步长

downsample:下采样

groups:分组数

base_width:宽度

当norm_layer为None的时候,则norm_layer为BN层。当采用groups(分组卷积)或者base_width不为64的时候抛出错误:

'BasicBlock only supports groups=1 and base_width=64'

该错误表示在BasicBlock仅支持groups=1和base_width=64

当dilation>1的时候,表示在BasicBlock采用了空洞卷积,抛出错误。 

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None):
        super(BasicBlock, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        if groups != 1 or base_width != 64:
            raise ValueError('BasicBlock only supports groups=1 and base_width=64')
        if dilation > 1:
            raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
        # Both self.conv1 and self.downsample layers downsample the input when stride != 1
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = norm_layer(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = norm_layer(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        identity = 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:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)
        """
        if norm_layer is None
            if downsample is not None:
                x -->conv_3x3-->bn-->relu-->conv_3x3-->bn--add--relu-->out
                |____________downsample_____________________|
            if downsample is None:
                x -->conv_3×3-->bn-->relu-->conv_3×3-->bn--add--relu-->out
                |___________________________________________|
        """
        return out

可以看到在BasicBlock(基础块)中当需要进行下采样的时候,残差边需要一次下采样。


ResNet

在正式讲Resnet之前需要讲一下_make_layer函数,因为网络结构中的残差层都是由这个函数决定的。

 _make_layer代码解析

参数说明:

        block:传入BasicBlock还是Bottleneck

        planes:输出通道数

        blocks:传入的layer

        stride:步长,默认为1

        dilate:是否采用空洞卷积,默认为False

这里以不采用空洞卷积,也就是dilate=False,block取Bottlenenck为例。

        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )

上面这段代码表示什么时候采用下采样的情况,当stride不为1,或者通道数inplanes(初始默认取值64) ≠ planes * block.expansion(此时block.expansion=4)。输入和输出通道不相等时候,下采样结构定义为

downsample:

        (conv1×1:conv2d(inplanes,4*planes,stride),

        norm_layer:BN)

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
                            self.base_width, previous_dilation, norm_layer))
        self.inplanes = planes * block.expansion

上面这段代码表示,创建一个空的列表layers, 此时的block为Bottleneck,将获得的Bottlenck放入layers列表中。放入以后下一层的inplanes输入通道数为变成上一层输出通道数planes的4倍【也就是为下一个block做准备】。

        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups,
                                base_width=self.base_width, dilation=self.dilation,
                                norm_layer=norm_layer))

而上面这一段代码就是表示当前的Bottleneck会重复几次(不过需要注意的是,在每个layer中只在第一个bottleneck用了1x1的残差边)。

    def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
        norm_layer = self._norm_layer
        downsample = None
        previous_dilation = self.dilation
        if dilate:
            self.dilation *= stride
            stride = 1
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
                            self.base_width, previous_dilation, norm_layer))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups,
                                base_width=self.base_width, dilation=self.dilation,
                                norm_layer=norm_layer))

        return nn.Sequential(*layers)

接下来是一步一步看ResNet中的代码。

参数说明:

block:表示传入BasicBlock或者Bottleneck层。

layers:传入的是个列表,可以通过获取layers[index]来控制stride,以及是否采用空洞卷积。

num_classes:分类数量

zero_init_residual:初始化

groups:分组数

replace_stride_with_dilation:表示是否传入空洞卷积参数。如果不指定,则赋值为 [False, False, False],表示不使用空洞卷积。

norm_layer:是否传入norm_layer层,不传入的时候则为BN层。

def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
                 groups=1, width_per_group=64, replace_stride_with_dilation=None,
                 norm_layer=None):

代码讲解将以Resnet50为例,那么此时传入的block就为Bottleneck,layer=[3,4,6,3],num_classes=1000,其他Resnet系列可以看下面这张图。在看代码的时候希望大家可以对着下面这个图来看,方便理解。

 先看下下面这几行代码,可以看到这三行代码是由一个输入通道为3,输出通道为64,k=7,s=2,paddind=3,bn层,relu函数构成的,这正好就对应到上面图中的conv1

        # conv1结构代码
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = norm_layer(self.inplanes)
        self.relu = nn.ReLU(inplace=True)

 然后再看conv2_x。conv2_x是由一个最大池化,还有3个Bottleneck组成(你可以理解为图中的3,4,6,3就是这类结构重复次数)。

        # conv2_x
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])

代码中的layer1调用的是_make_layer函数, 

下面这张图为layer1,表示为第一个bottleneck结构。在Resnet的每个Bottlenck中,只在第一个Bottlenck处的残差边会用1x1的卷积进行升维,其他的都是输入和输出直接相加,这个特点需要注意一下。

        self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
                                       dilate=replace_stride_with_dilation[0])  #
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
                                       dilate=replace_stride_with_dilation[1])
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
                                       dilate=replace_stride_with_dilation[2])

 然后看layer2,3,4,过程和layer1是一样的,只不过这里传入的stride=2.

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

最后就是连接一个平均池化和全连接用来分类。

完整的ResNet代码:

class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
                 groups=1, width_per_group=64, replace_stride_with_dilation=None,
                 norm_layer=None):
        super(ResNet, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self._norm_layer = norm_layer

        self.inplanes = 64
        self.dilation = 1
        if replace_stride_with_dilation is None:
            # each element in the tuple indicates if we should replace
            # the 2x2 stride with a dilated convolution instead
            replace_stride_with_dilation = [False, False, False]
        if len(replace_stride_with_dilation) != 3:
            raise ValueError("replace_stride_with_dilation should be None "
                             "or a 3-element tuple, got {}".format(replace_stride_with_dilation))
        self.groups = groups
        self.base_width = width_per_group
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = norm_layer(self.inplanes)
        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,
                                       dilate=replace_stride_with_dilation[0])
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
                                       dilate=replace_stride_with_dilation[1])
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
                                       dilate=replace_stride_with_dilation[2])
        self.avgpool = nn.AdaptiveAvgPool2d((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')
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

        # Zero-initialize the last BN in each residual branch,
        # so that the residual branch starts with zeros, and each residual block behaves like an identity.
        # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)

    def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
        norm_layer = self._norm_layer
        downsample = None
        previous_dilation = self.dilation
        if dilate:
            self.dilation *= stride
            stride = 1
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
                            self.base_width, previous_dilation, norm_layer))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups,
                                base_width=self.base_width, dilation=self.dilation,
                                norm_layer=norm_layer))

        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 = torch.flatten(x, 1)
        x = self.fc(x)

        return x

所以可以进行一个总结:

_make_layer函数用来制作残差块的结构,参数layer可以用来控制每个残差层是由多少个残差块组成的,在残差块结构中判断是否采用downsample(1x1卷积进行升维)是根据步长或者输入输出通道数是否相等,如果步长为1,输入通道数不等于输出通道数就会采用一个1x1卷积进行升维。每个残差层layer只有第一个残差块是采用了downsample。 


后续将结合这一部分做知识蒸馏的讲解,请持续关注

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

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

相关文章

华为手表开发:WATCH 3 Pro(19)传感器订阅 光线传感器

华为手表开发:WATCH 3 Pro(19)传感器订阅 光线传感器初环境与设备光线传感器鸿蒙开发文件夹:文件新增展示的文本标记index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发 环境与设备 系…

IP子网划分例题详解

子网划分概念: 通过改变ip的掩码长度来改变ip的网络地址,把原来的ip地址从网络位主机位,改成网络位子网位主机位。从而达到缩小主机个数或者扩大主机个数。缩小主机位,可以避免ip资源的浪费,减小广播域,提…

当对象释放时,避免析构函数调用两次

在上一篇文章中&#xff0c;我们提到过&#xff0c;在一个对象的析构函数中执行太多任务&#xff0c;可能导致对象被释放两次。解决此问题的标准方法是在析构过程中使用一个自定义的引用计数&#xff0c;如下图所示&#xff1a; >> 请移步至 topomel.com 以查看图片 <…

【Python】爬虫数据提取

目录 一、xpath提取数据 二、爬虫爬取图片资源 三、爬虫爬取视频资源 四、FLV文件转码为MP4文件 一、xpath提取数据 <bookstore> <book category"Python 基础"><title lang"cn">cook book</title><author>David Beazle…

PROFINET1.8.0.5协议移植问题汇总

注&#xff1a;记录个人移植过程遇到的问题&#xff0c;正在更新。。。 PROFINET1.8.0.5协议移植遇到问题汇总&#xff1a; 软件环境&#xff1a;TIA_V17 硬件环境&#xff1a;stm32F205_ZET6主控芯片TPS-1 PROFINET IO 20500PF00&#xff08;芯片物料编码B0001.0.2&#xff09…

LabVIEW-数组数据类型

数组是将一系列、同一类型的数据组合到一起。在LabVIEW 中&#xff0c;数组可以是字符串类型、数值型或者布尔型等多种数据类型中的同类数据的集合。但不能创建以数组为元素的数组&#xff0c;也不能创建图标和图形数组。 目录 创建数组 ​编辑 数组相关函数 求数组大小 初…

并发集合ConcurrentHashMap、CopyOnWriteArrayList

一、ConcurrentHashMap 1.1 存储结构 ConcurrentHashMap是线程安全的HashMap ConcurrentHashMap在JDK1.8中是以CAS+synchronized实现的线程安全 CAS:在没有hash冲突时(Node要放在数组上时) synchronized:在出现hash冲突时(Node存放的位置已经有数据了) 存储的结构:…

移动机器人路径跟踪的设计和仿真模型预测控制(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 在轨迹跟踪应用领域&#xff0c;通常 MPC 建模可根据机器人的控制方式选择基于运动学运动状态方程建模或者基于动力学运动状态…

【JavaEE初阶】计算机工作原理

摄影分享~ 文章目录冯诺依曼体系操作系统操作系统的定位进程/任务&#xff08;process/task&#xff09;内存管理冯诺依曼体系 线代的计算机&#xff0c;大多遵守冯诺依曼体系结构。 CPU 中央处理器: 进行算术运算和逻辑判断. 存储器: 分为外存和内存, 用于存储数据(使用二进…

从架构的角度看搜索与推荐

搜索与推荐的区别 1. 场景需求不同 搜索的场景故名思义&#xff0c;就是用户提供想要寻找的内容的描述&#xff0c;系统返回给用户匹配到的结果&#xff0c;常见的场景如文字输入框的搜索&#xff0c;图片搜索&#xff0c;听音识曲&#xff0c;标签筛选等&#xff0c;看似很多…

TypeScript - 泛型 Generics(通俗易懂详细教程)

前言 关于概念&#xff0c;本文不会过多叙述。 先来看个例子&#xff0c;体会一下泛型解决的问题吧。 我们定义一个 print 函数&#xff0c;这个函数的功能是把传入的参数打印出来&#xff0c;最后再返回这个参数&#xff0c;传入参数的类型是 string&#xff0c;函数返回类型…

Kyligence Zen 产品体验 --- 全方位总结

Kyligence Zen 是一个企业级大数据分析平台&#xff0c;基于 Hadoop 和 Spark 技术栈&#xff0c;具有高性能、可扩展性和易用性等优点。本文将从体验者角度出发&#xff0c;对 Kyligence Zen 进行详细的描述&#xff0c;包括使用场景、功能特点和使用体验。 一、使用场景 Kyl…

springboot 统一异常处理 + 日志记录

在项目的开发中&#xff0c;在某些情况下&#xff0c;比如非业务的操作&#xff0c;日志记录&#xff0c;权限认证和异常处理等。我们需要对客户端发出的请求进行拦截&#xff0c;常用的API拦截方式有Fliter&#xff0c;Interceptor&#xff0c;ControllerAdvice以及Aspect。先…

JavaScript【四】JavaScript中的函数

文章目录&#x1f31f;前言&#x1f31f;什么是函数?&#x1f31f;函数声明方式&#x1f31f; function关键字&#x1f31f; 字面量定义(匿名函数)&#x1f31f; 实例化构造函数&#x1f31f;函数调用方式&#x1f31f;通过括号调用&#x1f31f;自调用(IIFE)&#x1f31f;通过…

企业电子招投标采购系统——功能模块功能描述+数字化采购管理 采购招投标

​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

windows安装Metasploit

近期在大佬群里各种炫技&#xff0c;各种工具使用&#xff0c;漏洞利用与复现&#xff0c;感觉自己常规安全测试就是个小学生&#xff0c;于是好心的大佬发了个Rapid7Setup-Windows64.exe的渗透工具&#xff0c;但是自己的电脑安装了&#xff0c;破解不了&#xff0c;导致失败&…

计算机组成原理——第四章指令系统(上)

提示&#xff1a;待到秋来九月八&#xff0c;我花开后百花杀 文章目录前言4.1.1 指令格式4.1.2 扩展操作码指令格式4.2.1 指令寻址4.2.2 数据寻址4.2.3 偏移寻址4.2.4 堆栈寻址汇总前言 通过第二章我们学习了运算器是如何进行加减乘除&#xff0c;移位运算操作的&#xff0c;通…

【密码算法 之七】GCM 浅析

文章目录1. 概述1.1 GHASH1.3 GCTR2. GCM 加密3. GCM 解密4. 总结在我的另一篇博客【密码算法 之三】分组密码工作模式 &#xff08;ECB \ CBC \ CFB \ OFB \ CTR \ XTS&#xff09;浅析 中已经详细的介绍了对称算法&#xff08;也称为“分组密码算法”&#xff09;的各种工作模…

排序算法(一)

一、排序算法 排序算法就是将数组按照数值或者字母序排列&#xff0c;常用的排序算法有很多&#xff0c;如下&#xff1a; 详细细节可以直接看维基百科&#xff1a;https://zh.wikipedia.org/zh-cn/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95 C的库中实现了常用的排序算法&#x…

Linux_红帽8学习笔记分享_4

Linux_红帽8学习笔记分享_4 文章目录Linux_红帽8学习笔记分享_41.用户管理useradd命令的使用1.1使用useradd添加用户1.2 Linux系统底层的四个连锁反应2.账户信息文件/etc/passwd中七个字段的含义3.组信息文件/etc/group中四个字段的含义4.影子文件/etc/shadow两个字段的含义5.相…