深度学习----------------------残差网络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

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

最新Spring Security实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)

《最新SpringSecurity实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)》本章节介绍了如何通过SpringSecurity实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟... 目录前言改造准备开始登录页改造自定义用户名密码登陆成功失败跳转问题自定义登出前后端分离适配方案结语前言

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx