MNIST 代码解析(5)
2024-03-20 15:08:08
先对构建网络这一块的代码进行解析
源代码为:
# 5 构建网络模型
class CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 14, 5, 1, 2) # 卷积函数 1:灰度图片的通道 14:输出通达 5:kernel 1:stride 2:padding
self.conv2 = nn.Conv2d(14, 28, 5, 1, 2) # 14:输入通道 20 输出通道 3:kernel
self.fc1 = nn.Linear(28 * 7 * 7, 200) # 全连接层,28*7*7:输入通道 200输出通道
self.fc2 = nn.Linear(200, 10) # 200:输入通道,10:输出通道
def forward(self, x):
input_size = x.size(0) # batch_size
x = self.conv1(x) # 卷积操作 输入batch_size*1*28*28,输出:batch_size*14*28*28
x = F.relu(x) # 输出:batch_size*14*28*28·
x = F.max_pool2d(x, 2, 2) # 输入:batch*14*28*28,输出:batch*14*14*14,
x = self.conv2(x) # 输入:batch*14*14*14,输出:batch*28*14*14
x = F.max_pool2d(x, 2, 2)
x = x.view(input_size, -1) #28*14*14=1372
x = self.fc1(x) # 输入batch*1372 输出batch*200
x = F.relu(x) # 保持shape不变
x = self.fc2(x) # 输入:batch*200 输出:batch*10
output = F.log_softmax(x, dim=1) #
return output
1. 构造函数__init__(self)
1.1. class CNN(nn.Module),类定义
class CNN(nn.Module)是定义了一个名称为CNN的类,这个类继承自nn.Module。
在pytorch中,nn.Module是许多神经网络模块的基类,我们的模型应当继承自这个基类。
为什么要继承呢?因为继承可以让我们获得许多nn.Module所具备的功能,比如保存和加载模型、将模型移动到不同的设备商
1.2. 构造函数
def __init__(self)这一行命令,定义了类的构造函数。当我们创建了一个CNN类的实例时,这个函数会被自动调用。构造函数通常用于初始化类的内容部状态和成员变量。
super().__init__()这一行命令调用了父类nn.Module的构造函数。在继承自其他类的类中,这是初始化父类成员的标准做法。这确保了nn.Module类的初始化逻辑被正确执行,这是构建PyTorch模型的必要步骤。
1.3. 卷积层定义
self.conv1 = nn.Conv2(1, 14, 5, 1, 2)这一行创建了一个二维卷积层Conv2d并将其赋值给成员变量self.conv1。nn.Conv2是PyTorch中用于创建二维卷积层的类。这个卷积层的参数配置如下:
| 数值 | 名称 | 作用 |
|---|---|---|
| 1 | 输入通道数 | 表示只有一个颜色通道,也就是说输入图像是灰度图 |
| 14 | 输出通道数 | 表示这个卷积层会生成14个不同的卷积特征图 |
| 5 | 卷积核(kernel)的大小 | 表示使用5x5的卷积核进行卷积操作 |
| 1 | 步长(stride) | 表示卷积核在图像上移动时每次移动一个像素 |
| 2 | 填充(padding) | 表示在输入图像周围填充2个像素的0,以保持卷积操作后的特征图大小不变 |
self.conv2 = nn.Conv2d(14,28,5,1,2)定义了第二个卷积层,这个层接受第一个卷积层的14个特征图作为输入,并用28个5x5的卷积核,步长为1,边缘填充为2。输出将是28个特征图,空间尺寸保持不变。(conv2的输入通道数和conv1的输出通道数相匹配)
- 什么是卷积特征图?
卷积特征图是通过将卷积核应用于输入图像得到的输出图像。每个卷积核能够捕捉输入数据的某种特定特征,比如边缘、角点或者更加复杂的纹理
- 什么是卷积核?
卷积核,也称为滤波器,是一个小的矩阵,用于在卷积层中提取特征。它通过与输入图像的局部区域进行元素乘法,然后将结果求和(可能还包括一个偏置项),来生成输出特征图的一个像素值。卷积核的大小、形状和数值决定了它能够捕捉到的图像特征类型。
而其中,将5x5卷积核与局部图像进行对应元素相乘然后相加,这样的操作就叫做卷积。
- 卷积核在图像上移动
卷积核在图像上的移动指的是卷积核从输入图像的左上角开始,按照指定的步长(stride)水平和垂直滑动,与图像的局部区域进行逐元素的乘法操作,并将乘积求和(加上偏置之后,)作为输出特征图的一个像素值。步长决定了卷积核移动的速度和输出特征图的大小,步长越大,输出特征图越小
- 为什么要保持输出特征图大小不变化
有助于保留输入图像的空间分辨率;设计网络时更容易控制模型的复杂度和计算量;某些网络结构,例如U-Net用于图像分割,依赖于特定层次的特征图大小匹配,以便在网络的不同部分之间进行特征融合。
对于这些结构而言,保持特征图大小不变或在特定层次上保持一致,对它们的成功实现至关重要。
2024-03-20 17:19:32
1.4. 全连接层
self.fc1 = nn.Linear(28*7*7,200)定义了第一个全连接层。在这之前,我们可能需要一个池化操作或者调整特征图的尺寸,因为这里假设的输入维度是28x7x7,这意味着在两个卷积层之后,特征图的尺寸被减少到了7x7(这个细节在代码中没有明确给出,可能是通过池化层或者步长大于1的卷积实现的)。这个层将这些特征图展平成一个1960维的向量,然后连接到200个神经元上面。
self.fc2 = nn.Linear(200, 10)定义了第二个全连接层,它将第一个全连接层的200个输出连接到10个输出神经元上。这在分类任务中很常见,比如手写数字识别(MNIST),其中10代表了10个类别(从0到9)。
- 为什么说全连接层将这些特征图展平成了一个1960维的向量?
因为有28张图片,而每张图片是7x7的
7x7是什么含义,也就是说每张图有49个像素点。
- 全连接层的作用和重要性?
全连接层的作用是对前面层(可能是卷积层或池化层)提取的特征进行进一步的处理和组合,
以帮助模型能够学习从输入数据到期望输出之间的复杂映射关系(例如,分类任务中的类别)。
在深度学习模型中,全连接层一般位于后几层,以汇总前面卷积层提取的特征,并进行最终的决策或预测。
2. 前向传播函数,forward(self,x)
我们先来看一下前向传播函数大体长什么样子:
def forward(self, x):
input_size = x.size(0) # batch_size
x = self.conv1(x) # 卷积操作 输入batch_size*1*28*28,输出:batch_size*14*28*28
x = F.relu(x) # 输出:batch_size*14*28*28·
x = F.max_pool2d(x, 2, 2) # 输入:batch*14*28*28,输出:batch*14*14*14,
x = self.conv2(x) # 输入:batch*14*14*14,输出:batch*28*14*14
x = F.max_pool2d(x, 2, 2)
x = x.view(input_size, -1) #28*14*14=1372
x = self.fc1(x) # 输入batch*1372 输出batch*200
x = F.relu(x) # 保持shape不变
x = self.fc2(x) # 输入:batch*200 输出:batch*10
output = F.log_softmax(x, dim=1) #
return output
2.1. 获取批次大小:
input_size = x.size(0):这一步是获取当前处理的数据批次的大小,即批次中包含的样本数量。x.size(0)返回的是张量x的第一个维度的大小,对于图像数据,这通常表示批次中的图像数量。这个信息在后续将数据展平输入全连接层的时候非常重要。 至于为什么非常重要呢?我们稍后再回答,先往后看下一行代码。
2.2. 第一个卷积层:
x = self.conv1(x):应用第一个卷积层。这一步将输入图像通过卷积滤波器处理,提取初步的特征。由于设置了padding,输出的特征图大小与输入相同,但深度(通道数)变为了14。
我们再复习一下代码self.conv1 = nn.Conv2d(1, 14, 5, 1, 2),其中的1是输入通道,14是输出通道,5是卷积核,1是步长,2是填充。
2.3. ReLU激活函数
x = F.relu(x):应用ReLU(rectified Linear Unit)激活函数。ReLU函数将所有负值置为0,保留正值,这有助于增加模型的非线性,使得网络能够学习更加复杂的特征。 为什么ReLU函数可以帮助增加模型的非线性?是怎么做到的?稍后解答。
2.4. 最大池化
x = F.max_pool2d(x, 2, 2):应用2x2的最大池化操作,步长为2.这一步减少特征图的尺寸,增强了模型的抗噪声能力,并减少计算量。特征图的高度和宽度都被减半。 为什么最大池化操作可以减少特征图的尺寸?稍后回答。
2.5.第二个卷积层
x = self.conv2(x):经过第一轮卷积和池化后,数据再次通过一个卷积层。这一层进一步进行特征提取,输出通道数增加到28,以捕获更多的特征信息。
2.6. 再次最大池化
x = F.max_pool2d(x, 2, 2):再次应用最大池化,以减少图像尺寸,提高特征的抽象程度。 为什么要再一次提高特征的抽象程度?稍后回答。
2.7. 展平特征图
x = x.view(input_size, -1):在将特征图传递给全连接层之前,需要将多维特征图展平成一维向量。这里input_size是批次大小,-1表示自动计算该维度的大小,确保数据正常展平。
2.8. 第一个全连接层
x = self.fc1(x):展平后的数据进入的一个全连接层,这一步开始进行更高级的特征组合和抽象。 更高级的特征组合和抽象是什么?怎么进行的
2.9. ReLU激活函数
x = F.relu(x):再次应用ReLU激活函数,增加非线性
2.10. 第二个全连接层
x = self.fc2(x):数据通过第二个全连接层,进一步减少输出特征的维度,准备进行分类
2.11. Long-Softmax
output = F.log_softmax(x, dim=1):最后,应用log-softmax函数计算每个类别的概率对数。这一步常用于多分类问题,使得网络输出可以直接用于计算交叉熵损失。 什么是交叉熵损失?为什么要计算交叉熵损失?为什么log-softmax函数可以计算交叉熵损失?
接下来,我们统一来回答刚才遗留的一些问题。
ReLU
2024-03-21 10:34:26 下回再写

浙公网安备 33010602011771号