ResNet-18网络模型+原理解析+Pytorch实现+手写模型

ResNet-18 通过引入残差连接(Skip Connection) ,有效解决了深层卷积神经网络(CNN)中常见的梯度消失网络退化问题。

论文 https://arxiv.org/pdf/1512.03385v1

对于传统的深度学习网络,我们普遍认为网络深度越深(参数越多)非线性的表达能力越强,该网络所能学习到的东西就越多。 凭借这一基本规则,经典的CNN网络从LetNet-5(5层)和AlexNet(8层)发展到VGGNet(16-19),再到后来GoogleNet(22层)。 但是后来发现传统的CNN网络结构随着层数加深到一定程度之后,越深的网络反而效果更差,过深的网络竟然使分类的准确率下降了,如图所示 56层网络的错误率 高于 20层的错误率

网络退化

网络退化(Network Degradation)是指随着神经网络深度的增加,模型的性能并未如预期那样持续提升,反而可能出现饱和乃至下降的现象。这种现象并非由过拟合引起,因为在更深的网络中,即使在训练数据上的表现也开始变差,导致训练误差和测试误差之间的差距可能增大。


残差结构

传统深层网络直接学习映射 (H(x)),层数加深时梯度消失、性能下降。 ResNet 改为学习残差 : H(x)=F(x)+x

  • x:输入特征(跳跃连接,直接传到输出)

  • F(x):残差函数(2层3×3卷积+BN+ReLU)

  • 优势:

    • 梯度可沿跳跃连接直达浅层,缓解梯度消失

    • 网络易拟合恒等映射(F(x)\to0)),深层训练更稳

关键特性

  1. 轻量高效 :约11M参数1.8G FLOPs,推理快,适合移动端/嵌入式。

  2. 模块化设计:4个Stage,每阶段2个Block,易扩展(如ResNet-34/50)。

  3. 强表征能力:通道从64→512,空间逐级下采样,兼顾细粒度与全局特征。

  4. 训练稳定:残差连接让深层梯度有效回传,收敛快、泛化性好。

典型应用

  • 图像分类:ImageNet、CIFAR-10/100、自定义数据集

  • 迁移学习:特征提取、微调小样本任务

  • 检测/分割骨干:Faster R-CNN、YOLO、Mask R-CNN、U-Net

  • 轻量化部署:手机、边缘设备、实时系统

ResNet-18 结构总览

(输入 3×224×224) 共18个可训练层 (17卷积+1全连接),分5阶段:

阶段 操作 输出尺寸 通道数 层数
输入 RGB图像 224×224 3 -
Stage1 7×7卷积(64, stride=2)+BN+ReLU+MaxPool(3×3, stride=2) 56×56 64 1
Stage2 2×BasicBlock(3×3卷积×2) 56×56 64 4
Stage3 2×BasicBlock(第一个stride=2下采样) 28×28 128 4
Stage4 2×BasicBlock(第一个stride=2下采样) 14×14 256 4
Stage5 2×BasicBlock(第一个stride=2下采样) 7×7 512 4
分类头 全局平均池化+全连接(1000) 1×1 512→1000 1

BasicBlock(基础残差块)

  • 左图:Conv(3×3)→BN→ReLU→Conv(3×3)→BN

  • 右图跳跃连接:维度匹配时用恒等映射 ;不匹配时用1×1卷积 调整通道/步长

代码示例

复制代码
复制代码
ResNet18
@Author:  CSDN@王_teacher  bilibili@王_teacher
@Date: 2024/5/26
@Description: RestNet18 纯手写
"""
import torch
import torch.nn as nn
from torchvision.models import resnet18
​
# 纯平铺版 ResNet18,没有任何子模块、没有任何函数调用
class ResNet18(nn.Module):
    def __init__(self, num_classes=1000):
        super().__init__()
​
        # ==================== Stem 层 ====================
        self.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)
​
        # ==================== Layer 1 (64, 无下采样) ====================
        # Block 1
        self.layer1_conv1 = nn.Conv2d(64, 64, 3, 1, 1, bias=False)
        self.layer1_bn1 = nn.BatchNorm2d(64)
        self.layer1_conv2 = nn.Conv2d(64, 64, 3, 1, 1, bias=False)
        self.layer1_bn2 = nn.BatchNorm2d(64)
​
        # Block 2
        self.layer1_conv3 = nn.Conv2d(64, 64, 3, 1, 1, bias=False)
        self.layer1_bn3 = nn.BatchNorm2d(64)
        self.layer1_conv4 = nn.Conv2d(64, 64, 3, 1, 1, bias=False)
        self.layer1_bn4 = nn.BatchNorm2d(64)
​
        # ==================== Layer 2 (128, 下采样) ====================
        # Downsample
        self.downsample2 = nn.Sequential(
            nn.Conv2d(64, 128, 1, 2, bias=False),
            nn.BatchNorm2d(128)
        )
        # Block 1
        self.layer2_conv1 = nn.Conv2d(64, 128, 3, 2, 1, bias=False)
        self.layer2_bn1 = nn.BatchNorm2d(128)
        self.layer2_conv2 = nn.Conv2d(128, 128, 3, 1, 1, bias=False)
        self.layer2_bn2 = nn.BatchNorm2d(128)
​
        # Block 2
        self.layer2_conv3 = nn.Conv2d(128, 128, 3, 1, 1, bias=False)
        self.layer2_bn3 = nn.BatchNorm2d(128)
        self.layer2_conv4 = nn.Conv2d(128, 128, 3, 1, 1, bias=False)
        self.layer2_bn4 = nn.BatchNorm2d(128)
​
        # ==================== Layer3 (256, 下采样) ====================
        self.downsample3 = nn.Sequential(
            nn.Conv2d(128, 256, 1, 2, bias=False),
            nn.BatchNorm2d(256)
        )
        self.layer3_conv1 = nn.Conv2d(128, 256, 3, 2, 1, bias=False)
        self.layer3_bn1 = nn.BatchNorm2d(256)
        self.layer3_conv2 = nn.Conv2d(256, 256, 3, 1, 1, bias=False)
        self.layer3_bn2 = nn.BatchNorm2d(256)
​
        self.layer3_conv3 = nn.Conv2d(256, 256, 3, 1, 1, bias=False)
        self.layer3_bn3 = nn.BatchNorm2d(256)
        self.layer3_conv4 = nn.Conv2d(256, 256, 3, 1, 1, bias=False)
        self.layer3_bn4 = nn.BatchNorm2d(256)
​
        # ==================== Layer4 (512, 下采样) ====================
        self.downsample4 = nn.Sequential(
            nn.Conv2d(256, 512, 1, 2, bias=False),
            nn.BatchNorm2d(512)
        )
        self.layer4_conv1 = nn.Conv2d(256, 512, 3, 2, 1, bias=False)
        self.layer4_bn1 = nn.BatchNorm2d(512)
        self.layer4_conv2 = nn.Conv2d(512, 512, 3, 1, 1, bias=False)
        self.layer4_bn2 = nn.BatchNorm2d(512)
​
        self.layer4_conv3 = nn.Conv2d(512, 512, 3, 1, 1, bias=False)
        self.layer4_bn3 = nn.BatchNorm2d(512)
        self.layer4_conv4 = nn.Conv2d(512, 512, 3, 1, 1, bias=False)
        self.layer4_bn4 = nn.BatchNorm2d(512)
​
        # ==================== 头部 ====================
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512, num_classes)
​
    def forward(self, x):
        # Stem
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
​
        # ==================== Layer 1 ====================
        identity = x
        x = self.layer1_conv1(x)
        x = self.layer1_bn1(x)
        x = self.relu(x)
        x = self.layer1_conv2(x)
        x = self.layer1_bn2(x)
        x += identity
        x = self.relu(x)
​
        identity = x
        x = self.layer1_conv3(x)
        x = self.layer1_bn3(x)
        x = self.relu(x)
        x = self.layer1_conv4(x)
        x = self.layer1_bn4(x)
        x += identity
        x = self.relu(x)
​
        # ==================== Layer 2 ====================
        identity = self.downsample2(x)
        x = self.layer2_conv1(x)
        x = self.layer2_bn1(x)
        x = self.relu(x)
        x = self.layer2_conv2(x)
        x = self.layer2_bn2(x)
        x += identity
        x = self.relu(x)
​
        identity = x
        x = self.layer2_conv3(x)
        x = self.layer2_bn3(x)
        x = self.relu(x)
        x = self.layer2_conv4(x)
        x = self.layer2_bn4(x)
        x += identity
        x = self.relu(x)
​
        # ==================== Layer3 ====================
        identity = self.downsample3(x)
        x = self.layer3_conv1(x)
        x = self.layer3_bn1(x)
        x = self.relu(x)
        x = self.layer3_conv2(x)
        x = self.layer3_bn2(x)
        x += identity
        x = self.relu(x)
​
        identity = x
        x = self.layer3_conv3(x)
        x = self.layer3_bn3(x)
        x = self.relu(x)
        x = self.layer3_conv4(x)
        x = self.layer3_bn4(x)
        x += identity
        x = self.relu(x)
​
        # ==================== Layer4 ====================
        identity = self.downsample4(x)
        x = self.layer4_conv1(x)
        x = self.layer4_bn1(x)
        x = self.relu(x)
        x = self.layer4_conv2(x)
        x = self.layer4_bn2(x)
        x += identity
        x = self.relu(x)
​
        identity = x
        x = self.layer4_conv3(x)
        x = self.layer4_bn3(x)
        x = self.relu(x)
        x = self.layer4_conv4(x)
        x = self.layer4_bn4(x)
        x += identity
        x = self.relu(x)
​
        # 分类头
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x
​
if __name__ == '__main__':
    torch.manual_seed(0)
​
    # 模型
    model = ResNet18()
​
​
    x = torch.randn(1,3,224,224)
    model.eval()
    py = model.forward(x)
    print(py.shape)
相关推荐
火山引擎开发者社区4 小时前
没有长期记忆,Agent 谈何持续进化?一图看懂火山 Mem0:解锁 Agent 持续学习与进化之路
人工智能
冬奇Lab7 小时前
Workflow 系列(06):安全——跨步骤注入传播与四层防御
人工智能·工作流引擎
冬奇Lab7 小时前
每日一个开源项目(第149篇):RAG-Anything - 把图片、表格、公式当成一等公民的多模态 RAG 框架
人工智能·开源
米小虾8 小时前
AI Agent 安全实战指南:当智能体开始"不听话",开发者该如何应对?
人工智能·安全·agent
IT_陈寒9 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
阿里云大数据AI技术11 小时前
构建高转化海外电商搜索:阿里云OpenSearch行业算法版的全链路智能优化策略实战
人工智能·搜索引擎
Awu122711 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
字节跳动视频云技术团队11 小时前
让 Agent 成为音视频工作台:AI MediaKit CLI + Skill 发布
人工智能·音视频开发
魏祖潇11 小时前
framework 整合实战——DDD/TDD/SDD 三件套在 framework 仓的真实落地
人工智能·后端