PyTorch Dataset 与 DataLoader
Dataset 描述“一个样本如何被取出”,DataLoader 描述“如何按 batch、顺序和并行策略取样本”。先把数据集边界定义清楚,再在训练循环中消费 DataLoader,模型就不会和磁盘、路径、增强逻辑纠缠在一起。
学习目标
- 用
TensorDataset快速包装 Tensor,用自定义Dataset连接真实数据。 - 掌握
batch_size、shuffle、drop_last、collate_fn和next(iter(loader))。 - 知道数据划分、采样器、多进程和 pinned memory 的使用边界。
核心知识点
- 自定义 Dataset 至少实现
__len__()和__getitem__()。 - DataLoader 每次迭代返回一个 batch,默认 collate 会沿第 0 维堆叠同形状 Tensor。
- 训练集常见
shuffle=True;验证/测试集通常保持固定顺序,避免统计结果漂移。 drop_last=True会丢弃最后一个不足 batch 的小批次,适合要求固定 batch shape 的场景。num_workers、pin_memory和persistent_workers是性能调优项,应结合机器和数据读取成本测量。
常用用法
TensorDataset:把 Tensor 组成样本
import torch
from torch.utils.data import TensorDataset
features = torch.arange(12, dtype=torch.float32).reshape(6, 2)
labels = torch.arange(6)
dataset = TensorDataset(features, labels)
print(len(dataset), dataset[0][0].tolist(), int(dataset[0][1]))
# 输出:6 [0.0, 1.0] 0传入的 Tensor 第 0 维必须长度一致;它适合内存中已经整理好的小型数据。
DataLoader:按 batch 迭代
import torch
from torch.utils.data import DataLoader, TensorDataset
features = torch.arange(12, dtype=torch.float32).reshape(6, 2)
labels = torch.arange(6)
loader = DataLoader(TensorDataset(features, labels), batch_size=2, shuffle=False)
batch_features, batch_labels = next(iter(loader))
print(batch_features.shape, batch_labels.tolist())
# 输出:torch.Size([2, 2]) [0, 1]next(iter(loader)) 是快速检查 batch 形状和标签顺序的常用写法;训练时使用 for batch_features, batch_labels in loader 遍历全部 batch。
Dataset.__len__ 与 __getitem__:自定义数据集
import torch
from torch.utils.data import Dataset
class ToyDataset(Dataset):
def __init__(self):
self.features = torch.tensor([[1.0], [2.0], [3.0]])
self.labels = torch.tensor([0, 1, 1])
def __len__(self):
return len(self.features)
def __getitem__(self, index):
return self.features[index], self.labels[index]
dataset = ToyDataset()
print(len(dataset), dataset[1][0].item(), int(dataset[1][1]))
# 输出:3 2.0 1真实项目可以在 __getitem__ 里读取路径、解码图像、做变换,但不要在这里保存会随样本无限增长的临时状态。
shuffle:训练集随机打乱
import torch
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(7)
data = TensorDataset(torch.arange(5))
loader = DataLoader(data, batch_size=2, shuffle=True)
first_batch = next(iter(loader))[0]
print(first_batch.tolist())
# 输出:随机抽取的两个样本(固定种子后通常可复现)验证和测试一般使用 shuffle=False,训练集则根据任务需要打乱;时间序列等有顺序约束的数据不要盲目 shuffle。
drop_last:丢弃不完整 batch
import torch
from torch.utils.data import DataLoader, TensorDataset
data = TensorDataset(torch.arange(5))
kept = DataLoader(data, batch_size=2, drop_last=False)
fixed = DataLoader(data, batch_size=2, drop_last=True)
print(len(kept), len(fixed))
# 输出:3 2drop_last=True 会舍弃最后 1 个样本;记录样本覆盖率,避免在小数据集上无意损失大量数据。
collate_fn:自定义 batch 组装
import torch
from torch.utils.data import DataLoader, Dataset
class VariableLengthDataset(Dataset):
def __init__(self):
self.items = [torch.tensor([1, 2]), torch.tensor([3]), torch.tensor([4, 5, 6])]
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def pad_collate(items):
return torch.nn.utils.rnn.pad_sequence(items, batch_first=True, padding_value=0)
loader = DataLoader(VariableLengthDataset(), batch_size=3, collate_fn=pad_collate)
batch = next(iter(loader))
print(batch.shape, batch.tolist())
# 输出:torch.Size([3, 3]) [[1, 2, 0], [3, 0, 0], [4, 5, 6]]变长文本、检测框等数据不能直接用默认 collate;自定义函数要明确 padding、mask 和标签的形状。
random_split:划分训练集与验证集
import torch
from torch.utils.data import random_split, TensorDataset
dataset = TensorDataset(torch.arange(10))
generator = torch.Generator().manual_seed(7)
train_set, valid_set = random_split(dataset, [8, 2], generator=generator)
print(len(train_set), len(valid_set))
# 输出:8 2固定 Generator 能复现划分;实际项目还应记录划分索引,避免后续数据更新造成训练/验证泄漏。
不常用但需要知道
Subset:按索引复用数据集
import torch
from torch.utils.data import Subset, TensorDataset
dataset = TensorDataset(torch.arange(6))
subset = Subset(dataset, [1, 3, 5])
print(len(subset), int(subset[0][0]))
# 输出:3 1Subset 不复制底层数据,适合交叉验证或使用预先计算的训练/验证索引。
WeightedRandomSampler:按权重采样类别
import torch
from torch.utils.data import DataLoader, TensorDataset, WeightedRandomSampler
labels = torch.tensor([0, 0, 0, 1])
weights = torch.where(labels == 1, torch.tensor(3.0), torch.tensor(1.0))
sampler = WeightedRandomSampler(weights, num_samples=4, replacement=True)
loader = DataLoader(TensorDataset(torch.arange(4)), batch_size=2, sampler=sampler)
batch = next(iter(loader))[0]
print(batch.shape)
# 输出:torch.Size([2, 1])(具体索引按随机采样变化)使用 sampler 时不要同时设置 shuffle=True;权重应按样本给出,不能把类别权重数组直接传进去。
pin_memory 与 non_blocking:加速 CPU 到 CUDA 的拷贝
import torch
from torch.utils.data import DataLoader, TensorDataset
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
loader = DataLoader(TensorDataset(torch.ones(4, 2)), batch_size=2, pin_memory=device.type == "cuda")
batch = next(iter(loader))[0].to(device, non_blocking=True)
print(batch.device)
# 输出:cpu,或可用时的 cuda:0只有在 CUDA 拷贝确实是瓶颈时才启用并测量收益;CPU 上 non_blocking 不会把同步读取变成异步计算。
num_workers:并行读取样本
import torch
from torch.utils.data import DataLoader, TensorDataset
loader = DataLoader(TensorDataset(torch.arange(4)), batch_size=2, num_workers=0)
print(loader.num_workers)
# 输出:0(先用单进程确认逻辑,再按平台调优)Windows、Notebook 和包含不可序列化对象的 Dataset 对 worker 更敏感;先保证 num_workers=0 正确,再逐步增加并测量。
易混点
len(loader)是 batch 数,不是样本数;最后一个 batch 是否完整由drop_last决定。shuffle和sampler通常不能同时指定;采样策略要集中在一个地方。- Dataset 返回字典、元组或 Tensor 会影响默认 collate 的结果结构,第一次使用先打印一个 batch。
pin_memory只涉及主机内存到 CUDA 的拷贝,不等价于“把数据放进显存”。- 数据增强应只用于训练集;验证和测试需要固定、可比较的预处理。
课后小问
为什么建议先运行
next(iter(loader))?答案: 它能在训练开始前快速检查 batch 的类型、shape、标签范围和设备迁移边界。
解析: DataLoader 的错误通常在首次取样时才暴露,提前检查能把“数据管道问题”和“模型问题”分开。
drop_last=True是否一定更好?答案: 不一定;它保证 batch 尺寸一致,却会丢弃最后不足一批的样本。
解析: BatchNorm、固定 shape 的编译或多卡同步可能需要固定 batch;小数据集或验证统计则通常不应无意丢数据。
本节小结
Dataset 负责单样本,DataLoader 负责 batch 和采样策略。先用 TensorDataset 和单进程 Loader 验证契约,再按需加入自定义 Dataset、collate、采样器、多进程和 pinned memory。
快速回顾
Dataset:__len__ + __getitem__
DataLoader:batch_size / shuffle / sampler
快速验形:next(iter(loader))
变长数据:collate_fn
划分:random_split / Subset
性能:pin_memory + non_blocking + num_workers(测量后启用)
阿源的知识库