分类实战——以AlexNet为例的代码详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from torchvision import models
import torch
import torch.nn as nn

# 定义一个自定义的神经网络模型类,继承自 nn.Module
class myModel(nn.Module):
def __init__(self, num_cls):
super(myModel, self).__init__() # 调用父类的构造函数

# 定义第一个卷积层
# 输入通道数为3(RGB图像),输出通道数为64,卷积核大小为11x11,步长为4,padding为2
self.conv1 = nn.Conv2d(3, 64, 11, 4, 2)
# 定义第一个最大池化层,池化核大小为3x3,步长为2
self.pool1 = nn.MaxPool2d(3, 2)

# 定义第二个卷积层
# 输入通道数为64,输出通道数为192,卷积核大小为5x5,步长为1,padding为2
self.conv2 = nn.Conv2d(64, 192, 5, 1, 2)
# 定义第二个最大池化层,池化核大小为3x3,步长为2
self.pool2 = nn.MaxPool2d(3, 2)

# 定义第三个卷积层
# 输入通道数为192,输出通道数为384,卷积核大小为5x5,步长为1,padding为2
self.conv3 = nn.Conv2d(192, 384, 5, 1, 2)
# 定义第四个卷积层
# 输入通道数为384,输出通道数为256,卷积核大小为5x5,步长为1,padding为2
self.conv4 = nn.Conv2d(384, 256, 5, 1, 2)
# 定义第五个卷积层
# 输入通道数为256,输出通道数为256,卷积核大小为5x5,步长为1,padding为2
self.conv5 = nn.Conv2d(256, 256, 5, 1, 2)

# 定义第三个最大池化层,池化核大小为3x3,步长为2
self.pool3 = nn.MaxPool2d(3, 2)
# 定义自适应平均池化层,输出大小为6x6
self.pool4 = nn.AdaptiveAvgPool2d(6)

# 定义第一个全连接层,输入特征数为9216,输出特征数为4096
self.fc1 = nn.Linear(9216, 4096)
# 定义第二个全连接层,输入特征数为4096,输出特征数为4096
self.fc2 = nn.Linear(4096, 4096)
# 定义第三个全连接层,输入特征数为4096,输出特征数为num_cls(分类类别数)
self.fc3 = nn.Linear(4096, num_cls)

# 定义前向传播函数
def forward(self, x):
# 第一层卷积和池化
x = self.conv1(x)
x = self.pool1(x)

# 第二层卷积和池化
x = self.conv2(x)
x = self.pool2(x)

# 第三、四、五层卷积
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)

# 第三层池化和自适应平均池化
x = self.pool3(x)
x = self.pool4(x) # 输出形状为 batch*256*6*6

# 将多维张量展平为一维,以便输入全连接层
x = x.view(x.size()[0], -1) # 形状变为 batch*9216

# 全连接层
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)

return x # 返回最终的输出

# 创建一个模型实例,num_cls为1000(假设有1000个分类类别)
model = myModel(1000)

# 创建一个形状为 (4, 3, 224, 224) 的输入张量,表示4张3通道的224x224图像
data = torch.ones((4, 3, 224, 224))

# 将输入数据传入模型,得到预测结果
pred = model(data)

# 定义一个函数,用于计算模型的参数数量
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(model))

nn.Conv2d()的参数分别为

输入特征图数量
输出特征图数量
卷积核大小
步长
padding

各层次的代码

卷积第一层

1
2
3
4
5
# 定义第一个卷积层
# 输入通道数为3(RGB图像),输出通道数为64,卷积核大小为11x11,步长为4,padding为2
self.conv1 = nn.Conv2d(3, 64, 11, 4, 2)
# 定义第一个最大池化层,池化核大小为3x3,步长为2
self.pool1 = nn.MaxPool2d(3, 2)

我们输入的原始图片尺寸为3通道,我们用Conv2d转化为64通道输出,在Conc2d中使用长度为11的卷积核,步长为4,边框长度2

这时获取的特征图尺寸为 64 [(224 - 11 + 22)/4 + 1]^2, 即64 55 55

再通过池化,nn.MaxPool2d(3, 2)表示窗口大小为3*3,滑动步长为2

输出特征图尺寸为:[64 (55 - 3 +0)/2 + 1]^2 即 64 27*27

卷积第二层

1
2
3
4
5
# 定义第二个卷积层
# 输入通道数为64,输出通道数为192,卷积核大小为5x5,步长为1,padding为2
self.conv2 = nn.Conv2d(64, 192, 5, 1, 2)
# 定义第二个最大池化层,池化核大小为3x3,步长为2
self.pool2 = nn.MaxPool2d(3, 2)

将特征图尺寸化为192 [(27-5+22)/1 +1] 2,即192 27 *27

同理,再进行池化,化为19213 13

卷积第三四五层

1
2
3
4
5
6
7
8
9
# 定义第三个卷积层
# 输入通道数为192,输出通道数为384,卷积核大小为5x5,步长为1,padding为2
self.conv3 = nn.Conv2d(192, 384, 5, 1, 2)
# 定义第四个卷积层
# 输入通道数为384,输出通道数为256,卷积核大小为5x5,步长为1,padding为2
self.conv4 = nn.Conv2d(384, 256, 5, 1, 2)
# 定义第五个卷积层
# 输入通道数为256,输出通道数为256,卷积核大小为5x5,步长为1,padding为2
self.conv5 = nn.Conv2d(256, 256, 5, 1, 2)

通过三次连续的卷积变换将特征图尺寸变为256 13 13

再池化两次

1
2
3
4
# 定义第三个最大池化层,池化核大小为3x3,步长为2
self.pool3 = nn.MaxPool2d(3, 2)
# 定义自适应平均池化层,输出大小为6x6
self.pool4 = nn.AdaptiveAvgPool2d(6)

再通过两次池化,进一步缩小特征图,将尺寸缩小为256 6 6

将特征图降维成一维的

1
2
# 将多维张量展平为一维,以便输入全连接层
x = x.view(x.size()[0], -1) # 形状变为 batch*9216

再通过全连接层建立模型

1
2
3
4
5
6
# 定义第一个全连接层,输入特征数为9216,输出特征数为4096
self.fc1 = nn.Linear(9216, 4096)
# 定义第二个全连接层,输入特征数为4096,输出特征数为4096
self.fc2 = nn.Linear(4096, 4096)
# 定义第三个全连接层,输入特征数为4096,输出特征数为num_cls(分类类别数)
self.fc3 = nn.Linear(4096, num_cls)

这里和回归是一致的,不做过多叙述


各个层的作用

卷积层 (self.conv1, self.conv2, self.conv3, self.conv4, self.conv5)

卷积操作可以捕捉图像的局部信息,通过共享权重减少参数量,还可以提取更高级、复杂、抽象的特征

池化层 (self.pool1, self.pool2, self.pool3)

池化层可以通过对特征图进行下采样,降低计算量防止过拟合

自适应平均池化层 (self.pool4)

它将特征图尺寸调整为固定的输出尺寸,方便后面的全连接层进行处理

这里使用平均池化,可以让特征图更平滑

展平操作 (x.view(x.size()[0], -1))

将多维特征图降维成一维向量,便于后续分类

全连接层 (self.fc1, self.fc2, self.fc3)

将提取的特征通过全连接层映射到分类任务中


整体过程

  • 特征提取:
    • 卷积层和池化层逐步提取输入图像的低级到高级特征。
  • 降维:
    • 池化层和自适应池化层减少特征图的空间尺寸,降低计算复杂度。
  • 特征组合:
    • 全连接层将提取的特征组合起来,生成最终的分类结果。
  • 分类:
    • 最后一层全连接层输出每个类别的得分,用于分类任务。