云计算百科
云计算领域专业知识百科平台

Epoch、Batch 与 DataLoader

很多刚开始阅读 PyTorch 推荐系统训练代码的同学,都会卡在一组名词:
epoch、batch、DataLoader、shuffle、loss.backward()、optimizer.step()、test、HR、NDCG。

单独看每个概念不难,但放进训练循环里,很容易分不清整条流水线:
数据从哪里读取?如何切分成 batch?模型什么时候更新参数?测试阶段会不会偷偷修改权重?

本文用最简单的数字样本,完整拆解整条训练链路。
在这里插入图片描述

✅ 核心结论

  • 一个 epoch = 完整遍历一遍全部训练数据集。
  • 单个 epoch 内部,循环读取多个 batch。
  • 训练阶段,每拿到一个 batch,标准流程:
    前向传播 → 计算损失 → 清空梯度 → 反向传播 → 更新模型参数。
  • verbose 用来控制:每隔多少个 epoch,执行日志打印/离线评估。
  • 测试评估阶段:仅做预测、计算指标(HR@K、NDCG@K),不会更新模型参数。
  • shuffle=True 只改变样本读取顺序,不会修改 Dataset 内部原始数据。
  • shuffle=True:每一轮epoch开始,重新生成随机读取顺序。
  • shuffle=False:所有epoch读取样本顺序完全固定。
  • 一句话概括分工:

    Dataset 管理「有哪些数据」;DataLoader 管理「以什么顺序、多少条一组取出数据」;训练循环控制「拿到batch后如何迭代优化模型」。


    🔢 极简数字数据集演示

    假设我们一共有10条训练样本,使用索引编号代表样本:

    原始数据集索引:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    参数设置:

    batch_size = 4

    含义:一次性取出4条样本,封装为1个batch。

    场景1:shuffle=False(不打乱顺序)

    在这里插入图片描述

    数据会按原生索引切分batch:

    第1个 batch: [0, 1, 2, 3]
    第2个 batch: [4, 5, 6, 7]
    第3个 batch: [8, 9]

    总数10无法被4整除,最后一组为不完整batch。
    如果开启 drop_last=True,最后这个不完整batch [8, 9] 会直接丢弃。

    shuffle=False:所有epoch顺序固定
    读取索引永远不变,连续两轮epoch批次完全相同:

    epoch 1:
    batch 1: [0, 1, 2, 3]
    batch 2: [4, 5, 6, 7]
    batch 3: [8, 9]

    epoch 2:
    batch 1: [0, 1, 2, 3]
    batch 2: [4, 5, 6, 7]
    batch 3: [8, 9]

    场景2:shuffle=True(开启打乱)

    在这里插入图片描述

    ❗重点:打乱的是索引读取顺序,原始Dataset内的数据本身保持不变。
    原始数据依旧是 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    关键点总结:
    shuffle=True ≠ 永久修改数据;
    每一轮epoch启动时,DataLoader重新规划本轮样本读取次序。


    📦 DataLoader 到底如何生成一个 batch?

    这是新手最容易混淆的环节。我们拆成完整5步理解。

    自定义数据集模板:

    class NumberDataset(torch.utils.data.Dataset):
    def __init__(self):
    self.samples = [
    {"idx": 0, "feature": [0.0, 0.5], "label": 0},
    {"idx": 1, "feature": [1.0, 1.5], "label": 1},
    {"idx": 2, "feature": [2.0, 2.5], "label": 0},
    # …更多样本
    ]

    def __len__(self):
    return len(self.samples)

    def __getitem__(self, index):
    # 根据索引返回【单条样本】
    return self.samples[index]

    训练循环代码:

    for batch in data_loader:
    ...

    循环背后完整流程:

    第1步:生成索引顺序

    • shuffle=False:原生索引 [0,1,2,3,…]
    • shuffle=True:随机打乱索引序列

    注意:数字只是样本索引,不是样本内容。后续会调用 dataset[index] 获取单条数据。

    第2步:按batch_size分组索引

    索引序列 [3, 7, 1, 9, 0, 6, 2, 8, 4, 5]
    batch_size=4 → 分组:
    [3,7,1,9]、[0,6,2,8]、[4,5]
    当前仅规划索引,还没有读取真实样本。

    第3步:逐条调用 Dataset.__getitem__

    取索引组 [3,7,1,9]
    依次执行:

    sample_1 = dataset[3]
    sample_2 = dataset[7]
    sample_3 = dataset[1]
    sample_4 = dataset[9]

    得到4条独立样本字典:

    [
    {"idx": 3, "feature": [3.0, 3.5], "label": 1},
    {"idx": 7, "feature": [7.0, 7.5], "label": 1},
    {"idx": 1, "feature": [1.0, 1.5], "label": 1},
    {"idx": 9, "feature": [9.0, 9.5], "label": 1},
    ]

    第4步:collate_fn 打包,拼接成batch张量

    在这里插入图片描述
    默认 default_collate 负责打包:把多条样本同字段堆叠。
    打包完成后:

    {
    "idx": tensor([3, 7, 1, 9]),
    "feature": tensor([
    [3.0, 3.5],
    [7.0, 7.5],
    [1.0, 1.5],
    [9.0, 9.5],
    ]),
    "label": tensor([1, 1, 1, 1]),
    }

    这就是训练循环拿到的 batch。

    通俗理解:
    Dataset 产出一条条独立样本;collate_fn 打包员,把多条样本组装成一个batch张量。

    第5步:训练循环接收batch,送入模型

    外部代码直接使用:

    for batch in train_loader:
    features = batch["feature"]
    labels = batch["label"]
    scores = model(features)

    整条链路简化:
    生成索引顺序 → 索引分组 → __getitem__逐条读取样本 → collate_fn拼接batch → 返回循环


    🔁 完整训练循环执行流程

    示例标准训练代码:

    epochs = 3
    batch_size = 4
    verbose = 2

    for epoch in range(1, epochs + 1):
    model.train()
    # 内层循环遍历所有batch
    for batch in train_loader:
    features = batch["feature"]
    labels = batch["label"]
    scores = model(features)
    loss = loss_fn(scores, labels)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # 间隔verbose个epoch执行测试
    if epoch % verbose == 0:
    evaluate(model, test_loader)

    流程翻译:

    • Epoch 1:完整遍历训练集,所有batch更新参数;1%2≠0,不测试
    • Epoch 2:完整遍历训练集,所有batch更新参数;2%2=0,执行测试评估
    • Epoch 3:完整遍历训练集,所有batch更新参数;3%2≠0,不测试在这里插入图片描述

    单个batch内部标准训练闭环

    model.train() # 切换训练模式(Dropout/BatchNorm生效)
    scores = model(features)# 前向传播,得到预测值
    loss = loss_fn(scores, labels) # 计算损失
    optimizer.zero_grad() # 清空上一轮梯度
    loss.backward() # 反向传播,计算参数梯度
    optimizer.step() # 使用梯度更新模型权重


    ⚖️ 训练阶段 VS 测试评估阶段

    阶段是否计算梯度是否反向传播是否更新参数核心目的
    训练阶段 ✅ 是 ✅ 是 ✅ 是 迭代优化模型
    测试评估 ❌ 否 ❌ 否 ❌ 否 观测模型泛化效果

    训练代码模板

    model.train()
    for batch in train_loader:
    scores = model(batch["feature"])
    loss = loss_fn(scores, batch["label"])
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    测试评估模板

    model.eval()
    with torch.no_grad(): # 关闭梯度计算,节省显存
    for batch in test_loader:
    scores = model(batch["feature"])
    # 根据预测分数计算HR、NDCG指标

    torch.no_grad():告知PyTorch仅推理,不需要构建梯度计算图。


    📊 推荐系统指标:HR@K 和 NDCG@K

    推荐任务离线评估最常用两个指标:

    HR@K(Hit Ratio,命中率)

    含义:给用户推荐Top-K物品,真实交互物品是否出现在推荐列表内。

    • 命中:HR=1
    • 未命中:HR=0

    示例:
    推荐Top5列表:[item_8, item_2, item_6, item_1, item_9]
    用户真实喜爱物品:item_6
    item_6 在列表中 → HR@5 = 1。

    NDCG@K

    HR只关心「有没有命中」;NDCG额外关注命中物品的排序位置。
    案例:真实物品为 item_6

    • 推荐A:[item_6, item_2, item_8, item_1, item_9](命中排在第1位)
    • 推荐B:[item_8, item_2, item_1, item_9, item_6](命中排在第5位)

    两者HR都等于1,但推荐A的NDCG更高。
    简单理解:

    HR:是否猜中;NDCG:猜中之后,排得够不够靠前。


    🛠️ 可直接运行的演示代码

    复制运行,直观观察 __getitem__ 的调用时机:

    import torch
    from torch.utils.data import Dataset, DataLoader

    class NumberDataset(Dataset):
    def __init__(self):
    self.samples = []
    for idx in range(10):
    self.samples.append({
    "idx": idx,
    "feature": torch.tensor([float(idx), float(idx) + 0.5]),
    "label": torch.tensor(idx % 2, dtype=torch.float32),
    })

    def __len__(self):
    return len(self.samples)

    def __getitem__(self, index):
    print(f" __getitem__ 被调用,index={index}")
    return self.samples[index]

    dataset = NumberDataset()
    loader = DataLoader(dataset, batch_size=4, shuffle=False)

    for batch_id, batch in enumerate(loader, start=1):
    print(f"\\n第 {batch_id} 个 batch")
    print("idx:", batch["idx"])
    print("feature:", batch["feature"])
    print("label:", batch["label"])

    运行输出可以验证:

  • DataLoader 逐个调用 __getitem__ 获取单条样本
  • 收集足够样本后,collate_fn自动合并为batch张量
  • 训练循环拿到的是封装完成的批量数据,而非单样本

  • 💡 收尾总结

    看到推荐系统经典双层循环代码:

    for epoch in range(epochs):
    for batch in train_loader:
    ...

    脑海中自动翻译:

    外层epoch循环:控制一共完整训练多少轮;
    内层batch循环:一轮训练中分批读取数据;
    每一个训练batch都会更新模型参数;
    到达指定epoch间隔,进入评估模式,只预测、计算指标,不更新权重。

    打通这套流程后,再阅读 BPR、NeuMF、LightGCN 等推荐模型训练代码,理解门槛会大幅降低。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Epoch、Batch 与 DataLoader
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!