ResNet模型

1. 模型结构分析

1.1 顶层模块
  • conv1:
    卷积层,接受输入通道 3(RGB 图像的三个通道),输出 64 个特征通道,卷积核大小为 7×77 \times 77×7,步长为 2,填充为 3(确保输出特征图尺寸为输入的一半)。

  • bn1:
    批归一化层,对 conv1 的输出进行归一化。

  • relu:
    激活函数层,添加非线性。

  • maxpool:
    最大池化层,使用 3×33 \times 33×3 的池化核,步长为 2,填充为 1。再次将特征图尺寸减半。


1.2 主干部分(layer1layer4

每一层由若干个 BasicBlock 组成:

  • BasicBlock 是 ResNet 的核心模块,每个块有两个卷积层和两个批归一化层,最后加上残差连接(跳跃连接)。
  • 残差连接
    • 如果输入和输出的通道数或尺寸相同,则直接相加。
    • 如果通道数或尺寸不同,则通过 downsample(包含 1×11 \times 11×1 卷积层)对输入进行变换。
模块输入通道输出通道步长Downsample 是否存在功能
layer164641保持特征图大小不变
layer2641282将特征图大小减半,通道数加倍
layer31282562将特征图大小减半,通道数加倍
layer42565122将特征图大小减半,通道数加倍

1.3 末尾模块
  • avgpool:
    全局平均池化层,将特征图的空间维度 H×WH \times WH×W 压缩为 1×11 \times 11×1。

  • fc:
    全连接层,将最终的特征图展平后映射到 1000 类别的输出。


2. 参数统计

2.1 总参数

{'Total': 148224, 'Trainable': 148224}

  • Total: 表示该部分模块中所有的参数数量。
  • Trainable: 表示参与训练的参数数量。由于 ResNet 中的所有参数都是可训练的,因此两者相同。
2.2 单个模块的参数
  • myres.layer1:
    参数总数 36928,表明 layer1 包含两个 BasicBlock,每个块有两个卷积层和批归一化层。

  • myres.layer1[0].conv1:
    参数总数 36864,对应一个 3×33 \times 3 3×3 的卷积层。计算公式为:

    64×64×3×3=3686464 \times 64 \times 3 \times 3 = 3686464×64×3×3=36864
  • resNet.layer1[0].conv1:
    myres.layer1[0].conv1 相同,参数总数也为 36864。


3. 输出尺寸

输入尺寸:

x=(1,3,224,224)x = (1, 3, 224, 224)x=(1,3,224,224)
输入的批量大小为 1,通道数为 3(RGB),图像尺寸为 224×224224 \times 224224×224。

特征图尺寸变化过程:
模块输出尺寸
conv1(1,64,112,112)(1, 64, 112, 112)(1,64,112,112)
maxpool(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)
layer1(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)
layer2(1,128,28,28)(1, 128, 28, 28)(1,128,28,28)
layer3(1,256,14,14)(1, 256, 14, 14)(1,256,14,14)
layer4(1,512,7,7)(1, 512, 7, 7)(1,512,7,7)
avgpool(1,512,1,1)(1, 512, 1, 1)(1,512,1,1)
fc(1,1000)(1, 1000)(1,1000)

4. 输出说明

out = resNet(x)out = myres(x) 的输出均为 (1,1000)(1, 1000)(1,1000),表示 1000 个类别的预测概率。

当输入张量为 x = torch.rand((1, 3, 224, 224))(即批量大小为 1,通道数为 3,图像大小为 224×224224 \times 224224×224)时,MyResNet18 的前向传播涉及的特征图尺寸和计算步骤如下:


1. conv1 (7x7 卷积, stride=2, padding=3)

输入:(1,3,224,224)(1, 3, 224, 224)(1,3,224,224)
输出通道:64
卷积核:7×77 \times 77×7
步幅:222
填充:333

输出特征图尺寸:

Hout=Hin+2×padding−kernel_sizestride+1H_{\text{out}} = \frac{H_{\text{in}} + 2 \times \text{padding} - \text{kernel\_size}}{\text{stride}} + 1Hout​=strideHin​+2×padding−kernel_size​+1 Wout=Win+2×padding−kernel_sizestride+1W_{\text{out}} = \frac{W_{\text{in}} + 2 \times \text{padding} - \text{kernel\_size}}{\text{stride}} + 1Wout​=strideWin​+2×padding−kernel_size​+1 Hout=Wout=224+2×3−72+1=112H_{\text{out}} = W_{\text{out}} = \frac{224 + 2 \times 3 - 7}{2} + 1 = 112Hout​=Wout​=2224+2×3−7​+1=112

输出:(1,64,112,112)(1, 64, 112, 112)(1,64,112,112)


2. bn1

批归一化不改变特征图尺寸。
输出:(1,64,112,112)(1, 64, 112, 112)(1,64,112,112)


3. pool1 (3x3 最大池化, stride=2, padding=1)

输入:(1,64,112,112)(1, 64, 112, 112)(1,64,112,112)
池化核:3×33 \times 33×3
步幅:222
填充:111

输出特征图尺寸:

Hout=Wout=112+2×1−32+1=56H_{\text{out}} = W_{\text{out}} = \frac{112 + 2 \times 1 - 3}{2} + 1 = 56Hout​=Wout​=2112+2×1−3​+1=56

输出:(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)


4. layer1

layer1 包含两个 Residual_block,输入和输出通道均为 64,stride=1。

  1. 第一个 Residual_block

    • 两个 3×33 \times 33×3 卷积(stride=1, padding=1): 特征图尺寸不变:(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)
  2. 第二个 Residual_block

    • 特征图尺寸仍然不变:(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)

输出:(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)


5. layer2

layer2 包含两个 Residual_block,通道从 64 升至 128,第一个 Block 的 stride=2。

  1. 第一个 Residual_block

    • 3×33 \times 33×3 卷积(stride=2, padding=1): Hout=Wout=56+2×1−32+1=28H_{\text{out}} = W_{\text{out}} = \frac{56 + 2 \times 1 - 3}{2} + 1 = 28Hout​=Wout​=256+2×1−3​+1=28 输出:(1,128,28,28)(1, 128, 28, 28)(1,128,28,28)
  2. 第二个 Residual_block

    • stride=1,不改变尺寸: 输出:(1,128,28,28)(1, 128, 28, 28)(1,128,28,28)

输出:(1,128,28,28)(1, 128, 28, 28)(1,128,28,28)


6. layer3

layer3 包含两个 Residual_block,通道从 128 升至 256,第一个 Block 的 stride=2。

  1. 第一个 Residual_block

    • 3×33 \times 33×3 卷积(stride=2, padding=1): Hout=Wout=28+2×1−32+1=14H_{\text{out}} = W_{\text{out}} = \frac{28 + 2 \times 1 - 3}{2} + 1 = 14Hout​=Wout​=228+2×1−3​+1=14 输出:(1,256,14,14)(1, 256, 14, 14)(1,256,14,14)
  2. 第二个 Residual_block

    • stride=1,不改变尺寸: 输出:(1,256,14,14)(1, 256, 14, 14)(1,256,14,14)

输出:(1,256,14,14)(1, 256, 14, 14)(1,256,14,14)


7. layer4

layer4 包含两个 Residual_block,通道从 256 升至 512,第一个 Block 的 stride=2。

  1. 第一个 Residual_block

    • 3×33 \times 33×3 卷积(stride=2, padding=1): Hout=Wout=14+2×1−32+1=7H_{\text{out}} = W_{\text{out}} = \frac{14 + 2 \times 1 - 3}{2} + 1 = 7Hout​=Wout​=214+2×1−3​+1=7 输出:(1,512,7,7)(1, 512, 7, 7)(1,512,7,7)
  2. 第二个 Residual_block

    • stride=1,不改变尺寸: 输出:(1,512,7,7)(1, 512, 7, 7)(1,512,7,7)

输出:(1,512,7,7)(1, 512, 7, 7)(1,512,7,7)


8. adv_pool (全局平均池化)

自适应池化到 (1,1)(1, 1)(1,1),不需要计算公式。

输出:(1,512,1,1)(1, 512, 1, 1)(1,512,1,1)


9. flatten

将张量展平:

输入形状:(1,512,1,1)→输出形状:(1,512)\text{输入形状:}(1, 512, 1, 1) \quad \to \quad \text{输出形状:}(1, 512)输入形状:(1,512,1,1)→输出形状:(1,512)


10. fc (全连接层)

输入:(1,512)(1, 512)(1,512)
输出:(1,1000)(1, 1000)(1,1000)


总结

前向传播中各层的特征图尺寸变化如下:

层名称输出形状
conv1 + bn1(1,64,112,112)(1, 64, 112, 112)(1,64,112,112)
pool1(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)
layer1(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)
layer2(1,128,28,28)(1, 128, 28, 28)(1,128,28,28)
layer3(1,256,14,14)(1, 256, 14, 14)(1,256,14,14)
layer4(1,512,7,7)(1, 512, 7, 7)(1,512,7,7)
adv_pool(1,512,1,1)(1, 512, 1, 1)(1,512,1,1)
flatten(1,512)(1, 512)(1,512)
fc(1,1000)(1, 1000)(1,1000)

import torch
import torch.nn as nn
import torchvision.models as models


resNet = models.resnet18()

print(resNet)


class Residual_block(nn.Module):  #@save
    def __init__(self, input_channels, out_channels, down_sample=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, out_channels,
                               kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(out_channels, out_channels,
                               kernel_size=3, padding=1, stride= 1)
        if input_channels != out_channels:
            self.conv3 = nn.Conv2d(input_channels, out_channels,
                                   kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU()
    def forward(self, X):
        out = self.relu(self.bn1(self.conv1(X)))
        out= self.bn2(self.conv2(out))

        if self.conv3:
            X = self.conv3(X)
        out += X
        return self.relu(out)

class MyResNet18(nn.Module):
    def __init__(self):
        super(MyResNet18, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, 7, 2, 3)
        self.bn1 = nn.BatchNorm2d(64)
        self.pool1 = nn.MaxPool2d(3, stride=2, padding=1)
        self.relu = nn.ReLU()

        self.layer1 = nn.Sequential(
            Residual_block(64, 64),
            Residual_block(64, 64)
        )
        self.layer2 = nn.Sequential(
            Residual_block(64, 128, strides=2),
            Residual_block(128, 128)
        )
        self.layer3 = nn.Sequential(
            Residual_block(128, 256, strides=2),
            Residual_block(256, 256)
        )
        self.layer4 = nn.Sequential(
            Residual_block(256, 512, strides=2),
            Residual_block(512, 512)
        )
        self.flatten = nn.Flatten()
        self.adv_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Linear(512, 1000)
    def forward(self, x):

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.pool1(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.adv_pool(x)
        x = self.flatten(x)
        x = self.fc(x)

        return x

myres = MyResNet18()

def get_parameter_number(model):
    total_num = sum(p.numel() for p in model.parameters())
    trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
    return {'Total': total_num, 'Trainable': trainable_num}


print(get_parameter_number(myres.layer1))
print(get_parameter_number(myres.layer1[0].conv1))
print(get_parameter_number(resNet.layer1[0].conv1))

x = torch.rand((1,3,224,224))
out = resNet(x)

out = myres(x)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值