ResNet论文阅读和简单实现

news2024/11/15 10:11:55

论文:https://arxiv.org/pdf/1512.03385.pdf

Deep Residual Learning for Image Recognition

本模块主要是阅读论文,会做简单的翻译(至少满足我自己能看明白)。

Introduction

由上图可见,在20层和56层的网络上训练的训练误差和测试误差的变化,可以看到层数加深不一定能带来性能上的提升,甚至更糟了。这就引出了文章的疑问(有些和直觉相反的结论):为什么不是层数约多,结果越好呢?文中给出的解释是:梯度消失/爆炸问题从一开始就阻碍了收敛,虽然这一问题已经通过normalized initialization和intermediate normalization layers在很大程度上得到了解决,这使得几十层的网络可以收敛,但是当层数逐渐增加,出现了“退化”问题(degradation)。这里的“退化”指的是,随着网络的加深,accuracy先逐渐升高到达饱和,然后迅速衰退。文中指出这一问题并非由过拟合导致(并不是模型过于复杂)。这一问题也说明了,我们并不能简单的认为通过堆叠层数来优化模型。

论文中通过引入一个deep residual learning框架(深度残差)。并不使用简单堆叠层数来获得一个满意的潜在映射,而是让这些层复合残差映射。

定义:

H(x):满意的潜在映射

F(x):堆叠的非线性层产生的映射,并且满足关系F(x) := H(x) - x

原始映射:F(x) + x

假设优化残差映射要比优化原始映射更容易。极端点来说,对于一个恒等映射,将残差推到0要比吧非线性层推到0更简单(不记得在哪里听到过,理论上,通过增加层数的方法来改善模型非常符合直觉,因为我们直觉上觉得可以无限的加y=x这样的变换,来无限制的增加层数。但是就像论文中描写的那样,我们并没有得到更高的准确度,而是出现了退化现象,这可能是因为DL太善于计算非线性了,反而没办法在线性上给一个比较好的表现,我觉得这是一个直觉上比较好的解释,故在此记录)。

F(x) + x这一形式在前馈神经网络中可以采用“shortcut connections”来实现。这里所谓的“shortcut connections”指的是跳过一层或多层。在本文中,这种连接仅仅是做“identity mapping”(恒等映射:在数学中,指一个函数将每个元素映射到其自身,即输入和输出相等的映射。),这一做法并不会增加额外的参数,也不会增加计算的复杂度(反正y=x也没什么影响,也没必要更新),这就使得整个网络仍然可以采用反向传播SGD进行end-to-end的训练。文章中提到,采用的152层的网络虽然比之前的VGG网络要深,但是实际上并没有那么复杂。

Deep Residual Learning

Residual Learning

在前面的定义中(H(x):满意的潜在映射)。让我们先把H(x)看做是由几个堆叠的层拟合的潜在映射(这里的几个不一定指的是整个网络),那么此时x表示的就是这些层中第一层的输入。假设多个非线性层可以逐渐逼近residual function(即H(x)- x,假设输入和输出的dimension相同,这里看起来有点autoencoder那个感觉)。因此,与其期待用堆叠的层去近似H(x),不如让这些层去拟合residual function F(x) := H(x) - x。尽管这两种形式都应该能够渐进地逼近所需的函数(如假设的那样),但学习的难易程度可能有所不同。

简单来说就是,不再直接的拟合函数,而是拟合残差函数,并且这么做的原因是这样更容易学。

如果添加的层可以构造为identity mapping(恒等映射),那么较深的模型的训练误差不应该大于较浅的模型。退化问题表明,在逼近多个非线性层的恒等映射时可能存在困难。使用residual方法之后,如果恒等映射是最优的,那么就可以简单的把多个非线性层的权重向0逼近,这就近似于恒等变换。

Identity Mapping by Shortcuts

对每个堆叠在一起的层(every few stacked layers)使用residual learning,构建出building block。将这个building block定义为:

y = F(x, {W_i}) + x

x, y:对应的堆叠在一起的层的输入和输出。

F(x, {W_i}):学到的residual mapping(我们最后想得到的是H(x),但是这里学到的是F(x, {W_i}),也就是H(x) - x,但是没有关系,我们最后输出的是F(x, {W_i}) + x,也就是H(x) - x + x,这种思路很像小时候做那种数列找规律求和,虽然直接算很难算,但是可以加上一项之后先算出来结果,最后再把加上来的项去掉)

在figure 2中,我们有两层,也就是F = W_2\sigma(W_1x),这里σ代表ReLU,为了简化,这里省略了bias。F+x采用shortcut connection和element-wise addition(简单来说就是直接拽过来加上,既然要拽过来直接加,那么一定要满足维度的一致)。这种方法既不引入额外的参数,也不增加计算复杂度。那么如果我们要改变输入输出的通道数时,可以执行一个线性投影:

y = F(x, \left \{ {W_i} \right \}) + W_s x

F(x, {W_i})可以表示多层的卷积。

Network Architectures

卷积层大多具有3×3滤波器,并遵循两个简单的设计规则:(i)对于相同的输出特征图大小,各层具有相同数量的滤波器;(ii)如果特征图大小减半,则滤波器的数量增加一倍,以保持每层的时间复杂度。我们通过步长为2的卷积层直接执行下采样。网络以一个全局平均池化层和一个带有softmax的1000路全连接层结束。图3(中)加权层总数为34层。

对中间的plain network,增加shortcut connections,就能改成residual版本。当输入输出维度相同时,可以直接使用identity shortcut(Eqn.(1))(y = F(x, {W_i}) + x)
当维度增加时(图3中的虚线快捷方式),我们考虑两个方案:
(A)快捷方式仍然执行恒等映射,为增加维度填充额外的0。这个方案不引入额外的参数;
(B) y = F(x, \left \{ {W_i} \right \}) + W_s x中的投影shortcut用于匹配维度(通过1×1卷积完成)。

对于这两个方案,当快捷键跨越两个大小的特征映射时,它们的步幅为2。

Implementation

从图像或其水平翻转中随机采样224×224裁剪,并减去每像素平均值。使用中的标准颜色增强。在每次卷积之后和激活之前采用批归一化(BN)。初始化权重,并从头开始训练所有的plain/residual网络。使用SGD的小批量大小为256。学习率从0.1开始,当误差趋于平稳时除以10,模型的训练次数可达60 × 104次。

Experiments

Deeper Bottleneck Architectures

对于每个残差函数F,我们使用3层而不是2层的(图5)。这三层是1×1, 3×3和1×1卷积,其中1×1层负责减少然后增加(恢复)维度,使3×3层成为输入/输出维度较小的瓶颈。图5给出了一个例子,其中两种设计具有相似的时间复杂度。无参数标识快捷方式对于瓶颈体系结构尤其重要。如果将图5(右)中的标识快捷方式替换为投影,可以看出,由于shortcut连接到两个高维端点,时间复杂度和模型尺寸都增加了一倍。

关于论文的理解

卷积后特征图尺寸变化:H_out = (H_in + 2P - K) / S + 1

转载神经网络学习小记录20——ResNet50模型的复现详解_resnet50复现-CSDN博客末尾的resnet50结构图

同时针对之前论文中的结构图,要说明的是,有部分内容上面的结构图和论文都没有直接说明,比如conv1_x中需要加padding,否则(224-7)/2+1是没办法成112的,这里padding=3,conv2_x中stride=1,其他stride=2(比如56->28,(56-1)/2+1=28)

注意,在每一个小的block里面有些直接计算发现数字不对的,都是加了padding。

比如,conv2_x里面3×3那个就加了padding=1

代码实现

1

参考:https://www.youtube.com/watch?v=DkNIBBBvcPs

import torch
import torch.nn as nn

class block(nn.Module):
    def __init__(self, inchannels, out_channels, identity_downsample=None, stride=1):
        super(block, self).__init__()
        # 每一个resnet的block的输入和输出的通道数的比值都是1/4,也就是说通道数扩大了4倍
        self.expension = 4
        self.conv1 = nn.Conv2d(inchannels, outchannels, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(outchannels, outchannels, kernel_size=3, stride=stride, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.conv3 = nn.Conv2d(outchannels, outchannels*self.expansion, kernel_size=1, stride=1, padding=0)
        self.bn3 = nn.BatchNorm2d(out_channels*self.expansion)
        self.relu = nn.ReLU()

        self.identity_downsample = identity_downsample
    
    def forward(self, x):
        identity = x
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)

        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu(x)

        x = self.conv3(x)
        x = self.bn3(x)

        if self.identity_downsample is not None:
            identity = self.identity_downsample(identity)
        # 方案A or 方案B
        x += identity
        x = self.relu(x)
        return x

class ResNet(nn.Module):
    def __init__(self, block, layers, image_channels, num_classes):
        super(ResNet, self).__init__()
        # 在res50中block的堆叠是3 4 6 3
        # conv1_x
        # 刚刚输入的时候channel是3,在这里conv一下转成64
        self.in_channels = 64
        self.conv1 = nn.Conv2d(image_channels, 64, kernel_size=7, stride=2, padding=3)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()
        # conv2_x
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # 理论上我们在这里就可以开始一层一层写了,比如:
        # self.layer1 = ...
        # self.layer2 = ...
        # 直接定义一个函数替我们写
        self.layer1 = self._make_layer(block, layer[0], out_channel=64, stride=1)
        self.layer2 = self._make_layer(block, layer[1], out_channel=128, stride=2)
        self.layer3 = self._make_layer(block, layer[2], out_channel=256, stride=2)
        self.layer4 = self._make_layer(block, layer[3], out_channel=512, stride=2)

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

    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.reshape(x.shape[0], -1)
        x = self.fc(x)
        return x


    def _make_layer(self, block, num_residual_block, out_channels, stride):
        identity_downsample = None
        layers = []
        # 如果不能直接相加(channel数量变化了)
        # 比如说conv2_x中的第一个block,输入64输出256,这种显然没办法把64硬加到256上
        if stride != 1 or self.inchannels != out_channels*4:
          identity_downsample = nn.Sequential(nn.Conv2(self.in_channels, out_channels*4, kernel_size=1,
                                                      stride=1),
                                              nn.BatchNorm2d(out_channels*4))
        layers.append(block(self.inchannels, out_channels, identity_downsample, stride))
        self.inchannels = out_channels*4

        for i in range(num_residual_block-1):
          layers.append(block(self.in_channels, out_channels))
        return nn.Sequential(*layers)


def ResNet50(img_channels, num_classes=1000):
    return ResNet(block, [3,4,6,3], img_channels, num_classes)

2

神经网络学习小记录20——ResNet50模型的复现详解_resnet50复现-CSDN博客

https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py

ResNet50 with PyTorch | Kaggle

GitHub - JayPatwardhan/ResNet-PyTorch: Basic implementation of ResNet 50, 101, 152 in PyTorch

3

Writing ResNet from Scratch in PyTorch

与1中的类似,引用如下:

class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride = 1, downsample = None):
        super(ResidualBlock, self).__init__()
        self.conv1 = nn.Sequential(
                        nn.Conv2d(in_channels, out_channels, kernel_size = 3, stride = stride, padding = 1),
                        nn.BatchNorm2d(out_channels),
                        nn.ReLU())
        self.conv2 = nn.Sequential(
                        nn.Conv2d(out_channels, out_channels, kernel_size = 3, stride = 1, padding = 1),
                        nn.BatchNorm2d(out_channels))
        self.downsample = downsample
        self.relu = nn.ReLU()
        self.out_channels = out_channels
        
    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.conv2(out)
        if self.downsample:
            residual = self.downsample(x)
        out += residual
        out = self.relu(out)
        return out



class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes = 10):
        super(ResNet, self).__init__()
        self.inplanes = 64
        self.conv1 = nn.Sequential(
                        nn.Conv2d(3, 64, kernel_size = 7, stride = 2, padding = 3),
                        nn.BatchNorm2d(64),
                        nn.ReLU())
        self.maxpool = nn.MaxPool2d(kernel_size = 3, stride = 2, padding = 1)
        self.layer0 = self._make_layer(block, 64, layers[0], stride = 1)
        self.layer1 = self._make_layer(block, 128, layers[1], stride = 2)
        self.layer2 = self._make_layer(block, 256, layers[2], stride = 2)
        self.layer3 = self._make_layer(block, 512, layers[3], stride = 2)
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(512, num_classes)
        
    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        if stride != 1 or self.inplanes != planes:
            
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes, kernel_size=1, stride=stride),
                nn.BatchNorm2d(planes),
            )
        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes
        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.maxpool(x)
        x = self.layer0(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x




num_classes = 10
num_epochs = 20
batch_size = 16
learning_rate = 0.01

model = ResNet(ResidualBlock, [3, 4, 6, 3]).to(device)

# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.001, momentum = 0.9)  

# Train the model
total_step = len(train_loader)

Layers in PyTorch

Now coming to the different types of layers available in PyTorch that are useful to us:

  • nn.Conv2d: These are the convolutional layers that accepts the number of input and output channels as arguments, along with kernel size for the filter. It also accepts any strides or padding if we want to apply those
  • nn.BatchNorm2d: This applies batch normalization to the output from the convolutional layer
  • nn.ReLU: This is a type of  activation function applied to various outputs in the network
  • nn.MaxPool2d : This applies max pooling to the output with the kernel size given
  • nn.Dropout: This is used to apply dropout to the output with a given probability
  • nn.Linear: This is basically a fully connected layer
  • nn.Sequential: This is technically not a type of layer but it helps in combining different operations that are part of the same step

看起来和之前的resnet50不太一样的原因是这里是34层的。

因此这里没有313这样的结构了。但是本质上是一样的。

————————————————————————————

一些题外话,虽然在很多地方看到说resnet已经是很老的模型了,但是相比于之前的CNN方法而言,在方法上确实是非常厉害的创新,虽然现在似乎CNN已经被调侃的像上世纪的产物了orz。似乎现在已经是transformer的天下了……

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

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

相关文章

深信服技术认证“SCCA-C”划重点:云计算关键技术

为帮助大家更加系统化地学习云计算知识,高效通过云计算工程师认证,深信服特推出“SCCA-C认证备考秘笈”,共十期内容。“考试重点”内容框架,帮助大家快速get重点知识。 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08…

滑动窗口最大值(力扣239题)

单调递减队列: 在解决题目之前,我们先来了解一下单调递减队列,它其实就是在队列的基础上多加了一些限制,如下图: 要求队列中的元素必须按从大到小的顺序排列。 如果向单调递减队列中加入数字 1,可以直接加入…

基于ElementUI封装的下拉树选择可搜索单选多选清空功能

效果&#xff1a; 组件代码 /*** 树形下拉选择组件&#xff0c;下拉框展示树形结构&#xff0c;提供选择某节点功能&#xff0c;方便其他模块调用* author wy* date 2024-01-03 * 调用示例&#xff1a;* <tree-select * :height"400" // 下拉框中树形高度* …

智慧工厂:科技与制造融合创新之路

随着科技的迅猛发展&#xff0c;智慧工厂成为制造业领域的热门话题。智慧工厂利用先进的技术和智能化系统&#xff0c;以提高生产效率、降低成本、增强产品质量和灵活性为目标&#xff0c;正在引领着未来制造业的发展。 智慧工厂的核心是数字化和自动化生产&#xff0c;相较于传…

C语言实例_string.h库函数功能及其用法详解

一、前言 在计算机编程中&#xff0c;字符串处理是一项常见而重要的任务。C语言的string.h头文件提供了一系列函数和工具&#xff0c;用于对字符串进行操作和处理。这些函数包括字符串复制、连接、比较、查找等功能&#xff0c;为开发人员提供了强大的字符串处理能力。本文将对…

vue3中pdf打印问题处理

1 get请求参数问题 之前的请求是post得不到参数&#xff0c;今天发现的问题很奇怪&#xff0c;从前端进入网关&#xff0c;网关居然得不到参数。 前端代码 const print () > {let linkUrlStr proxy.$tool.getUrlStr(proxy.$api.invOrder.psiInvOrder.printSalOutstock,{a…

Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中

场景 SpringBootVue整合WebSocket实现前后端消息推送&#xff1a; SpringBootVue整合WebSocket实现前后端消息推送_websocket vue3.0 springboot 往客户端推送-CSDN博客 上面实现ws推送数据流程后&#xff0c;需要在windows上使用ws客户端定时记录收到的数据到文件中&#x…

鸿蒙开发学习——基本组件

文章目录 引言正文Image组件设置加载网络图片图片属性设置 Text组件设置文本显示内容text属性设置 TextInput输入文本TextInput Controller获取输入文本 Button按钮 引言 最近在学习鸿蒙系统开发&#xff0c;然后对着文档看还是有很多问题&#xff0c;这里结合官方给的demo进行…

在ARMv8中aarch64与aarch32切换

需求描述 在项目调试过程中,由于内存或磁盘空间不足需要将系统从aarch64切换到aarch32的运行状态去执行,接下来记录cortexA53的调试过程。 相关寄存器描述 ARM64: SPSR_EL3 N (Negative):表示运算结果的最高位,用于指示运算结果是否为负数。 Z (Zero):表示运算结果是否…

设计模式之建造者模式【创造者模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

c++语言基础17-判断集合成员

题目描述 请你编写一个程序&#xff0c;判断给定的整数 n 是否存在于给定的集合中。 输入描述 有多组测试数据&#xff0c;第一行有一个整数 k&#xff0c;代表有 k 组测试数据。 每组数据第一行首先是一个正整数 m&#xff0c;表示集合中元素的数量&#xff08;1 < m &…

程序媛的mac修炼手册--MacOS系统更新升级史

啊&#xff0c;我这个口罩三年从未感染过新冠的天选免疫王&#xff0c;却被支原体击倒&#x1f637;大意了&#xff0c;前几天去医院体检&#xff0c;刚检查完出医院就摘口罩了&#x1f926;大伙儿还是要注意戴口罩&#xff0c;保重身体啊&#xff01;身体欠恙&#xff0c;就闲…

微软截图工具SnippingTool_6.1.7601免费版

SnippingTool是一款win7系统自带的一款非常实用型截图工具&#xff0c;操作简单&#xff0c;点击“新建"可一键截图&#xff0c;截图之后会弹出编辑器&#xff0c;可以进行一些简单的勾画编辑操作&#xff0c;您可以使用笔、荧光笔、电子邮件或保存等选项。如果您的系统丢…

Liunx(CentOS)安装Nacos(单机启动,绑定Mysql)

Liunx安装Nacos(单机启动&#xff0c;绑定Mysql) 一&#xff0c;准备安装包 github下载点 二&#xff0c;在/usr/local/目录下创建一个文件夹用于上传和解压Nacos cd /usr/local/ #这里创建文件夹名字可随意&#xff0c;解压后会生成一个名为nacos的文件夹&#xff0c;后续…

全国计算机等级考试| 二级Python | 真题及解析(6)

全国计算机等级考试二级Python真题及解析(8)图文 一、选择题 1.python中表达式4**3=( )。 A.12 B.1 C.64 D.7 2.在Python中,通过( )函数查看字符的编码。 …

安全防御之授权和访问控制技术

授权和访问控制技术是安全防御中的重要组成部分&#xff0c;主要用于管理和限制对系统资源&#xff08;如数据、应用程序等&#xff09;的访问。授权控制用户可访问和操作的系统资源&#xff0c;而访问控制技术则负责在授权的基础上&#xff0c;确保只有经过授权的用户才能访问…

jQuery框架

1.1、jQuery简介 jQuery 是一个高效、精简并且功能丰富的 JavaScript 工具库。它提供的 API 易于使用且兼容众多浏览器&#xff0c;这让诸如 HTML 文档遍历和操作、事件处理、动画和 Ajax 操作更加简单。目前超过90%的网站都使用了jQuery库&#xff0c;jQuery的宗旨&#xff1…

ElecardStreamEye使用教程(视频质量分析工具、视频分析)

文章目录 Elecard StreamEye 使用教程安装与设置下载安装 界面导航主菜单视频窗口分析窗口 文件操作打开视频文件 视频流分析帧类型识别码率分析分析报告 高级功能视觉表示比较模式自动化脚本 下载地址1&#xff1a;https://www.onlinedown.net/soft/58792.htm 下载地址2&…

计算机系统基础

C 语言相关内容省略&#xff0c;复习自用&#xff0c;仅供参考~ 概述 冯诺伊曼结构 存储程序工作方式&#xff1a;将事先编好的程序和原始数据送入主存后才能执行程序&#xff0c;程序被启动执行后&#xff0c;计算机能在不需要操作人员干预下自动完成逐条指令取出和执行的任…

【电商项目实战】实现订单超时支付取消

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《电商项目实战》。&#x1f3af;&#x1f3af; &am…