1 数据集
本次BP神经网络分类实验所用数据集为MNIST手写数据集。
MNIST 数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST)。训练集 (training set) 由来自 250 个不同人的手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员。测试集(test set) 也是同样比例的手写数字数据。
训练数据集包含60000个样本, 测试数据集包含10000样本。在 MNIST 数据集中,每张图片由28×28个像素点构成, 每个像素点用一个灰度值表示。使用时,将28×28的像素展开成一个维度为784的一维的向量, 向量中的值对应图片中的像素值。此外,还有一个标签数组,对应每张图片所属的手写数字类别(整数0-9)。
本次实验基于Pytorch框架,使用Pytorch自带工具包可完成MNIST数据集的下载及预处理。下图所示代码分别完成了训练数据和测试数据的下载,预处理及加载。
#1.Load the train datasets and test datasets
train_transformations = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_set = MNIST(root="./data", train=True, transform=train_transformations, download=True)
train_loader = DataLoader(train_set,batch_size=128,shuffle=True,num_workers=4)
test_transformations = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
test_set = MNIST(root="./data", train=False, transform=test_transformations, download=True)
test_loader = DataLoader(test_set,batch_size=128,shuffle=False,num_workers=0)
观察MNIST单个样本数据时,发现其中的像素值范围在(0,1)之间,说明已经被整除256,后续无需再重复这一操作。先将28×28的二维数据转换成张量Tensor的形式,再进行标准化处理,将数据映射到(-1,1)之间,最后将数据进行批处理,每批包含128(2的倍数,方便GPU处理)个样本,完成数据的加载。加载时设置打乱训练样本的顺序,不打乱测试样本的顺序。
2 分类网络设计
本次分类BP神经网络共有5层,包括1个输入层,3个隐藏层和一个输出层。层与层之间均采用全连接的形式。输入层有784个节点,对应MNIST单个样本的像素值总数;隐藏层的节点数分别为1024,1024,2048;输出层共有10个节点,用于分类,输出结果为(0-9)的预测概率。详细网络结构图如下所示。
3 训练过程
训练过程主要分为以下四个步骤,分别为:
-
加载训练数据集和测试数据集;
-
初始化模型;
-
定义优化器和损失函数;
-
训练模型及保存权重参数;
下面对每个步骤详细展开介绍。
3.1 加载训练数据集和测试数据集
数据集的加载包括训练数据集和测试数据集的加载,训练数据集用于训练模型,测试数据集用于训练过程中对模型的性能进行实时监测。本次所用数据集为MNIST书写数字数据集,包括60000个训练样本和10000个测试样本,具体加载方式在第一章已详细介绍。
3.2 初始化模型
本次分类BP神经网络共有5层,包括1个输入层,3个隐藏层和一个输出层。层与层之间均采用全连接的形式。代码实现时,只需要实现3个隐藏层和1个输出层的设计即可。
每个隐藏层都由一个线性全连接层和一个激活函数构成,为了方便起见,自定义一个隐藏层模块,主要是定义线性全连接层的输入和输出节点数,激活函数的选取,实现过程如下所示。
# Build the neural network
class Unit(nn.Module):
def __init__(self, in_features, out_features):
super(Unit, self).__init__()
self.fc=nn.Linear(in_features, out_features, True)
self.relu = nn.ReLU()
def forward(self, input):
output = self.fc(input)
output = self.relu(output)
return output
接着,调用上面定义好的隐藏层模块实现整个网络的设计。将3个隐藏层根据其输入和输出节点数一一定义出来,再将这3个隐藏层组合成一个小网络。然后根据分类的类别数定义出输出层,也就是最后一个全连接层。最后在前向传播函数中,将这些层的输入输出拼接起来,实现整个网络的结构设计。代码实现如下所示。
class SimpleNet(nn.Module):
def __init__(self, num_classes