PyTorch生成式人工智能实战(3)——分类任务详解
0. 前言
在本节中,我们将介绍神经网络的基本概念,包括损失函数、激活函数、优化器和学习率,这些对于构建和训练深度神经网络至关重要,如果想要深入理解这些知识,推荐通过《PyTorch深度学习实战》了解所需的基本技能和概念,包括多种人工神经网络的架构和训练。
在本节中,将学习如何使用 PyTorch
创建深度神经网络来执行二分类和多类别分类任务,以便熟练掌握深度学习和分类任务。具体而言,我们将构建一个完整的端到端深度学习项目,使用 PyTorch
将灰度图像的服装物品分类为不同类别,包括外套、包、运动鞋、衬衫等。目的是创建能够执行二分类和多类别分类任务的深度神经网络,为后续学习奠定基础。
1. 使用 PyTorch 进行端到端的深度学习
在本节中,我们首先介绍深度学习模型训练流程,然后讨论如何获取训练数据以及如何进行数据预处理。
1.1 PyTorch 深度神经网络训练流程
在本节中,我们的任务是创建并训练一个深度神经网络,用于对服装物品的灰度图像进行分类,模型构建与训练步骤如下图所示。
首先,获取一组灰度服装图像数据集,这些图像是原始像素数据,将它们转换为 PyTorch
张量,并且数据类型为浮动数值,每张图像都有一个标签。
接着,使用 PyTorch
创建深度神经网络,对于这个简单的分类问题,我们仅使用全连接层 (dense layer
)。通常选择交叉熵损失 (cross-entropy loss
) 作为多类别分类任务的损失函数,交叉熵损失衡量的是预测的概率分布与标签的真实分布之间的差异。使用 Adam
优化器在训练过程中更新网络的权重,并将学习率设置为 0.001
,学习率控制的是在训练过程中模型的权重如何根据损失的梯度进行调整。
机器学习中的优化器是基于梯度信息更新模型参数,以最小化损失函数的算法。随机梯度下降 (Stochastic Gradient Descent, SGD
) 是最基本的优化器,基于损失梯度进行更新。Adam
是以其高效性和开箱即用的性能而流行,因为它结合了自适应梯度算法 (Adaptive Gradient Algorithm
, AdaGrad
) 和均方根传播 (Root Mean Square Propagation
, RMSProp
) 的优点。尽管不同优化器之间存在差异,但所有优化器的目标都是通过迭代调整参数以最小化损失函数,每种优化器都通过一条独特的优化路径以实现这一目标。
我们把训练数据分为训练集和验证集。在机器学习中,通常使用验证集获取对模型的无偏评估,并选择最佳超参数,例如学习率、训练的轮数 (epochs
) 等。验证集还可以用来避免模型过拟合的情况,即模型在训练集上表现良好,但在未见过的数据上表现不佳。一个训练轮次 (epoch
) 是指使用所有训练集上训练模型一次。
在训练过程中,迭代遍历训练数据。在前向传播过程中,将图像输入到网络中以获得预测结果,并通过将预测标签与实际标签进行比较来计算损失。然后,通过反向传播将梯度传递回网络,以更新权重,这就是模型的学习过程。
使用验证集来确定何时停止训练。计算验证集中的损失,如果模型在经过一定数量的训练 epoch
后不再改进,我们就认为模型已经训练完成。然后,在测试集上评估训练好的模型,以评估模型的性能表现。
1.2 数据预处理
在本节中,我们将使用 Fashion-MNIST
数据集,学习 Torchvision
库中的 datasets
和 transforms
包,以及 PyTorch
中的 Dataloader
包,使用这些工具进行数据预处理。Torchvision
库提供了图像处理工具,包括流行的数据集、模型架构和常见的图像变换,专为深度学习应用而设计。
(1) 首先,导入所需的库,并实例化 Compose()
类,用于将原始图像转换为 PyTorch
张量:
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as T
transform=T.Compose([
T.ToTensor(),
T.Normalize([0.5],[0.5])])
Torchvision
中的 transforms
包可以创建一系列变换来预处理图像。ToTensor()
类将图像数据(无论是 PIL
图像格式,还是 NumPy
数组)转换为 PyTorch
张量。图像数据是从 0
到 255
的整数,而 ToTensor()
类将它们转换为浮动类型张量,值的范围从 0.0
到 1.0
。
Normalize()
类使用均值和标准差对张量图像进行归一化,适用于 n
个通道。Fashion MNIST
数据集是灰度图像,只包含一个颜色通道。在以上代码中,Normalize([0.5],[0.5])
表示将数据减去 0.5
,并将结果除以 0.5
,归一化后的图像数据的值范围是 -1
到 1
。将输入数据归一化到 [–1, 1]
的范围内可以使梯度下降更加高效,有助于在训练过程中更快地收敛。需要注意的是,以上代码仅定义了数据变换过程,并未执行实际的变换。
(2) 使用 Torchvision
中的 datasets
包下载数据集,并执行数据变换:
train_set=torchvision.datasets.FashionMNIST(
root=".", # 数据集下载目录
train=True, # 下载训练数据集或测试数据集
download=True, # 是否将数据集下载到本地
transform=transform)# 执行数据变换
test_set=torchvision.datasets.FashionMNIST(root=".",
train=False,download=True,transform=transform)
(3) 打印出训练集中第一个样本:
print(train_set[0])
该图像样本由一个包含 784
个值的张量和标签 9
组成。784
个数值代表一张 28×28
的灰度图像 (28 × 28 = 784
),标签 9
表示它是一双踝靴。数据集中有 10
种不同类型的服装,数据集中的标签从 0
到 9
编号,可以在线搜索找到这 10
个类别的文本标签。
(4) 定义列表 text_labels
,包含与数字标签 0
到 9
对应的 10
个文本标签。例如,如果某个物品的数字标签是 0
,那么对应的文本标签是 t-shirt
:
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
(5) 可视化数据集中的数据样本:
import matplotlib.pyplot as plt
plt.figure(dpi=300,figsize=(8,4))
for i in range(24):
ax=plt.subplot(3, 8, i + 1)
img=train_set[i][0]
img=img/2+0.5 # 将数值从 [–1, 1] 转换为 [0, 1]
img=img.reshape(28, 28) # 将图像重塑为 28 x 28 形状
plt.imshow(img,
cmap="binary")
plt.axis('off')
plt.title(text_labels[train_set[i][1]], fontsize=8) # 添加文本标签
plt.show()
接下来,将学习如何使用 PyTorch
创建深度神经网络,解决二分类和多类别分类问题。
2. 二分类
在本节中,我们首先为训练创建数据批次。然后,使用 PyTorch
构建一个深度神经网络,并使用训练数据训练模型。最后,使用训练好的模型进行预测,并测试预测的准确性。
2.1 创建数据批次
创建一个训练集和一个测试集(在介绍多类别分类时,还将学习如何创建一个验证集,以确定何时停止训练),这两个数据集只包含两种类型的服装:T恤和踝靴:
binary_train_set = [x for x in train_set if x[1] in [0,9]]
binary_test_set = [x for x in test_set if x[1] in [0,9]]
只保留标签为 0
和 9
的样本,来创建一个具有平衡训练集的二分类问题。接下来,为训练深度神经网络创建数据批次:
batch_size=64
binary_train_loader=torch.utils.data.DataLoader(
binary_train_set, # 为二分类训练集创建批次
batch_size=batch_size, # 每个批次中的样本数量
shuffle=True) # 在批处理时打乱数据
binary_test_loader=torch.utils.data.DataLoader(
binary_test_set, # 为二分类测试集创建批次
batch_size=batch_size,shuffle=True)
PyTorch utils
包中的 DataLoader
类以批次的形式创建数据迭代器,本节将批大小设置为 64
。在以上代码中,我们创建了两个数据加载器:一个用于训练集,另一个用于测试集,以进行二分类。当创建数据批次时,会打乱样本顺序,以避免原始数据集中存在的相关性,不同标签的数据在数据加载器中均匀分布,训练会更加稳定。
2.2 模型构建与训练
(1) 使用 PyTorch
通过 nn.Sequential
类创建神经网络:
# 自动检测是否有支持 CUDA 的 GPU 可用
device="cuda" if torch.cuda.is_available() else "cpu"
binary_model=nn.Sequential( # 创建一个顺序神经网络
nn.Linear(28*28, 256), # 线性层中的输入和输出神经元的数量
nn.ReLU(), # 应用 ReLU 激活函数
nn.Linear(256,128),
nn.ReLU(),
nn.Linear(128,32),
nn.ReLU(),
nn.Linear(32,1),
nn.Dropout(p=0.25),
nn.Sigmoid()).to(device)
PyTorch
中的 Linear()
类创建了对输入数据的线性变换,这实际上是在神经网络中创建了一个全连接层。输入的形状为 784
,因为我们稍后会将二维图像展平成一个包含 28 × 28 = 784
个值的一维向量。我们将二维图像展平为一维张量,因为全连接层只接受一维输入。在使用卷积层时,则不需要展平图像。网络中有三个隐藏层,分别包含 256
、128
和 32
个神经元。
在三个隐藏层上应用了 ReLU
激活函数。ReLU
激活函数根据加权和来决定一个神经元是否会激活。激活函数为神经元的输出引入了非线性,使得网络能够学习输入与输出之间的非线性关系。ReLU
是一个常用的激活函数,关于更多激活函数的介绍,参考《深度学习常用激活函数》。
模型最后一层的输出包含一个单一的值,使用 sigmoid
激活函数将该数值压缩到 [0, 1]
的范围内,可以解释为该物品是踝靴的概率,与之相对的概率则表示该物品是T恤衫的概率。
(2) 接下来,设置学习率,并定义优化器和损失函数:
lr=0.001
optimizer=torch.optim.Adam(binary_model.parameters(),lr=lr)
loss_fn=nn.BCELoss()
将学习率设置为 0.001
。学习率的设置是一个经验性问题,通常需要根据具体问题而定,也可以通过使用验证集进行超参数调优来确定。PyTorch
中大多数优化器的默认学习率是 0.001
。Adam
优化器是梯度下降算法的一种变体,用于确定每次训练步骤中模型参数的调整量。在传统的梯度下降算法中,只有当前迭代的梯度被考虑,而 Adam
优化器则考虑了前几次迭代的梯度。
使用损失函数 nn.BCELoss()
,即二元交叉熵损失函数。损失函数用于衡量机器学习模型的表现,训练模型的过程就是调整模型参数,以最小化损失函数。二元交叉熵损失函数在机器学习中广泛应用,尤其是在二分类问题中,它衡量的是输出值介于 0
和 1
之间概率值的分类模型的表现。当预测的概率与实际标签偏差增大时,交叉熵损失会增大。
(3) 训练神经网络:
for i in range(50): # 训练 50 个 epoch
tloss=0
for n,(imgs,labels) in enumerate(binary_train_loader): # 遍历所有批数据
imgs=imgs.reshape(-1,28*28) # 在将张量移动到 GPU 之前,先将图像展平
imgs=imgs.to(device)
labels=torch.FloatTensor([x if x==0 else 1 for x in labels]) # 转换标签为 0 和 1
labels=labels.reshape(-1,1).to(device)
preds=binary_model(imgs)
loss=loss_fn(preds,labels) # 计算损失
optimizer.zero_grad()
loss.backward() # 反向传播
optimizer.step()
tloss+=loss
tloss=tloss/n
print(f"at epoch {i}, loss is {tloss}")
loss.backward()
计算损失函数相对于每个模型参数的梯度,从而实现反向传播;而 optimizer.step()
则根据这些计算出的梯度更新模型参数,以最小化损失。为了简化训练过程,将模型训练进行 50
个 epoch
(一个 epoch
是指使用整个训练数据训练模型一次)。在二分类问题中,由于我们只保留了T恤衫和踝靴,标签分别为 0
和 9
,因此我们需要将它们转换为 0
和 1
。
2.3 模型测试
经过训练的二元分类模型的预测值是介于 0
和 1
之间的值,使用 torch.where()
方法能够将预测结果转换为 0
和 1
,如果预测的概率小于 0.5
,则该预测标签为 0
;否则,预测标签为 1
。然后,将这些预测值与实际标签进行比较,计算预测的准确率。接下来,使用训练好的模型对测试数据集进行预测。
import numpy as np
results=[]
for imgs,labels in binary_test_loader: # 遍历测试集中的所有批次
imgs=imgs.reshape(-1,28*28).to(device)
labels=torch.FloatTensor([x if x==0 else 1 for x in labels])
labels=(labels).reshape(-1,1).to(device)
preds=binary_model(imgs) # 使用训练好的模型进行预测
pred10=torch.where(preds>0.5,1,0)
correct=(pred10==labels) # 将预测结果与标签进行比较
results.append(correct.detach().cpu().numpy().mean())
accuracy=np.array(results).mean() # 计算测试集准确率
print(f"the accuracy of the predictions is {accuracy}")
3. 多类别分类
3.1 验证集和提前停止
在本节中,我们将使用 PyTorch
构建一个深度神经网络,将服装分类为 10
个类别之一。然后,使用 Fashion-MNIST
数据集训练该模型。最后,使用训练好的模型进行预测,并查看预测的准确率。首先,创建一个验证集,并定义一个提前停止类,以便我们可以决定何时停止训练。
当我们构建和训练一个深度神经网络时,可以选择多个超参数(例如学习率和训练的 epoch
数)。这些超参数会影响模型的性能,为了得到最佳的超参数,我们可以创建一个验证集,测试不同超参数下模型的性能。例如,在多类别分类中,创建一个验证集,以确定训练的最佳 epoch
数。
(1) 在本节中,将训练数据集的 60,000
个样本分为训练集( 50000
个样本)和验证集( 10000
个样本):
train_set,val_set=torch.utils.data.random_split(train_set, [50000, 10000])
(2) 使用 PyTorch
的 DataLoader
类将训练集、验证集和测试集转换为三个数据迭代器:
train_loader=torch.utils.data.DataLoader(
train_set,
batch_size=batch_size,
shuffle=True)
val_loader=torch.utils.data.DataLoader(
val_set,
batch_size=batch_size,
shuffle=True)
test_loader=torch.utils.data.DataLoader(
test_set,
batch_size=batch_size,
shuffle=True)
(2) 定义提前停止类 EarlyStop()
并创建该类的实例:
class EarlyStop:
def __init__(self, patience=10):
self.patience = patience # patience 值默认为 10
self.steps = 0
self.min_loss = float('inf')
def stop(self, val_loss): # 定义 stop() 方法
if val_loss < self.min_loss: # 如果达到新的最小损失,更新 min_loss 的值
self.min_loss = val_loss
self.steps = 0
elif val_loss >= self.min_loss: # 计算自上次最小损失以来经过了多少个 epoch
self.steps += 1
if self.steps >= self.patience:
return True
else:
return False
stopper=EarlyStop()
EarlyStop()
类用来判断在最近的 patience
个 epoch
中,模型在验证集上的损失是否停止改善。patience
的值表示自模型上次达到最小损失以来,希望训练的 epoch
数。stop()
方法记录最小损失值及自最小损失以来的训练 epoch
数,并将该数量与 patience
值进行比较,如果自最小损失以来的训练 epoch
数大于 patience
的值,则该方法返回 True
。
3.2 模型构建与训练
接下来,将学习如何使用 PyTorch
创建多类别分类神经网络并进行训练,并学习如何使用训练好的模型进行预测并评估预测的准确率。
(1) 创建一个多类别分类模型来对 Fashion MNIST
数据集中的服装进行分类:
model=nn.Sequential(
nn.Linear(28*28,256),
nn.ReLU(),
nn.Linear(256,128),
nn.ReLU(),
nn.Linear(128,64),
nn.ReLU(),
nn.Linear(64,10) # 输出层中有 10 个神经元
).to(device)
与二分类模型相比,多分类模型有所不同。首先,输出包含 10
个值,表示数据集中 10
种不同类型的服装。其次,将倒数第二层的神经元数量从 32
个改为 64
个。
(2) 使用 PyTorch
的 nn.CrossEntropyLoss()
类作为损失函数,nn.CrossEntropyLoss()
类将 nn.LogSoftmax()
和 nn.NLLLoss()
合并为一个类,计算输入的 logits
与目标值之间的交叉熵损失。如果在模型中使用 nn.LogSoftmax()
并使用 nn.NLLLoss()
作为损失函数,可以获得相同的结果。因此,nn.CrossEntropyLoss()
类会在输出上应用 softmax
激活函数,将输出压缩到 [0, 1]
的范围内,然后再进行对数运算。经过 softmax
激活后的 10
个数值的总和为 1
,可以将其解释为对应 10
种服装类型的概率。设置学习率为 0.001
,并使用 Adam
优化器:
lr=0.001
optimizer=torch.optim.Adam(model.parameters(),lr=lr)
loss_fn=nn.CrossEntropyLoss()
(3) 定义 train_epoch()
函数:
def train_epoch():
tloss=0
for n,(imgs,labels) in enumerate(train_loader):
imgs=imgs.reshape(-1,28*28).to(device)
labels=labels.reshape(-1,).to(device)
preds=model(imgs)
loss=loss_fn(preds,labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
tloss+=loss
return tloss/n
(4) 定义 val_epoch()
函数,使用模型对验证集中的图像进行预测,并计算每批数据的平均损失:
def val_epoch():
vloss=0
for n,(imgs,labels) in enumerate(val_loader):
imgs=imgs.reshape(-1,28*28).to(device)
labels=labels.reshape(-1,).to(device)
preds=model(imgs)
loss=loss_fn(preds,labels)
vloss+=loss
return vloss/n
(5) 训练多类别分类器:
for i in range(1,101):
tloss=train_epoch()
vloss=val_epoch()
print(f"at epoch {i}, tloss is {tloss}, vloss is {vloss}")
if stopper.stop(vloss)==True:
break
模型最多训练 100
个 epoch
。在每个 epoch
中,首先使用训练集训练模型。然后,计算验证集每个批次的平均损失,使用 EarlyStop()
类通过查看验证集中的损失来判断是否应该提前停止训练。如果在过去的 10
个 epoch
内损失没有改善,则训练就会停止。
(6) 模型的输出是一个包含 10
个数值的向量。使用 torch.argmax()
方法基于最大概率为每个数据样本分配一个标签。然后,将预测标签与实际标签进行比较:
plt.figure(dpi=300,figsize=(5,1))
# 绘制测试集中前五张图像及其标签
for i in range(5):
ax=plt.subplot(1,5, i + 1)
img=test_set[i][0]
label=test_set[i][1]
img=img/2+0.5
img=img.reshape(28, 28)
plt.imshow(img, cmap="binary")
plt.axis('off')
plt.title(text_labels[label]+f"; {label}", fontsize=8)
plt.show()
for i in range(5):
# 获取测试集中第 i 张图像及其标签
img,label = test_set[i]
img=img.reshape(-1,28*28).to(device)
pred=model(img) # 使用训练好的模型进行预测
index_pred=torch.argmax(pred,dim=1) # 使用 torch.argmax() 方法获取预测标签
idx=index_pred.item()
# 打印出实际标签和预测标签
print(f"the label is {label}; the prediction is {idx}")
使用训练好的模型对每件服装进行预测,预测结果是一个包含 10
个值的张量。torch.argmax()
方法返回张量中最大概率的位置,将其作为预测标签。最后,打印出实际标签和预测标签进行比较,观察预测是否正确。
上图显示了测试集中前五件服装分别为:踝靴、套头衫、裤子、裤子和衬衫,标签分别是 9
、2
、1
、1
和 6
,可以看到模型在五件服装上的预测都是正确的。
(7) 接下来,计算整个测试数据集的预测准确率:
import numpy as np
results=[]
# 遍历测试集中的所有批次
for imgs,labels in test_loader:
imgs=imgs.reshape(-1,28*28).to(device)
labels=(labels).reshape(-1,).to(device)
preds=model(imgs) # 使用训练好的模型进行预测
pred10=torch.argmax(preds,dim=1) # 将概率转换为预测标签
correct=(pred10==labels) # 将预测标签与实际标签进行比较
results.append(correct.detach().cpu().numpy().mean())
accuracy=np.array(results).mean() # 计算测试集上的准确率
print(f"the accuracy of the predictions is {accuracy}")
输出结果如下所示:
the accuracy of the predictions is 0.8941082802547771
遍历测试集中的所有服装,使用训练好的模型进行预测。然后,将预测结果与实际标签进行比较。经过测试,准确率约为 88%
,表明我们已经成功构建并训练了有效的深度学习模型。
小结
- 深度学习是一种使用深度人工神经网络来学习输入与输出数据之间关系的机器学习方法
ReLU
激活函数根据加权和决定一个神经元是否应该激活,为神经元的输出引入了非线性- 损失函数用于衡量机器学习模型的表现,模型的训练涉及就是调整参数以最小化损失函数
- 二分类是一个将样本数据分为两个类别之一的机器学习模型
- 多类别分类是一个将样本数据分为多种类别之一的机器学习模型
系列链接
PyTorch生成式人工智能实战:从零打造创意引擎
PyTorch生成式人工智能实战(1)——神经网络与模型训练过程详解
PyTorch生成式人工智能实战(2)——PyTorch基础