使用 PyTorch 创建您的第一个 ML 模型

通过从头开始构建分类模型来学习 PyTorch 基础知识。

使用 PyTorch 创建您的第一个 ML 模型
推荐:使用NSDT编辑器快速搭建3D应用场景

赋予动机

PyTorch是使用最广泛的基于Python的深度学习框架。它为所有机器学习体系结构和数据管道提供了巨大的支持。在本文中,我们将介绍所有框架基础知识,以帮助您开始实现算法。

所有机器学习实现都有 4 个主要步骤:

  • 数据处理
  • 模型架构
  • 训练循环
  • 评估

我们在 PyTorch 中实现我们自己的 MNIST 图像分类模型时会完成所有这些步骤。这将使您熟悉机器学习项目的一般流程。

进口

import torch
import torch.nn as nn
import torch.optim as  optim

from torch.utils.data import DataLoader

# Using MNIST dataset provided by PyTorch
from torchvision.datasets.mnist import MNIST
import torchvision.transforms as transforms

# Import Model implemented in a different file
from model import Classifier

import matplotlib.pyplot as plt

torch.nn 模块提供对神经网络架构的支持,并具有针对流行层(如密集层、卷积神经网络等)的内置实现。

torch.optim 为优化器提供了实现,如随机梯度下降和 Adam。

其他实用程序模块可用于数据处理支持和转换。稍后我们将更详细地介绍每个。

声明超参数

在适当的情况下,将进一步解释每个超参数。但是,最佳做法是在文件顶部声明它们,以便于更改和理解。

INPUT_SIZE = 784	# Flattened 28x28 images
NUM_CLASSES = 10	# 0-9 hand-written digits.
BATCH_SIZE = 128	# Using Mini-Batches for Training
LEARNING_RATE = 0.01	# Opitimizer Step
NUM_EPOCHS = 5  	# Total Training Epochs

数据加载和转换

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 x 28 像素,数据集包含 60000 张训练图像和 10000 张测试图像。

我们分别加载训练数据集和测试数据集,由 MNIST 初始化函数中的 train 参数表示。根参数声明要在其中下载数据集的目录。

但是,我们还传递了一个额外的转换参数。对于 PyTorch,所有输入和输出都应该是 Torch.Tensor 格式。这相当于 numpy 中的 numpy.ndarray。这种张量格式为数据操作提供了额外的支持。但是,我们从中加载的 MNIST 数据位于 PIL 中。图像格式。我们需要将图像转换为兼容 PyTorch 的张量。因此,我们传递以下转换:

data_transforms = transforms.Compose([
    	transforms.ToTensor(),
    	transforms.Lambda(lambda x: torch.flatten(x))
])

ToTensor() 变换将图像转换为张量格式。接下来,我们传递一个额外的 Lambda 转换。Lambda 函数允许我们实现自定义转换。在这里,我们声明一个函数来扁平化输入。图像的大小为 28x28,但是,我们将它们展平,即将它们转换为大小为 28x28 或 784 的一维数组。当我们稍后实现我们的模型时,这将很重要。

撰写函数按顺序组合所有转换。首先,将数据转换为张量格式,然后展平为一维数组。

将数据分成几批

出于计算和训练目的,我们不能一次将完整的数据集传递到模型中。我们需要将数据集划分为小批量,这些批次将按顺序提供给模型。这允许更快的训练,并为我们的数据集增加随机性,这有助于稳定的训练。

PyTorch 为批处理数据提供了内置支持。来自火炬的数据加载器类。UTILS模块可以创建成批的数据,给定一个Torch数据集模块。如上所述,我们已经加载了数据集。

train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)

我们将数据集传递给我们的数据加载器,并将我们的batch_size超参数作为初始化参数。这将创建一个可迭代的数据加载器,因此我们可以使用简单的 for 循环轻松迭代每个批处理。

我们最初的图像大小为(784,),带有单个相关标签。然后,批处理将不同的图像和标签组合成一个批处理。例如,如果我们的批大小为 64,则批次中的输入大小将变为 (64, 784),并且每个批次将有 64 个关联的标签。

我们还对训练批次进行洗牌,这会更改每个时期的训练批次中的图像。它允许稳定的训练和更快的模型参数收敛。

定义我们的分类模型

我们使用由 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):
    	  # Pass Input Sequentially through each dense layer and activation
    	  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__和转发。

在 __init__ 方法中,我们声明模型将使用的所有层。我们使用 PyTorch 提供的线性(也称为密集)层。第一层将输入映射到512个神经元。我们可以将input_size作为模型参数传递,因此我们以后也可以将其用于不同大小的输入。第二层将512个神经元映射到256个神经元。第三个隐藏层将前一层的 256 个神经元映射到 128 个神经元。然后,最后一层最终减小到输出大小。我们的输出大小将是大小为 (10, ) 的张量,因为我们预测了十个不同的数字。


从零到英雄:使用 PyTorch 创建您的第一个 ML 模型


图片来源:作者

此外,我们在模型中初始化了一个ReLU激活层以实现非线性。

转发函数接收图像,我们提供用于处理输入的代码。我们使用声明的层,并按顺序将输入传递到每一层,中间有一个 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),每次都优化我们的模型。但是,我们已将数据分成几批。然后,对于每个纪元,我们还必须迭代每个批次。其代码如下所示:

for epoch in range(NUM_EPOCHS):
    	for batch in iter(train_dataloader):
          # Train the Model for each batch.

现在,我们可以在给定单个输入批处理的情况下训练模型。我们的批次由图像和标签组成。首先,我们必须将两者分开。我们的模型只需要图像作为输入来进行预测。然后,我们将预测与真实标签进行比较,以估计模型的性能。

for epoch in range(NUM_EPOCHS):
    	for batch in iter(train_dataloader):
        	images, labels = batch # Separate inputs and labels
        	# Convert Tensor Hardware Devices to either GPU or CPU
        	images = images.to(DEVICE)
        	labels = labels.to(DEVICE)
       	 
        	# Calls the model.forward() function to generate predictions 
        	predictions = model(images)

我们将这批图像直接传递给模型,该模型将由模型中定义的前向函数处理。一旦我们有了预测,我们就可以优化我们的模型权重。

优化代码如下所示:

# Calculate Cross Entropy Loss
loss = criterion(predictions, labels)
# Clears gradient values from previous batch
optimizer.zero_grad()
# Computes backprop gradient based on the loss
loss.backward()
# Optimizes the model weights
optimizer.step()

使用上面的代码,我们可以计算所有的反向传播梯度,并使用 Adam 优化器优化模型权重。上述所有代码组合在一起可以训练我们的模型趋同。

完整的训练循环如下所示:

for epoch in range(NUM_EPOCHS):
    	total_epoch_loss = 0
    	steps = 0
    	for batch in iter(train_dataloader):
        	images, labels = batch # Separate inputs and labels
        	# Convert Tensor Hardware Devices to either GPU or CPU
        	images = images.to(DEVICE)
        	labels = labels.to(DEVICE)
       	 
        	# Calls the model.forward() function to generate predictions       	 
        	predictions = model(images)
       	 
        	# Calculate Cross Entropy Loss
        	loss = criterion(predictions, labels)
        	# Clears gradient values from previous batch
        	optimizer.zero_grad()
        	# Computes backprop gradient based on the loss
        	loss.backward()
        	# Optimizes the model weights
        	optimizer.step()
       	 
        	steps += 1
        	total_epoch_loss += loss.item()
       	 
    	print(f'Epoch: {epoch + 1} / {NUM_EPOCHS}: Average Loss: {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)
   	 
    	# Taking the predicted label with highest probability
    	predictions = torch.argmax(predictions, dim=1)
   	 
    	correct_predictions += (predictions == labels).sum().item()
    	total_predictions += labels.shape[0]
    
print(f"\nTEST ACCURACY: {((correct_predictions / total_predictions) * 100):.2f}")

与训练循环类似,我们迭代测试数据集中的每个批次进行评估。我们为输入生成预测。但是,对于评估,我们只需要概率最高的标签。argmax 函数提供此功能来获取预测数组中具有最高值的值的索引。

对于准确率分数,我们可以比较预测的标签是否与真实的目标标签匹配。然后,我们计算正确标签数除以预测标签总数的准确性。

结果

我只训练了五个时期的模型,测试准确率超过96%,而训练前的准确率为10%。下图显示了训练五个 epoch 后的模型预测。


从零到英雄:使用 PyTorch 创建您的第一个 ML 模型


你有它。您现在已经从头开始实现了一个模型,该模型可以仅区分手写数字和图像像素值。

这绝不是 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):
    	  # Pass Input Sequentially through each dense layer and activation
    	  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

# Using MNIST dataset provided by PyTorch
from torchvision.datasets.mnist import MNIST
import torchvision.transforms as transforms

# Import Model implemented in a different file
from model import Classifier

import matplotlib.pyplot as plt

if __name__ == "__main__":
    
	INPUT_SIZE = 784	# Flattened 28x28 images
	NUM_CLASSES = 10	# 0-9 hand-written digits.
	BATCH_SIZE = 128	# Using Mini-Batches for Training
	LEARNING_RATE = 0.01	# Opitimizer Step
	NUM_EPOCHS = 5  	# Total Training Epochs

	DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
    
	# Will be used to convert Images to PyTorch Tensors
	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 # Separate inputs and labels
        	# Convert Tensor Hardware Devices to either GPU or CPU
        	images = images.to(DEVICE)
        	labels = labels.to(DEVICE)
       	 
        	# Calls the model.forward() function to generate predictions       	 
        	predictions = model(images)
       	 
        	# Calculate Cross Entropy Loss
        	loss = criterion(predictions, labels)
        	# Clears gradient values from previous batch
        	optimizer.zero_grad()
        	# Computes backprop gradient based on the loss
        	loss.backward()
        	# Optimizes the model weights
        	optimizer.step()
       	 
        	steps += 1
        	total_epoch_loss += loss.item()
       	 
    	print(f'Epoch: {epoch + 1} / {NUM_EPOCHS}: Average Loss: {total_epoch_loss / steps}')
	# Save Trained Model
	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)
   	 
    	# Taking the predicted label with highest probability
    	predictions = torch.argmax(predictions, dim=1)
   	 
    	correct_predictions += (predictions == labels).sum().item()
    	total_predictions += labels.shape[0]
    
	print(f"\nTEST ACCURACY: {((correct_predictions / total_predictions) * 100):.2f}")
    
    
    
	# --  Code For Plotting Results  -- #
    
	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: {prediction.item()}')
	plt.show()

3D建模学习工作室 整理翻译,转载请注明出处!

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号