【图像分类】【深度学习】【Pytorch版本】ResNet模型算法详解

news2024/11/28 0:46:51

【图像分类】【深度学习】【Pytorch版本】 ResNet模型算法详解

文章目录

  • 【图像分类】【深度学习】【Pytorch版本】 ResNet模型算法详解
  • 前言
  • ResNet讲解
    • Deep residual learning framework(深度残差学习框架)
    • ResNet残差结构
    • ResNet模型结构
  • ResNet Pytorch代码
  • 完整代码
  • 总结


前言

ResNet是微软研究院的He, Kaiming等人在《Deep Residual Learning for Image Recognition【CVPR-2016】》【论文地址】一文中提出的模型,通过残差模块发现并解决深层网络的退化问题,使更深的卷积神经网络的训练成为可能,大大提升神经网络深度。

网络的退化问题:随着网络层数的增加,模型性能反而变得更差的现象。


ResNet讲解

神经网络将一个端到端的多层模型中的低/中/高级特征以及分类器整合起来,特征的等级可以通过所堆叠层的数量(深度)来丰富,模型的深度发挥着至关重要的作用。
训练一个更好的网络是否和堆叠更多的层一样简单呢?解决这一问题的障碍是梯度消失/梯度爆炸 (退化问题),这阻碍了模型的收敛。传统解决办法通过归一初始化(normalized initialization) 和**中间归一化(intermediate normalization)**在很大程度上解决了这一问题,它使得数十层的网络在反向传播的随机梯度下降上能够收敛。
但是当更深的网络能够开始收敛时又暴露了新的退化问题:随着网络深度的增加,准确率达到饱和后迅速下降。这种下降并不是由过拟合引起的,并且在深度适当的模型上添加更多的网络层会导致更高的训练误差。

论文中提到了一种构建神经网络的理论(思路):假设浅层网络已经取得不错的效果,新增加的网络层啥也不干,只是拟合一个恒等映射(identity mapping),就是新增网络层的输出就拟合新增网络层的输入,这样构建的深层网络至少不应该比没有新增网络层的浅层网络的训练误差要高。该理论在当时没能用实验进行证明。

Deep residual learning framework(深度残差学习框架)

依据:一个更深的模型不应当产生比它的浅层版本更高的训练错误率。
为了解决退化问题,作者在该论文中提出了“深度残差学习框架”的网络。在该框架中,每个堆叠网络块(building block) 拟合残差映射(Residual mapping)

相较于残差结构(Plaint net),将以前的结构称之为非残差结构(Residual net)。

网络块由若干卷积层组成,非残差结构直接拟合网络块期望的基础映射(Underlying mapping) H ( X ) H(X) H(X),基础映射即将当前网络块的输入与输出之间的映射。残差结构拟合网络块同样是基础映射 H ( X ) H(X) H(X),但网络块新增了另一条表示恒等映射/投影(identity)的支路 X X 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是最优的,不需要后续网络块处理,残差结构将 F ( X ) F(X) F(X)置为零比非残差结构将 H ( X ) H(X) H(X)直接拟合成原始输入的 X X X更容易,即使 F ( X ) F(X) F(X)不可能得到理想的置零,但是接近于零,也足以缓解了退化问题。

论文实验证明,优化残差映射比优化原始的基础映射容易很多,残差结构确实比其他结构更易学习!

ResNet残差结构

正是因为提出了残差模块,可以搭建更深的网络,论文中提到了俩种残差结构,左边的图主要是针对层数较少的网络使用的残差结构,右边的图是针对层数较多的网络使用的残差结构。

残差结构在主路上经过一系列的卷积层之后输出的特征图再与输入特征图进行一个相加的操作,主分支与shortcut分支的特征图在相同的维度上做相加的操作,之后通过激活函数输出,这就要求主分支与shortcut的输出特征图形状必须完全一致。

1×1卷积是为了升维和降维

随着网络层深度增加时,输出的特征图的尺寸会减小而通道数会增加,部分残差结构的输入特征图和输出特征图的形状不再保持一致,为了应对这种情况,会在shortcut分支上新增一层1×1卷积层,目的是为了残差结构的主分支输出特征图和shortcut分支输出特征图的形状再次保持一致。

ResNet模型结构

下图是原论文给出的关于ResNet模型结构的详细示意图:
Resnet在图像分类中分为两部分:backbone部分: 主要由残差结构、卷积层和池化层(汇聚层)组成,分类器部分:由全连接层组成 。
论文通过前文提到的两种残差结构的堆叠搭建了5种网络,分别为Resnet-18、Resnet-34、Resnet-50、Resnet-101和Resnet-152。
下图以Resnet-34为例,根据Vgg的启发构建卷积神经网络:

对于输出特征图尺寸相同的层,具有相同数量的卷积核;当特征图尺寸减半则卷积核数量加倍以保持每层网络计算的时间复杂度;主要通过步长为2的卷积层直接执行下采样。

当输入和输出具有相同形状(实线shortcut),可以直接使用恒等快捷连接;当输入和输出具有不同形状时(虚线shortcut),新增一层1×1卷积层用于配准形状。


ResNet Pytorch代码

残差结构BasicBlock(小网络): 卷积层+BN层+激活函数

class BasicBlock(nn.Module):
    # 小网络的输入输出的channel是保存一致的
    expansion = 1
    def __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 = downsample

    def forward(self, x):
        identity = x
        if 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 += identity
        out = self.relu(out)
        return out

残差结构Bottleneck(大网络): 卷积层+BN层+激活函数

class Bottleneck(nn.Module):
    # 大网络的输出channel是输入channel的4倍
    expansion = 4
    def __init__(self, in_channel, out_channel, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        # 第一层卷积层
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=1, stride=1, bias=False)  # 压缩channel
        self.bn1 = nn.BatchNorm2d(out_channel)
        # 第二层卷积层
        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, bias=False, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channel)
        # 第三层卷积层
        self.conv3 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel*self.expansion,
                               kernel_size=1, stride=1, bias=False)  # 扩展channel
        self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample

    def forward(self, x):
        identity = x
        if 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 += identity
        out = self.relu(out)
        return out

downsample(输入和输出形状不同): 卷积层+BN层

downsample = nn.Sequential(
    nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
    nn.BatchNorm2d(channel * block.expansion))

完整代码

import torch.nn as nn
import torch
from torchsummary import summary

class BasicBlock(nn.Module):
    # 小网络的输入输出的channel是保存一致的
    expansion = 1
    def __init__(self, in_channel, out_channel, stride=1, downsample=None):
        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 = downsample

    def forward(self, x):
        identity = x
        if 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 += identity
        out = self.relu(out)
        return out

class Bottleneck(nn.Module):
    # 大网络的输出channel是输入channel的4倍
    expansion = 4
    def __init__(self, in_channel, out_channel, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        # 第一层卷积层
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=1, stride=1, bias=False)  # 压缩channel
        self.bn1 = nn.BatchNorm2d(out_channel)
        # 第二层卷积层
        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, bias=False, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channel)
        # 第三层卷积层
        self.conv3 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel*self.expansion,
                               kernel_size=1, stride=1, bias=False)  # 扩展channel
        self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample

    def forward(self, x):
        identity = x
        if 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 += identity
        out = self.relu(out)
        return out

class ResNet(nn.Module):

    def __init__(self,
                 block,
                 blocks_num,
                 num_classes=1000):
        super(ResNet, self).__init__()
        self.in_channel = 64

        self.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)
        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 = None
        # 残差块组中的第一个残差块
        '''
        第一个残差块组是不执行下采样的,但是小网络的输入输出是channel一致,大网络输出是输入channel的四倍
        因此shortcut是否需要卷积层的判断条件不再是stride,而是channel
        channel不一致则是大网络的Bottleneck,第一个残差块需要卷积层调整维度
        '''
        if 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))
        self.in_channel = channel * block.expansion
        # 残差块组中的剩余残差块
        for _ in range(1, block_num):
            layers.append(block(self.in_channel,
                                channel))
        return nn.Sequential(*layers)

    def forward(self, x):
        # backbone主干网络部分
        # resnet34为例
        # N x 3 x 224 x 224
        x = self.conv1(x)
        # N x 64 x 112 x 112
        x = self.bn1(x)
        # N x 64 x 112 x 112
        x = self.relu(x)
        # N x 64 x 112 x 112
        x = self.maxpool(x)
        # N x 64 x 56 x 56
        # 第一组残差块组
        x = self.layer1(x)
        # N x 64 x 56 x 56
        # 第二组残差块组
        x = self.layer2(x)
        # N x 128 x 28 x 28
        # 第三组残差块组
        x = self.layer3(x)
        # N x 256 x 14 x 14
        # 第四组残差块组
        x = self.layer4(x)
        # N x 512 x 7 x 7
        # 分类器部分
        x = self.avgpool(x)
        # N x 512 x 1 x 1
        x = torch.flatten(x, 1)
        # N x 512
        x = self.fc(x)
        # N x 1000
        return x

def resnet18(num_classes=1000):
    # https://download.pytorch.org/models/resnet34-333f7ec4.pth
    return ResNet(BasicBlock, [2, 2, 2, 2], num_classes=num_classes)

def resnet34(num_classes=1000):
    # https://download.pytorch.org/models/resnet34-333f7ec4.pth
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes)

def resnet50(num_classes=1000):
    # https://download.pytorch.org/models/resnet50-19c8e357.pth
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes)

def resnet101(num_classes=1000):
    # https://download.pytorch.org/models/resnet101-5d3b4d8f.pth
    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes)

def resnet152(num_classes=1000):
    # https://download.pytorch.org/models/resnet101-5d3b4d8f.pth
    return ResNet(Bottleneck, [3, 8, 36, 3], num_classes=num_classes)

if __name__ == '__main__':
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    model = resnet34().to(device)
    summary(model, input_size=(3, 224, 224))

summary可以打印网络结构和参数,方便查看搭建好的网络结构。


总结

尽可能简单、详细的介绍了残差结构的原理和在卷积神经网络中的作用,讲解了ResNet模型的结构和pytorch代码。

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

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

相关文章

【OpenCV实现图像:OpenCV进行OCR字符分割】

文章目录 概要基本概念读入图像图像二值化小结 概要 在处理OCR(Optical Character Recognition,光学字符识别)时,利用传统的图像处理方法进行字符切分仍然是一种有效的途径。即便当前计算机视觉领域主导的是卷积神经网络&#xf…

AI中文版怎么用,版本分享,GPT官网入口

网页版上线啦,在线助力大学生、上班族的高效生活! GPT4.0是OpenAI最新推出的聊天模型,它的语言理解和生成能力比以前的版本更强大。对于忙碌的上班族来说,GPT4.0能帮助你高效处理工作中的大部分写作任务,比如撰写报告…

TS7031: Binding element ‘role‘ implicitly has an ‘any‘ type.

文章 前言错误场景问题分析解决方案后言 前言 ✨✨ 他们是天生勇敢的开发者,我们创造bug,传播bug,毫不留情地消灭bug,在这个过程中我们创造了很多bug以供娱乐。 前端bug这里是博主总结的一些前端的bug以及解决方案,感兴…

【C++】类和对象(5)--拷贝构造函数

目录 一 概念 二 拷贝构造函数特性 1. 重载形式 2. 参数原则 3 默认拷贝函数 三 拷贝构造函数的实现 一 概念 在创建对象时,可否创建一个与已存在对象一某一样的新对象呢? class Date { public:Date(int year 1900, int month 1, int day 1)/…

nestJs 高阶用法

真正的服务器需要做全局的数据核查、敏感操作落库和数据转化。 拥有这些能力才能让后台能力更加丰富,本篇将主要使用 NestJs 的高级能力,来实现这些功能。 使用 guards 和 decorators 实现数据校验核查通过 interceptors 和 decorators 实现敏感操作录…

Linux三剑客:grep的基本使用

目录 grep介绍 什么是grep和egrep 使用grep 命令格式 命令功能 命令参数 grep配合正则表达式使用 认识正则 基本正则表达式 匹配字符 配置次数 位置锚定:定位出现的位置 分组和后向引用 作为学习一名计算机专业的学生,我想Linux应该需要了解…

详细自动化测试介绍

📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…

【10套模拟】【5】

关键字: 数据的最小单位、归并排序(两两归并)、单链表顺序存取、邻接表表头顶点顺序存储随机访问、三角矩阵元素个数、堆的性质、冒泡排序、二叉树是否相同

我对需求分析的理解

一、背景 最近做了一个项目,也算是踩坑过程,产品上线了,用户不怎么买单,使用者聊聊无几,前期一直不清楚为什么会这样,诚然新系统的开发设计上采用了更新的技术,设计上采用了更好的理念&#xf…

知识梳理到了领域榜一,意外,开心。

我的护城河 就是掌握的不断更新的技术。 一直被认可的能力。 完美的项目交付。 写的文章得到了读者们的认可。 希望我做的努力被更多的人看到。 分享的代码片可以解决他人的问题。 很惊喜,今早我的文章被数据结构和算法领域内容榜排到了第一名。 被认可的感觉很棒。…

Python-pptx教程之二操作已有PPT模板文件

文章目录 简单的案例找到要修改的元素修改幻灯片中的文本代码使用示例 修改幻灯片的图片代码使用示例 删除幻灯片代码使用示例 获取PPT中所有的文本内容获取PPT中所有的图片总结 在上一篇中我们已经学会了如何从零开始生成PPT文件,从零开始生成较为复杂的PPT是非常消…

【重点文章】服务升级惨痛教训

文章目录 事故解析:避免方法涉及知识 以前怎么接触过大表,所以alter操作我都是一次性执行好几条的,这几条一下子干过去了   结果就是一直在转圈执行,因为alter产生的是表级排它锁,所以有关这几个表的查询更新操作全部处于阻塞…

04-学成在线之系统管理服务模块之查询数据字典表中的内容,前后端联调测试

前后端联调 配置前端环境 实际开发中先由后端工程师将接口设计好并编写接口文档并交给前端工程师,前后端的工程师就开始并行开发 前端开发人员先自己mock数据即使用假数据进行开发,当后端代码完成后前端工程师尝试请求后端接口获取数据然后渲染到页面 第一步: 首…

大力说企微第一课:企业微信的注册验证和认证

这段时间有好几个朋友问我,怎么用企业微信,还有一些朋友反馈,企业微信使用起来不太方便。 在我的印象中,企业微信确实不如微信那么简单,毕竟用户对象是企业,是企业就有多个部门,就有流程&#x…

ubuntu20源码编译搭建SRS流媒体服务器

第一、下载源码 下载源码,推荐用Ubuntu20: git clone -b develop https://gitee.com/ossrs/srs.git第二、编译 2.1、切换到srs/trunk目录: cd srs/trunk2.2、执行configure脚本 ./configure2.3、执行make命令 make2.4、修改conf/rtmp.c…

零代码实现问卷网与巨量引擎的对接

通过数环通,您可以使用不到几分钟的时间即可实现问卷网与巨量引擎的对接与集成,从而高效实现工作流程自动化,降本增效! 1.产品介绍 巨量引擎是字节跳动旗下的营销服务品牌,它整合了字节跳动旗下的产品及海量内容&…

简单高效的定制移动固态硬盘,稳定易用的办公小助手

我在平时的工作中,常常需要处理各种大文件和数据,如果硬盘速度跟不上,那工作效率就会大幅降低。今年固态硬盘的价格大幅下降,有很多国产品牌加入其中,很容易找到一款性价比高的固态硬盘,搭配硬盘盒使用&…

mac清除所有数据,不抹除的情况下如何实现?

mac清除所有数据是一个比较复杂的任务,尤其是在不进行系统抹除的情况下。但是,如果你想要将mac完全恢复到出厂设置的状态,同时保留数据,本文将介绍一些可行的方法,帮助您在不抹除硬盘数据的情况下,让mac清除…

Ganache结合内网穿透实现远程不同局域网公网访问

文章目录 前言1. 安装Ganache2. 安装cpolar3. 创建公网地址4. 公网访问连接5. 固定公网地址 正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能学习网站, 通俗易懂,风趣幽默,忍不住分享一下给大家。 点击跳转到网站…

Zookeeper Java 开发,自定义分布式锁示例

文章目录 一、概述二、导入依赖包三、创建锁的过程3.1 通过 create 创建节点信息3.2 AsyncCallback.StringCallback 回调函数3.3 AsyncCallback.Children2Callback 的回调函数3.4 Watcher 的回调函数 四、完整示例4.1 完整分布式锁代码4.2 测试类 如果您还没有安装Zookeeper请看…