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 主干部分(layer1 至 layer4)
每一层由若干个 BasicBlock 组成:
BasicBlock是 ResNet 的核心模块,每个块有两个卷积层和两个批归一化层,最后加上残差连接(跳跃连接)。- 残差连接:
- 如果输入和输出的通道数或尺寸相同,则直接相加。
- 如果通道数或尺寸不同,则通过
downsample(包含 1×11 \times 11×1 卷积层)对输入进行变换。
| 模块 | 输入通道 | 输出通道 | 步长 | Downsample 是否存在 | 功能 |
|---|---|---|---|---|---|
layer1 | 64 | 64 | 1 | 否 | 保持特征图大小不变 |
layer2 | 64 | 128 | 2 | 是 | 将特征图大小减半,通道数加倍 |
layer3 | 128 | 256 | 2 | 是 | 将特征图大小减半,通道数加倍 |
layer4 | 256 | 512 | 2 | 是 | 将特征图大小减半,通道数加倍 |
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,每个块有两个卷积层和批归一化层。 -
64×64×3×3=3686464 \times 64 \times 3 \times 3 = 3686464×64×3×3=36864myres.layer1[0].conv1:
参数总数 36864,对应一个 3×33 \times 3 3×3 的卷积层。计算公式为: -
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。
-
第一个
Residual_block- 两个 3×33 \times 33×3 卷积(stride=1, padding=1): 特征图尺寸不变:(1,64,56,56)(1, 64, 56, 56)(1,64,56,56)
-
第二个
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。
-
第一个
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)
-
第二个
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。
-
第一个
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)
-
第二个
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。
-
第一个
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)
-
第二个
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)
6371

被折叠的 条评论
为什么被折叠?



