带残差连接的ResNet18

目录

1 模型构建

        1.1 残差单元

        1.2 残差网络的整体结构

        2 没有残差连接的ResNet18

        2.1 模型训练

        2.2 模型评价

3 带残差连接的ResNet18

        3.1 模型训练

         3.2 模型评价

4 与高层API实现版本的对比实验

        总结


残差网络(Residual Network,ResNet)是在神经网络模型中给非线性层增加直连边的方式来缓解梯度消失问题,从而使训练深度神经网络变得更加容易。

在残差网络中,最基本的单位为残差单元

假设$f(\mathbf x;\theta)$为一个或多个神经层,残差单元在$f()$的输入和输出之间加上一个直连边

不同于传统网络结构中让网络$f(x;\theta)$去逼近一个目标函数$h(x)$,在残差网络中,将目标函数$h(x)$拆为了两个部分:恒等函数$x$和残差函数$h(x)-x$


\mathrm{ResBlock}_f(\mathbf x) = f(\mathbf x;\theta) + \mathbf x

其中$\theta$为可学习的参数。

一个典型的残差单元如图所示,由多个级联的卷积层和一个跨层的直连边组成。

残差单元结构

 一个残差网络通常有很多个残差单元堆叠而成。下面我们来构建一个在计算机视觉中非常典型的残差网络:ResNet18,并重复上一节中的手写体数字识别任务。

1 模型构建

在本节中,我们先构建ResNet18的残差单元,然后在组建完整的网络。

        1.1 残差单元

这里,我们实现一个算子ResBlock来构建残差单元,其中定义了use_residual参数,用于在后续实验中控制是否使用残差连接。

残差单元包裹的非线性层的输入和输出形状大小应该一致。如果一个卷积层的输入特征图和输出特征图的通道数不一致,则其输出与输入特征图无法直接相加。为了解决上述问题,我们可以使用$1 \times 1$大小的卷积将输入特征图的通道数映射为与级联卷积输出特征图的一致通道数。

$1 \times 1$卷积:与标准卷积完全一样,唯一的特殊点在于卷积核的尺寸是$1 \times 1$,也就是不去考虑输入数据局部信息之间的关系,而把关注点放在不同通道间。通过使用$1 \times 1$卷积,可以起到如下作用:

  •  实现信息的跨通道交互与整合。考虑到卷积运算的输入输出都是3个维度(宽、高、多通道),所以$1 \times 1$卷积实际上就是对每个像素点,在不同的通道上进行线性组合,从而整合不同通道的信息;
  •  对卷积核通道数进行降维和升维,减少参数量。经过$1 \times 1$卷积后的输出保留了输入数据的原有平面结构,通过调控通道数,从而完成升维或降维的作用;
  •  利用$1 \times 1$卷积后的非线性激活函数,在保持特征图尺寸不变的前提下,大幅增加非线性。
class ResBlock(nn.Module):def __init__(self, in_channels, out_channels, stride=1, use_residual=True):super(ResBlock, self).__init__()self.stride = strideself.use_residual = use_residual# 第一个卷积层,卷积核大小为3×3,可以设置不同输出通道数以及步长self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1, stride=self.stride)# 第二个卷积层,卷积核大小为3×3,不改变输入特征图的形状,步长为1self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)# 如果conv2的输出和此残差块的输入数据形状不一致,则use_1x1conv = True# 当use_1x1conv = True,添加1个1x1的卷积作用在输入数据上,使其形状变成跟conv2一致if in_channels != out_channels or stride != 1:self.use_1x1conv = Trueelse:self.use_1x1conv = False# 当残差单元包裹的非线性层输入和输出通道数不一致时,需要用1×1卷积调整通道数后再进行相加运算if self.use_1x1conv:self.shortcut = nn.Conv2d(in_channels, out_channels, 1, stride=self.stride)# 每个卷积层后会接一个批量规范化层,批量规范化的内容在7.5.1中会进行详细介绍self.bn1 = nn.BatchNorm2d(out_channels)self.bn2 = nn.BatchNorm2d(out_channels)if self.use_1x1conv:self.bn3 = nn.BatchNorm2d(out_channels)def forward(self, inputs):y = F.relu(self.bn1(self.conv1(inputs)))y = self.bn2(self.conv2(y))if self.use_residual:if self.use_1x1conv:  # 如果为真,对inputs进行1×1卷积,将形状调整成跟conv2的输出y一致shortcut = self.shortcut(inputs)shortcut = self.bn3(shortcut)else:  # 否则直接将inputs和conv2的输出y相加shortcut = inputsy = torch.add(shortcut, y)out = F.relu(y)return out

        1.2 残差网络的整体结构

        残差网络就是将很多个残差单元串联起来构成的一个非常深的网络。ResNet18 的网络结构如图所示。

其中为了便于理解,可以将ResNet18网络划分为6个模块:

  •  第一模块:包含了一个步长为2,大小为$7 \times 7$的卷积层,卷积层的输出通道数为64,卷积层的输出经过批量归一化、ReLU激活函数的处理后,接了一个步长为2的$3 \times 3$的最大汇聚层;
  •  第二模块:包含了两个残差单元,经过运算后,输出通道数为64,特征图的尺寸保持不变;
  •  第三模块:包含了两个残差单元,经过运算后,输出通道数为128,特征图的尺寸缩小一半;
  •  第四模块:包含了两个残差单元,经过运算后,输出通道数为256,特征图的尺寸缩小一半;
  •  第五模块:包含了两个残差单元,经过运算后,输出通道数为512,特征图的尺寸缩小一半;
  •  第六模块:包含了一个全局平均汇聚层,将特征图变为$1 \times 1$的大小,最终经过全连接层计算出最后的输出。

ResNet18模型的代码实现如下:

         定义模块一

def make_first_module(in_channels):m1 = nn.Sequential(nn.Conv2d(in_channels, 64, 7, stride=2, padding=3), nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))return m1

        定义模块二到模块五

def resnet_module(input_channels, out_channels, num_res_blocks, stride=1, use_residual=True):blk = []for i in range(num_res_blocks):if i == 0:blk.append(ResBlock(input_channels, out_channels, stride=stride, use_residual=use_residual))else:blk.append(ResBlock(out_channels, out_channels, use_residual=use_residual))return blk

        封装模块二到模块五

def make_modules(use_residual):# 模块二:包含两个残差单元,输入通道数为64,输出通道数为64,步长为1,特征图大小保持不变m2 = nn.Sequential(*resnet_module(64, 64, 2, stride=1, use_residual=use_residual))# 模块三:包含两个残差单元,输入通道数为64,输出通道数为128,步长为2,特征图大小缩小一半。m3 = nn.Sequential(*resnet_module(64, 128, 2, stride=2, use_residual=use_residual))# 模块四:包含两个残差单元,输入通道数为128,输出通道数为256,步长为2,特征图大小缩小一半。m4 = nn.Sequential(*resnet_module(128, 256, 2, stride=2, use_residual=use_residual))# 模块五:包含两个残差单元,输入通道数为256,输出通道数为512,步长为2,特征图大小缩小一半。m5 = nn.Sequential(*resnet_module(256, 512, 2, stride=2, use_residual=use_residual))return m2, m3, m4, m5

        定义完整网络

class Model_ResNet18(nn.Module):def __init__(self, in_channels=3, num_classes=10, use_residual=True):super(Model_ResNet18, self).__init__()m1 = make_first_module(in_channels)m2, m3, m4, m5 = make_modules(use_residual)self.net = nn.Sequential(m1, m2, m3, m4, m5, nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(512, num_classes))def forward(self, x):return self.net(x)

        这里同样可以使用torchsummary.summary统计模型的参数量。

from torchsummary import summarydevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # PyTorch v0.4.0
model = Model_ResNet18(in_channels=1, num_classes=10, use_residual=True).to(device)
summary(model, (1, 32, 32))

         实验结果:

        使用thop.profile统计模型的计算量

from thop import profiledevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # PyTorch v0.4.0
model = Model_ResNet18(in_channels=1, num_classes=10, use_residual=True).to(device)
dummy_input = torch.randn(1, 1, 32, 32).to(device)flops, params = profile(model, (dummy_input,))
print(flops)

        为了验证残差连接对深层卷积神经网络的训练可以起到促进作用,接下来先使用ResNet18(use_residual设置为False)进行手写数字识别实验,再添加残差连接(use_residual设置为True),观察实验对比效果。 

        2 没有残差连接的ResNet18

为了验证残差连接的效果,先使用没有残差连接的ResNet18进行实验。

        2.1 模型训练

        使用训练集和验证集进行模型训练,共训练5个epoch。在实验中,保存准确率最高的模型作为最佳模型。代码实现如下

# 固定随机种子
random.seed(0)
# 学习率大小
lr = 0.005
# 批次大小
batch_size = 64
# 加载数据
train_loader = data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dataset=dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(dataset=test_dataset, batch_size=batch_size)
# 定义网络,不使用残差结构的深层网络
model = Model_ResNet18(in_channels=1, num_classes=10, use_residual=False)
# 定义优化器
optimizer = opt.SGD(lr=lr, params=model.parameters())
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric = Accuracy(is_logist=True)
# 实例化RunnerV3
runner = RunnerV3(model, optimizer, loss_fn, metric)
# 启动训练
log_steps = 15
eval_steps = 15
runner.train(train_loader, dev_loader, num_epochs=5, log_steps=log_steps,eval_steps=eval_steps, save_path="best_model.pdparams")
# 可视化观察训练集与验证集的Loss变化情况
plot(runner, 'cnn-loss2.pdf')

 

        2.2 模型评价

        使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。代码实现如下 

3 带残差连接的ResNet18

        3.1 模型训练

使用带残差连接的ResNet18重复上面的实验,代码实现如下:

random.seed(0)
# 加载 mnist 数据集
train_dataset = MNIST_dataset(dataset=train_set, transforms=transforms, mode='train')
test_dataset = MNIST_dataset(dataset=test_set, transforms=transforms, mode='test')
dev_dataset = MNIST_dataset(dataset=dev_set, transforms=transforms, mode='dev')
# 学习率大小
lr = 0.01
# 批次大小
batch_size = 128
# 加载数据
train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)
# 定义网络,通过指定use_residual为True,使用残差结构的深层网络
model = Model_ResNet18(in_channels=1, num_classes=10, use_residual=True)
# 定义优化器
optimizer = opt.SGD(lr=lr, params=model.parameters())
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric = Accuracy(is_logist=True)
# 实例化RunnerV3
runner = RunnerV3(model, optimizer, loss_fn, metric)
# 启动训练
log_steps = 15
eval_steps = 15
runner.train(train_loader, dev_loader, num_epochs=5, log_steps=log_steps,eval_steps=eval_steps, save_path="best_model.pdparams")
# 可视化观察训练集与验证集的Loss变化情况
plot(runner, 'cnn-loss3.pdf')

         3.2 模型评价

        使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。

# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))

 

4 与高层API实现版本的对比实验

对于Reset18这种比较经典的图像分类网络,pytorch中都为大家提供了实现好的版本,大家可以不再从头开始实现。这里为高层API版本的resnet18模型和自定义的resnet18模型赋予相同的权重,并使用相同的输入数据,观察输出结果是否一致。

import torchvision.models as models
from collections import OrderedDict
import warningswarnings.filterwarnings("ignore")# 使用飞桨HAPI中实现的resnet18模型,该模型默认输入通道数为3,输出类别数1000
hapi_model = models.resnet18()
# 自定义的resnet18模型
model = Model_ResNet18(in_channels=3, num_classes=1000, use_residual=True)# 获取网络的权重
params = hapi_model.state_dict()# 用来保存参数名映射后的网络权重
new_params = {}
# 将参数名进行映射
for key in params:if 'layer' in key:if 'downsample.0' in key:new_params['net.' + key[5:8] + '.shortcut' + key[-7:]] = params[key]elif 'downsample.1' in key:new_params['net.' + key[5:8] + '.bn3.' + key[22:]] = params[key]else:new_params['net.' + key[5:]] = params[key]elif 'conv1.weight' == key:new_params['net.0.0.weight'] = params[key]elif 'conv1.bias' == key:new_params['net.0.0.bias'] = params[key]elif 'bn1' in key:new_params['net.0.1' + key[3:]] = params[key]elif 'fc' in key:new_params['net.7' + key[2:]] = params[key]new_params['net.0.0.bias'] = torch.zeros([64])
# 将飞桨HAPI中实现的resnet18模型的权重参数赋予自定义的resnet18模型,保持两者一致
model.load_state_dict(OrderedDict(new_params))# 这里用np.random创建一个随机数组作为测试数据
inputs = np.random.randn(*[3, 3, 32, 32])
inputs = inputs.astype('float32')
x = torch.tensor(inputs)output = model(x)
hapi_out = hapi_model(x)# 计算两个模型输出的差异
diff = output - hapi_out
# 取差异最大的值
max_diff = torch.max(diff)
print(max_diff)

        注意这里代码跑不通显示如下:

Traceback (most recent call last): File "C:\Users\29134\PycharmProjects\pythonProject\DL\实验12\ResNet.py", line 236, in <module> model.load_state_dict(OrderedDict(new_params)) File "C:\ANACONDA\envs\pytorch\Lib\site-packages\torch\nn\modules\module.py", line 2041, in load_state_dict raise RuntimeError('Error(s) in loading state_dict for {}:\n\t{}'.format( RuntimeError: Error(s) in loading state_dict for Model_ResNet18: Missing key(s) in state_dict: "net.0.0.bias", "net.1.0.conv1.bias", "net.1.0.conv2.bias", "net.1.1.conv1.bias", "net.1.1.conv2.bias", "net.2.0.conv1.bias", "net.2.0.conv2.bias", "net.2.0.shortcut.bias", "net.2.1.conv1.bias", "net.2.1.conv2.bias", "net.3.0.conv1.bias", "net.3.0.conv2.bias", "net.3.0.shortcut.bias", "net.3.1.conv1.bias", "net.3.1.conv2.bias", "net.4.0.conv1.bias", "net.4.0.conv2.bias", "net.4.0.shortcut.bias", "net.4.1.conv1.bias", "net.4.1.conv2.bias".

         找了很多资料但是依旧没找到怎么解决,同班同学的代码也跑不通,结论怎么出来的疑惑,这两天时间不太充裕全是结课论文,过两天会回来再次尝试解决这个问题的

        总结

首先,使用带残差连接的ResNet模型相比于不带残差的模型,在训练过程中表现出更好的性能。带残差的模型具有更快的收敛速度、更低的损失和更高的准确率。这证明了残差块确实能够为网络带来性能提升,而无脑堆砌网络层并不能有效地提高模型的性能。这个结果也打破了我一直都认为神经网络越深性能越好的理论认知,同时通过学长的博客我认识到残差连接能够有效地缓解梯度消失问题,减少训练难度,并提高了网络的深度和表达能力。这也算一个小小的收获吧(那一大堆推导我真没看懂!!哭)

放上学长的博客:

NNDL 实验六 卷积神经网络(4)ResNet18实现MNIST-CSDN博客

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

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

相关文章

4.4-Docker bridge0详解

在Docker世界中&#xff0c;两个container是通过bridge0连接起来的。 首先&#xff0c;介绍一个命令&#xff1a;docker network ls 这个docker network ls明令会列举出来当前这台机器上docker有哪些网络。 先看一下bridge。 现在有一个容器flask-hello-docker&#xff0c;它是…

Unity打出的安卓包切换后台再恢复前台,卡顿许久问题记录

连接AndroidStudio发现当切换后台时提示&#xff1a;D/Unity: Multi-casting "[IP] 192.168.31.231 [Port] 55000 [Flags] 19 [Guid] 1268732307 [EditorId] 264356214 [Version] 1048832 [Id] AndroidPlayer(11,Xiaomi_M2012K11AC192.168.31.231) [Debug] 0 [PackageName…

docker镜像分层、仓库、容器数据卷与常用软件安装

一、镜像分层 1、镜像概念&#xff1a; 镜像是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;将应用程序和配置依赖打包好行成一个可交付的运行环境&#xff0c;这个打包好的运行环境就是image镜像文件。 2、镜像分层&#xff1a…

文章解读与仿真程序复现思路——电力系统保护与控制EI\CSCD\北大核心《基于深度强化学习的城市配电网多级动态重构优化运行方法》

这个标题涉及到城市配电网&#xff08;Urban Power Distribution Network&#xff09;的优化运行方法&#xff0c;其中使用了深度强化学习&#xff08;Deep Reinforcement Learning&#xff09;技术&#xff0c;并且特别强调了多级动态重构。 解读每个关键部分&#xff1a; 基…

微机原理_7

一、单项选择题(本大题共15小题&#xff0c;每小题3分&#xff0c;共45分。在每小题给出的四个备选项中&#xff0c;选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 下列属于串行通信接口标准的有&#xff08;) A. PCI B. IDE C. USB D. EISA Intel 8086/8088…

【Rust】所有权的认识

所有权 所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制&#xff0c;在程序运行时有规律地寻找不再使用的内存&#xff1b;在另一些语言中&#xff0c;程序员必须亲自分配和释放内存。 Rust 则选择了第三种方式&#xff1a;通过所有权系统管理内…

数据结构-单链表

文章目录 单链表概念链接存储方法头指针head和终端结点链接过程单链表的优缺点&#xff1a;实现代码一览 单链表概念 链表中的数据是以结点来表示的&#xff0c;每个结点的构成&#xff1a;元素(数据元素的映象) 指针(指示后继元素存储位置)&#xff0c;元素就是存储数据的存储…

MYSQL 8.X Linux-Generic 通用版本安装

下载对应版本MySQL :: Download MySQL Community Server (Archived Versions) 这里我选择的是Linux - Generic (glibc 2.12) (x86, 64-bit), TAR 解压到服务器 只需要里面的mysql-8.0.24-linux-glibc2.12-x86_64.tar.xz 在目录下创建需要的文件夹 这里我改名为mysql-8.0.24…

GitLab 登录中,LDAP和 Standard 验证有什么区别

在 GitLab 中&#xff0c;LDAP&#xff08;Lightweight Directory Access Protocol&#xff09;和 Standard 验证是两种不同的身份验证方法&#xff0c;它们有以下区别&#xff1a; LDAP&#xff08;Lightweight Directory Access Protocol&#xff09;身份验证&#xff1a; L…

西南科技大学(数据结构A)期末自测练习二

一、填空题(每空1分,共10分) 1、在线性表的下列运算中,不改变数据元素之间结构关系的运算是( D ) A、插入 B、删除 C、排序 D、定位 2、顺序表中第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是( B ) A.110 B.108 C.100 …

C#中的async/await异步编程模型

前言 当谈到异步编程时&#xff0c;C#中的async/await是一个强大且方便的工具。它使得编写并发和异步操作变得更加简单和可读&#xff0c;同时提供良好的可维护性。本文将详细解释async/await的使用&#xff0c;以及如何在C#中有效地利用它来实现异步操作。 目录 前言1. async…

在线教育机构如何借助小程序技术创新

随着人工智能AI技术的发展&#xff0c;我们的生活学习工作方式都在经历变化。在线教育也处于这场变化的核心之中&#xff0c;同样借助这股东风引来了行业的一波红利期。 在正式分享在线教育行业的开始&#xff0c;我们先简单搞清楚什么是在线教育。 在线教育行业是指通过互联…

PCF8591多通道数据读取异常问题

问题描述 PCF8591在循环读取两个通道时&#xff0c;两个通道数据出现交错问题。 例如我们想实现&#xff1a;第一次读取通道一、第二次读取通道二、第三次读取通道一、第四次读取通道二……依次循环 但实际数据&#xff1a;第一次读取的值为0x80、第二次读取的值为通道一的值、…

2023.11.28-电商平台建设03 - 大数据调优手段

1.优化手段 1.1分桶表 HIVE的分桶本质上就是MR的分区操作 建表语句: create table 表名(字段 类型,.... ) clustered by(分桶字段) [sorted by (字段 [asc | desc])] into N buckets --- 定义分桶表核心语句 row format...... 分桶的作用 1) 进行数据采样工作 1.1) …

final关键字-Java

final关键字 一、使用场景1、当不希望类被继承时&#xff0c;可以用final修饰。2、当不希望父类的某个方法被子类覆盖/重写(override)时&#xff0c;可以用final修饰。3、当不希望类的的某个属性的值被修改&#xff0c;可以用final修饰。4、当不希望某个局部变量被修改&#xf…

智能优化算法应用:基于花授粉算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于花授粉算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于花授粉算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.花授粉算法4.实验参数设定5.算法结果6.参考文献7.…

好用的json处理工具He3 JSON

官网地址&#xff1a;https://he3app.com/zh/ json格式化 https://portal.he3app.com/home/extension/json-to-pretty 其他 https://portal.he3app.com/home/category

芯能科技-603105 三季报分析(20231123)

芯能科技-603105 基本情况 公司名称&#xff1a;浙江芯能光伏科技股份有限公司 A股简称&#xff1a;芯能科技 成立日期&#xff1a;2008-07-09 上市日期&#xff1a;2018-07-09 所属行业&#xff1a;电气机械和器材制造业 周期性&#xff1a;1 主营业务&#xff1a;分布式光伏解…

每日一练:简易计算器

1. 题目 设计实现一个简易的计算器&#xff0c;可以进行加减乘除的计算。可以考虑通过GUI和命令行输入等方式实现。 2. 设计思路 创建一个简单的用户界面&#xff0c;可以使用 Python 的 Tkinter模块。在界面上放置按钮&#xff0c;每个按钮代表一个数字、运算符或其他功能。…

家政预约服务管理系统,轻松搭建专属家政小程序

家政预约服务管理系统&#xff0c;轻松搭建专属家政小程序app&#xff1b; 家政服务app开发架构包括&#xff1a; 1. 后台管理端&#xff1a;全面管理家政服务、门店、员工、阿姨信息、订单及优惠促销等数据&#xff0c;并进行统计分析。 2. 门店端&#xff1a;助力各门店及员工…