从零到英雄:用PyTorch创建你的第一个机器学习模型
用PyTorch创建你的第一个机器学习模型
动机
PyTorch是最广泛使用的基于Python的深度学习框架。它为所有机器学习架构和数据流水线提供了巨大的支持。在本文中,我们将介绍所有框架基础知识,以帮助您开始实现自己的算法。
- 认识Verba:一个开源工具,用于构建您自己的RAG检索增强生成流水线并利用LLM进行基于内部的输出
- 索尼研究人员提出了BigVSAN:通过GAN-Based Vocoders中的切片对抗网络彻底改变音频质量
- “认识ResFields:一种新颖的人工智能方法,克服了时空神经场在有效建模长期和复杂时间信号方面的局限性”
所有机器学习实现都有4个主要步骤:
- 数据处理
- 模型架构
- 训练循环
- 评估
我们在PyTorch中实现自己的MNIST图像分类模型时将经历所有这些步骤。这将使您熟悉机器学习项目的一般流程。
导入
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
# 使用PyTorch提供的MNIST数据集
from torchvision.datasets.mnist import MNIST
import torchvision.transforms as transforms
# 从另一个文件中导入实现的模型
from model import Classifier
import matplotlib.pyplot as plt
torch.nn模块提供了对神经网络架构的支持,并具有内置的常用层的实现,例如Dense Layers、卷积神经网络等等。
torch.optim提供了诸如随机梯度下降和Adam等优化器的实现。
其他实用模块可用于数据处理支持和转换。我们稍后将详细介绍每个模块。
声明超参数
每个超参数在适当的位置将进一步解释。然而,最佳实践是在文件顶部声明它们,以便易于更改和理解。
INPUT_SIZE = 784 # 展平的28x28图像
NUM_CLASSES = 10 # 0-9手写数字。
BATCH_SIZE = 128 # 使用小批量进行训练
LEARNING_RATE = 0.01 # 优化器步长
NUM_EPOCHS = 5 # 总训练轮数
数据加载和转换
data_transforms = transforms.Compose([
transforms.ToTensor(),
transforms.Lambda(lambda x: torch.flatten(x))
])
train_dataset = MNIST(root=".data/", train=True, download=True, transform=data_transforms)
test_dataset = MNIST(root=".data/", train=False, download=True, transform=data_transforms)
MNIST是一个流行的图像分类数据集,默认在PyTorch中提供。它包含10个手写数字的灰度图像,从0到9。每个图像的大小为28×28像素,数据集包含60000个训练图像和10000个测试图像。
我们分别加载训练和测试数据集,通过MNIST初始化函数中的train参数来指示。root参数声明要下载数据集的目录。
然而,我们还传递了一个额外的transform参数。对于PyTorch,所有输入和输出都应该是Torch.Tensor格式。这相当于numpy中的numpy.ndarray。该张量格式提供了对数据操作的额外支持。然而,我们从中加载的MNIST数据处于PIL.Image格式中。我们需要将图像转换为PyTorch兼容的张量。因此,我们传递以下转换:
data_transforms = transforms.Compose([
transforms.ToTensor(),
transforms.Lambda(lambda x: torch.flatten(x))
])
ToTensor()转换将图像转换为张量格式。接下来,我们传递了一个额外的Lambda转换。Lambda函数允许我们实现自定义转换。在这里,我们声明一个将输入展平的函数。图像的大小为28×28,但是我们将其展平,即将其转换为大小为28×28或784的一维数组。这在我们实现模型时将非常重要。
Compose函数按顺序组合所有转换。首先将数据转换为张量格式,然后将其展平为一维数组。
将数据分成批次
为了计算和训练的目的,我们不能一次将完整的数据集传入模型中。我们需要将数据集分成小批次,按顺序将其馈送给模型。这样可以加快训练速度,并为数据集添加随机性,有助于稳定的训练。
PyTorch提供了对数据进行分批处理的内置支持。来自torch.utils模块的DataLoader类可以根据torch数据集模块创建数据批次。正如上面所述,我们已经加载了数据集。
train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)
我们将数据集传递给数据加载器,并将批次大小超参数作为初始化参数。这将创建一个可迭代的数据加载器,因此我们可以使用简单的for循环轻松地迭代每个批次。
我们初始的图像大小为(784, ),带有一个关联的标签。分批处理将不同的图像和标签组合在一起。例如,如果我们的批次大小为64,则批次中的输入大小将变为(64, 784),并且我们将为每个批次有64个关联的标签。
我们还对训练批次进行了洗牌操作,这会在每个epoch内更改批次中的图像顺序。这样可以实现稳定的训练,并加快模型参数的收敛速度。
定义我们的分类模型
我们使用了一个由3个隐藏层组成的简单实现。尽管简单,但这可以让您对组合不同层以实现更复杂实现有一个基本的理解。
如上所述,我们有一个大小为(784, )的输入张量和10个不同的输出类别,分别对应数字0-9。
** 对于模型实现,我们可以忽略批处理维度。
import torch
import torch.nn as nn
class Classifier(nn.Module):
def __init__(
self,
input_size:int,
num_classes:int
) -> None:
super().__init__()
self.input_layer = nn.Linear(input_size, 512)
self.hidden_1 = nn.Linear(512, 256)
self.hidden_2 = nn.Linear(256, 128)
self.output_layer = nn.Linear(128, num_classes)
self.activation = nn.ReLU()
def forward(self, x):
# 通过每个全连接层和激活函数对输入进行顺序处理
x = self.activation(self.input_layer(x))
x = self.activation(self.hidden_1(x))
x = self.activation(self.hidden_2(x))
return self.output_layer(x)
首先,模型必须继承torch.nn.Module类。这提供了神经网络架构的基本功能。然后,我们必须实现两个方法,__init__和forward。
在__init__方法中,我们声明模型将使用的所有层。我们使用PyTorch提供的Linear(也称为Dense)层。第一层将输入映射到512个神经元。我们可以将input_size作为模型参数传递,这样我们以后可以将其用于不同大小的输入。第二层将512个神经元映射到256个神经元。第三个隐藏层将前一层的256个神经元映射到128个神经元。最后一层最终缩小到输出大小。我们的输出大小将是一个大小为(10, )的张量,因为我们要预测十个不同的数字。
此外,我们在模型中初始化了一个ReLU激活层,用于非线性。
forward函数接收图像,并提供处理输入的代码。我们使用声明的层,并顺序地将输入通过每个层,使用中间的ReLU激活层。
在我们的主要代码中,我们可以初始化模型,并为数据集提供输入和输出大小。
model = Classifier(input_size=784, num_classes=10)
model.to(DEVICE)
初始化后,我们更改模型设备(可以是CUDA GPU或CPU)。我们在初始化超参数时检查了设备。现在,我们必须手动更改张量和模型层的设备。
训练循环
首先,我们必须声明我们将用于优化模型参数的损失函数和优化器。
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
首先,我们必须声明我们将用于优化模型参数的损失函数和优化器。
我们使用交叉熵损失函数,它主要用于多标签分类模型。它首先对预测结果应用softmax,并计算给定的目标标签和预测值之间的差异。
Adam优化器是最常用的优化器函数,它允许稳定的梯度下降以达到收敛。它是现在的默认优化器选择,并提供了令人满意的结果。 我们将模型参数作为参数传递,表示将要优化的权重。
对于我们的训练循环,我们逐步构建并填充缺失的部分,以便我们能够理解。
作为起点,我们多次迭代完整的数据集(称为epoch),并在每次迭代中优化模型。然而,我们将数据分成了批次。然后,对于每个epoch,我们还必须迭代每个批次。以下是此代码的样子:
for epoch in range(NUM_EPOCHS):
for batch in iter(train_dataloader):
# 对每个批次训练模型。
现在,我们可以训练模型,给定单个输入批次。我们的批次包含图像和标签。首先,我们必须分别提取它们。我们的模型只需要图像作为输入进行预测。然后,我们将预测结果与真实标签进行比较,以估计模型的性能。
for epoch in range(NUM_EPOCHS):
for batch in iter(train_dataloader):
images, labels = batch # 分离输入和标签
# 将张量硬件设备转换为GPU或CPU
images = images.to(DEVICE)
labels = labels.to(DEVICE)
# 调用model.forward()函数生成预测结果
predictions = model(images)
我们将图像批次直接传递给模型,模型将通过其定义的前向函数进行处理。一旦我们有了预测结果,我们就可以优化模型的权重。
优化代码如下所示:
# 计算交叉熵损失
loss = criterion(predictions, labels)
# 清除先前批次的梯度值
optimizer.zero_grad()
# 根据损失计算反向传播梯度
loss.backward()
# 优化模型权重
optimizer.step()
使用以上代码,我们可以计算所有反向传播梯度,并使用Adam优化器优化模型权重。所有以上代码的组合可以使我们的模型训练达到收敛。
完整的训练循环如下所示:
for epoch in range(NUM_EPOCHS):
total_epoch_loss = 0
steps = 0
for batch in iter(train_dataloader):
images, labels = batch # 分离输入和标签
# 将张量硬件设备转换为GPU或CPU
images = images.to(DEVICE)
labels = labels.to(DEVICE)
# 调用model.forward()函数生成预测结果
predictions = model(images)
# 计算交叉熵损失
loss = criterion(predictions, labels)
# 清除先前批次的梯度值
optimizer.zero_grad()
# 根据损失计算反向传播梯度
loss.backward()
# 优化模型权重
optimizer.step()
steps += 1
total_epoch_loss += loss.item()
print(f'Epoch: {epoch + 1} / {NUM_EPOCHS}: 平均损失: {total_epoch_loss / steps}')
损失逐渐减少并接近0。然后,我们可以在最初声明的测试数据集上评估模型。
评估模型性能
for batch in iter(test_dataloader):
images, labels = batch
images = images.to(DEVICE)
labels = labels.to(DEVICE)
predictions = model(images)
# 取概率最高的预测标签
predictions = torch.argmax(predictions, dim=1)
correct_predictions += (predictions == labels).sum().item()
total_predictions += labels.shape[0]
print(f"\n测试准确率: {((correct_predictions / total_predictions) * 100):.2f}")
与训练循环类似,我们迭代测试数据集中的每个批次进行评估。我们为输入生成预测。然而,对于评估,我们只需要具有最高概率的标签。argmax函数提供此功能,以获取预测数组中具有最高值的值的索引。
对于准确率分数,我们可以比较预测的标签是否与真实目标标签匹配。然后,我们计算正确标签数量除以总预测标签的准确率。
结果
我只对模型进行了五个时期的训练,并获得了超过96%的测试准确率,而在训练之前准确率为10%。下图显示了训练五个时期后的模型预测。
就是这样。您现在已经从头开始实现了一个模型,它可以根据图像像素值区分手写数字。
这绝不是PyTorch的全面指南,但它确实为您提供了机器学习项目中的结构和数据流的一般理解。这仍然是足够的知识,可以让您开始实现深度学习中的最先进架构。
完整代码
完整代码如下:
model.py:
import torch
import torch.nn as nn
class Classifier(nn.Module):
def __init__(
self,
input_size:int,
num_classes:int
) -> None:
super().__init__()
self.input_layer = nn.Linear(input_size, 512)
self.hidden_1 = nn.Linear(512, 256)
self.hidden_2 = nn.Linear(256, 128)
self.output_layer = nn.Linear(128, num_classes)
self.activation = nn.ReLU()
def forward(self, x):
# 通过每个密集层和激活依次传递输入
x = self.activation(self.input_layer(x))
x = self.activation(self.hidden_1(x))
x = self.activation(self.hidden_2(x))
return self.output_layer(x)
main.py
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
# 使用PyTorch提供的MNIST数据集
from torchvision.datasets.mnist import MNIST
import torchvision.transforms as transforms
# 从不同文件中导入实现的模型
from model import Classifier
import matplotlib.pyplot as plt
if __name__ == "__main__":
INPUT_SIZE = 784 # 展平的28x28图像
NUM_CLASSES = 10 # 0-9手写数字。
BATCH_SIZE = 128 # 使用小批量进行训练
LEARNING_RATE = 0.01 # 优化器步骤
NUM_EPOCHS = 5 # 总训练时期数
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
# 将图像转换为PyTorch张量
data_transforms = transforms.Compose([
transforms.ToTensor(),
transforms.Lambda(lambda x: torch.flatten(x))
])
train_dataset = MNIST(root=".data/", train=True, download=True, transform=data_transforms)
test_dataset = MNIST(root=".data/", train=False, download=True, transform=data_transforms)
train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)
model = Classifier(input_size=784, num_classes=10)
model.to(DEVICE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
for epoch in range(NUM_EPOCHS):
total_epoch_loss = 0
steps = 0
for batch in iter(train_dataloader):
images, labels = batch # 分离输入和标签
# 将张量硬件设备转换为GPU或CPU
images = images.to(DEVICE)
labels = labels.to(DEVICE)
# 调用model.forward()函数生成预测
predictions = model(images)
# 计算交叉熵损失
loss = criterion(predictions, labels)
# 清除先前批次的梯度值
optimizer.zero_grad()
# 基于损失计算反向传播梯度
loss.backward()
# 优化模型权重
optimizer.step()
steps += 1
total_epoch_loss += loss.item()
print(f'Epoch: {epoch + 1} / {NUM_EPOCHS}: Average Loss: {total_epoch_loss / steps}')
# 保存训练好的模型
torch.save(model.state_dict(), 'trained_model.pth')
model.eval()
correct_predictions = 0
total_predictions = 0
for batch in iter(test_dataloader):
images, labels = batch
images = images.to(DEVICE)
labels = labels.to(DEVICE)
predictions = model(images)
# 取具有最高概率的预测标签
predictions = torch.argmax(predictions, dim=1)
correct_predictions += (predictions == labels).sum().item()
total_predictions += labels.shape[0]
print(f"\n测试准确率:{((correct_predictions / total_predictions) * 100):.2f}")
# -- 绘制结果的代码 -- #
batch = next(iter(test_dataloader))
images, labels = batch
fig, ax = plt.subplots(nrows=1, ncols=4, figsize=(16,8))
for i in range(4):
image = images[i]
prediction = torch.softmax(model(image), dim=0)
prediction = torch.argmax(prediction, dim=0)
# print(type(prediction), type(prediction.item()))
ax[i].imshow(image.view(28,28))
ax[i].set_title(f'预测:{prediction.item()}')
plt.show()
Muhammad Arham(穆罕默德·阿尔汉姆) 是一名从事计算机视觉和自然语言处理的深度学习工程师。他曾在Vyro.AI参与了多个生成式人工智能应用的部署和优化工作,并在全球排行榜上取得了巨大成功。他对构建和优化智能系统的机器学习模型充满兴趣,并坚信不断改进。