第2 章对MNIST 数据做了介绍,描述了其构成方式及其数据的特征和标签的含义等。了解这些有助于编写 合 适的程序来对MNIST 数据集进行分析和识别。 本节 将使用同样的数据集完成对其 进行 分类 的 任务。
3.1.1 数据图像的获取与标签的说明
MNIST 数据集 的 详细介绍在 第2 章中已经完成,读者可以使用相同的代码对数据进行获取,代码如下:
import numpy as np
x_train = np.load("./dataset/mnist/x_train.npy")
y_train_label = np.load("./dataset/mnist/y_train_label.npy")
基本数据的获取与 第2 章类似,这里就不过多阐述 了 ,不过需要注意的是,在 第2 章介绍数据集时只使用了图像数据,没有对标签进行说明,在这里重点对数据标签,也就是y_train_label 进行介绍。
我们可以使用 下面 语句 打印出数据集的前10 个标签 :
print(y_train_label[:10])
结果如下:
[5 0 4 1 9 2 1 3 1 4]
可以很清楚 地 看到,这里打印出了10 个 数字 字符,每个字符对应相同序号的数据图像 所 对应的数字标签 , 即图像3 的标签对应的就是4 这个数字字符。
可以说训练集中每个实例的标签对应0~9 的任意一个数字,用以对图片进行标注。 另外 需要注意的是,对于提取出来的MNIST 的特征值,默认使用一个0~9 的数值进行标注,但是这种标注方法并不能使得损失函数获得一个好的结果,因此 通 常 使 用one_hot 计算方法,将 数 值具体落在某个标注区间中。
one_hot 的标注方法请读者自行学习掌握。这里主要介绍将单一序列转换成one_hot 的方法。一般情况下,可以用NumPy 实现one_hot 的表示方法,但是转换生成的是numpy.array 格式的数据,并不适合直接输入 到PyTorch 中 使用 。
如果读者能够自行编写
将序列值转换成 one_hot 的函数,那么编程功底真是不错。 PyTorch 提供了已经编写好的转换函数:
torch.nn.functional.one_hot
完整的one_hot 使用方法如下:
import numpy as np
import torch
x_train = np.load("./dataset/mnist/x_train.npy")
y_train_label = np.load("./dataset/mnist/y_train_label.npy")
x = torch.tensor(y_train_label[:5],dtype=torch.int64)
# 定义一个张量输入,因为此时有 5 个数值,且最大值为9,类别数为10
# 所以我们可以得到 y 的输出结果的形状为 shape=(5,10),即5行12列
y = torch.nn.functional.one_hot(x, 10) # 一个参数张量x,10为类别数
ptint(y)
结果如下:
tensor([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]])
可以看到,one_hot 的作用是将一个序列转换成以one_hot 形式表示的数据集。所有的行或者列都被设置成0 ,而每个特定的位置都对应一个1 来 表示 , 如图3-1 所示。
图3-1 one_hot 形式表示的 数据集
对于MNIST 数据集的标签来说, 这 实际上就是一个60 000 幅 图片的60 000×10 大小的矩阵张量[60 000,10] 。前面的数指的是数据集中图片的个数为60 000 个,后面的10 指的 是10 个列向量。
下面使用PyTorch 2.0 框架完成手写体的识别。
3.1.2 模型的准备(多层感知机)
在 第2 章已经 讲过 了,PyTorch 最重要的一项内容是模型的准备与设计,而模型的设计最关键的一点就是了解输出和输入的数据结构类型。
通过 第2 章 有关 图像去噪的演示 , 读者已经了解了我们的输入数据格式是一个[28,28] 大小的二维图像。而 通过 对数据结构的分析 , 我们可以 知道 ,对于每个图形都有一个确定的分类结果,也就是0~10 的一个确定数字。
下面将按这个想法 来 设计模型 。 从 前 面对图像的分析来看,对整体图形进 行 判别的一个基本想法就是将图像作为一个整体直观 地进行 判别,因此基于这种解决问题的思路,简单的模型设计就是同时对图像所有参数进行计算,即使用一个多层感知机(Multi-Layer Perceptron ,MLP )对图像进行分类。整体的模型设计结构如图3-2 所示。
图3-2 整体的模型设计结构
从图3-2 可以看到,一个多层感知机模型就是将数据输入后,分散到每个模型的节点(隐藏层), 进行数据计算后 ,再 将计算结果输出到对应的输出层中 。 多层感知机的模型结构如下:
class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.flatten = nn.Flatten() self.linear_relu_stack = nn.Sequential( nn.Linear(28*28,312), nn.ReLU(), nn.Linear(312, 256), nn.ReLU(), nn.Linear(256, 10) ) def forward(self, input): x = self.flatten(input) logits = self.linear_relu_stack(x) return logits
3.1.3 损失函数的表示与计算
第2 章使用了MSELoss 作为目标图形与预测图形的损失值,而在本例中,我们需要预测的目标是图形的 “ 分类 ”, 而不是图形表示本身,因此我们需要寻找并使用一种新的能够对类别归属进行“计算”的函数。
本例所使用的 交叉熵 损失函数为torch.nn.CrossEntropyLoss 。PyTorch 官 方 网 站 对其介绍如下:
CLASS torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=- 100,reduce=None, reduction='mean', label_smoothing=0.0)
该损失函数计算输入值(Input )和目标值(Target )之间的交叉熵损失。交叉熵损失函数CrossEntropyLoss 可用于训练 单类别或者多 类别的分类问题。 给定 参数weight 时, 会 为 传递进来的 每个类别的 计算数值重新加载一个修正权重 。当数据集分布不均衡时,这是很有用的。
同样需要注意的是,因为torch.nn.CrossEntropyLoss 内置了Softmax 运算,而Softmax 的作用是计算分类结果中最大的那个类。 从图3-3 所示的对PyTorch 2.0 中CrossEntropyLoss 的实现 可以看到 , 此时CrossEntropyLoss 已经在计算 的同 时实现了Softmax 计算,因此在使用torch.nn.CrossEntropyLoss 作为损失函数时,不 需 要在网络的最后添加Softmax 层。此外 ,label 应为一个整数,而不是One-Hot 编码形式 。
图3-3 使用torch.nn.CrossEntropyLoss() 作为损失函数
CrossEntropyLoss 示例 代码如下:
import torch y = torch.LongTensor([0]) z = torch.Tensor([[0.2,0.1,-0.1]]) criterion = torch.nn.CrossEntropyLoss() loss = criterion(z,y) print(loss)
CrossEntropyLoss 的数学公式较为复杂,建议学有余力的读者查阅相关内容进行学习,目前只需要 掌握 这方面内容即可。
3.1.4 基于PyTorch 的手写体识别的实现
下面介绍基于 PyTorch 的手写体识别的实现。通过前文的介绍,我们还需要定义深度学习的优化器部分,在这里采用 Adam 优化器,相关代码如下:
model = NeuralNetwork() optimizer = torch.optim.Adam(model.parameters(), lr=2e-5) #设定优化函数
在这 个实战案例中 首先需要定义模型,之后将模型参数传入优化器中,lr 是对学习率的设定,根据设定的学习率进行模型计算。完整的手写体识别模型如下:
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0' #指定GPU编号
import torch
import numpy as np
from tqdm import tqdm
batch_size =320#设定每次训练的批次数
epochs=1024 #设定训练次数
#device="cpu" #PyTorch的特性,需要指定计算的硬件,如果没有GPU,就使用CPU进行计算
device="cuda" #在这里默认使用GPU,如果读者运行出现问题,可以将其改成CPU模式
#设定的多层感知机网络模型
class NeuralNetwork(torch.nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = torch.nn.Flatten()
self.linear_relu_stack = torch.nn.Sequential(
torch.nn.Linear(28*28,312),
torch.nn.ReLU(),
torch.nn.Linear(312, 256),
torch.nn.ReLU(),
torch.nn.Linear(256, 10)
)
def forward(self, input):
x = self.flatten(input)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork()
model = model.to(device) #将计算模型传入GPU硬件等待计算
model = torch.compile(model) #PyTorch 2.0的特性,加速计算速度
loss_fu = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=2e-5) #设定优化函数
#载入数据
x_train = np.load("../../dataset/mnist/x_train.npy")
y_train_label = np.load("../../dataset/mnist/y_train_label.npy")
train_num = len(x_train)//batch_size
#开始计算
for epoch in range(20):
train_loss = 0
for i in range(train_num):
start = i * batch_size
end = (i + 1) * batch_size
train_batch = torch.tensor(x_train[start:end]).to(device)
label_batch = torch.tensor(y_train_label[start:end]).to(device)
pred = model(train_batch)
loss = loss_fu(pred,label_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item() # 记录每个批次的损失值
# 计算并打印损失值
train_loss /= train_num
accuracy = (pred.argmax(1) == label_batch).type(torch.float32).sum().item() / batch_size
print("train_loss:", round(train_loss,2),"accuracy:",round(accuracy,2))
此时模型的训练结果如图3-4 所示。
图3-4 模型的训练结果
可以看到随着模型循环次数的增加,模型的损失值在降低,而准确率在增高,具体请读者自行验证测试。
本文节选自《PyTorch 2.0深度学习从零开始学》。
