深度学习----------------------残差网络ResNet

2024-08-23 10:20

本文主要是介绍深度学习----------------------残差网络ResNet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • ResNet
    • 加更多的层总是改进精度吗?
    • 残差块
    • ResNet块细节
    • 不同的残差块
    • ResNet块
    • ResNet架构
    • 总结
  • ResNet代码实现
    • 残差块
    • 输入和输出形状一致
    • 增加输出通道数的同时,减半输出的高和宽
    • ResNet模型
    • 观察ResNet中不同模块的输入形状是如何变化的
    • 训练模型
  • 问题
  • ResNet为什么能训练出1000层的模型?
    • ResNet是这样解决的
  • 问题

ResNet

在这里插入图片描述




加更多的层总是改进精度吗?

在这里插入图片描述

虽然 F 6 F_6 F6这个模型更加复杂了,但是实际上可能学偏了,学到的可能不如小模型的最优点近。

那么应该怎么做呢?
就是说每一层增加模型的复杂度,将包含前一层模型(即: F 2 F_2 F2包含 F 1 F_1 F1)。如右边图。所以学到的模型比之前的模型更大,所以不会比之前的更差。(函数的大小代表着它的复杂程度)




残差块

串联一个层改变函数类,我们希望能扩大函数类
残差块加入快速通道(右边)来得到f(x)=x+g(x)的结构

在这里插入图片描述




ResNet块细节

在这里插入图片描述
1×1的卷积是为了变换通道,为了最后能够进行相加。

假设卷积层要对通道进行变换,那么没加1×1的卷积就加不回去了。加入了1×1的卷积来把通道数变到合适的范围能够按照原数相加。

在这里插入图片描述




不同的残差块

在这里插入图片描述




ResNet块

高宽减半ResNet块(步幅2)
后接多个高宽不变ResNet块

虚线框的叫ResNet块。
在这里插入图片描述

进入第一步将高宽减半,然后通道数增加1倍。右边的1×1卷积将输入的通道数也增加1倍。

在这里插入图片描述




ResNet架构

类似VGG和GoogleNet的总体框架,但替换成了ResNet块。

在这里插入图片描述

下面的152是指包含152个卷积层,层数越少越快。
在这里插入图片描述




总结

    ①残差块使得很深的网络更加容易训练
        甚至可以训练一千层的网络

    ②残差网络对随后的深度神经网络设计产生了深远影响,无论是卷积类网络还是全连接类网络




ResNet代码实现

残差块

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lclass Residual(nn.Module):  #@savedef __init__(self, input_channels, num_channels,use_1x1conv=False, strides=1):super().__init__()# 可以设置stride=2self.conv1 = nn.Conv2d(input_channels, num_channels,kernel_size=3, padding=1, stride=strides)# 第一个参数,这里它接收的是self.conv1的输出通道数,意味着self.conv2的输入通道数与self.conv1的输出通道数相同。self.conv2 = nn.Conv2d(num_channels, num_channels,kernel_size=3, padding=1)# 如果存在一个额外的卷积层,它主要用于调整输入X的通道数,使其与self.conv2的输出通道数相匹配if use_1x1conv:# 可以设置stride=2self.conv3 = nn.Conv2d(input_channels, num_channels,kernel_size=1, stride=strides)else:self.conv3 = None# 定义两个批归一化层self.bn1和self.bn2,用来加速训练过程。self.bn1 = nn.BatchNorm2d(num_channels)self.bn2 = nn.BatchNorm2d(num_channels)self.relu = nn.ReLU(inplace=True) # inplace原地操作,不创建新变量,对原变量操作,节约内存def forward(self, X):Y = F.relu(self.bn1(self.conv1(X)))Y = self.bn2(self.conv2(Y))if self.conv3:X = self.conv3(X)Y += Xreturn F.relu(Y)



输入和输出形状一致

blk = Residual(3,3)
X = torch.rand(4, 3, 6, 6)
Y = blk(X)
print(Y.shape)

结果:
在这里插入图片描述




增加输出通道数的同时,减半输出的高和宽

# 输入通道数为3,输出通道数为6,原先是3将它增加到6则输出的高和宽将会减半
blk = Residual(3,6, use_1x1conv=True, strides=2)
print(blk(X).shape)

结果:
在这里插入图片描述




ResNet模型

ResNet:

在这里插入图片描述
GoogLeNet:
在这里插入图片描述

    ResNet的前两层跟之前介绍的GoogLeNet中的一样:

# 第一个卷积层接受1个通道(例如灰度图像)的输入,并输出64个特征图(或称为通道)。
# 在输出通道数为64、步幅为2的 7×7 卷积层后,接步幅为2的 3×3 的最大池化层。
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))#减少特征图的高度和宽度,并通过1的填充来稍微保持空间信息。

    GoogLeNet在后面接了4个由Inception块组成的模块。
     ResNet则使用4个残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。第一个模块的通道数同输入通道数一致。由于之前已经使用了步幅为2最大池化层,所以无须减小高和宽。之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。

# resnet_block函数用于生成一个阶段的残差块列表。第三个参数是残差块的数量,后面我们都使用的是2
def resnet_block(input_channels, num_channels, num_residuals,first_block=False):blk = []# 在循环中,对于每个残差块,如果它不是阶段中的第一个块且不是第一个阶段的第一个块(通过not first_block判断),则使用strides=2的Residual来减半特征图的高度和宽度。for i in range(num_residuals):# 这里的first_block就是b2,b2作为第一阶段if i == 0 and not first_block:blk.append(Residual(input_channels, num_channels,use_1x1conv=True, strides=2))else:blk.append(Residual(num_channels, num_channels))return blk

问题:预备阶段b1高宽减半,第一阶段b2第一块为什么不减少高宽,第二阶段b3,第三阶段b4,第四阶段b5又减少高宽了呢?
由于b1(b1通常不被视为一个单独的“阶段”,而是作为网络的预处理部分)已经完成了特征图的尺寸减少。
b2作为第一个阶段,其第一个残差块不会改变特征图的尺寸,因为前面已经减半已经很多了。
b3、b4、b5等后续阶段继续减少特征图尺寸是为了适应网络结构的深层设计、减少计算量。

    接着在ResNet加入所有残差块,这里每个模块使用2个残差块。


# b2作为第一个阶段,其第一个残差块不会改变特征图的尺寸(因为first_block=True)。
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

    最后,与GoogLeNet一样,在ResNet中加入全局平均池化层,以及全连接层输出。

net = nn.Sequential(b1, b2, b3, b4, b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(), nn.Linear(512, 10))

    每个模块有4个卷积层(不包括恒等映射的 1×1 卷积层)。加上第一个 7×7 卷积层和最后一个全连接层,共有18层。 因此,这种模型通常被称为ResNet-18。如下图:

在这里插入图片描述
    通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。 虽然ResNet的主体架构跟GoogLeNet类似,但ResNet架构更简单,修改也更方便。这些因素都导致了ResNet迅速被广泛使用。




观察ResNet中不同模块的输入形状是如何变化的

X = torch.rand(size=(1, 1, 224, 224))
for layer in net:X = layer(X)print(layer.__class__.__name__,'output shape:\t', X.shape)

结果:
批量大小、通道数、高度、宽度

在这里插入图片描述
①第一步经过一个conv,第二步经过一个max pooling其中步幅都为2,所以224/2/2=56。

②没有进行高宽减半所以没有变化

③进行减半(strides=2)

④进行减半(strides=2)

⑤进行减半(strides=2)

⑥AdaptiveAvgPool2d设置的高宽为1×1

⑦经过展平层,只剩下参数批量大小和所有通道数的总和。

⑧固定输出为10,即:只有10种类别




训练模型

lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

结果:
在这里插入图片描述




问题

①为什么LeNet 的batch size大于1000收敛会比较慢?
batch size等于1000的时候,里面大部分图片都是很相似的图片,所以重复的图片在重复计算,所以会导致收敛进度。

②f(x)=x+g(x),这样就能保证至少不会变坏吗?如果g(x)不是变好,而是变坏呢?
训练g(x),模型发现g(x)很难训练或者训练没什么好处的话,它就拿不到梯度。加上g(x)对loss下降没什么明显的话,它在做梯度反传的时候拿不到什么梯度,所以它的权重不会被更新,很可能是一个很小的权重,所以不会有什么贡献。(有梯度下降在,最次就是0作用,不可能负作用。)

③ResNet实现里最后* ResNet_Block里 * 号是什么意思?
* 是把python中的list展开,ResNet_Block是一个list,* 号就是把list展开成一堆的输入。




ResNet为什么能训练出1000层的模型?

ResNet是怎么处理梯度消失使得训练出1000层的模型?
避免梯度消失(方法一):将乘法变加法,ResNet就是这么做的。

x是输入,y是输出,f其中的变换(例:10个卷积层的样子)。里面有个w是要进行更新的,计算梯度的话

在这里插入图片描述
在这里插入图片描述
假设在这个网络上面的一层再加一层会怎么样?
假设f为10层卷积层,这个g的意思是在这10层上面再加10层。

在这里插入图片描述
在这里插入图片描述

假设新加的层拟合能力比较强的话,下面这个会很快变得特别小。可以简单认为它的导数等价于你的预测值和真实值之间的差别是有一定的关系的。假设预测的比较好的情况下,则下面的值会变得比较小。如果它很小的话,一个很小的值乘之前的梯度,那么梯度会比之前小很多。梯度小很多的话,要么增大学习率(但很有可能增大也没有太多用,因为不能增的太大,因为这是对于靠近数据底部层的更新,如果增的太大的话可能会不稳定。)
在这里插入图片描述

ResNet是这样解决的

y"=本来来自于下一层网络的输入在加上g(f(x))

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
假设这个值很小的话
在这里插入图片描述
最差也等于下面这个
在这里插入图片描述

ResNet能够有效训练深层(1000层)的关键是:将乘法运算改为加法运算(残差连接),从而避免底层的梯度消失,使得最靠近数据的层的权重也能够获得较大的梯度(大数+小数=大数,大数×小数=小数),深层网络得以有效更新。




问题

学习率可不可以让靠近输出的小一些,靠近输入的大一些,这样会不会就可以缓解梯度消失的问题?
可以的,但是不是那么好设置,就是不知到靠近输入设置多大,靠近输出设置多大点。

为什么深层的网络,底层比较难训练?是因为它拿到的梯度一般比较小?
是的

这篇关于深度学习----------------------残差网络ResNet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1099111

相关文章

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

五大特性引领创新! 深度操作系统 deepin 25 Preview预览版发布

《五大特性引领创新!深度操作系统deepin25Preview预览版发布》今日,深度操作系统正式推出deepin25Preview版本,该版本集成了五大核心特性:磐石系统、全新DDE、Tr... 深度操作系统今日发布了 deepin 25 Preview,新版本囊括五大特性:磐石系统、全新 DDE、Tree

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用