首页人工智能Pytorch【深度学习(PyTorch...

【深度学习(PyTorch篇)】42.搭建ResNet34

本系列文章配套代码获取有以下两种途径:

  • 通过百度网盘获取:
链接:https://pan.baidu.com/s/1XuxKa9_G00NznvSK0cr5qw?pwd=mnsj 提取码:mnsj
  • 前往GitHub获取
https://github.com/returu/PyTorch





ResNet,即残差网络(Residual Network),是一种深度神经网络结构,它在计算机视觉任务中,特别是图像分类、目标检测和语义分割等领域有着广泛的应用。
ResNet的核心思想是通过引入残差结构,让网络学习残差函数(即输入与输出的差),而不是直接学习输出。这种思想是通过“跳跃连接”(skip connection)或“shortcut connection”实现的,即将输入直接添加到网络的深层,使得梯度在反向传播时可以更好地流动,从而避免了深层网络中的梯度消失问题。


01

ResNet34


ResNet34ResNet系列模型中的一种。34表示该网络包含34个卷积层。这些卷积层被组织成多个残差块(Residual Block),每个残差块都包含若干个卷积层以及一个跳跃连接(shortcut connection)。
以下是 ResNet34 的详细网络结构:
  • 输入层:
输入图像尺寸:224x224x3
  • 卷积层:

7×7 卷积核,步长为2,输出通道数为64,后接最大池化层,池化核大小为3×3,步长为2。
  • 残差块层:

ResNet34 的残差块层由4个Layer组成,每个Layer包含多个残差块。每个Layer的输出通道数不同,且从Layer 2开始,每个Layer的第一个残差块会将特征图的尺寸减半(通过步长为2的卷积操作)。

  • Layer 1包含3个残差块,每个残差块的输出通道数为64。

  • Layer 2包含4个残差块,每个残差块的输出通道数为128。

  • Layer 3包含6个残差块,每个残差块的输出通道数为256。

  • Layer 4包含3个残差块,每个残差块的输出通道数为512。
  • 平均池化层:
全局平均池化层,将特征图的尺寸压缩至1×1。
  • 全连接层:

输出节点数通常为1000,对应ImageNet数据集的类别数。
  • 输出层:

输出最终的分类结果。

每个残差块包含以下结构:

  • 两个3×3的卷积层,每层后跟一个批量归一化(Batch Normalization)层和ReLU激活函数。
  • 残差块的输入会通过一个跨层直连(如果需要的话,通过一个1×1的卷积层进行通道数的调整)与第二个卷积层的输出相加,形成残差连接。

ResNet34中,当需要改变通道数或步长时,会使用1×1卷积作为短接路径的一部分,以匹配维度,使得残差块的输入和输出可以直接相加。


02

实现代码


PyTorch中,可以使用torchvision.models模块中的预定义模型来快速搭建ResNet34

models.resnet34()

但如果想从头开始手动搭建ResNet34以更深入地理解其结构,以下是一个简化的示例代码,展示了如何定义基本的ResNet34网络结构:

  • 定义残差块(ResidualBlock):

ResidualBlockResNet中的基础残差块,每个块包含两个卷积层,每个卷积层后都接有批量归一化(BatchNorm)层。如果输入和输出的维度或步长不一致,则通过一个快捷路径(shortcut)来调整维度,以确保能够进行残差连接。
# 定义残差块
# 实现子module:Residual Block
class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, 3 ,stride, 1 ,bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(out_channels, out_channels,31 ,1 ,bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)

        # 如果输入和输出的维度或步长不一致,则通过一个快捷路径(shortcut)来调整维度,以确保能够进行残差连接
        self.downsample = downsample

    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        residual = x if self.downsample is None else self.downsample(x)
        out += residual
        out = self.relu(out)

        return out


  • 定义ResNet34网络:

定义一个基本的ResNet34网络,包括初始的卷积层、批量归一化层、ReLU激活函数、最大池化层,以及由ResidualBlock组成的四个残差层。每个ResidualBlock都包含两个卷积层,并使用跨层直连(shortcut)来解决梯度消失问题。网络的最后是一个全局平均池化层和一个全连接层,用于分类任务。

ResNet34类的forward方法中定义了数据在网络中的前向传播过程。输入数据首先通过初始的卷积、归一化、激活和池化层,然后依次通过四个残差层。最后,数据通过一个全局平均池化层压缩成一维特征向量,并通过一个全连接层得到最终的分类输出。

ResNet34类的make_layer 方法用于创建由多个残差块组成的层。它首先检查是否需要下采样,并据此创建下采样层。然后,它循环创建指定数量的残差块,并将它们添加到一个序列模型中。

# 实现主module:ResNet
class ResNet(nn.Module):
    def __init__(self, block_model, layers, num_classes=1000):
        super().__init__()

        self.in_channels = 64

        # 前几层图像转换
        # 初始的卷积层、批量归一化层、ReLU激活函数、最大池化层
        self.pre = nn.Sequential(
            nn.Conv2d(3 , 64 , kernel_size=7 , stride=2 , padding=3 , bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
            )

        # 由BasicBlock组成的四个残差层
        # 重复的layers,分别有多个Residual Block
        self.layer1 = self.make_layer(block_model, 64, layers[0])
        self.layer2 = self.make_layer(block_model, 128, layers[1], 2)
        self.layer3 = self.make_layer(block_model, 256, layers[2], 2)
        self.layer4 = self.make_layer(block_model, 512, layers[3], 2)

        # 网络的最后是一个全局平均池化层和一个全连接层,用于分类任务
        self.avg_pool = nn.AvgPool2d(7)
        self.fc = nn.Linear(512, num_classes)

    # _make_layer方法用于搭建由多个BasicBlock组成的残差层。
    # 这个方法首先检查是否需要降采样(即改变特征图的尺寸或通道数),如果需要,则创建一个降采样层(downsample)。
    # 然后,它循环创建指定数量的BasicBlock,并将它们添加到一个序列模型中。
    def make_layer(self, block_model, out_channels, block_num, stride=1):
        # 判断是否需要下采样
        # 如果 stride 不为1或者输入通道数 self.in_channels 与输出通道数 out_channels 不一致,则需要下采样
        # 下采样通过一个1x1的卷积层和一个批量归一化层实现,目的是将输入数据的通道数和尺寸调整到与主路径的输出相匹配
        downsample = None
        if (stride != 1) or (self.in_channels != out_channels):
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channels, out_channels, 1,stride=stride, bias=False),
                nn.BatchNorm2d(out_channels))
        # 初始化一个空列表来保存残差块
        layers = []
        # 添加第一个残差块,这里可能需要下采样
        layers.append(block_model(self.in_channels, out_channels, stride, downsample))
        # 更新输入通道数,因为第一个残差块可能已经改变了它
        self.in_channels = out_channels
        # 循环添加剩余的残差块,这些残差块不需要下采样
        for i in range(1, block_num):
            layers.append(block_model(out_channels, out_channels))

        # 将列表中的残差块转换为一个顺序模型(Sequential)并返回
        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.pre(x)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.avg_pool(out)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out

  • 实例化模型:
最后,通过以下代码实例化了一个ResNet模型:
# 实例化模型
model = ResNet(ResidualBlock, [3463])
其中,ResidualBlock 是用于构建ResNet的残差块类型,[3, 4, 6, 3] 是一个列表,表示每个残差层的残差块数量。具体来说,这个模型有四个残差层,分别包含3、4、6和3个残差块。


更多内容可以前往官网查看

https://pytorch.org/


本篇文章来源于微信公众号: 码农设计师

RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments