实验3:卷积神经网络

2024年秋季《软件工程原理与实践》实验报告

姓名:孙卓异 学号:22020011034

一、实验内容

1.1 MNIST 数据集分类

1. 数据集加载和准备
  • 加载 MNIST 数据集:使用 torchvision.datasets.MNIST 将数据集下载并加载到本地。训练集和测试集分别定义为 train_loadertest_loader

  • 数据转换和标准化:在数据加载时使用 transforms.Compose,将图像转换为张量并进行标准化,以提高模型训练效率。

  • 展示样本数据:使用 Matplotlib 可视化部分数据集样本,确保数据加载成功且符合预期。

2. 模型构建
2.1. 全连接网络(FC2Layer)
  • 定义网络结构FC2Layer 类中使用 nn.Sequential 将网络层级(包括线性层和激活函数)顺序连接,实现一个简单的三层全连接网络。
  • 前向传播函数forward 函数使用 .view() 函数将输入数据重构为二维张量,适应全连接层输入的格式。
  • 参数数量计算:定义 get_n_params 函数计算模型参数,作为后续模型性能比较的参考指标。
2.2. 卷积神经网络(CNN)
  • 定义卷积层与全连接层:在 CNN 类中定义两个卷积层、池化层以及线性层。卷积层提取特征,池化层缩小特征图尺寸。
  • 前向传播函数:在 forward 函数中按照卷积层、池化层、全连接层的顺序对输入数据进行传递和变换。
  • 卷积网络的优势:卷积层能够利用局部性和图像的平移不变性特点,从而提升图像特征提取能力。
3. 训练与测试函数
  • 训练函数 train:模型进入训练模式,按批次获取数据并传入模型,通过计算损失和反向传播优化模型参数。每 100 个批次打印损失。

  • 测试函数 test:模型进入评估模式,对测试集进行前向传播计算损失,并记录正确预测的数量以计算准确率。

    # 训练函数
    def train(model):
        model.train()
        # 主里从train_loader里,64个样本一个batch为单位提取样本进行训练
        for batch_idx, (data, target) in enumerate(train_loader):
            # 把数据送到GPU中
            data, target = data.to(device), target.to(device)
    
            optimizer.zero_grad()
            output = model(data)
            loss = F.nll_loss(output, target)
            loss.backward()
            optimizer.step()
            if batch_idx % 100 == 0:
                print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                    batch_idx * len(data), len(train_loader.dataset),
                    100. * batch_idx / len(train_loader), loss.item()))
    
    
    def test(model):
        model.eval()
        test_loss = 0
        correct = 0
        for data, target in test_loader:
            # 把数据送到GPU中
            data, target = data.to(device), target.to(device)
            # 把数据送入模型,得到预测结果
            output = model(data)
            # 计算本次batch的损失,并加到 test_loss 中
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            # get the index of the max log-probability,最后一层输出10个数,
            # 值最大的那个即对应着分类结果,然后把分类结果保存在 pred 里
            pred = output.data.max(1, keepdim=True)[1]
            # 将 pred 与 target 相比,得到正确预测结果的数量,并加到 correct 中
            # 这里需要注意一下 view_as ,意思是把 target 变成维度和 pred 一样的意思                                                
            correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()
    
        test_loss /= len(test_loader.dataset)
        accuracy = 100. * correct / len(test_loader.dataset)
        print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
            test_loss, correct, len(test_loader.dataset),
            accuracy))
    
4. 训练与评估
4.1. 全连接网络训练与评估
  • 定义模型与优化器:实例化 FC2Layer 并设置优化器为 SGD,使用学习率 0.01 和动量 0.5。

  • 模型训练与测试:调用 traintest 函数,打印模型参数数量和测试结果。

  • 观察结果:全连接网络在准确性上略显不足为86%。

4.2. 卷积神经网络训练与评估
  • 定义模型与优化器:实例化 CNN 并设置相同的优化器。

  • 模型训练与测试:调用 traintest 函数,观察卷积神经网络在测试集上的损失和准确率。

  • 观察结果:卷积神经网络在相同参数数量下表现明显优于全连接网络为95%。

    对比解读:通过上面的测试结果,可以发现,含有相同参数的 CNN 效果要明显优于 简单的全连接网络,是因为 CNN 能够更好的挖掘图像中的信息,主要通过两个手段:

    • 卷积:Locality and stationarity in images
    • 池化:Builds in some translation invariance
5. 像素打乱后的训练与测试
  • 像素打乱实验:使用 torch.randperm 随机打乱图像像素顺序,并可视化展示打乱前后的图像。
  • 重新定义训练和测试函数:在 train_permtest_perm 函数中对数据进行像素打乱,测试模型对无序数据的适应能力。
5.1. 全连接网络的打乱效果
  • 训练与测试:全连接网络对打乱的图像仍能取得较好的性能,这说明全连接网络并不依赖像素之间的空间位置。
5.2. 卷积神经网络的打乱效果
  • 训练与测试:卷积神经网络在打乱像素后的性能明显下降,表明卷积操作在空间结构一致的图像上更有效,打乱后的图像失去了局部性信息,导致卷积层无法有效提取特征。
解读

卷积神经网络的优势在于其空间不变性特征,可以很好地提取图像的空间信息,而全连接网络更依赖于整体特征而非空间关系。因此,卷积网络在像素无序的情况下,效果大打折扣。这次实验中也验证了CNN的局部特征提取能力,使其在结构化的图像分类任务上有显著优势,而全连接网络则因其结构的全局性特点,在不同图像结构上的表现差异较小。

1.2 CIFAR10 数据集分类

1. 数据加载与准备
  • 加载 CIFAR-10 数据集:使用 torchvision.datasets.CIFAR10 下载并加载训练和测试集。
  • 数据标准化:通过 transforms.Normalize 方法将图像数据标准化至 [-1, 1] 范围内,方便网络训练。
  • 定义 DataLoader:训练集设定为 shuffle=True 增加数据多样性,测试集设定为 shuffle=False 保持数据一致性。
  • 数据可视化:利用 Matplotlib 和 torchvision.utils.make_grid 展示部分样本,以检查数据加载是否正确。

9002d67c60150e1cddd36db3a9d8403.png&pos_id=img-cCci6rkV-1728441045486)

2. 构建卷积神经网络
2.1. 定义网络结构
  • 网络架构
    • 第一个卷积层 conv1:输入 3 个通道,输出 6 个特征图,卷积核大小为 5x5。
    • 第一个池化层 pool:最大池化操作,核大小为 2x2,步长为 2。
    • 第二个卷积层 conv2:输入 6 个通道,输出 16 个特征图,卷积核大小为 5x5。
    • 三个全连接层 fc1, fc2, fc3:用于将卷积层提取的特征转换成类别预测。
  • 激活函数:ReLU 函数作为非线性激活函数。
  • 前向传播:定义 forward 方法描述数据流向,将输入数据通过卷积、池化、全连接层的顺序依次处理。
2.2. 初始化模型与优化器
  • 模型移动至 GPU:如果可用,将模型移动至 GPU 加快运算速度。
  • 损失函数:交叉熵损失函数 CrossEntropyLoss 用于多分类任务。
  • 优化器:Adam 优化器以学习率 0.001 优化网络参数。
3. 网络训练与测试
3.1. 训练过程
  • 迭代多轮训练:在每一轮训练中,对所有训练数据分批次输入网络。
  • 前向传播:计算预测值,并通过损失函数计算损失值。
  • 反向传播与优化:通过反向传播计算梯度,并更新网络参数。
  • 输出训练状态:每 100 个批次输出一次当前的损失,方便观察收敛情况。
3.2. 测试网络性能
  • 从测试集中获取样本:取出一批测试样本进行可视化展示。
  • 预测标签:通过模型预测样本标签,并与真实标签对比,显示模型的预测结果。
  • 计算准确率:遍历整个测试集,计算模型的总体准确率。
4. 实验结果与解读
  • 训练过程观察:随着训练轮次增加,损失值逐步下降,说明网络逐渐学习到数据特征。
  • 测试样本预测:从展示结果中可以看到,模型在部分类别上预测较准确,但对部分样本预测错误。
  • 准确率分析:最终模型在 CIFAR-10 测试集上的准确率为 64%。虽然这是一个基本的卷积网络模型,且准确率较基础,但已表明模型能有效识别 CIFAR-10 数据集的一部分特征。
解读:

这个卷积神经网络架构较为简单,只包含了两个卷积层和三个全连接层。对于 CIFAR-10 这种包含细节较多的图像集,它的特征提取能力较弱,导致分类准确率相对较低。如果要提升模型性能,可以考虑:

  • 增加卷积层与特征图数量:更多的卷积层和特征图可以提取更高层次的特征信息。
  • 使用更复杂的架构:例如引入 ResNet、VGG 等架构。

1.3 使⽤ VGG16 对 CIFAR10 分类

1. 数据加载与准备
  • 数据预处理
    • 训练集转换:使用 RandomCropRandomHorizontalFlip 进行数据增强,有助于提升模型的泛化能力。
    • 归一化:将图像归一化至均值和标准差分别为 (0.4914, 0.4822, 0.4465)(0.2023, 0.1994, 0.2010),以加速模型训练。
  • 数据加载:通过 DataLoader 实例化训练集和测试集。训练集 batch_size=128 并设置 shuffle=True;测试集同样使用批量大小 128,shuffle=False 保持数据顺序。
  • 类别定义:CIFAR-10 数据集包含 10 个类别(如飞机、汽车、鸟等),方便后续测试和分析。
2. 构建 VGG16 网络
2.1. 定义 VGG 网络结构
  • VGG16 模型
    • VGG16 使用多层卷积层提取图像特征,每次卷积后进行 Max Pooling 操作,缩小特征图尺寸。
    • 配置列表 cfg 定义了模型的层级和过滤器数量,例如:第一个 64 表示使用 64 个卷积核进行卷积,'M' 表示池化层。
    • _make_layers 方法根据 cfg 配置自动生成模型结构,并添加了 Batch Normalization 层,改善训练稳定性。
  • 全连接层:包含一个分类器,将卷积层输出的数据展平为二维,通过全连接层进行分类。
2.2. 初始化模型与优化器
  • 损失函数:使用 CrossEntropyLoss 处理多分类任务。
  • 优化器:使用 Adam 优化器,以学习率 0.001 更新网络权重。
3. 网络训练
  • 训练过程:模型训练 10 轮,每轮对所有训练数据进行批次训练。

  • 前向传播、反向传播与参数更新:计算损失,反向传播梯度,更新模型参数。

  • 输出训练信息:每 100 个批次输出当前损失,跟踪模型的收敛情况。

4. 模型测试与性能评估
  • 准确率计算:遍历测试集,计算网络在 10,000 张测试图像上的准确率。
  • 结果展示:训练完成后,模型在测试集上的准确率为 84.06%,显著优于之前使用简单卷积神经网络得到的 64%。
解读

VGG16 网络通过多层卷积层与池化层的堆叠,形成了深层次网络结构,极大地增强了特征提取能力,从而使模型能在 CIFAR-10 这样的复杂数据集上获得较高的准确率。具体优势如下:

  • 深度结构:VGG16 通过增加网络深度,捕捉更多的图像细节特征。
  • 数据增强:数据增强技术在小型数据集上显著提高模型的泛化能力。
  • 特征提取能力:逐层的卷积和池化操作有效提取不同尺度的特征信息,增强了对图像局部细节的捕获能力。

二、问题总结与体会

2.1 实验中遇到的问题及解决方法

**1. 问题:**1.2 CIFAR10 数据集分类实.验

**解决:**在 PyTorch 1.5及以上版本中,DataLoaderter已被弃用。应该使用DataLoader的迭代器方法来代替,即不再使用.next()方法。所以,你可以尝试使用以下代码:
dataiter =iter(test loader)
images,labels =next(dataiter)

因此将iter(trainloader).next改为next(iter(trainoader))即可成功运行

**2. 问题:**1.3 使⽤ VGG16 对 CIFAR10 分类,代码中的两矩阵不符合矩阵相乘的要求

**解决:**修改后一个矩阵为512*10

2.2 问题总结与解答

1. dataloader ⾥⾯ shuffle 取不同值有什么区别?

DataLoader 中,shuffle 参数控制是否在每个 epoch 之前将数据集打乱。代码中,我们对训练数据集设置 shuffle=True,而测试数据集使用 shuffle=False

trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False)
  • 训练集shuffle=True 在每个 epoch 开始时随机打乱数据。这样,模型在训练时不会学到数据的固定顺序,有助于提升模型的泛化能力,尤其是在小数据集上。
  • 测试集shuffle=False 保持测试数据的顺序一致,这样可以确保每次评估的结果可重复。
2. transform ⾥,取了不同值,这个有什么区别?

transforms 用于数据预处理与增强操作。在 CIFAR-10 实验中,我们为训练集和测试集分别设置了不同的 transform:

transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
  • 训练集的 transform:使用了 RandomCropRandomHorizontalFlip 进行数据增强,增加了数据的多样性,帮助模型在训练过程中学习到更多样的特征,提高其在真实场景中的表现。
  • 测试集的 transform:仅进行了标准化操作,保持图像分布一致,以确保评估的稳定性。

不同的 transforms 组合会影响数据分布,从而影响模型的训练与测试表现。

3. epoch 和 batch 的区别?
  • Epoch 表示模型遍历整个数据集一次。代码中通常使用 for epoch in range(10) 来进行多轮训练,每轮训练都包含多个 batch。
  • Batch 表示在每个迭代中使用的数据子集。例如,在代码中,batch_size=64 表示每个 batch 包含 64 个样本,训练集中包含多个 batch。

在模型训练代码中,这样的结构示例:

for epoch in range(10):
    for i, (inputs, labels) in enumerate(trainloader):
        # 进行训练

此结构表示在每个 epoch 中,模型会逐个 batch 获取数据进行训练,从而实现批量更新模型参数。

在大数据集上,分批训练(mini-batch training)是一种常用的策略。这样不仅可以有效利用内存资源,还可以使模型更快地收敛。模型参数的更新是在每个 batch 后进行的,而整个 epoch 表示一次完整的数据遍历。

4.1x1的卷积和 FC 有什么区别?主要起什么作⽤?
  • 1x1 卷积:1x1 卷积只作用于通道维度,不会改变特征图的空间尺寸。它能在不改变空间信息的情况下对通道进行加权,常用于:
    • 降低通道数,以减少计算量。
    • 改变特征维度,为后续网络层的输入提供更合适的通道数。
  • 全连接层 (FC):全连接层会将特征展平成一维,并作用于整个特征空间。常用于分类器部分,将卷积层提取的特征转换为类别预测。

1x1 卷积层具有局部性和位置敏感性,而全连接层更适合对全局特征进行分类。

5. residual leanring 为什么能够提升准确率?

残差学习(Residual Learning)通过引入“残差连接”(即跳跃连接),将前一层的输出直接添加到后续层的输入。这样做有以下优点:

  • 避免梯度消失:残差连接提供了较短的梯度传播路径,使得梯度可以直接传回前几层,有助于解决深层网络中的梯度消失问题。
  • 加速训练:通过残差连接,网络可以更快地收敛,同时提升准确率。
  • 增强特征组合:跳跃连接能够帮助模型学习更丰富的特征组合,从而提升模型的表达能力和泛化性能。
6. 代码练习⼆⾥,⽹络和1989年 Lecun 提出的 LeNet 有什么区别?

LeNet 是一个经典的 CNN 模型,适用于小型图像数据集。相比之下,代码练习二中的网络做了扩展:

  • 更多卷积层:代码练习二的网络使用了更多卷积层,能够捕捉更复杂的特征,这在处理 CIFAR-10 这种更复杂的数据集时十分重要。
  • 激活函数不同:LeNet 使用 Sigmoid 或 Tanh 函数,而现代网络(包括代码中实现的网络)使用 ReLU 激活函数,能有效解决梯度消失问题。
  • 池化方式:LeNet 使用平均池化,而练习二中的网络使用了最大池化(MaxPool2d),更有助于保留特征中的显著信息。
7. 代码练习⼆⾥,卷积以后feature map 尺⼨会变⼩,如何应⽤ Residual Learning?

在代码练习二中,卷积操作会使特征图的尺寸缩小,如果要结合残差学习(Residual Learning),需要确保残差连接的输入和输出的维度一致,否则两者无法相加。

1. 使用 1x1 卷积调整尺寸

当卷积层的输出特征图尺寸小于输入特征图时,可以使用 1x1 卷积 来调整残差连接的通道数或空间尺寸,使得两者匹配。1x1 卷积层不会改变空间信息,但可以改变通道数量以便在特征图维度上进行相加。

例如,如果卷积层输出尺寸为 16x16x64,但输入的尺寸为 32x32x64,以下代码可以调整输入以匹配输出的特征图尺寸:

class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(ResidualBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)
        
        # 用 1x1 卷积匹配尺寸和通道数
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        # 将调整后的输入与输出相加
        out += self.shortcut(x)
        out = F.relu(out)
        return out

解释:如果步长(stride)不是 1,或者输入和输出通道数不同,使用 1x1 卷积调整输入特征图的尺寸与通道数,以匹配卷积输出,确保维度一致后再相加。

2. 增加步长实现下采样

在 ResNet 中,某些残差块使用卷积层的步长为 2 实现空间下采样,同时在残差连接上使用 1x1 卷积进行下采样:

# 使用步长为2的卷积进行下采样
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=2, padding=1)

在这种方式下,主路径中的卷积层会将特征图尺寸缩小一半(例如,从 32x32 到 16x16),而残差连接中的 1x1 卷积也会相应地调整输入特征图尺寸,使它们可以进行相加。

3. 直接使用零填充

如果卷积层缩小了特征图的尺寸,可以在残差连接上直接填充零,匹配两者的尺寸。这种方法简单有效,但较少使用,因为直接通过卷积调整维度更加灵活且高效。

例如,可以将输入 x 的尺寸填充至 out 的尺寸,然后相加:

def forward(self, x):
    out = F.relu(self.bn1(self.conv1(x)))
    out = self.bn2(self.conv2(out))
    
    if self.shortcut:
        x = self.shortcut(x)
    else:
        # 直接填充零,使输入尺寸与输出尺寸相同
        x = F.pad(x, (0, 0, 0, 0, 0, out.size(1) - x.size(1)))
        
    out += x
    out = F.relu(out)
    return out

解释:这种方法填充后的残差连接直接与输出特征图相加。

4. 使用恒等映射进行尺寸不变的残差连接

在部分残差块中,可以保持输入和输出特征图的尺寸相同,此时直接通过恒等映射进行残差连接。典型的实现如下:

def forward(self, x):
    out = F.relu(self.bn1(self.conv1(x)))
    out = self.bn2(self.conv2(out))
    
    # 恒等映射直接相加
    out += x
    out = F.relu(out)
    return out

解释:这里 xout 的尺寸相同,直接相加。这种残差块的优势在于其计算效率和灵活性,可以有效提升模型深度。

8. 有什么⽅法可以进⼀步提升准确率?

实验中,模型在 CIFAR-10 上取得了良好的表现,但可以进一步提升:

  • 增加网络深度:例如使用 ResNet 或更深的 VGG 模型,能够学习到更丰富的特征。
  • 数据增强:可以增加更多的增强操作,例如随机旋转、色彩抖动等。
  • 使用预训练模型:将预训练的 VGG 或 ResNet 模型迁移到 CIFAR-10 任务中,这样可以利用在大数据集上学习到的特征。
  • 调参:尝试不同的学习率、优化器或批量大小。Adam、RMSProp 等优化器在许多任务中表现优异,可以加快收敛速度。

通过这些方法可以有效提高模型的准确性,并增强其在不同数据集上的泛化能力。

2.3 个人体会感悟

在完成这些实验后,我深刻感受到数据预处理和数据增强对模型训练的重要性。通过标准化、随机裁剪和翻转等技术,不仅有效提升了模型的泛化能力,还在小数据集上减少了过拟合的可能性,尤其是在 CIFAR-10 数据集上,数据增强对模型表现的提升尤为显著。这些预处理操作让我意识到,在训练深度学习模型时,数据准备是至关重要的一环。

此外,通过构建和对比不同深度的网络,我进一步理解了网络结构的复杂度如何影响模型性能。相比于简单的 LeNet,VGG 网络这种更深层的结构能更充分地提取特征,在更复杂的分类任务上表现得更优越。而在了解残差学习后,我认识到残差连接的引入,不仅解决了深层网络的梯度消失问题,还提升了网络的可训练性,使得深层网络变得更加稳定。

通过这次实验,我也认识到 1x1 卷积在网络结构中的灵活应用,以及优化器和超参数对模型训练结果的影响。像 Adam 优化器在模型的收敛速度上表现得非常好,这让我深刻理解到,除了网络架构,选择合适的优化器和参数调优同样能带来显著的效果。总的来说,这次实验让我更全面地了解了卷积神经网络的各个组成部分,也让我对未来深度学习方向的学习充满了信心和期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值