使用PyTorch演示​​实现神经网络过程

借助著名的开源PyTorch 框架,可以使用Python创建和训练神经网络。本教程将教您如何使用 PyTorch 创建基本神经网络并对 MNIST 数据集中的手写数字进行分类。

现代人工智能依赖于神经网络,神经网络赋予机器类似于人类的学习和判断能力。回归、分类和创建只是神经网络(作为计算机模型)在从输入中学习后可能执行的一些任务。流行的开源 PyTorch 框架可用于用 Python 设计和训练神经网络。在本教程中,您将学习如何使用 PyTorch 使用基本神经网络对 MNIST 数据集中的手写数字进行分类。

如何在 PyTorch 中创建神经网络?
通过 nn.Module 类或 nn.Sequential 容器,PyTorch 提供了两种构建神经网络的主要方法。如果您继承 nn.Module 类并实现 __init__ 和转发函数,您可以构建自己独特的网络。前向函数指定如何通过级别传输输入并作为输出返回,而 __init__ 方法则建立网络的层和参数。

如果您提供层列表作为参数,则 nn.Sequential 容器可让您建立网络。指定顺序后,图层会自动连接。

PyTorch 提供的几个模块和方法使 Python 中的神经网络实现变得简单:

  • 引入(导入)所有必需的模块,包括 torch、torch.nn 和 torch.optim。
  • 描述数据,包括目标标签和输入特征集。您可以构建自己的张量或利用 PyTorch 中的内置数据集。
  • 描述神经网络的架构,包括层数和类型、激活函数和输出大小。您可以子类 torch.nn.Module 来构造自己独特的层,也可以利用 PyTorch 预设层,例如 torch.nn.Linear、torch.nn.Conv2d或 torch.nn.LSTM。
  • 指定损失函数(torch.nn.MSELoss、torch.nn.CrossEntropyLoss、torch.nn.BCELoss等)。网络输出与目标的相似程度是通过损失函数来衡量的。
  • 指定优化器(torch.optim.SGD、torch.optim.Adam或 torch.optim.RMSprop)。利用梯度和学习率——优化器修改网络的权重。
  • 为了训练网络,运行前向和后向传递,并对数据应用循环优化器。通过发布损失或其他指标(例如准确性或精确度),您可以密切关注训练的进展情况。
  • 使用新数据(例如验证集或测试集)测试网络,以评估其性能。此外,torch.save 和 torch.load 允许您加载和保存网络状态。

为 MNIST 实现前馈神经网络
为了更好地理解,让我们看看如何在 PyTorch 中创建神经网络。请注意,这些只是简短的示例,您可以扩展和更改以满足您的需求;它们不是全面的解决方案。在此示例中,使用简单的前馈神经网络对 MNIST 数据集中的手写数字进行分类。

  • 我们在本例中定义了一个具有两个完全连接层的简单前馈神经网络。当权重矩阵和偏置向量用于链接每个输入和输出单元时,该层被称为完全链接。
  • 第一层在接收扁平化图片(28×28像素)作为输入后产生512个特征。第二层使用 512 个特征作为输入生成 10 个类别,即数字 0 到 9。
  • 为了生成完全链接的层并将它们提供为网络对象特征,我们利用 nn.Linear 类。为了赋予网络一些非线性并帮助其学习复杂模式的能力。我们还使用 F.relu 函数将ReLU激活函数应用于第一层。
  • 输入图像仅在前向方法中被展平,然后应用第一层 ReLU 函数和第二层。每个类别的 10 个 logits 的张量是网络输出。

第1步:导入必要的库

# Import the necessary libraries
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np

第 2 步:定义超参数和变换
所提供的代码定义了超参数和转换,以便在机器学习中应用于图像。超参数(包括 batch_size、num_epochs 和 learning_rate)被初始化,以控制训练过程。此外,还定义了一个转换管道 transform,用于预处理输入图像。该管道采用了两种连续变换:

  • transforms.ToTensor() 将图像转换为 PyTorch 张量,这是神经网络计算所需的格式;
  • transforms.Normalize() 则通过减去平均值(0.1307)并除以标准偏差(0.3081)使像素值标准化。

# Define the hyperparameters
batch_size = 64 # The number of samples per batch
num_epochs = 10 # The number of times to iterate over the whole dataset
learning_rate = 0.01 # The learning rate for the optimizer

# Define the transformation to apply to the images
transform = transforms.Compose([
    transforms.ToTensor(), # 将图像转换为张量
    transforms.Normalize((0.1307,), (0.3081,)) # 用平均值和 std 对像素值进行归一化处理
])

步骤 3:加载并准备数据集
所提供的代码会从网上加载 MNIST 数据集,该数据集由手写数字图像及其相应标签组成。它初始化了两个数据集:用于训练数据的 train_dataset 和用于测试数据的 test_dataset。

这两个数据集都配置了之前定义的转换,实现了图像张量转换和像素值归一化。

随后,创建数据加载器 train_loader 和 test_loader,以便在训练和测试阶段分别对数据进行批处理和洗牌。

# Load the MNIST dataset from the web
train_dataset = datasets.MNIST(root='.', train=True, download=True, transform=transform) # The training set
test_dataset = datasets.MNIST(root='.', train=False, download=True, transform=transform) # The test set

# 创建数据加载器,用于批处理和洗牌数据
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) # The training loader
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False) # The test loader


步骤 4:定义神经网络模型
我们使用 PyTorch 的 nn.Module 定义一个简单的神经网络类 Net。该网络由两个全连接层(fc1 和 fc2)组成。下面是代码的详细说明:

__init__(self):这是定义网络架构的构造方法。它使用 nn.Linear 初始化两个全连接层。第一层(fc1)接收大小为 28*28 的输入(假设输入图像为 28×28 像素,并平铺成一个向量),并输出 512 个特征。第二层(fc2)将第一层的 512 个特征作为输入,并输出 10 个类别(假设这是一个有 10 个类别的分类任务)。

forward(self, x):该方法定义网络的前向传递。它接收输入张量 x(代表一批图像)并执行以下操作:

  • 使用 x.view(-1, 28*28) 将输入张量扁平化为一个向量。
  • 使用 F.relu(self.fc1(x))将扁平化后的输入通过第一个全连接层 (fc1) 并应用 ReLU 激活函数。
  • 将第一层的输出通过第二层全连接层 (fc2) 得到最终输出 logits。

# Define the neural network model
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 网络有两个完全连接的层
        self.fc1 = nn.Linear(28*28, 512) # 第一层将扁平化图像作为输入,输出 512 个特征点
        self.fc2 = nn.Linear(512, 10) # 第二层将 512 个特征作为输入,输出 10 个类别

    def forward(self, x):
        # The forward pass of the network
        x = x.view(-1, 28*28) # Flatten the image into a vector
        x = F.relu(self.fc1(x)) # Apply the ReLU activation function to the first layer
        x = self.fc2(x) # Apply the second layer
        return x # Return the output logits


第 5 步:定义损失函数、优化器和模型实例
提供的代码段会初始化神经网络模型,将其移动到可用设备(CPU 或 GPU)上,并定义损失函数和优化器。

# 创建模型实例并将其移动到设备(CPU 或 GPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # Get the device
model = Net().to(device) # Move the model to the device
print(model) # Print the model summary

# Define the loss function and the optimizer
criterion = nn.CrossEntropyLoss() # The cross entropy loss for multi-class classification
optimizer = optim.SGD(model.parameters(), lr=learning_rate) # The stochastic gradient descent optimizer

# 定义一个计算模型精度的函数
def accuracy(outputs, labels):
    # The accuracy is the percentage of correct predictions
    _, preds = torch.max(outputs, 1) # 从输出日志中获取预测类别
    return torch.sum(preds == labels).item() / len(labels) # 返回正确预测的比率

输出:

Net(
  (fc1): Linear(in_features=784, out_features=512, bias=True)
  (fc2): Linear(in_features=512, out_features=10, bias=True)
)

第 6 步:定义训练和测试循环

  • train(model,device,train_loader,criterion,optimizer,epoch):该函数使用训练数据训练模型。它将模型设置为训练模式,循环处理来自 train_loader 的批次数据,将输入和标签移动到指定的设备,对模型执行前向传递以获得输出对数,使用指定的准则计算损失,执行后向传递以计算梯度,并使用指定的优化器更新模型参数。它还会打印各批次的平均损失和准确率。
  • test(model, device, test_loader, criterion):该函数使用测试数据对模型进行评估。它将模型设置为评估模式,循环处理来自 test_loader 的批次数据,将输入和标签移动到指定的设备,对模型执行前向传递以获取输出对数,使用指定的准则计算损耗,并打印批次的平均损耗和准确度。

# Define the training loop
def train(model, device, train_loader, criterion, optimizer, epoch):
    # 将模型设置为训练模式
    model.train()
    # Initialize the running loss and accuracy
    running_loss = 0.0
    running_acc = 0.0
    # 循环浏览批次数据
    for i, (inputs, labels) in enumerate(train_loader):
        将输入和标签移到设备上
        inputs = inputs.to(device)
        labels = labels.to(device)
        # 参数梯度归零
        optimizer.zero_grad()
        # Forward pass
        outputs = model(inputs) # 获取模型的输出对数
        loss = criterion(outputs, labels) # Calculate the loss
        # 后向传递和优化
        loss.backward() # 计算梯度
        optimizer.step() 更新参数
        # Print the statistics
        running_loss += loss.item() # Accumulate the loss累计损失
        running_acc += accuracy(outputs, labels) # Accumulate the accuracy
        if (i + 1) % 200 == 0: # Print every 200 batches
            print(f'Epoch {epoch}, Batch {i + 1}, Loss: {running_loss / 200:.4f}, Accuracy: {running_acc / 200:.4f}')
            running_loss = 0.0
            running_acc = 0.0

# Define the test loop
def test(model, device, test_loader, criterion):
    # 将模型设置为评估模式
    model.eval()
    # Initialize the loss and accuracy
    test_loss = 0.0
    test_acc = 0.0
    # Loop over the batches of data
    with torch.no_grad(): # No need to track the gradients
        for inputs, labels in test_loader:
            # Move the inputs and labels to the device
            inputs = inputs.to(device)
            labels = labels.to(device)
            # Forward pass
            outputs = model(inputs) # Get the output logits from the model
            loss = criterion(outputs, labels) # Calculate the loss
            # Print the statistics
            test_loss += loss.item() # Accumulate the loss
            test_acc += accuracy(outputs, labels) # Accumulate the accuracy
    # Print the average loss and accuracy
    print(f'Test Loss: {test_loss / len(test_loader):.4f}, Test Accuracy: {test_acc / len(test_loader):.4f}')

步骤 7:训练和测试模型,同时可视化一些样本图像和预测结果
该代码段将对模型进行指定次数的训练和测试,然后将一些样本图像及其预测结果可视化。

# Train and test the model for the specified number of epochs
for epoch in range(1, num_epochs + 1):
    train(model, device, train_loader, criterion, optimizer, epoch) # Train the model
    test(model, device, test_loader, criterion) # Test the model

可视化一些示例图像和预测
samples, labels = next(iter(test_loader)) # Get a batch of test data
samples = samples.to(device) # Move the samples to the device
outputs = model(samples) 获取模型的输出对数
_, preds = torch.max(outputs, 1) # 从输出日志中获取预测类别
samples = samples.cpu().numpy() # 将样本移回 CPU 并转换为 numpy 数组
fig, axes = plt.subplots(3, 3, figsize=(8, 8)) # 创建 3x3 网格的子绘图
for i, ax in enumerate(axes.ravel()):
    ax.imshow(samples[i].squeeze(), cmap='gray') # Plot the image
    ax.set_title(f'Label: {labels[i]}, Prediction: {preds[i]}') # Set the title
    ax.axis('off') # Hide the axes
plt.tight_layout() # Adjust the spacing调整间距
plt.show() # Show the plot


输出:

Epoch 1, Batch 200, Loss: 1.1144, Accuracy: 0.7486
Epoch 1, Batch 400, Loss: 0.4952, Accuracy: 0.8739
Epoch 1, Batch 600, Loss: 0.3917, Accuracy: 0.8903
Epoch 1, Batch 800, Loss: 0.3515, Accuracy: 0.9042
Test Loss: 0.3018, Test Accuracy: 0.9155
Epoch 2, Batch 200, Loss: 0.3067, Accuracy: 0.9123
Epoch 2, Batch 400, Loss: 0.2929, Accuracy: 0.9168
Epoch 2, Batch 600, Loss: 0.2878, Accuracy: 0.9185
Epoch 2, Batch 800, Loss: 0.2735, Accuracy: 0.9210
Test Loss: 0.2471, Test Accuracy: 0.9314
Epoch 3, Batch 200, Loss: 0.2580, Accuracy: 0.9256
Epoch 3, Batch 400, Loss: 0.2442, Accuracy: 0.9301
Epoch 3, Batch 600, Loss: 0.2354, Accuracy: 0.9338
Epoch 3, Batch 800, Loss: 0.2281, Accuracy: 0.9359
Test Loss: 0.2130, Test Accuracy: 0.9403
Epoch 4, Batch 200, Loss: 0.2149, Accuracy: 0.9403
Epoch 4, Batch 400, Loss: 0.2055, Accuracy: 0.9441
Epoch 4, Batch 600, Loss: 0.2050, Accuracy: 0.9395
Epoch 4, Batch 800, Loss: 0.2018, Accuracy: 0.9425
Test Loss: 0.1860, Test Accuracy: 0.9465
Epoch 5, Batch 200, Loss: 0.1925, Accuracy: 0.9464
Epoch 5, Batch 400, Loss: 0.1850, Accuracy: 0.9473
Epoch 5, Batch 600, Loss: 0.1813, Accuracy: 0.9481
Epoch 5, Batch 800, Loss: 0.1753, Accuracy: 0.9503
Test Loss: 0.1691, Test Accuracy: 0.9517
Epoch 6, Batch 200, Loss: 0.1719, Accuracy: 0.9521
Epoch 6, Batch 400, Loss: 0.1599, Accuracy: 0.9557
Epoch 6, Batch 600, Loss: 0.1627, Accuracy: 0.9521
Epoch 6, Batch 800, Loss: 0.1567, Accuracy: 0.9562
Test Loss: 0.1549, Test Accuracy: 0.9547
Epoch 7, Batch 200, Loss: 0.1441, Accuracy: 0.9620
Epoch 7, Batch 400, Loss: 0.1474, Accuracy: 0.9587
Epoch 7, Batch 600, Loss: 0.1447, Accuracy: 0.9601
Epoch 7, Batch 800, Loss: 0.1426, Accuracy: 0.9580
Test Loss: 0.1404, Test Accuracy: 0.9602
Epoch 8, Batch 200, Loss: 0.1360, Accuracy: 0.9627
Epoch 8, Batch 400, Loss: 0.1359, Accuracy: 0.9620
Epoch 8, Batch 600, Loss: 0.1304, Accuracy: 0.9631
Epoch 8, Batch 800, Loss: 0.1322, Accuracy: 0.9634
Test Loss: 0.1308, Test Accuracy: 0.9624
Epoch 9, Batch 200, Loss: 0.1152, Accuracy: 0.9690
Epoch 9, Batch 400, Loss: 0.1188, Accuracy: 0.9674
Epoch 9, Batch 600, Loss: 0.1303, Accuracy: 0.9637
Epoch 9, Batch 800, Loss: 0.1236, Accuracy: 0.9645
Test Loss: 0.1234, Test Accuracy: 0.9633
Epoch 10, Batch 200, Loss: 0.1112, Accuracy: 0.9679
Epoch 10, Batch 400, Loss: 0.1120, Accuracy: 0.9707
Epoch 10, Batch 600, Loss: 0.1158, Accuracy: 0.9681
Epoch 10, Batch 800, Loss: 0.1138, Accuracy: 0.9688
Test Loss: 0.1145, Test Accuracy: 0.9665

结论
这篇文章教我们如何使用 PyTorch 构建的基本神经网络识别 MNIST 数据集中的手写数字。我们还学习了如何使用 nn.Module 类、nn.Sequential 容器、损失函数、优化器和数据加载器在 PyTorch 中构建、训练和测试神经网络。你可以使用 PyTorch 这个强大且适应性强的框架来创建和测试不同的神经网络模型。