Dataset、DataLoader 与批次
前置知识:Tensor、形状与 NumPy 对照 · 缺失、重复与类型清洗
本章目录
本章学习目标
- 定义样本与批次的形状
- 正确处理训练与验证数据加载
- 按样本数量而不是批次数量汇总损失
Dataset 规定一项样本返回什么
Dataset 把数据组织成能逐项读取的接口,DataLoader 负责组合批次、遍历顺序以及可选的并行读取。对于已经放在内存中的特征矩阵和目标向量,TensorDataset 就足够表达这两个对象的逐行对应关系。只有需要定制文件读取、变换或额外元数据时,才需要自己编写 Dataset 类。
假设特征有十一个样本,每行两列,目标每行一个数。第零项返回形状为二的特征和形状为一的目标;批大小设为四后,第一批是四行两列特征和四行一列目标,第二批也是四项,第三批只有三项。样本形状与批次形状不同,第一维在拼接后表达批次数量。
输入和标签的第一维必须相等,并且对应同一批样本键。将分别排序的 X 和 y 放进 TensorDataset,可能不会报形状错误,却已经破坏监督关系。前面的表处理阶段应按唯一键完成对齐,转换前保留样本顺序,转换后做数量和抽样检查。
训练打乱与数据划分不是一件事
对于本课程的固定训练集合,可以在训练 DataLoader 中使用 shuffle,让一个 epoch 内的样本顺序重新排列。验证和测试一般保持固定顺序,方便输出对齐与错误分析。shuffle 只是改变现有集合内部的读取顺序,不会修复先把时间数据随机拆分带来的泄漏问题。
面向未来预测的任务先按时间划分训练、验证、测试,再考虑是否打乱训练集合中的批次。模型或任务若依赖序列顺序,还要采用符合其输入结构的采样方式,不能直接使用普通逐行打乱。DataLoader 的便捷接口不会替你定义研究目标和时间合法性。
可用一个手动设置种子的 Generator 控制采样顺序,提高同环境复现能力。随机种子只能约束一部分随机过程,不能保证所有设备、库版本和算法都产生位级一致结果。报告应写出种子、版本、划分政策和执行设备。
最后一批和平均损失的权重
默认 drop_last=False 保留最后不足一个完整批次的样本。十一项数据以四为批大小,会得到四、四、三项。如果每批损失是批内样本均值,直接对三个批次均值再平均,会让最后三项相对获得更大权重。正确的整体均值是把每批均值乘批内样本数后求和,再除以总样本数。
设三批平均损失分别是二、四和十,直接平均得到十六除三;按样本数加权是二乘四加四乘四加十乘三,再除十一,得到五十四除十一。两者不同,错误在大多数满批次场景里不明显,却会影响小验证集和最后批次。
drop_last=True 可在某些需要固定批次形状的训练场景使用,但会丢弃样本。验证和测试应优先保留全部记录,并在汇总中处理实际批次大小。不能通过丢弃最后一批来掩盖损失统计错误。
从简单加载开始排查错误
本课程使用 num_workers=0,在主进程读取,减少 Windows 新手遇到多进程启动问题的可能。如果将来增加工作进程,需要把脚本入口放在 main 保护中,并理解工作进程的数据复制和种子设置。更大的并行数量不保证更快,小型内存数据反而可能增加启动和通信成本。
Dataset 中的变换应明确是否随机、是否只作用于训练集,以及是否需要拟合参数。均值填充和标准化参数来自训练集,而不是在每次 __getitem__ 时用全体数据重新计算。验证集加载器只应用已经确定的规则。
本章脚本读取固定顺序的小张量,记录所有批次大小和访问样本总数,并对完整覆盖加 assert。实现自己的 Dataset 时,也应先在三条数据上打印返回值,再扩展到 DataLoader。shape 错误、重复样本、遗漏尾部、标签错位都应在网络训练之前发现。
完整示例与实际输出
import torch
from torch.utils.data import TensorDataset, DataLoader
X=torch.arange(22,dtype=torch.float32).reshape(11,2)
y=torch.arange(11,dtype=torch.float32).reshape(11,1)
loader=DataLoader(TensorDataset(X,y),batch_size=4,shuffle=False,num_workers=0)
sizes=[]; total=0.; count=0
for xb,yb in loader:
assert xb.shape[1]==2 and yb.shape==(len(xb),1)
sizes.append(len(xb))
mean=yb.square().mean().item()
total+=mean*len(xb); count+=len(xb)
assert sizes==[4,4,3] and count==11
assert abs(total/count-y.square().mean().item())<1e-6
print('batch_sizes=',sizes,'sample_count=',count,'mean_target_square=',total/count)
实际运行通过CPU Python execution.
batch_sizes= [4, 4, 3] sample_count= 11 mean_target_square= 34.99999930641868
五道自测:先回答,再核对
01 · 十一项数据、batch_size=4、drop_last=False 会产生哪些批次?
查看提示
不要遗漏最后一批。
查看参考答案
4、4、3,共十一项。
02 · shuffle=True 能让时间随机拆分变合法吗?
查看提示
先确定训练与未来评价的边界。
查看参考答案
不能。集合划分与集合内采样顺序是两个问题。
03 · 批损失是均值时,怎样汇总整个验证集?
查看提示
考虑最后一批只有三项。
查看参考答案
每批均值乘实际样本数累加,再除总样本数。
04 · TensorDataset 会自动通过样本键对齐特征和标签吗?
查看提示
它不含业务主键语义。
查看参考答案
不会,它按位置配对;转换前必须自己按键对齐。
05 · 为什么课程先使用 num_workers=0?
查看提示
小数据未必受益于更多进程。
查看参考答案
减少多进程与平台差异,先验证正确性,再按实际性能需求扩展。
两项动手任务
任务 1 · 构造十项数据,验证 batch_size=3 的完整样本覆盖。
提示
先写出输入、输出、形状和判断条件,再运行小数据。
完整参考解答与检查方式
import torch
from torch.utils.data import TensorDataset,DataLoader
x=torch.arange(10)
loader=DataLoader(TensorDataset(x),batch_size=3,shuffle=False)
seen=[]
for (batch,) in loader: seen.extend(batch.tolist())
assert seen==list(range(10))
print(seen)任务 2 · 手算批损失2、4、10,对应样本数4、4、3,解释错误汇总。
提示
先写出输入、输出、形状和判断条件,再运行小数据。
完整参考解答与检查方式
正确值为 (2×4+4×4+10×3)/11=54/11≈4.9091。错误的 (2+4+10)/3≈5.3333 让尾批每项获得更大权重。
官方资料与掌握状态
完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。
动手任务记录
掌握状态与复习
关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。
记录尽量保存在当前浏览器,建议定期导出备份。