使用 PyTorch 进行模型训练train

使用 PyTorch 进行模型训练

你可以观看下方视频或 YouTube 上的对应内容跟随学习。

引言

在之前的视频中,我们已经讨论并演示了:

  • 使用 torch.nn 模块的神经网络层和函数构建模型
  • 自动梯度计算的原理(这是基于梯度的模型训练的核心)
  • 使用 TensorBoard 可视化训练进度和其他过程

在本视频中,我们将为你新增一些实用工具:

  • 熟悉 DatasetDataLoader 抽象类,以及它们如何简化训练循环中的数据喂入流程
  • 讲解特定的损失函数及其适用场景
  • 学习 PyTorch 优化器(Optimizer)------ 它们能根据损失函数的计算结果调整模型权重
  • 最后,将所有这些组件整合,完整演示 PyTorch 训练循环的运行过程

Dataset 和 DataLoader

DatasetDataLoader 类封装了从存储介质读取数据,并以批次形式提供给训练循环的全过程:

  • Dataset 负责读取和处理单个数据样本
  • DataLoaderDataset 中抽取样本(自动抽取或通过自定义采样器)、组合成批次,并返回给训练循环使用。DataLoader 适用于所有类型的数据集,与数据类型无关

本教程中,我们将使用 TorchVision 提供的 Fashion-MNIST 数据集。通过 torchvision.transforms.Normalize() 实现图像数据的零均值化和归一化,并下载训练集和验证集。

python 复制代码
import torch
import torchvision
import torchvision.transforms as transforms

# PyTorch TensorBoard 支持
from torch.utils.tensorboard import SummaryWriter
from datetime import datetime

# 数据预处理管道
transform = transforms.Compose(
    [transforms.ToTensor(),  # 将PIL图像转为张量
    transforms.Normalize((0.5,), (0.5,))])  # 归一化:均值0.5,标准差0.5

# 创建训练集和验证集,若本地不存在则自动下载
training_set = torchvision.datasets.FashionMNIST('./data', train=True, transform=transform, download=True)
validation_set = torchvision.datasets.FashionMNIST('./data', train=False, transform=transform, download=True)

# 创建数据加载器:训练集打乱数据,验证集不打乱
training_loader = torch.utils.data.DataLoader(training_set, batch_size=4, shuffle=True)
validation_loader = torch.utils.data.DataLoader(validation_set, batch_size=4, shuffle=False)

# 类别标签
classes = ('T恤/上衣', '裤子', '套头衫', '连衣裙', '外套',
        '凉鞋', '衬衫', '运动鞋', '包', '短靴')

# 打印数据集大小
print('训练集包含 {} 个样本'.format(len(training_set)))
print('验证集包含 {} 个样本'.format(len(validation_set)))

数据下载输出示例

shell 复制代码
  0%|          | 0.00/26.4M [00:00<?, ?B/s]
  0%|          | 65.5k/26.4M [00:00<01:12, 365kB/s]
  1%|          | 164k/26.4M [00:00<00:55, 472kB/s]
  2%|▏         | 655k/26.4M [00:00<00:17, 1.50MB/s]
 10%|▉         | 2.62M/26.4M [00:00<00:04, 5.22MB/s]
 31%|███▏      | 8.29M/26.4M [00:00<00:01, 14.7MB/s]
 54%|█████▍    | 14.4M/26.4M [00:01<00:00, 21.1MB/s]
 77%|███████▋  | 20.2M/26.4M [00:01<00:00, 24.9MB/s]
 98%|█████████▊| 26.0M/26.4M [00:01<00:00, 27.2MB/s]
100%|██████████| 26.4M/26.4M [00:01<00:00, 18.3MB/s]

  0%|          | 0.00/29.5k [00:00<?, ?B/s]
100%|██████████| 29.5k/29.5k [00:00<00:00, 328kB/s]

  0%|          | 0.00/4.42M [00:00<?, ?B/s]
  1%|▏         | 65.5k/4.42M [00:00<00:12, 360kB/s]
  5%|▌         | 229k/4.42M [00:00<00:06, 678kB/s]
 21%|██        | 918k/4.42M [00:00<00:01, 2.09MB/s]
 83%|████████▎ | 3.67M/4.42M [00:00<00:00, 7.23MB/s]
100%|██████████| 4.42M/4.42M [00:00<00:00, 6.06MB/s]

  0%|          | 0.00/5.15k [00:00<?, ?B/s]
100%|██████████| 5.15k/5.15k [00:00<00:00, 58.0MB/s]
训练集包含 60000 个样本
验证集包含 10000 个样本

和往常一样,我们先可视化数据以验证数据加载的正确性:

python 复制代码
import matplotlib.pyplot as plt
import numpy as np

# 辅助函数:内嵌显示图像
def matplotlib_imshow(img, one_channel=False):
    if one_channel:
        img = img.mean(dim=0)  # 单通道图像处理
    img = img / 2 + 0.5     # 反归一化(恢复原始像素值范围)
    npimg = img.numpy()     # 转为numpy数组
    if one_channel:
        plt.imshow(npimg, cmap="Greys")  # 灰度图显示
    else:
        plt.imshow(np.transpose(npimg, (1, 2, 0)))  # 调整维度顺序为(H,W,C)

# 迭代数据加载器
dataiter = iter(training_loader)
images, labels = next(dataiter)

# 创建图像网格并显示
img_grid = torchvision.utils.make_grid(images)
matplotlib_imshow(img_grid, one_channel=True)
# 打印对应标签
print('  '.join(classes[labels[j]] for j in range(4)))

图像可视化输出示例

复制代码
凉鞋  套头衫  短靴  凉鞋

模型定义

本示例中使用的模型是 LeNet-5 的变体------如果你看过本系列之前的视频,应该对这个结构很熟悉。

python 复制代码
import torch.nn as nn
import torch.nn.functional as F

# PyTorch 模型需继承 torch.nn.Module
class GarmentClassifier(nn.Module):
    def __init__(self):
        super(GarmentClassifier, self).__init__()
        # 卷积层1:输入通道1(灰度图),输出通道6,卷积核5x5
        self.conv1 = nn.Conv2d(1, 6, 5)
        # 池化层:2x2最大池化,步长2
        self.pool = nn.MaxPool2d(2, 2)
        # 卷积层2:输入通道6,输出通道16,卷积核5x5
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 全连接层1:输入维度16*4*4,输出维度120
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        # 全连接层2:输入维度120,输出维度84
        self.fc2 = nn.Linear(120, 84)
        # 全连接层3:输入维度84,输出维度10(对应10个类别)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 前向传播路径
        x = self.pool(F.relu(self.conv1(x)))  # 卷积1 → ReLU → 池化
        x = self.pool(F.relu(self.conv2(x)))  # 卷积2 → ReLU → 池化
        x = x.view(-1, 16 * 4 * 4)            # 展平张量(batch_size, 16*4*4)
        x = F.relu(self.fc1(x))               # 全连接1 → ReLU
        x = F.relu(self.fc2(x))               # 全连接2 → ReLU
        x = self.fc3(x)                       # 全连接3(无激活,输出原始得分)
        return x

# 实例化模型
model = GarmentClassifier()

损失函数

本示例中我们使用交叉熵损失(CrossEntropyLoss)。为了演示,我们创建虚拟的输出和标签批次,传入损失函数并查看计算结果。

python 复制代码
loss_fn = torch.nn.CrossEntropyLoss()

# 注意:损失函数期望输入是批次数据,因此我们创建4个样本的批次
# 虚拟输出:代表模型对每个输入在10个类别上的置信度
dummy_outputs = torch.rand(4, 10)
# 虚拟标签:代表每个输入对应的正确类别
dummy_labels = torch.tensor([1, 5, 3, 7])

print(dummy_outputs)
print(dummy_labels)

# 计算损失
loss = loss_fn(dummy_outputs, dummy_labels)
print('该批次的总损失值:{}'.format(loss.item()))

损失函数输出示例

css 复制代码
tensor([[0.8112, 0.4959, 0.7542, 0.3991, 0.2028, 0.4509, 0.1630, 0.8028, 0.2457,         0.2236],
        [0.5571, 0.2879, 0.1011, 0.6718, 0.4791, 0.4399, 0.9477, 0.7674, 0.4289,         0.0269],
        [0.2238, 0.1125, 0.2443, 0.2570, 0.5993, 0.0243, 0.3440, 0.2244, 0.6830,         0.9687],
        [0.6951, 0.7913, 0.3395, 0.5821, 0.1017, 0.6777, 0.8524, 0.4503, 0.5550,         0.3649]])
tensor([1, 5, 3, 7])
该批次的总损失值:2.3832545280456543

优化器

本示例中我们使用简单的带动量的随机梯度下降(SGD)优化器。

尝试调整优化器参数会很有启发:

  • 学习率(lr):决定优化器每次更新权重的步长。不同的学习率会如何影响训练的准确率和收敛速度?
  • 动量(momentum):使优化器在多步更新中向梯度最大的方向靠拢。调整这个值会带来什么变化?
  • 尝试其他优化算法(如平均SGD、Adagrad、Adam),结果会有何不同?
python 复制代码
# 优化器来自 torch.optim 包
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

训练循环

下面定义一个执行单个训练轮次(epoch) 的函数。它遍历 DataLoader 中的数据,在每次循环中执行以下操作:

  1. DataLoader 获取一批训练数据
  2. 清零优化器的梯度
  3. 执行推理:模型根据输入批次生成预测结果
  4. 计算预测结果与数据集标签之间的损失
  5. 反向传播计算权重的梯度
  6. 让优化器执行一次权重更新(即根据当前批次的梯度调整模型权重)

函数会每 1000 个批次打印一次损失值,并返回最后 1000 个批次的平均损失(用于与验证损失对比)。

python 复制代码
def train_one_epoch(epoch_index, tb_writer):
    running_loss = 0.
    last_loss = 0.

    # 使用 enumerate(training_loader) 而非 iter(training_loader)
    # 这样可以跟踪批次索引,方便在轮次内进行进度报告
    for i, data in enumerate(training_loader):
        # 每个数据样本包含输入和标签
        inputs, labels = data

        # 重要:每个批次都要清零梯度!
        optimizer.zero_grad()

        # 前向传播:模型预测
        outputs = model(inputs)

        # 计算损失并反向传播
        loss = loss_fn(outputs, labels)
        loss.backward()

        # 更新权重
        optimizer.step()

        # 累计损失并报告
        running_loss += loss.item()
        if i % 1000 == 999:
            last_loss = running_loss / 1000  # 计算每批次平均损失
            print('  批次 {} 损失值:{}'.format(i + 1, last_loss))
            # 记录到 TensorBoard
            tb_x = epoch_index * len(training_loader) + i + 1
            tb_writer.add_scalar('Loss/train', last_loss, tb_x)
            running_loss = 0.

    return last_loss

轮次级操作(Per-Epoch Activity)

每个训练轮次结束后,我们需要执行两项关键操作:

  1. 验证:使用未参与训练的数据计算相对损失并报告
  2. 保存模型副本

这里我们将使用 TensorBoard 进行结果可视化。需要在命令行启动 TensorBoard,并在新的浏览器标签页中打开。

python 复制代码
# 单独初始化 TensorBoard 写入器,方便后续继续训练
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
writer = SummaryWriter('runs/fashion_trainer_{}'.format(timestamp))
epoch_number = 0

# 训练轮次总数
EPOCHS = 5

# 记录最佳验证损失(初始值设为很大的数)
best_vloss = 1_000_000.

for epoch in range(EPOCHS):
    print('第 {} 轮训练开始:'.format(epoch_number + 1))

    # 开启梯度跟踪,执行训练
    model.train(True)
    avg_loss = train_one_epoch(epoch_number, writer)

    # 验证阶段
    running_vloss = 0.0
    # 设置模型为评估模式:关闭 dropout,使用批次归一化的全局统计量
    model.eval()

    # 禁用梯度计算,减少内存消耗
    with torch.no_grad():
        for i, vdata in enumerate(validation_loader):
            vinputs, vlabels = vdata
            voutputs = model(vinputs)
            vloss = loss_fn(voutputs, vlabels)
            running_vloss += vloss

    # 计算验证集平均损失
    avg_vloss = running_vloss / (i + 1)
    print('损失值:训练 {} 验证 {}'.format(avg_loss, avg_vloss))

    # 将训练和验证的批次平均损失记录到 TensorBoard
    writer.add_scalars('训练 vs. 验证损失',
                    { '训练损失' : avg_loss, '验证损失' : avg_vloss },
                    epoch_number + 1)
    writer.flush()

    # 跟踪最佳性能,并保存模型状态
    if avg_vloss < best_vloss:
        best_vloss = avg_vloss
        model_path = 'model_{}_{}'.format(timestamp, epoch_number)
        torch.save(model.state_dict(), model_path)

    epoch_number += 1

训练过程输出示例

yaml 复制代码
第 1 轮训练开始:
  批次 1000 损失值:1.9134942837655544
  批次 2000 损失值:0.9125308708772063
  批次 3000 损失值:0.7421692375075072
  批次 4000 损失值:0.6706725437843707
  批次 5000 损失值:0.6199451773476321
  批次 6000 损失值:0.5670827368514146
  批次 7000 损失值:0.5232817540006945
  批次 8000 损失值:0.5094636065706145
  批次 9000 损失值:0.48667522480562914
  批次 10000 损失值:0.46624169800852544
  批次 11000 损失值:0.4639233185091289
  批次 12000 损失值:0.4594155636130599
  批次 13000 损失值:0.4203426251715282
  批次 14000 损失值:0.4268976293912856
  批次 15000 损失值:0.4139624496238539
损失值:训练 0.4139624496238539 验证 0.4292227625846863
第 2 轮训练开始:
  批次 1000 损失值:0.4067946516573429
  批次 2000 损失值:0.39065092354803344
  批次 3000 损失值:0.38280759067872716
  批次 4000 损失值:0.38190718797489537
  批次 5000 损失值:0.3618355706045404
  批次 6000 损失值:0.38087676811014537
  批次 7000 损失值:0.38793834817246536
  批次 8000 损失值:0.3708066731508006
  批次 9000 损失值:0.37955640835637316
  批次 10000 损失值:0.3687095919056446
  批次 11000 损失值:0.3686677168744791
  批次 12000 损失值:0.3549465914624743
  批次 13000 损失值:0.33877516484307124
  批次 14000 损失值:0.3504257514170604
  批次 15000 损失值:0.3441471123093143
损失值:训练 0.3441471123093143 验证 0.3788158595561981
第 3 轮训练开始:
  批次 1000 损失值:0.317229493965453
  批次 2000 损失值:0.3474526008111425
  批次 3000 损失值:0.3238617765499948
  批次 4000 损失值:0.319467453146819
  批次 5000 损失值:0.32635068734473316
  批次 6000 损失值:0.3054303097274678
  批次 7000 损失值:0.3287870975938349
  批次 8000 损失值:0.33125486329917475
  批次 9000 损失值:0.32507053730732877
  批次 10000 损失值:0.3185750473064691
  批次 11000 损失值:0.34855063943209824
  批次 12000 损失值:0.3281271118210134
  批次 13000 损失值:0.3221102311969153
  批次 14000 损失值:0.3190491863854768
  批次 15000 损失值:0.30807110028983153
损失值:训练 0.30807110028983153 验证 0.3304081857204437
第 4 轮训练开始:
  批次 1000 损失值:0.2946426784224022
  批次 2000 损失值:0.287669164258632
  批次 3000 损失值:0.3078085952404508
  批次 4000 损失值:0.29501632773253367
  批次 5000 损失值:0.29912974609442244
  批次 6000 损失值:0.3017247214637937
  批次 7000 损失值:0.2840568649524648
  批次 8000 损失值:0.29276608184371433
  批次 9000 损失值:0.3079204446072108
  批次 10000 损失值:0.2956557339110841
  批次 11000 损失值:0.2810574857474421
  批次 12000 损失值:0.30411081384155114
  批次 13000 损失值:0.30809544625972196
  批次 14000 损失值:0.30132432371701906
  批次 15000 损失值:0.3057659530805977
损失值:训练 0.3057659530805977 验证 0.3213053345680237
第 5 轮训练开始:
  批次 1000 损失值:0.26890792168607003
  批次 2000 损失值:0.27582182378186915
  批次 3000 损失值:0.29604251561401546
  批次 4000 损失值:0.2800273624816691
  批次 5000 损失值:0.2783352249941127
  批次 6000 损失值:0.26813480685117974
  批次 7000 损失值:0.29592685499209254
  批次 8000 损失值:0.302574578157466
  批次 9000 损失值:0.2913306496424302
  批次 10000 损失值:0.27227645949238105
  批次 11000 损失值:0.2863563198988322
  批次 12000 损失值:0.2805033384739072
  批次 13000 损失值:0.27869366186248956
  批次 14000 损失值:0.2735917175477589
  批次 15000 损失值:0.27029165163794916
损失值:训练 0.27029165163794916 验证 0.3117460012435913

加载保存的模型

要加载已保存的模型,执行以下代码:

python 复制代码
# 重新实例化模型
saved_model = GarmentClassifier()
# 加载权重(PATH 替换为实际的模型文件路径)
saved_model.load_state_dict(torch.load(PATH))

加载完成后,模型即可用于后续操作------继续训练、推理或分析。

注意:如果你的模型构造函数参数会影响模型结构,加载时需要传入与保存时完全相同的参数,确保模型结构一致。

其他资源

  • PyTorch 官方文档:数据工具(包括 Dataset 和 DataLoader):pytorch.org
  • GPU 训练中固定内存(pinned memory)的使用说明
  • TorchVision、TorchText、TorchAudio 内置数据集文档
  • PyTorch 损失函数文档
  • torch.optim 包文档(包含优化器和学习率调度器等工具)
  • 模型保存与加载详细教程
  • PyTorch 官网教程区:包含各类训练任务的教程(如不同领域的分类任务、生成对抗网络、强化学习等)

脚本总运行时间

复制代码
2 分 57.614 秒

总结

  1. PyTorch 训练循环核心组件包括:Dataset/DataLoader(数据加载)、模型(网络结构)、损失函数(误差计算)、优化器(权重更新);
  2. 训练过程需区分 train()eval() 模式,验证阶段需禁用梯度计算以节省内存;
  3. 关键实践:每个批次清零梯度、监控训练/验证损失、保存最佳模型权重。
相关推荐
用户2862810093406 小时前
PyTorch TensorBoard 支持
pytorch
SNWCC7 小时前
autodl_M000_pytorch
人工智能·pytorch·python
zhangfeng11339 小时前
国产GPU与ROCm架构的关系 国产GPU架构总结 ROCm 7.1 在 PyTorch 官网上被划掉(横线)直接支持
人工智能·pytorch·架构
koo36410 小时前
pytorch深度学习笔记22
pytorch·笔记·深度学习
Shining059610 小时前
AI 编译器系列(三)《PyTorch 中图优化》
人工智能·pytorch·python·深度学习·学习·机器学习·infinitensor
爱打代码的小林11 小时前
从模型到 API:Flask+PyTorch 快速搭建图像分类
人工智能·pytorch·分类·api
Dxy123931021612 小时前
PyTorch的CosineAnnealingWarmRestartsLR详细介绍:给模型训练来一场“热启动”的艺术
人工智能·pytorch·python
墨染天姬12 小时前
【AI】PyTorch/TF 也会变成考古?
人工智能·pytorch·python
闻道且行之1 天前
PyTorch 深度学习开发 常见疑难报错与解决方案汇总
人工智能·pytorch·深度学习