2023-2024 学年秋季学期 创新创业实训

一、实训过程简述

1 实训概况、实训过程

实践概况:本学期在马老师的指导下,借助网络资源学习了机器学习和深度学习的一些 入门知识,了解了一些基础的概念,模型背后的数学原理,也动手实践做了一个通过全连接 神经网络模型来预测手写数字集的小项目,加深了我对理论知识的理解。

实践过程:学期初,我在网络上学习了北京邮电大学鲁鹏老师的计算机视觉与深度学习 的课程,这门课主要是讲解一些分类器模型背后的数学原理,我分别听了图像分类任务,线 性分类器,全连接神经网络以及卷积的相关课程。学习了如何构建模型、通过损失函数刻画 模型性能,并利用优化算法更新模型权值等基础的机器学习与深度学习知识。

更为具体一点来说,在课程中我学习到了多类支撑向量机损失,交叉熵损失,正则损失 三种具有不同的特点的损失函数来刻画模型的预测性能;此外优化算法方面,我了解了梯度 下降算法,以及在其基础上优化形成的小批量随机梯度下降算法。全连接神经网络方面,我 了解了激活函数对线性分类器进行的非线性操作,使得模型可以处理更为复杂的分类任务; 借助计算图实现前向传播和反向传播,实现导数的计算从而进行权值的更新等等; 在学习了一些理论知识并了解了基础模型的数学背景之后,我想要做一个实际的小项目 来实践我学到的知识,于是我找到了 Mnist 手写数据集,想要利用一个简单的全连接神经网 络来实现对这个数据集的预测。经过查阅资料,学习了 Pytorch 的一些操作之后,我成功实 现了对这个数据集的预测,应用到了很多之前学到的理论知识,加深了对相关理论知识的理 解。

2 实训体会及建议

在实训过程中,老师给予了我们很好的平台,让我们能够及时的和其他同学一起交流 学习的内容,不仅能够学到每个人分享的知识,也能够了解大家的学习路径和方法。我觉 得这种学习方式对于刚刚入门机器学习和深度学习的我们非常有效。在实训中不断发现问 题,解决问题的过程也给我一种满满的获得感。同时,实训让我了解了机器学习的基础逻 辑,这也让我对这个领域产生了浓厚的兴趣,想要在之后的学习过程中进一步地去学习这些 知识。

二、报告正文

全连接神经网络实现手写数据集预测

23121608 周宸宇

摘要

本文旨在使用全连接神经网络(FCNN)对 MNIST 手写数字数据集进行分类预测。通过采 用 PyTorch 框架,我们构建了一个多层神经网络模型,并使用 Adam 优化器和负对数似然损 失函数对其进行训练与优化。在本报告中,我们将介绍 MNIST 数据集的特点、模型的设计与 实现,并通过实验结果验证其性能。最终,模型在测试集上达到了较高的分类准确率,验证 了 FCNN 在该任务中的有效性。 关键字:全连接神经网络,MNIST,PyTorch,图像分类,深度学习

1 引言

随着人工智能技术的发展,图像分类任务得到了广泛的研究与应用。MNIST手写数字数 据集是图像分类领域的一个经典数据集,广泛用于验证各种机器学习算法的有效性。本文采 用全连接神经网络(Fully Connected Neural Network, FCNN)解决MNIST数据集的分类问 题。全连接神经网络能够通过多层结构逐步提取图像的特征,并将其用于分类。本项目通过 对FCNN进行设计与训练,探索其在手写数字分类任务中的表现。

2 问题分析

MNIST数据集由60,000张训练图像和10,000张测试图像组成,每张图像为28×28像素的 灰度图,表示从0到9的手写数字。每个像素的灰度值在0到255之间。任务目标是通过神经网 络将这些图像分类到10个类别中。

Figure 1:MNIST 图像识别数据集

全连接神经网络通过线性映射和非线性激活函数的组合,对高维数据进行处理和分类。

该问题的主要挑战包括:

  1. 高维输入:每张图像被展平成一个784维向量,需要有效提取特征。

  2. 计算复杂度:全连接神经网络的参数较多,可能导致训练时间过长或过拟合。

  3. 泛化能力:如何保证在测试集上的良好性能,避免过拟合是训练中的重要挑战。

3 技术实现

3.1 加载MNIST数据集

1
2
3
4
5
6
7
8
 train_batch_size = 64 test_batch_size = 1000 img_size = 28 def get_dataloader(train=True):
\#准备数据集,其中 0.1307,0.3081 为 MNIST 数据的均值和标准差,这样操作能够对其 进行标准化
\#因为 MNIST 只有一个通道(黑白图片),所以元组中只有一个值 dataset =
torchvision.datasets.MNIST('/Users/jackchen/Jupyter_notebook/data', train=train, download=True, transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.1307,), (0.3081,)),])) \#准备数据迭代器 batch_size = train_batch_size if train else test_batch_size dataloader =
torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=Tru e)
return dataloader

3.2 网络结构设计

我们设计了一个全连接神经网络MnistNet,该网络包含若干线性层和ReLU激活函数:

  • 输入层:接收28×28像素的展平输入(784维向量)。这一步将图像的二维像素数据拉平成 一维向量,作为神经网络的输入。

  • 隐藏层:若干全连接层,使用ReLU激活函数。

    ReLU(修正线性单元)是一种常见的激活函数,当输入x为正数时,ReLU输出为x本 身;当输入为负数时,ReLU输出为0。通过这一简单的非线性变换,ReLU能够引入网络所 需的复杂性,帮助模型学习非线性特征。与其他激活函数(如Sigmoid或Tanh)相比, ReLU有几个显著优势。首先,它的计算效率非常高,输出仅依赖于判断输入是否大于0, 这使得它在大规模深度神经网络训练中非常流行。其次,ReLU能有效缓解梯度消失问题。 在反向传播时,ReLU在正数区域的梯度为1,不会像Sigmoid那样导致梯度逐渐变小,使得 深层网络的训练更加顺利。

  • 输出层:一个具有 10 个神经元的全连接层,每个神经元表示一个数字类别的预测概率。

通过 Softmax 激活函数将输出层转化为概率分布,模型能够输出每个类别的概率,最终 选择最大概率对应的类别作为预测结果。

1
2
3
4
5
6
7
8
9
10
# 导入相关库
from torchvision import transforms import numpy as np import torch import torchvision import torch.nn.functional as F from torch import nn from torch import optim class MnistNet(torch.nn.Module):
def __init__(self):
super(MnistNet,self).__init__() self.fc1 = torch.nn.Linear(28*28,28)
self.fc2 = torch.nn.Linear(28,10)
def forward(self,x): \# 输入层 x = x.view(-1,28*28*1)\#[batch_size,28*28]
\# 隐藏层 x = self.fc1(x) \#[batch_size,28]
\# 隐藏层的激活函数 x = F.relu(x) \#[batch_size,28] \# 输出层 x = self.fc2(x) \#[batch_size,10]
\# 输出层的激活函数 out = F.log_softmax(x,dim=-1) \#[batch_size,10]
return out mnist_net = MnistNet() \# 初始化网络模型

3.3 模型训练

我们使用Adam优化器对模型进行训练,并使用负对数似然损失函数来计算分类误差。在 使用代码说明之前,我们先解释一下Adam优化器和对数似然损失:
Adam(Adaptive Moment Estimation)是一种自适应梯度优化算法,广泛用于神经网络 的训练。相较于传统的随机梯度下降(SGD),Adam 结合了动量和自适应学习率的优点,使 参数的更新速度更快且更具稳定性。Adam 尤其适用于处理稀疏梯度和高维数据的优化问 题,在深度学习中具有显著的效果。

Adam 的工作机制依赖于两个动量项的计算:

  1. 第一个动量(均值动量):Adam 计算梯度的指数加权平均,用于估计梯度的一阶 矩(即梯度的均值)。该动量项有助于捕捉梯度的整体趋势,类似于 SGD 中引入动量的做 法,通过跟踪过去的梯度信息来平滑更新方向,从而避免参数更新频繁波动。

  2. 第二个动量(方差动量):Adam 计算梯度平方的指数加权平均,用于估计梯度的 二阶矩(即方差)。通过监测梯度变化的方差,Adam 可以动态调整学习率,降低高方差的梯 度更新步伐,避免在梯度变化较大的方向上出现不稳定更新。

在更新参数时,Adam 将每个动量项分别进行偏差校正,使得初始阶段动量估计更加准 确,进而对每个参数的学习率进行自动调整。Adam 的具体更新规则如下:

  • 学习率调整:通过动量项的偏差校正和自适应学习率,Adam 不需要在训练过程 中频繁手动调整学习率。它能够自动适应不同参数的更新速度,使得训练在复杂数据上更高 效。

Adam 的核心优势如下:

  • 高效稳定的更新:动量结合自适应学习率,使得 Adam 在参数空间的更新更加平 滑,有助于减少梯度变化剧烈时的震荡问题,提升训练的稳定性。

  • 自动调节学习率:无需手动调整学习率,Adam 在训练复杂模型和大数据集上尤 其有效,能够自动适应不同维度和参数的学习步伐。

  • 适用广泛:Adam 适合高维优化问题,能够处理稀疏梯度问题,这使得其在 NLP、图像分类、生成对抗网络等深度学习领域广泛应用。

在 MNIST 数据集的分类任务中,Adam 能加速收敛过程,使 MnistNet 在较短时间内达 到较高的准确率,提升模型的泛化能力。同时,Adam 在参数更新上的自适应性能够保证训 练稳定性,减少手动调参的工作量,从而提升开发效率。

负对数似然损失(Negative Log-Likelihood Loss, NLL),是一种基于概率最大化的 损失函数,用于衡量模型预测分布与真实分布之间的差异。其设计初衷在于最大化模型在正 确类别上的预测概率,因此广泛应用于分类任务。对数似然损失通过最大化模型在正确标签 上的概率,使模型在每次迭代中逐渐提高对真实标签的预测置信度。

定义与公式: 假设分类模型输出一个样本属于各个类别的概率分布。给定一个样本 x 及其真实类别标签 y,若模型预测类别 y 的概率为 p(y|x),则该样本的对数似然损失定义为:

123456.png

其中,N 表示样本数,p(yi|xi) 是模型对样本 xi 在真实类别 yi 下的预测概率。

直观解释:

对数似然损失的目的是提升模型在正确类别上的预测概率,使得模型在面对真实标签 时能给出较高的预测置信度。直观上,当模型预测结果与真实类别接近时,模型的损失较 小;当预测概率较低时,损失会显著增大。具体而言:

  • 高度自信:如果模型对真实类别的预测概率接近 1,则对数似然损失趋近于 0。 - 高度不自信:如果预测概率接近 0,模型在真实标签上的置信度低,损失值会急 剧增大。

因此,通过最小化对数似然损失,模型被优化为逐步提高对正确类别的预测自信度, 从而更精确地反映样本的实际分类结果。

应用与特点:

  1. 分类任务的核心损失:对数似然损失特别适合多类别分类任务,尤其在多分类神 经网络模型中,通常与Softmax激活函数配合使用,以便将输出转化为类别概率。

  2. 对错误预测敏感:当模型输出的预测概率与真实标签偏差较大时,对数似然损失 会显著增大,促使模型在训练过程中更快修正偏差。

  3. 与交叉熵损失的关系:在分类问题中,负对数似然损失与交叉熵损失(Cross-
    Entropy Loss)在形式上等价,并且在多类别分类任务中,两者常被交替使用。

对数似然损失基于最大似然估计,通过最小化损失,模型逐步调整参数,使得预测分 布更符合数据的真实分布。模型参数优化后,能够在测试集上对未见样本提供稳健的预测概 率。因此,对数似然损失在提升分类任务的泛化能力与优化效果上具有理论与实用价值。

Adam优化器和对数似然损失的结合,使得模型在复杂任务中的训练过程更加高效,损 失函数衡量误差,优化器则根据误差调整模型的参数,二者配合实现模型的逐步优化。

以下是代码实现部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
 def Train(Epoch):
mnist_net.train(True) \# 启用训练模式 train_dataloader = get_dataloader(True) \# 获取训练集数据 print("开始训练:")
for idx, (data, target) in enumerate(train_dataloader):
\# 遍历每个批次 optimizer.zero_grad() \# 清除先前梯度 output = mnist_net(data) \# 前向传播,计算输出 loss = F.nll_loss(output, target) \# 计算损失 loss.backward() \# 反向传播计算梯度 optimizer.step() \# 更新模型参数 if idx % 200 == 0:
print(f'Train Epoch: {epoch} [{idx *
len(data)}/{len(train_dataloader.dataset)} ({100. * idx /
len(train dataloader):.0f}%)]\tLoss: {loss.item():.6f}')
train_loss_list.append(loss.item()) \# 记录损失 train_count_list.append(idx * train_batch_size + (epoch - 1) *
len(train_dataloader)) \# 记录进度 print("结束训练。")
epoch = 3 \# 训练的轮次 for i in range(epoch):
train(i)
test()
\#

12345.png

3.4 模型评估

测试时,不计算梯度,以提高效率。我们计算模型的平均损失和分类准确率:

1
2
3
4
5
6
7
8
9
10
def test():
test_loss = 0 correct = 0 mnist net.eval()
test_dataloader = get_dataloader(train=False)
with torch.no_grad():
for data, target in test_dataloader: output = mnist_net(data)
test_loss += F.nll_loss(output, target, reduction='sum').item()
pred = output.data.max(1, keepdim=True)[1] \#获取最大值的位置,[batch_size,1]
correct += pred.eq(target.data.view_as(pred)).sum() test_loss /= len(test_dataloader.dataset)
print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format( test_loss, correct, len(test_dataloader.dataset), 100. * correct / len(test_dataloader.dataset)))
test()

Figure 3:模型评估输出结果

3.5 模型的保存与加载

为了复现训练结果,我们使用torch.save和torch.load来保存和加载模型参数。

1
2
3
4
5
6
7
\# 保存模型参数 
torch.save(mnist_net.state_dict(), "model/mnist_net.pt") \# 保存模型
torch.save(optimizer.state_dict(), 'results/mnist_optimizer.pt') \# 保存优化
\# 加载模型参数
mnist_net.load_state_dict(torch.load("model/mnist_net.pt"))\# 加载模型
optimizer.load_state_dict(torch.load("results/mnist_optimizer.pt"))
\# 加载优化器

4 分析总结

通过对 MNIST 数据集的分类任务研究,使用全连接神经网络(FCNN)模型取得了较高的 测试集准确率,表明该网络能够有效提取图像特征并准确完成分类。然而,FCNN 对图像数 据的处理方式可能存在局限性,因为它并未利用图像的空间结构信息。随着学习的深入,未 来可以尝试引入卷积神经网络(CNN),这种网络更适合图像数据,能够提取出更丰富的局部 特征,从而有望进一步提升模型性能。此外,针对 FCNN 模型,可以通过调节模型参数(如 层数和节点数)、学习率以及优化器(如使用 Adam 或 RMSprop)等,进一步提升模型的泛化 能力和稳定性。

最后,感谢马老师本学期给予我们的指导和帮助!

参考文献

[1] CSDN 技术社区 https://blog.csdn.net/xjm850552586/article/details/109171016 [2] 知乎 知识社区 https://www.zhihu.com/tardis/zm/art/492674150?source_id=1005