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.conv1nn.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 下回再写

posted @ 2024-03-21 10:35  MoonSheep|  阅读(135)  评论(0)    收藏  举报