• 【深度学习实验】卷积神经网络(七):实现深度残差神经网络ResNet


    目录

    一、实验介绍

    二、实验环境

    1. 配置虚拟环境

    2. 库版本介绍

    三、实验内容

    0. 导入必要的工具包

    1. Residual(残差连接)

    __init__(初始化)

    forward(前向传播)

    2. resnet_block(残差网络块)

    3. ResNet(网络模型)

    __init__(初始化)

    forward(前向传播)

    4. 代码整合


    一、实验介绍

            本实验实现了实现深度残差神经网络ResNet

            残差网络(ResNet)是一种深度神经网络架构,用于解决深层网络训练过程中的梯度消失和梯度爆炸问题。通过引入残差连接(residual connection)来构建网络层与层之间的跳跃连接,使得网络可以更好地优化深层结构。

            残差网络的一个重要应用是在图像识别任务中,特别是在深度卷积神经网络(CNN)中。通过使用残差模块,可以构建非常深的网络,例如ResNet,其在ILSVRC 2015图像分类挑战赛中取得了非常出色的成绩。

            在ResNet中,每个残差块由一个或多个卷积层组成,其中包含了跳跃连接。跳跃连接将输入直接添加到残差块的输出中,从而使得网络可以学习残差函数,即残差块只需学习将输入的变化部分映射到输出,而不需要学习完整的映射关系。这种设计有助于减轻梯度消失问题,使得网络可以更深地进行训练。

    二、实验环境

            本系列实验使用了PyTorch深度学习框架,相关操作如下:

    1. 配置虚拟环境

    conda create -n DL python=3.7 
    conda activate DL
    pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
    
    conda install matplotlib
     conda install scikit-learn

    2. 库版本介绍

    软件包本实验版本目前最新版
    matplotlib3.5.33.8.0
    numpy1.21.61.26.0
    python3.7.16
    scikit-learn0.22.11.3.0
    torch1.8.1+cu1022.0.1
    torchaudio0.8.12.0.2
    torchvision0.9.1+cu1020.15.2

    三、实验内容

    0. 导入必要的工具包

    1. from torch import nn
    2. import torch.nn.functional as F

    1. Residual(残差连接)

    1. class Residual(nn.Module):
    2. def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
    3. super().__init__()
    4. self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides)
    5. self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)
    6. if use_1x1conv:
    7. self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
    8. else:
    9. self.conv3 = None
    10. # 批量归一化层,将会在第7章讲到
    11. self.bn1 = nn.BatchNorm2d(num_channels)
    12. self.bn2 = nn.BatchNorm2d(num_channels)
    13. def forward(self, X):
    14. Y = F.relu(self.bn1(self.conv1(X)))
    15. Y = self.bn2(self.conv2(Y))
    16. if self.conv3:
    17. X = self.conv3(X)
    18. Y += X
    19. return F.relu(Y)

    __init__(初始化)

    • 参数:
      • 输入通道数`input_channels`
      • 输出通道数`num_channels`
      • 是否使用1x1卷积`use_1x1conv`
      • 步幅`strides`
    • 在初始化过程中,创建了两个卷积层`conv1`和`conv2`,分别使用不同的输入和输出通道数,并指定了卷积核的大小、填充和步幅。
    • 如果`use_1x1conv`为True,则创建一个1x1卷积层`conv3`,用于进行维度匹配;
    • 否则,将`conv3`设为None。
    • 创建两个批量归一化层`bn1`和`bn2`,用于对卷积层的输出进行批量归一化操作。

    forward(前向传播)

    • 将输入`X`通过`conv1`进行卷积操作,然后经过批量归一化层`bn1`和ReLU激活函数。
    • 将输出通过`conv2`进行卷积操作,再经过批量归一化层`bn2`。
    • 如果`conv3`不为None,则将输入`X`通过`conv3`进行卷积操作,用于进行维度匹配。
    • 最后,将经过卷积和批量归一化的结果与输入相加,得到残差连接的输出。
    • 通过ReLU激活函数处理输出,并返回结果。

    2. resnet_block(残差网络块)

            生成由多个残差块组成的残差网络块。

    1. def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
    2. blk = []
    3. for i in range(num_residuals):
    4. if i == 0 and not first_block:
    5. blk.append(Residual(input_channels, num_channels,
    6. use_1x1conv=True, strides=2))
    7. else:
    8. blk.append(Residual(num_channels, num_channels))
    9. return blk
    • 参数
      • input_channels:输入通道数,即每个残差块的输入的通道数。
      • num_channels:每个残差块中卷积层的输出通道数,也是每个残差块内部卷积层的通道数。
      • num_residuals:残差块的数量。
      • first_block:一个布尔值,表示是否为整个 ResNet 中的第一个残差块。
    • 创建一个空列表 blk,用于存储构建的残差块。
    • 通过一个循环迭代 num_residuals 次,每次迭代都构建一个残差块并将其添加到 blk 列表中。
      • 在每个迭代中,首先检查是否为第一个残差块且 first_block 为 False。
        • 如果是,则创建一个具有下采样(strides=2)的残差块,并将其添加到 blk 列表中。这是为了在整个 ResNet 中的第一个残差块中进行下采样。
        • 如果不是第一个残差块或者 first_block 为 True,则创建一个普通的残差块,并将其添加到 blk 列表中。
    • 返回构建好的残差块列表 blk

    3. ResNet(网络模型

            ResNet 网络模型,包含了多个残差块,用于实现图像分类任务。

    1. class ResNet(nn.Module):
    2. def __init__(self, num_classes):
    3. super().__init__()
    4. self.b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
    5. nn.BatchNorm2d(64), nn.ReLU(),
    6. nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
    7. self.b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
    8. self.b3 = nn.Sequential(*resnet_block(64, 128, 2))
    9. self.b4 = nn.Sequential(*resnet_block(128, 256, 2))
    10. self.b5 = nn.Sequential(*resnet_block(256, 512, 2))
    11. self.head = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(512, num_classes))
    12. def forward(self, x):
    13. net = nn.Sequential(self.b1, self.b2, self.b3, self.b4, self.b5, self.head)
    14. return net(x)

    __init__(初始化)

    • 参数:
      • num_classes,表示分类的类别数目
    • 调用父类的构造函数 `super().__init__()`。
    • self.b1是一个包含了卷积层、批归一化层、ReLU激活函数和最大池化层的序列。它对输入数据进行卷积操作,然后进行批归一化、ReLU激活和最大池化,用于提取输入图像的特征。
      • nn.Conv2d,使用 7x7 的卷积核对输入进行卷积操作,输出通道数为 64,步长为 2,填充为 3。
      • nn.BatchNorm2d 层,用于进行批归一化操作。
      •  ReLU 激活函数层 nn.ReLU()。
      • nn.MaxPool2d`层,使用 3x3 的池化核进行最大池化操作,步长为 2,填充为 1。
    • self.b2self.b3self.b4self.b5分别是几个残差块(resnet_block)的序列。这些残差块包含了卷积层、批归一化层和ReLU激活函数,用于进一步提取输入数据的特征。
      • self.b2使用构建了 2 个残差块,输入通道数为 64,输出通道数也为 64,并且指定 `first_block=True`,表示它是第一个残差块;
      • ……
    • self.head是一个包含自适应平均池化层(AdaptiveAvgPool2d)、展平层(Flatten)和全连接层(Linear)的序列。它将输入数据进行自适应平均池化,然后展平为一维向量,并通过全连接层将特征映射到分类的类别数目上:
      • 自适应平均池化层nn.AdaptiveAvgPool2d:将输入的特征图池化为大小为 1x1 的特征图。
      • 展平层nn.Flatten,将池化后的特征图展平成一维向量。
      • 全连接层nn.Linear,将展平后的特征映射到输出类别的数量。

    forward(前向传播)

            输入数据通过上述序列模块self.b1self.b2self.b3self.b4self.b5self.head进行处理,最终输出分类结果

    4. 代码整合

    1. # 导入必要的工具包
    2. from torch import nn
    3. import torch.nn.functional as F
    4. # 残差连接, 输入和输出的维度有时是相同的, 有时是不同的, 所以需要 use_1x1conv来判断是否需要
    5. class Residual(nn.Module):
    6. def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
    7. super().__init__()
    8. self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides)
    9. self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)
    10. if use_1x1conv:
    11. self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
    12. else:
    13. self.conv3 = None
    14. # 批量归一化层,将会在第7章讲到
    15. self.bn1 = nn.BatchNorm2d(num_channels)
    16. self.bn2 = nn.BatchNorm2d(num_channels)
    17. def forward(self, X):
    18. Y = F.relu(self.bn1(self.conv1(X)))
    19. Y = self.bn2(self.conv2(Y))
    20. if self.conv3:
    21. X = self.conv3(X)
    22. Y += X
    23. return F.relu(Y)
    24. # 残差网络是由几个不同的残差块组成的
    25. def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
    26. blk = []
    27. for i in range(num_residuals):
    28. if i == 0 and not first_block:
    29. blk.append(Residual(input_channels, num_channels,
    30. use_1x1conv=True, strides=2))
    31. else:
    32. blk.append(Residual(num_channels, num_channels))
    33. return blk
    34. class ResNet(nn.Module):
    35. def __init__(self, num_classes):
    36. super().__init__()
    37. self.b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
    38. nn.BatchNorm2d(64), nn.ReLU(),
    39. nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
    40. self.b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
    41. self.b3 = nn.Sequential(*resnet_block(64, 128, 2))
    42. self.b4 = nn.Sequential(*resnet_block(128, 256, 2))
    43. self.b5 = nn.Sequential(*resnet_block(256, 512, 2))
    44. self.head = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(512, num_classes))
    45. def forward(self, x):
    46. net = nn.Sequential(self.b1, self.b2, self.b3, self.b4, self.b5, self.head)
    47. return net(x)

  • 相关阅读:
    Python-算法编程100例-滑动窗口(入门级)
    用cpolar发布Ubuntu上的网页(2)
    封装JDBCUtil工具
    Seata TCC、Saga、XA模式初识
    1万个基因批量检验
    在局域网里怎么在windows 10里连接到龙梦福珑2.0的Fedora 28图形界面?
    【CVAdd】Filter 滤波器
    网络通信安全
    【数据结构】树与二叉树(九):二叉树的后序遍历(非递归算法NPO)
    【AI视野·今日Robot 机器人论文速览 第六十一期】Tue, 24 Oct 2023
  • 原文地址:https://blog.csdn.net/m0_63834988/article/details/133705834