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

【图像分类】【深度学习】【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代码。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入探讨TensorFlow:张量与矩阵

在机器学习和深度学习领域中,TensorFlow作为一款强大且受欢迎的开源机器学习框架,为研究人员和开发者提供了丰富的工具和资源。在TensorFlow中,张量(tensor)和矩阵(matrix)是核心概念&#xff0…

Odoo 15开发手册第七章 记录集 - 使用模型数据

在前面的章节中,我们概览了模型创建以及如何向模型加载数据。现在我们已有数据模型和相关数据,是时候学习如何编程与其进行交互了。 业务应用需要业务逻辑来计算数据、执行验证或自动化操作。Odoo框架API为开发者提供了工具用于实现这种业务逻辑。大多数…

(二)什么是Vite——Vite 和 Webpack 区别(冷启动)

vite分享ppt,感兴趣的可以下载: ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录: (一)什么是Vite——vite介绍与使用-CSDN博客 (二)什么是Vite——Vite 和 Webpack 区别&#xff0…

2018年五一杯数学建模C题江苏省本科教育质量综合评价解题全过程文档及程序

2019年五一杯数学建模 C题 江苏省本科教育质量综合评价 原题再现 随着中国的改革开放,国家的综合实力不断增强,中国高等教育发展整体已进入世界中上水平。作为一个教育大省,江苏省的本科教育发展在全国名列前茅,而江苏省13个地级…

【PB续命05】WinHttp.WinHttpRequest的介绍与使用

0 WinHttp.WinHttpRequest简介 winhttp.winhttprequest是Windows操作系统中的一个API函数,用于创建和发送HTTP请求。它可以用于从Web服务器获取数据,或将数据发送到Web服务器。该函数提供了许多选项,例如设置请求头、设置代理服务器、设置超…

花 200 元测试 1300 个实时数据同步任务

背景 对于将数据作为重要生产资料的公司来说,超大规模的数据迁移同步系统( 1k、5k、10k 条同步任务)是刚需。 本文以此为出发点,介绍近期 CloudCanal 所做的一个容量测试:在单个 CloudCanal 集群上创建 1300 实时任务,验证系统是…

2023年中国骨质疏松治疗仪发展趋势分析:小型且智能将成为产品优化方向[图]

骨质疏松治疗仪利用磁场镇静止痛、消肿消炎的治疗作用迅速缓解患者腰背疼痛等骨质疏松临床症状。同时利用磁场的磁-电效应产生的感生电势和感生电流,改善骨的代谢和骨重建,通过抑制破骨细胞、促进成骨细胞的活性来阻止骨量丢失、提高骨密度。 骨质疏松治…

【软件推荐】我的常用Windows软件

文章目录 前言Colors Lite(颜色吸取)Everything(文件搜索)知云文献翻译Directory Opus(文件管理器)Snipaste(截图)AxMath(公式编辑器)Deskpin(窗口…

【Android】导入三方jar包/系统的framework.jar

1.Android.mk导包 1).jar包位置 与res和src同一级的libs中(没有就新建) 2).Android.mk文件 LOCAL_STATIC_ANDROID_LIBRARIES:android静态库,经常用于一些support的导包 LOCAL_JAVA_LIBRARIES:依赖的java库,一般为系统的jar…

Linux常用命令——bzcmp命令

在线Linux命令查询工具 bzcmp 比较两个压缩包中的文件 补充说明 bzcmp命令主要功能是在不真正解压缩.bz2压缩包的情况下,比较两个压缩包中的文件,省去了解压缩后在调用cmp命令的过程。 语法 bzcmp(参数)参数 文件1:指定要比较的第一个…

UE基础篇十:材质

导语: 视频文档在文末 虚幻引擎默认是延迟渲染(延迟渲染是通过先算出需要着色的像素,然后再迭代灯光,从而减少大量无效的灯光计算,来达到优化的目的) 一、基础知识 1.1 贴图分辨率尺寸 2的幂次方,长宽随意组合 非2的幂次方,不能设置MipMaps(引擎会生成多张分辨率更…

任务栏上的超萌小猫,实时显示CPU占用率,有趣.Net开源工具

推荐一个非常有趣的.Net开源小工具,它可以在电脑任务栏显示一只奔跑的小猫,实时显示CPU使用率! 01 项目简介 一款基于.NET 6.0运行环境的开源小工具,通过它,用户可以直观地查看CPU的使用情况,它会根据 CP…

程序员导航站

探路者 hello.alluniverse.vip 开发者导航 - Pro Developer网站导航 探路者是一款极简导航工具,致力于收录的每个站点都有其独特的作用。同时支持自定义导航,让用户快速实现个性化的导航站点。 特性概述 免费ChatGPT 装机必备 开发工具 Git精选项目 …

云计算(Docker)

Docker简介 Docker 是一个开源的应用容器引擎,基于 Go 语言,并遵从 Apache2.0 协议开源。它可以让开发者打包应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。Docker 可用于开发…

2023年中国涂料树脂需求量、市场规模及行业竞争现状分析[图]

涂料用树脂是涂料的主要原材料,是涂料的主要成膜物,且了为涂料成品提供耐醇、耐磨、耐高温、耐高湿、减少涂料在涂装完成后的损耗、保持涂装后外观以及性状的稳定性等功能。 根据生产产品的性状不同,其下游产品,即涂料成品广泛应用…

锐捷EG易网关login.php以及其后台cli.php/branch_passw.php RCE漏洞复现 [附POC]

文章目录 锐捷EG易网关login.php以及其后台cli.php/branch_passw.php远程代码执行漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 锐捷EG易网关login.php以及其后台cli.php/branch_passw.php远程代码执行漏洞复…

电脑集中管理软件有哪些?电脑集中管控怎么做

电脑集中管理软件有哪些?电脑集中管控怎么做 电脑集中管理软件是指通过一种软件或系统,对多台电脑进行集中管理和控制的工具。这种软件通常具备远程控制、软硬件监控、自动部署等功能。可以方便企业或组织统一管理电脑资源,提高工作效率。今…

java“贪吃蛇”小游戏

基于java实现贪吃蛇小游戏,主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。 我是在javaSwing项目下创建了一个包 名字叫做:Snakes包 包下有一个启动类和一个设置代码的主界面两个类 代码主界面: 代码主界面主要讲解的是 …

linux关于cmake,makefile和gdb的使用

c文件的编译 安装环境(centos 7) 检查命令是否齐全 gcc --version g --version gdb–version 安装命令 yum -y install gcc-c安装g命令(用于编译c/c文件) yum -y install gcc安装gcc命令(用于编译c文件) 每个都出现版本号,证明…