ResNet (Residual Network) - 残差网络:深度卷积神经网络的突破
一、引言
在计算机视觉领域,图像识别一直是一个核心且具有挑战性的任务。随着深度学习的发展,卷积神经网络(CNN)在图像识别方面取得了显著的成果。然而,随着网络深度的增加,出现了梯度消失或梯度爆炸等问题,导致网络性能下降,这被称为 “退化问题”。ResNet(残差网络)的出现,为解决这一难题提供了有效的方案,它通过引入残差连接,使得深度卷积神经网络能够更好地学习图像特征,显著提高了图像识别的准确率,成为了深度学习领域的重要里程碑。
二、ResNet 的背景与发展历程
(一)深度学习与卷积神经网络的兴起
深度学习的兴起得益于大数据的发展和计算能力的提升。卷积神经网络作为深度学习的重要分支,因其在处理具有网格结构数据(如图像)方面的优势,成为图像识别的主流模型。早期的卷积神经网络如 LeNet-5 等,在简单的图像分类任务上取得了不错的效果,但随着任务复杂度的增加,网络深度的不足限制了其性能的进一步提升。
(二)深度网络的挑战 - 退化问题
当研究人员尝试增加网络深度时,发现网络性能并没有如预期般提升,反而出现了退化现象。即更深的网络在训练集和测试集上的准确率反而不如较浅的网络。这一现象引起了广泛关注,传统的观点认为,增加网络深度应该能够学习到更复杂的特征,从而提高性能,但实际情况并非如此。
(三)ResNet 的诞生
为了解决深度网络的退化问题,何恺明等人于 2015 年提出了 ResNet。ResNet 的核心思想是引入残差连接(Residual Connection),使得网络能够更容易地学习到恒等映射(Identity Mapping),从而缓解了梯度消失或梯度爆炸问题,使得深度网络的训练成为可能。ResNet 在当年的 ImageNet 大规模视觉识别挑战赛(ILSVRC)中取得了优异的成绩,引起了学术界和工业界的广泛关注,并迅速成为深度学习领域的研究热点之一。

三、ResNet 的基本原理
(一)残差块(Residual Block)
ResNet 的基本构建模块是残差块。一个简单的残差块由两部分组成:主路径和残差连接。主路径通常包含若干卷积层、批量归一化(Batch Normalization)层和激活函数(如 ReLU)。残差连接则是将输入直接连接到主路径的输出,然后将两者相加作为残差块的最终输出。
以下是一个使用 PyTorch 实现的简单残差块代码示例:
import torch
import torch.nn as nnclass ResidualBlock(nn.Module):def __init__(self, in_channels, out_channels, stride=1):super(ResidualBlock, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(out_channels)self.relu = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels)if stride!= 1 or in_channels!= out_channels:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels))else:self.shortcut = nn.Identity()def forward(self, x):residual = xout = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out += self.shortcut(residual)out = self.relu(out)return out
在上述代码中,ResidualBlock类定义了一个残差块。conv1和conv2是卷积层,bn1和bn2是批量归一化层,relu是 ReLU 激活函数。shortcut表示残差连接,如果输入和输出的通道数或步长不一致,shortcut会通过一个卷积层和批量归一化层进行维度调整,否则直接使用nn.Identity()。在forward方法中,先计算主路径的输出,然后将其与残差连接的输出相加,并通过 ReLU 激活函数得到最终结果。
(二)残差学习的原理
假设我们要学习的函数为H(x),残差网络尝试学习的是残差函数
那么原始函数就可以表示为
这种学习方式使得网络更容易学习到恒等映射,因为如果残差函数为 0,那么网络就可以轻松地学习到恒等映射,而不需要对网络参数进行大幅调整。在深度网络中,这种残差学习的方式有助于梯度的传播,缓解了梯度消失或梯度爆炸问题,使得网络能够更好地训练。
例如,在一个非常深的网络中,假设某一层的梯度很小,如果没有残差连接,那么这个小梯度在反向传播过程中会不断衰减,导致前面的层难以训练。而有了残差连接,即使某一层的梯度很小,通过残差连接,前面层的梯度仍然可以通过残差路径得到一定程度的保留,从而使得网络能够更有效地训练。
四、ResNet 的网络架构

(一)不同深度的 ResNet 模型
ResNet 系列包括多种不同深度的模型,如 ResNet-18、ResNet-34、ResNet-50、ResNet-101 和 ResNet-152 等。这些模型的主要区别在于残差块的数量和网络的宽度(即通道数)。以下是 ResNet-18 的网络架构示例:
class ResNet18(nn.Module):def __init__(self, num_classes=1000):super(ResNet18, self).__init__()self.in_channels = 64self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(64, 2, stride=1)self.layer2 = self._make_layer(128, 2, stride=2)self.layer3 = self._make_layer(256, 2, stride=2)self.layer4 = self._make_layer(512, 2, stride=2)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512, num_classes)def _make_layer(self, out_channels, num_blocks, stride):strides = [stride] + [1] * (num_blocks - 1)layers = []for stride in strides:layers.append(ResidualBlock(self.in_channels, out_channels, stride))self.in_channels = out_channelsreturn nn.Sequential(*layers)def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.maxpool(out)out = self.layer1(out)out = self.layer2(out)out = self.layer3(out)out = self.layer4(out)out = self.avgpool(out)out = torch.flatten(out, 1)out = self.fc(out)return out
在上述代码中,ResNet18类定义了 ResNet-18 模型。conv1是第一个卷积层,后面跟着批量归一化层和 ReLU 激活函数,然后是一个最大池化层。_make_layer方法用于构建残差块组成的层,根据指定的输出通道数、残差块数量和步长来创建相应的残差块序列。layer1到layer4分别是不同阶段的残差层,最后通过自适应平均池化层将特征图转换为固定大小,再通过全连接层进行分类。
(二)瓶颈结构(Bottleneck Structure)
对于更深的 ResNet 模型(如 ResNet-50 及以上),为了减少计算量和参数数量,采用了瓶颈结构。瓶颈结构在残差块中增加了一个卷积层来降低通道数,然后通过11卷积层进行特征提取,最后再通过一个33卷积层恢复通道数。以下是一个使用瓶颈结构的残差块代码示例:
class BottleneckBlock(nn.Module):expansion = 4def __init__(self, in_channels, out_channels, stride=1):super(BottleneckBlock, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)self.bn1 = nn.BatchNorm2d(out_channels)self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels)self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)if stride!= 1 or in_channels!= out_channels * self.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * self.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * self.expansion))else:self.shortcut = nn.Identity()self.relu = nn.ReLU(inplace=True)def forward(self, x):residual = xout = 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 += self.shortcut(residual)out = self.relu(out)return out
在BottleneckBlock类中,expansion表示通道扩展倍数,这里设置为 4。conv1用于降低通道数,conv2进行主要的特征提取,conv3恢复通道数。通过这种方式,在不损失太多性能的前提下,大大减少了计算量和参数数量,使得更深的网络能够在实际应用中得以训练和使用。
五、ResNet 在图像识别中的应用
(一)ImageNet 数据集上的表现
ResNet 在 ImageNet 数据集上取得了非常出色的成绩。ImageNet 是一个大规模的图像数据集,包含了数百万张图像和上千个类别,是图像识别领域的重要基准数据集。ResNet-18、ResNet-34 等较浅的模型在 ImageNet 上的准确率已经超过了很多传统的卷积神经网络模型,而 ResNet-50、ResNet-101 和 ResNet-152 等更深的模型则进一步提高了准确率,在图像分类任务上达到了当时的先进水平。
以下是使用 ResNet-50 在 ImageNet 数据集上进行训练和测试的示例代码(使用 PyTorch 和 torchvision 库):
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim# 数据预处理
transform_train = transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])transform_test = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# 加载训练集和测试集
trainset = torchvision.datasets.ImageNet(root='./data', split='train', transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=4)testset = torchvision.datasets.ImageNet(root='./data', split='val', transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=4)# 定义ResNet-50模型
net = torchvision.models.resnet50(pretrained=False)
num_classes = 1000
net.fc = nn.Linear(net.fc.in_features, num_classes)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)# 训练模型
def train(epoch):net.train()running_loss = 0.0for i, data in enumerate(trainloader, 0):inputs, labels = dataoptimizer.zero_grad()outputs = net(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item()if i % 100 == 99:print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100))running_loss = 0.0# 测试模型
def test():net.eval()correct = 0total = 0with torch.no_grad():for data in testloader:images, labels = dataoutputs = net(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print('Accuracy of the network on the test images: %d %%' % (100 * correct / total))# 训练和测试循环
for epoch in range(10):train(epoch)test()
在上述代码中,首先定义了数据预处理的转换操作,包括随机裁剪、随机水平翻转、转换为张量以及标准化等。然后加载 ImageNet 数据集的训练集和测试集,并使用torchvision.models.resnet50创建 ResNet-50 模型,将最后一层全连接层修改为适应 ImageNet 的类别数。接着定义了损失函数(交叉熵损失)和优化器(随机梯度下降),并实现了训练和测试函数。在训练过程中,对每个批次的数据进行前向传播、计算损失、反向传播和参数更新。在测试过程中,计算模型在测试集上的准确率。
(二)其他图像识别任务中的应用
除了在 ImageNet 数据集上的图像分类任务,ResNet 还被广泛应用于其他图像识别任务,如目标检测、图像分割、人脸识别等。
目标检测:在目标检测任务中,ResNet 常作为骨干网络(Backbone Network)用于提取图像的特征。例如,在 Faster R-CNN、Mask R-CNN 等目标检测模型中,ResNet 可以提供丰富的语义特征,帮助模型准确地检测和定位图像中的目标。以下是使用 ResNet-50 作为骨干网络的 Faster R-CNN 模型的示例代码(使用 PyTorch 和 torchvision 库):
import torchvision
from torchvision.models.detection import FasterRCNN
from torchvision.models.detection.rpn import AnchorGenerator# 加载ResNet-50作为骨干网络
backbone = torchvision.models.resnet50(pretrained=True)
backbone = nn.Sequential(*list(backbone.children())[:-2])# 定义锚点生成器
anchor_generator = AnchorGenerator(sizes=((32, 64, 128, 256, 512),), aspect_ratios=((0.5, 1.0, 2.0),))# 创建Faster R-CNN模型
roi_pooler = torchvision.ops.MultiScaleRoIAlign(featmap_names=['0'], output_size=7, sampling_ratio=2)
model = FasterRCNN(backbone, num_classes=2, rpn_anchor_generator=anchor_generator, box_roi_pool=roi_pooler)# 定义损失函数和优化器
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
相关文章:
ResNet (Residual Network) - 残差网络:深度卷积神经网络的突破
一、引言 在计算机视觉领域,图像识别一直是一个核心且具有挑战性的任务。随着深度学习的发展,卷积神经网络(CNN)在图像识别方面取得了显著的成果。然而,随着网络深度的增加,出现了梯度消失或梯度爆炸等问题…...
MOSFET体二极管的反向恢复分析
1、MOSFET体二极管的反向恢复分析 MOSFET体二极管反向恢复会导致MOSFET工作时超出安全工作区(SOA),并引发其他电磁干扰问题(EMI。 二极管在反向恢复过程中会产生比较大的损耗。在正向偏置状态下,大量的电子和空穴载流…...
80_Redis内存策略
Redis性能之所以这么强,最主要的原因就是基于内存存储。而单节点的Redis其内存大小不宜过大,否则会影响持久化或主从同步的性能。 我们可以通过修改redis.conf配置文件来设置Redis的最大内存。 maxmemory <bytes> 当Redis内存使用达到上限时,就无法存储更多数据了。…...
【HarmonyOS NAPI 深度探索6】使用 N-API 创建第一个 Hello World 原生模块
【HarmonyOS NAPI 深度探索6】使用 N-API 创建第一个 Hello World 原生模块 开发一个 N-API 模块听起来可能有点技术感十足,但实际上入门并不复杂。今天,我们就来一步步实现一个简单的 Hello World 原生模块,感受一下 N-API 开发的魅力。 环…...
Java语言的软件工程
Java语言的软件工程 引言 在当今信息技术飞速发展的时代,软件工程作为一门应用广泛的学科,承担着开发高质量软件系统的重要责任。Java语言以其跨平台特性、安全性和强大的库支持,已经成为软件工程领域中最流行的编程语言之一。本文将深入探…...
【Mysql进阶知识】Mysql 程序的介绍、选项在命令行配置文件的使用、选项在配置文件中的语法
目录 一、程序介绍 二、mysqld--mysql服务器介绍 三、mysql - MySQL 命令行客户端 3.1 客户端介绍 3.2 mysql 客户端选项 指定选项的方式 mysql 客户端命令常用选项 在命令行中使用选项 选项(配置)文件 使用方法 选项文件位置及加载顺序 选项文件语法 使用举例&am…...
wireshark抓路由器上的包 抓包路由器数据
文字目录 抓包流程概述设置抓包配置选项 设置信道设置无线数据包加密信息设置MAC地址过滤器 抓取联网过程 抓包流程概述 使用Omnipeek软件分析网络数据包的流程大概可以分为以下几个步骤: 扫描路由器信息,确定抓包信道;设置连接路由器的…...
玩转大语言模型——使用graphRAG+Ollama构建知识图谱
系列文章目录 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 文章目录 系列文章目录前言下载和安装用下载项目的方式下载并安装用pip方式下载并安装 生成知识图谱初始化文件夹修改模型配置修改知识库生成配置创…...
python flask简单实践
项目结构 project/ │ ├── app.py ├── instance/ │ └── database.db ├── templates/ │ └── index.html ├── static/ │ └── style.css │ └── favicon.ico └── database.db首先创建目录,static 存放一些页面的样式或图标文件…...
JAVA实现五子棋小游戏(附源码)
文章目录 一、设计来源捡金币闯关小游戏讲解1.1 主界面1.2 黑棋胜利界面1.3 白棋胜利界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载更多优质源码分享 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/145161039 JA…...
kotlin的dagger hilt依赖注入
依赖注入(dependency injection, di)是设计模式的一种,它的实际作用是给对象赋予实例变量。 基础认识 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceSta…...
速通Docker === 常用命令
目录 Docker命令 镜像操作 容器操作 基础操作 启动参数 容器内部操作 打包成指定文件 发布镜像 总结 镜像操作 容器操作 启动容器参数 容器内部操作 打包镜像 启动指定镜像的容器 发布镜像 Docker命令 启动一个nginx,并将它的首页改为自己的页面,发布…...
【redis】键的全局命令
Redis提供了一系列用于管理和操作键的全局命令。这些命令允许你查看、删除、迁移键,以及执行其他与键相关的操作。 有关全局通用类型的命令可以通过help generic命令来查看。有关命令的使用可以通过help 命令来查看,例如help keys。 KEYS keys&#x…...
深度学习-卷积神经网络实战文档注释
1、call 方法 是一个特殊的方法,它允许类的实例表现得像函数一样。也就是说,你可以使用圆括号 () 来调用一个实例,就像调用普通函数一样。 当你调用 model(input_data) 时,实际上是调用了模型的 __ call __ 方法,其会自…...
GR2103高压半桥栅极驱动芯片
产品简介 GR2103封装和丝印 GR2103是一款高性价比的高压半桥栅极驱动专用芯片,设计用于高压、高速驱动N型大功率 MOS管、IGBT管。内置欠压(UVLO)保护功能,防止功率管在过低的电压下工作,提高效率。内置防止直通功能…...
学习threejs,使用OrbitControls相机控制器
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.OrbitControls 相机控…...
说说Babylon.js中scene.deltaTime的大坑
诡异的问题 下面是给一个材质设置发光颜色周期变化和纹理偏移的代码,你能感觉到这里面可能出现的问题吗? var passTime 0;var uOffset 0;var deltaTime 0;function SetEmissiveColor() {passTime scene.deltaTime * 0.05;if(passTime > 6.2…...
【React】win系统环境搭建
动图更精彩 方案如下 在Visual Studio Code(VSCode)中搭建React开发环境是一个相对简单但非常重要的步骤,可以帮助你更高效地进行前端开发。以下是详细的步骤和配置指南: 一、准备工作 安装Visual Studio Code (VSCode)&#x…...
ThinkPHP 8的一对一关联
【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…...
Linux 下配置 Golang 环境
go sdk 下载环境:https://golang.google.cn/dl/选择对应的版本: 使用 wget 直接拉包下载到服务器中 wget https://golang.google.cn/dl/go1.23.4.linux-amd64.tar.gz如果找不到 wget 命令,yum 下载 wget yum -y install wget配置 go 的环境…...
机器学习结合基因无关通路映射:从临床数据挖掘新药靶点
1. 项目概述:当机器学习遇见代谢通路,如何从数据中“挖”出新药靶点?在生物医学研究的前沿,我们正面临一个核心矛盾:一方面,我们拥有海量的临床数据,比如血糖、血压、BMI等指标;另一…...
基于Arduino的模块化DIY智能时钟:从RTC到RGB LED的完整实现
1. 项目概述:打造一台高度可定制的DIY RGB LED时钟如果你和我一样,对市面上千篇一律的电子钟感到审美疲劳,同时又对Arduino和电子DIY充满热情,那么这个项目可能就是为你准备的。我们不是在简单地组装一个套件,而是在亲…...
3步解锁网易云音乐NCM加密:让音乐真正属于你
3步解锁网易云音乐NCM加密:让音乐真正属于你 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为下载的网易云音乐只能在特定客户端播放而烦恼吗?当你精心收藏的歌曲被NCM格式"锁"在单一平台时&a…...
二十六.签名与脚本(1)--脚本介绍
1.区块链脚本介绍在之前的章节中,我们了解了签名与验证相关,但是btc的交易数据,签名和验证,不是单纯的,还有脚本深度参与其中。我们从开始来:bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletT…...
我们公司全员把 Cursor 换成了自研的 全开源AtomCode
【引子】这是一篇实录——一位 CTO 用 28 天,用 Claude GLM 双模型调度,造出了一个让全公司放弃 Cursor 的工具。然后我意识到我们正在经历的事情,比"换工具"大得多。【读者承诺】接下来 15 分钟,你会拿到三件东西:一个真实案例(28 天 1,146 commits 是怎么做出来的…...
C语言(12) 指针的常见操作
指针的常见操作指针变量,有两方面的意思:一个指针指向的内容(数据值,一级)指针变量本身存储的数据 (地址值)#include <stdio.h>int main() {int a 10;int b 0 ;int c 50;int *p NULL;int *q NULL;p &a; // 对指针变量本身进行修改// 对指…...
Java网络编程基础分享
在学习 Java 的过程中,网络编程是非常重要的一环。无论是后端开发、分布式系统、即时通讯、文件传输,还是游戏服务、物联网设备,都离不开网络通信一、计算机网络基础1.1 什么是计算机网络把不同地理位置、具有独立功能的计算机,通…...
Unity项目实战:用TriLib插件动态加载FBX模型,5分钟搞定外部资源读取
Unity项目实战:用TriLib插件高效加载外部FBX模型的完整指南在VR展示、产品配置器等需要动态加载用户上传模型的场景中,如何快速实现外部FBX文件的读取是许多Unity开发者面临的挑战。传统的手动导入方式不仅效率低下,更无法满足运行时动态加载…...
终极Obsidian笔记模板指南:如何用kepano-obsidian构建你的第二大脑
终极Obsidian笔记模板指南:如何用kepano-obsidian构建你的第二大脑 【免费下载链接】kepano-obsidian My personal Obsidian vault template. A bottom-up approach to note-taking and organizing things I am interested in. 项目地址: https://gitcode.com/gh_…...
当卫星在天上“读懂”人间:ICLR 2025 论文深度解读师玉娇、昃向辉的CS2S
把一张卫星图变成一张街景照片,就像把一个俯视棋盘拼成一面看台——不仅要摆对每一枚棋子,还要看懂整场比赛想象这样一个场景:你在城市规划部门工作,需要快速生成某条街道在不同季节、不同天气条件下的真实渲染效果,以…...
