第五章:PyTorch 实战应用


文档摘要

第五章:PyTorch 实战应用 第五章:PyTorch 实战应用 在之前的章节中,我们已经系统地学习了PyTorch的基础知识,包括张量操作、自动求导机制、神经网络构建以及常用模块的使用。现在,我们来到了激动人心的第五章——PyTorch实战应用。本章将带领大家走出理论的象牙塔,深入到实际问题的解决中,通过一系列精心设计的案例,让您切身体验PyTorch在各个领域的强大威力。 本章将涵盖以下核心内容: 图像分类实战: 构建卷积神经网络 (CNN) 模型,并在经典图像数据集 (如 CIFAR-10) 上进行训练和评估,深入理解图像识别任务的流程。

第五章:PyTorch 实战应用

第五章:PyTorch 实战应用

在之前的章节中,我们已经系统地学习了PyTorch的基础知识,包括张量操作、自动求导机制、神经网络构建以及常用模块的使用。现在,我们来到了激动人心的第五章——PyTorch实战应用。本章将带领大家走出理论的象牙塔,深入到实际问题的解决中,通过一系列精心设计的案例,让您切身体验PyTorch在各个领域的强大威力。

本章将涵盖以下核心内容:

  • 图像分类实战: 构建卷积神经网络 (CNN) 模型,并在经典图像数据集 (如 CIFAR-10) 上进行训练和评估,深入理解图像识别任务的流程。

  • 文本分类实战: 利用循环神经网络 (RNN) 或 Transformer 模型,处理文本数据,完成情感分析或文本分类任务,掌握自然语言处理的基本技巧。

  • 序列预测实战: 探索时间序列数据的预测问题,例如股票价格预测或天气预报,学习如何使用 RNN 或 LSTM 网络进行序列建模。

  • 生成对抗网络 (GANs) 初探: 了解 GANs 的基本原理,并尝试构建简单的 GAN 模型,生成图像或文本数据,感受生成模型的魅力。

本章的重点在于实践,我们将结合代码示例、详细解释以及可视化工具,帮助您将所学知识转化为解决实际问题的能力。让我们开始实战之旅吧!

5.1 图像分类实战:CIFAR-10 图像识别

图像分类是计算机视觉领域最基础也是最重要的任务之一。本节我们将使用 PyTorch 构建一个卷积神经网络 (CNN),并在 CIFAR-10 数据集上训练一个图像分类器。CIFAR-10 数据集包含 10 个类别的共 60000 张 32x32 彩色图像,每个类别有 6000 张图像。我们的目标是训练一个模型,能够准确地将图像分类到对应的类别中。

5.1.1 数据准备:CIFAR-10 数据集加载与预处理

首先,我们需要加载 CIFAR-10 数据集并进行预处理。PyTorch 的 torchvision 模块提供了方便的数据集和数据转换工具。

import torch import torchvision import torchvision.transforms as transforms import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import matplotlib.pyplot as plt import numpy as np # 定义数据转换 transform = transforms.Compose( [transforms.ToTensor(), # 将 PIL Image 或 numpy.ndarray 转换为 tensor,并将像素值缩放到 [0, 1] transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 标准化处理,均值和标准差都设为 0.5,将像素值缩放到 [-1, 1] # 加载训练集和测试集 trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') # 可视化部分训练数据 def imshow(img): img = img / 2 + 0.5 # 反标准化,将像素值恢复到 [0, 1] 范围 npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) # 将通道维度放到最后,符合 matplotlib 的图像显示格式 plt.show() # 获取一批训练数据 dataiter = iter(trainloader) images, labels = next(dataiter) # 显示图像 imshow(torchvision.utils.make_grid(images)) # 打印标签 print(' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))

代码详解:

  1. 数据转换 transforms.Compose:

    • transforms.ToTensor(): 将图像数据转换为 PyTorch 张量,并将像素值从 [0, 255] 缩放到 [0, 1] 范围。这是因为神经网络通常在较小的数值范围内工作效果更好。

    • transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)): 对图像进行标准化处理。标准化可以将数据分布调整到均值为 0,标准差为 1 的正态分布附近,有助于模型训练的稳定性和收敛速度。这里我们使用 CIFAR-10 数据集的近似均值和标准差 (实际上 CIFAR-10 的均值和标准差略有不同,但这里为了简化示例,使用了常用的 0.5)。

  2. 数据集加载 torchvision.datasets.CIFAR10:

    • root='./data': 指定数据集的下载路径。如果数据集不存在,PyTorch 会自动下载。

    • train=True/False: 指定加载训练集或测试集。

    • download=True: 如果数据集不存在,则下载。

    • transform=transform: 应用之前定义的数据转换。

  3. 数据加载器 torch.utils.data.DataLoader:

    • trainset/testset: 指定要加载的数据集。

    • batch_size=4: 设置批量大小为 4。每次迭代训练,模型将处理 4 张图像。

    • shuffle=True/False: 训练集设置为 shuffle=True,表示在每个 epoch 开始时打乱数据顺序,有助于模型学习更鲁棒的特征。测试集设置为 shuffle=False,保持数据顺序不变。

    • num_workers=2: 使用 2 个子进程加载数据,加快数据加载速度。

  4. 类别名称 classes: 定义 CIFAR-10 数据集的 10 个类别名称,方便后续结果的可视化。

  5. 图像显示 imshow 函数:

    • img = img / 2 + 0.5: 反标准化操作,将像素值从 [-1, 1] 恢复到 [0, 1] 范围,以便正确显示图像。

    • npimg = img.numpy(): 将 PyTorch 张量转换为 NumPy 数组,方便使用 matplotlib 显示。

    • plt.imshow(np.transpose(npimg, (1, 2, 0))): 使用 matplotlib 显示图像。np.transpose(npimg, (1, 2, 0)) 将通道维度从第一个维度移动到最后一个维度,因为 PyTorch 张量的通道维度在第一个,而 matplotlib 显示图像时通道维度通常在最后一个。

运行这段代码,您将下载 CIFAR-10 数据集,并看到一批训练图像以及它们对应的标签。

5.1.2 模型构建:卷积神经网络 (CNN)

接下来,我们定义一个简单的卷积神经网络 (CNN) 模型用于图像分类。

class Net(nn.Module): def __init__(self): super().__init__() # 卷积层 1:输入通道 3 (彩色图像),输出通道 6,卷积核 5x5 self.conv1 = nn.Conv2d(3, 6, 5) # 池化层 1:最大池化,池化窗口 2x2,步长 2 self.pool = nn.MaxPool2d(2, 2) # 卷积层 2:输入通道 6,输出通道 16,卷积核 5x5 self.conv2 = nn.Conv2d(6, 16, 5) # 全连接层 1:输入特征 16 * 5 * 5 (经过两次池化后图像尺寸变为 5x5),输出特征 120 self.fc1 = nn.Linear(16 * 5 * 5, 120) # 全连接层 2:输入特征 120,输出特征 84 self.fc2 = nn.Linear(120, 84) # 全连接层 3:输入特征 84,输出特征 10 (CIFAR-10 类别数) self.fc3 = nn.Linear(84, 10) def forward(self, x): # 卷积层 1 -> ReLU 激活 -> 池化层 1 x = self.pool(F.relu(self.conv1(x))) # 卷积层 2 -> ReLU 激活 -> 池化层 2 x = self.pool(F.relu(self.conv2(x))) # 将多维特征图展平成一维向量 x = torch.flatten(x, 1) # flatten all dimensions except batch # 全连接层 1 -> ReLU 激活 x = F.relu(self.fc1(x)) # 全连接层 2 -> ReLU 激活 x = F.relu(self.fc2(x)) # 全连接层 3 (输出层,无需激活函数,因为 CrossEntropyLoss 内部自带 Softmax) x = self.fc3(x) return x net = Net() print(net)

模型结构可视化 (mermaid graph TD):

代码详解:

  1. class Net(nn.Module): 定义一个名为 Net 的类,继承自 nn.Module,表示这是一个神经网络模型。

  2. __init__(self): 模型的构造函数,用于定义模型的各个层。

    • self.conv1 = nn.Conv2d(3, 6, 5): 定义第一个卷积层。

      • nn.Conv2d: 二维卷积层。

      • 3: 输入通道数,CIFAR-10 是彩色图像,所以输入通道数为 3 (RGB)。

      • 6: 输出通道数,卷积层输出 6 个特征图。

      • 5: 卷积核大小为 5x5。

    • self.pool = nn.MaxPool2d(2, 2): 定义最大池化层。

      • nn.MaxPool2d: 最大池化层。

      • (2, 2): 池化窗口大小为 2x2。

      • 2: 步长为 2。

    • self.conv2 = nn.Conv2d(6, 16, 5): 定义第二个卷积层,输入通道数为 6 (来自 conv1 的输出),输出通道数为 16。

    • self.fc1 = nn.Linear(16 * 5 * 5, 120): 定义第一个全连接层。

      • nn.Linear: 全连接层。

      • 16 * 5 * 5: 输入特征数。经过两次池化后,图像尺寸从 32x32 变为 (32-4)/2/2 = 5x5,通道数为 16,所以总特征数为 16 * 5 * 5。

      • 120: 输出特征数。

    • self.fc2 = nn.Linear(120, 84): 定义第二个全连接层。

    • self.fc3 = nn.Linear(84, 10): 定义第三个全连接层 (输出层),输出特征数为 10,对应 CIFAR-10 的 10 个类别。

  3. forward(self, x): 模型的前向传播函数,定义数据在模型中的流动路径。

    • x = self.pool(F.relu(self.conv1(x))): 输入 x 经过第一个卷积层 conv1,然后应用 ReLU 激活函数 F.relu,最后经过最大池化层 self.pool

    • x = self.pool(F.relu(self.conv2(x))): 类似地,经过第二个卷积层、ReLU 激活和最大池化层。

    • x = torch.flatten(x, 1): 将特征图展平成一维向量。torch.flatten(x, 1) 表示从维度 1 开始展平,保持 batch 维度不变。

    • x = F.relu(self.fc1(x)): 经过第一个全连接层和 ReLU 激活。

    • x = F.relu(self.fc2(x)): 经过第二个全连接层和 ReLU 激活。

    • x = self.fc3(x): 经过输出层 fc3。输出层通常不需要激活函数,因为 nn.CrossEntropyLoss 损失函数内部已经包含了 Softmax 操作。

    • return x: 返回模型的输出。

运行 print(net) 可以打印模型的结构,方便我们查看模型定义是否正确。mermaid 图也清晰地展示了模型的网络结构和数据流动过程。

5.1.3 损失函数和优化器

我们需要定义损失函数和优化器,用于模型的训练。对于图像分类任务,常用的损失函数是交叉熵损失函数 nn.CrossEntropyLoss,常用的优化器是随机梯度下降优化器 optim.SGD 或 Adam 优化器 optim.Adam

criterion = nn.CrossEntropyLoss() # 交叉熵损失函数 optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # SGD 优化器 # optimizer = optim.Adam(net.parameters(), lr=0.001) # Adam 优化器 (可选)

代码详解:

  1. 损失函数 nn.CrossEntropyLoss():

    • nn.CrossEntropyLoss: 交叉熵损失函数,常用于多分类任务。它将 Softmax 操作和负对数似然损失函数结合在一起,可以直接输入模型的原始输出 (logits),无需手动添加 Softmax 层。
  2. 优化器 optim.SGD:

    • optim.SGD: 随机梯度下降优化器。

    • net.parameters(): 指定要优化的参数,这里是模型 net 的所有可学习参数。

    • lr=0.001: 学习率,控制每次参数更新的步长。

    • momentum=0.9: 动量,用于加速 SGD 的收敛,并有助于跳出局部最小值。

    • optim.Adam (可选): Adam 优化器是另一种常用的优化器,通常比 SGD 收敛速度更快,且对学习率不敏感。您可以尝试使用 Adam 优化器,只需将代码注释掉的 optim.Adam 那行取消注释即可。

5.1.4 模型训练

现在我们可以开始训练模型了。训练过程包括前向传播、计算损失、反向传播和参数更新。

for epoch in range(2): # 循环训练数据集 epochs 次 running_loss = 0.0 for i, data in enumerate(trainloader, 0): # 获取输入数据;data 是一个列表 [inputs, labels] inputs, labels = data # 梯度清零 optimizer.zero_grad() # 前向传播 + 反向传播 + 优化 outputs = net(inputs) # 前向传播 loss = criterion(outputs, labels) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 参数更新 # 打印统计信息 running_loss += loss.item() if i % 2000 == 1999: # 每 2000 个 mini-batches 打印一次 print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}') running_loss = 0.0 print('Finished Training') PATH = './cifar_net.pth' torch.save(net.state_dict(), PATH) # 保存模型参数

代码详解:

  1. for epoch in range(2): 循环训练数据集 2 个 epochs。一个 epoch 表示完整地遍历一遍训练数据集。实际应用中,通常需要训练更多的 epochs,例如 10-20 个甚至更多,才能获得更好的模型性能。这里为了演示,只训练 2 个 epochs。

  2. for i, data in enumerate(trainloader, 0): 循环遍历训练数据加载器 trainloader,每次迭代获取一个 batch 的数据。enumerate(trainloader, 0) 返回 batch 的索引 i 和数据 data

  3. inputs, labels = data: 从 data 中解包出输入图像 inputs 和标签 labels

  4. optimizer.zero_grad(): 在每次反向传播之前,需要将优化器的梯度清零。因为 PyTorch 默认会累积梯度,如果不清零,梯度会在多个 batch 之间累加,导致参数更新错误。

  5. outputs = net(inputs): 将输入图像 inputs 送入模型 net 进行前向传播,得到模型的输出 outputs

  6. loss = criterion(outputs, labels): 使用交叉熵损失函数 criterion 计算模型输出 outputs 和真实标签 labels 之间的损失值。

  7. loss.backward(): 执行反向传播,计算模型参数的梯度。

  8. optimizer.step(): 使用优化器 optimizer 根据计算出的梯度更新模型参数。

  9. 打印统计信息: 每 2000 个 mini-batches 打印一次平均损失值,方便监控训练过程。

  10. 保存模型参数: 训练结束后,使用 torch.save(net.state_dict(), PATH) 保存模型的参数到文件 cifar_net.pthnet.state_dict() 返回一个字典,包含模型的所有可学习参数。

5.1.5 模型测试

训练完成后,我们需要在测试集上评估模型的性能。

# 加载模型参数 (如果需要从保存的文件加载) # net = Net() # net.load_state_dict(torch.load(PATH)) correct = 0 total = 0 # 在测试集上不需要计算梯度,使用 torch.no_grad() 上下文管理器 with torch.no_grad(): for data in testloader: images, labels = data # 前向传播计算输出 outputs = net(images) # 预测类别是概率最高的类别 _, predicted = torch.max(outputs.data, 1) # torch.max 返回每行最大值和最大值索引,这里我们只需要索引 total += labels.size(0) # 累加总样本数 correct += (predicted == labels).sum().item() # 累加预测正确的样本数 print(f'Accuracy of the network on the 10000 test images: {100 * correct // total} %') # 查看每个类别的准确率 correct_pred = {classname: 0 for classname in classes} total_pred = {classname: 0 for classname in classes} with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predictions = torch.max(outputs, 1) for label, prediction in zip(labels, predictions): if label == prediction: correct_pred[classes[label]] += 1 total_pred[classes[label]] += 1 for classname, correct_count in correct_pred.items(): accuracy = 100 * float(correct_count) / total_pred[classname] print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')

代码详解:

  1. 加载模型参数 (可选): 如果之前保存了模型参数,可以使用 net.load_state_dict(torch.load(PATH)) 加载模型参数。

  2. with torch.no_grad():: 使用 torch.no_grad() 上下文管理器,表示在测试过程中不需要计算梯度,可以节省内存并加速计算。

  3. 循环遍历测试数据加载器 testloader: 每次迭代获取一个 batch 的测试数据。

  4. outputs = net(images): 将测试图像 images 送入模型 net 进行前向传播,得到模型的输出 outputs

  5. _, predicted = torch.max(outputs.data, 1): 获取每个样本预测概率最高的类别。torch.max(outputs.data, 1) 返回每行 (每个样本) 的最大值和最大值索引,_ 表示忽略最大值,我们只需要最大值索引,即预测的类别。

  6. 计算总样本数 total 和正确预测样本数 correct: 用于计算整体准确率。

  7. 计算每个类别的准确率: 使用字典 correct_predtotal_pred 记录每个类别的正确预测数和总样本数,然后计算每个类别的准确率。

运行这段代码,您将看到模型在测试集上的整体准确率以及每个类别的准确率。根据训练 epochs 的数量,您可能会得到 50% - 70% 左右的准确率。为了获得更高的准确率,您可以尝试:

  • 增加训练 epochs

  • 调整模型结构 (例如,增加卷积层、全连接层,使用更复杂的网络结构如 ResNet、VGG 等)

  • 调整超参数 (例如,学习率、优化器、批量大小等)

  • 使用数据增强 (例如,随机裁剪、旋转、翻转等)

  • 使用 GPU 加速训练 (如果您的机器有 GPU)

5.2 文本分类实战:情感分析 (待补充)

(本节内容待补充,由于篇幅限制,将在后续版本中加入文本分类实战内容,例如使用 RNN 或 Transformer 模型进行情感分析。)

5.3 序列预测实战:时间序列预测 (待补充)

(本节内容待补充,由于篇幅限制,将在后续版本中加入序列预测实战内容,例如使用 LSTM 网络进行时间序列预测。)

5.4 生成对抗网络 (GANs) 初探:图像生成 (待补充)

(本节内容待补充,由于篇幅限制,将在后续版本中加入 GANs 初探内容,例如使用 GANs 生成简单的图像。)

5.5 总结与展望

本章我们通过图像分类实战案例,深入了解了 PyTorch 在实际问题中的应用流程。从数据准备、模型构建、损失函数和优化器选择,到模型训练和测试,我们完整地走过了一个深度学习项目的生命周期。

虽然本章只涉及了图像分类一个领域,但 PyTorch 的应用远不止于此。在自然语言处理、语音识别、推荐系统、强化学习等领域,PyTorch 都发挥着重要的作用。希望通过本章的学习,您能够掌握 PyTorch 实战的基本方法,并在此基础上,探索更多 PyTorch 的应用领域,解决更多实际问题。

在未来的学习中,您可以继续深入研究以下方向:

  • 更复杂的模型结构: 学习和应用更先进的神经网络模型,例如 ResNet、Transformer、BERT 等,以解决更复杂的任务。

  • 更丰富的数据集: 尝试在更具挑战性的数据集上训练模型,例如 ImageNet、COCO、SQuAD 等。

  • 更高级的训练技巧: 学习和应用更高级的训练技巧,例如学习率调度、正则化、Dropout、Batch Normalization 等,以提高模型性能。

  • PyTorch 生态系统: 探索 PyTorch 生态系统中丰富的工具和库,例如 PyTorch Lightning、TorchServe、PyTorch Hub 等,以提高开发效率和模型部署能力。

PyTorch 的学习之路永无止境,希望您能保持学习的热情,不断探索,成为一名优秀的深度学习工程师!


发布者: 作者: 转发
评论区 (0)
U