0

0

基于Paddle复现残差连接网络ResNet

P粉084495128

P粉084495128

发布时间:2025-07-29 11:31:27

|

491人浏览过

|

来源于php中文网

原创

ResNet有18、34等多种结构,仅残差块数量不同。其通过残差块解决深度神经网络“退化”问题,残差块含残差路径和恒等映射路径,有基础块和瓶颈块等类型。网络用平均池化得特征,卷积后接批归一化,易扩展。文中还给出Paddle复现代码,经训练,其泛化能力和准确率弱于DenseNet,或因层数少。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

基于paddle复现残差连接网络resnet - php中文网

ResNet网络结构概览

可见有18,34,50,101,152layer种,这五种结构基本类似,只是堆叠的残差块数量不同,这里为了简单起见,采用18 layer作为演示

基于Paddle复现残差连接网络ResNet - php中文网

  • 通过Average Pooling得到最终的特征,而不是通过全连接层;
  • 每个卷积层之后都紧接着BatchNorm layer,为了简化,图中并没有标出;

ResNet结构非常容易修改和扩展,通过调整block内的channel数量以及堆叠的block数量,就可以很容易地调整网络的宽度和深度,来得到不同表达能力的网络,而不用过多地担心网络的“退化”问题,只要训练数据足够,逐步加深网络,就可以获得更好的性能表现。

ResNet网络解决了什么

ResNet可以解决深度神经网络中的“退化”问题!

我们知道,针对浅层网络,其模型性能会随着网络层的堆叠而逐渐变好。因为非线性层增多,特征提取能力越好,即模型拟合数据的能力越好。而模型退化指的是,给网络叠加更多的层后,分类准确率达到饱和,并且性能快速下降。

基于Paddle复现残差连接网络ResNet - php中文网

如上图所示,左右两张图分别表示plain网络(类似VGG构造的深度神经网络)和ResNet网络在ImageNet数据集上的误差。其中横坐标表示迭代次数,纵坐标表示误差,细线表示训练误差,粗线表示验证误差。

按照经验,我们知道模型越深,模型的性能会越好,误差理应越小。但是,如上左图所示,越深的plain网络误差反而越大。在训练集上越深的模型性能反而下降,可以排除过拟合。同时,batchnorm(BN)层的引入也基本解决了plain 网络梯度消失和梯度爆炸的问题。如果不是过拟合和梯度消失导致的,那么什么原因导致模型“退化”呢?

我们假设有一个浅层网络,我们通过堆积新层的方式来建立更深的网络。那么深层网络的解空间应该是包含浅层网络的解空间。如果让那些新增的层不做任何的学习,仅仅简单的复制浅层网络的特征,即新层做恒等映射(identity mapping)。那么,在这种情况下,深层网络应该和浅层网络的性能一样,也不应该出现“退化现象”。更好的解明明存在,为什么找不到?找到的反而是最差的解?

显然,这是个优化问题,反应出结构相似的模型,其优化难度是不一样的,且难度的增长并不是线性的,越深的模型越难以优化。

有两种解决思想。一种是调整求解方法,比如更好的初始化方式,更好的梯度下降算法等;另一种则是调整模型结构,让模型更易于优化。

ResNet的作者从后者入手,探求更好的模型结构。将堆叠的几层layer称之为一个block,对于某个block,其可以拟合的函数为F(x),如果期望的潜在映射为H(x),与其让F(x) 直接学习潜在的映射,不如去学习残差H(x)−x,即F(x):=H(x)−x,这样原本的前向路径上就变成了F(x)+x,用F(x)+x来拟合H(x)。作者认为这样可能更易于优化,因为相比于让F(x)学习潜在映射,让F(x)学习成0要更加容易。这样,对于冗余的block,只需F(x)→0就可以得到恒等映射,性能不减。

为什么残差学习更容易?从直觉上讲,学的东西越少越容易学习。残差学习只需要学习一个近似于0的东西,难度相对小点。从数学上讲,我们假设残差单元表示为:

如下图所示,F(x)+x构成的block称之为Residual Block,即残差块。多个相似的Residual Block串联构成ResNet。

基于Paddle复现残差连接网络ResNet - php中文网

一个残差块有2条路径F(x)和x,F(x)路径拟合残差,不妨称之为残差路径,x路径为identity mapping恒等映射,称之为”shortcut”。图中的⊕为element-wise addition,要求参与运算的F(x)和x的尺寸要相同

不同的残差路径结构

在原论文中,残差路径可以大致分成2种,一种有bottleneck结构,即下图右中的1×1 卷积层,用于先降维再升维,主要出于降低计算复杂度的现实考虑,称之为“bottleneck block”,另一种没有bottleneck结构,如下图左所示,称之为“basic block”。basic block由2个3×3卷积层构成,bottleneck block由两个1×1卷积和1个3x3卷积层构成。

基于Paddle复现残差连接网络ResNet - php中文网

不同的恒等映射路径结构

shortcut路径大致也可以分成2种,取决于残差路径是否改变了feature map数量和尺寸,一种是将输入x原封不动地输出,另一种则需要经过1×1卷积来升维 or/and 降采样,主要作用是将输出与F(x)路径的输出保持shape一致,对网络性能的提升并不明显,两种结构如下图所示,

基于Paddle复现残差连接网络ResNet - php中文网

AIBox 一站式AI创作平台
AIBox 一站式AI创作平台

AIBox365一站式AI创作平台,支持ChatGPT、GPT4、Claue3、Gemini、Midjourney等国内外大模型

下载

至于Residual Block之间的衔接,在原论文中,F(x)+x经过ReLU后直接作为下一个block的输入x。

Paddle复现代码部分

In [ ]
import timeimport paddleimport paddle.nn as nnimport paddle.nn.functional as F 
from paddle.vision.transforms import Compose, Resizefrom PIL import Imageimport matplotlib.pyplot as pltfrom collections import OrderedDictimport copyimport numpy as npimport paddle.fluid as fluidfrom paddle.vision import transforms as transformsfrom paddle.vision.datasets import Cifar10from paddle.vision.transforms import Normalizefrom time import strftimefrom time import gmtime

定义残差块

基于Paddle复现残差连接网络ResNet - php中文网

这个残差块其实包含了这两种结构

In [ ]
#定义残差块class ResidualBlock(nn.Layer):
    def __init__(self, inchannel, outchannel, stride):
        super(ResidualBlock, self).__init__()

        self.left = nn.Sequential(
            nn.Conv2D(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias_attr=False),
            nn.BatchNorm2D(outchannel),
            nn.ReLU(),
            nn.Conv2D(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias_attr=False),
            nn.BatchNorm2D(outchannel)
        )

        self.shortcut = nn.Sequential()        if stride != 1 or inchannel != outchannel:            # 使x的变化一致 使用与F相同的outchannel与stride
            self.shortcut = nn.Sequential(
                nn.Conv2D(inchannel, outchannel, kernel_size=1, stride=stride, bias_attr=False),
                nn.BatchNorm2D(outchannel)
            )    def forward(self, x):
        out = self.left(x)        # 这里是直接加上去了,DenseNet是累起来的,相当于扩充了通道数
        out += self.shortcut(x)
        out = F.relu(out)        return out

ResNet网络

In [ ]
#resnet主体class ResNet(nn.Layer):
    def __init__(self, ResidualBlock, num_classes=10):
        super(ResNet, self).__init__()
        self.inchannel = 64
        self.conv1 = nn.Sequential(
            nn.Conv2D(3, 64, kernel_size=3, stride=1, padding=1, bias_attr=False),
            nn.BatchNorm2D(64),
            nn.ReLU(),
        )        # 把函数传了进去
        self.layer1 = self.make_layer(ResidualBlock, 64,  2, stride=1)
        self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
        self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
        self.layer4 = self.make_layer(ResidualBlock, 512, 2, stride=2)
        self.fc = nn.Linear(512, num_classes) 
    def make_layer(self, block, channels, num_blocks, stride):
        # 堆叠的层数与num_blocks有关
        strides = [stride] + [1] * (num_blocks - 1)
        layers = []        for stride in strides:
            layers.append(block(self.inchannel, channels, stride))            # 储存上次输出的通道数,以便一次输入回去
            self.inchannel = channels        return nn.Sequential(*layers)    # 假设输入3*32*32 这样训练的快点哈哈
    # 如果不用32*32,可以在开头几个conv中缩小尺寸
    def forward(self, x):
        # 3*32*32
        out = self.conv1(x)        # 64*32*32
        out = self.layer1(out)        # 64*32*32
        out = self.layer2(out)        # 128*16*16
        out = self.layer3(out)        # 256*8*8
        out = self.layer4(out)        # 512*4*4
        out = F.avg_pool2d(out,4)        # 512*1*1
        out = paddle.reshape(out,[out.shape[0], -1])        # x 1*1*512=512
        out = self.fc(out)        return out#定义resnet18def ResNet18():
    return ResNet(ResidualBlock)

数据加载与增强

这里为了加快训练,略微修改了原论文中的模型结构,取消了部分卷积层,因为原论文是224*224输入的,这里的输入仅为32*32

In [ ]
transform_train = Compose([
    transforms.RandomHorizontalFlip(prob=0.5),
    transforms.RandomVerticalFlip(prob=0.5),
    transforms.ColorJitter(brightness=0.5),
    transforms.ColorJitter(contrast=0.5),
    transforms.ColorJitter(saturation=0.5),
    transforms.ColorJitter(hue=0.3),
    transforms.Resize([32, 32]),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

transform_test = Compose([
    transforms.Resize([32, 32]),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

LR = 0.001EPOCHES = 100BATCHSIZE = 150trainset=Cifar10(data_file=None, mode='train', transform=transform_train, download=True)
testset=Cifar10(data_file=None, mode='test', transform=transform_test, download=True)

trainloader = paddle.io.DataLoader(trainset, batch_size=BATCHSIZE, shuffle=True, num_workers=2)
testloader = paddle.io.DataLoader(testset, batch_size=BATCHSIZE, shuffle=False, num_workers=2)

优化器与损失函数

In [ ]
resnet18 = ResNet18()
optimizer = paddle.optimizer.Adam(learning_rate=LR,parameters=resnet18.parameters())
loss_function = nn.CrossEntropyLoss()

训练过程

训练了20个epoch

这里加了个计时的功能,感觉不错,精确摸鱼时间

In [1]
for ep in range(EPOCHES):
    train_correct = 0
    train_sum=0
    epoch_used_time=0
    epoch_ave_time=0
    time_start=time.time()    for i,(img, label) in enumerate(trainloader):

        optimizer.clear_grad()
        resnet18.train()
        out = resnet18(img)

        prediction = paddle.argmax(out, 1)
        pre_num = prediction.cpu().numpy()
        train_correct += (pre_num == label.cpu().numpy()).sum()
        train_sum+=BATCHSIZE

        loss = loss_function(out, label)
        loss.backward()
        optimizer.step()

        epoch_used_time+=(time.time()-time_start)
        time_start=time.time()        
        # 加了个比较简陋的计时方式,显示训练剩余时间,以便估计摸鱼时间
        used_t=strftime("%H:%M:%S", gmtime(epoch_used_time))
        total_t=strftime("%H:%M:%S", gmtime((epoch_used_time/(i+1))*len(trainloader)))        print(f"\rEpoch:{str(ep)} Iter {train_sum}/{len(trainloader)*BATCHSIZE} Train ACC: {(train_correct/train_sum):.5}\
    Used_Time:{used_t} / Total_Time:{total_t}",end="")    
    print('')

    vote_correct = 0
    test_sum=0
    for img, label in testloader:
        resnet18.eval()
        out = resnet18(img)
        prediction = paddle.argmax(out, 1)
        pre_num = prediction.cpu().numpy()
        vote_correct += (pre_num == label.cpu().numpy()).sum()
        test_sum+=BATCHSIZE    
        print(f"\rEpoch:{str(ep)} Iter {test_sum}/{len(testloader)*BATCHSIZE} Test ACC: {(vote_correct/test_sum):.5}",end="")    print('')

训练结果展示

经过与DenseNet对比,发现其泛化能力与准确率确实弱于DenseNet

不过这可能是网络层数较少的缘故

DenseNet部分

https://aistudio.baidu.com/aistudio/projectdetail/3618847?shared=1

Epoch:1 Iter 50100/50100 Train ACC: 0.67275 Used_Time:00:00:37 / Total_Time:00:00:37

Epoch:1 Iter 10050/10050 Test ACC: 0.68965

Epoch:3 Iter 50100/50100 Train ACC: 0.75012 Used_Time:00:00:39 / Total_Time:00:00:39

Epoch:3 Iter 10050/10050 Test ACC: 0.73731

Epoch:19 Iter 50100/50100 Train ACC: 0.9289 Used_Time:00:00:38 / Total_Time:00:00:388

Epoch:19 Iter 10050/10050 Test ACC: 0.8415

Epoch:20 Iter 50100/50100 Train ACC: 0.93236 Used_Time:00:00:39 / Total_Time:00:00:39

Epoch:20 Iter 10050/10050 Test ACC: 0.83751

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号