使用 PyTorch 进行模型训练train

使用 PyTorch 进行模型训练

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

引言

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

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

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

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

Dataset 和 DataLoader

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

  • Dataset 负责读取和处理单个数据样本
  • DataLoader 从 Dataset 中抽取样本(自动抽取或通过自定义采样器)、组合成批次,并返回给训练循环使用。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. 关键实践:每个批次清零梯度、监控训练/验证损失、保存最佳模型权重。
相关推荐
Thomas.Sir3 分钟前
第26课:工业零部件外观缺陷检测系统:从学术Demo到产线工程的重构实战
pytorch·ai
Zguigo14 小时前
【CUDA6】CUDA Stream 是什么,为什么 CUDA 是异步执行,如何正确测量 GPU 时间以及多个任务如何重叠执行
人工智能·pytorch·深度学习
论文复现现场1 天前
RTX 3090 24GB 适合科研吗?单卡论文复现、PyTorch 显存检查与 OOM 排错
人工智能·pytorch·python·深度学习·cuda·rtx3090
论文复现现场1 天前
AutoDL、算家云与公有云 GPU 怎么选?环境复现、计费与断点恢复对比
pytorch·深度学习·云计算·gpu
盘古开天16662 天前
PPO算法代码实战(三):PyTorch从零实现PPO求解CartPole
人工智能·pytorch·算法
Είναι η κοπέλα2 天前
PyTorch 安装与验证
人工智能·pytorch·python
盼小辉丶3 天前
PyTorch强化学习实战(27)——进化策略在强化学习中的应用
人工智能·pytorch·深度学习·强化学习
weixin_447195293 天前
【无标题】
pytorch·python
bigdata-余建新3 天前
week2
人工智能·pytorch·深度学习
HwJack203 天前
【共创稿事节】HarmonyOS 7材质与光照:真实感的营造与性能的平衡
pytorch·harmonyos·材质