3、数据加载错误:Dataset与DataLoader配置错误、数据路径不存在、文件格式不支持、数据增强导致的维度不匹配
数据加载这块,说难不难,说简单也真容易翻车。我见过太多同学在训练脚本里折腾半天,结果跑起来第一行就报错——数据没读进来。说白了,数据加载是整个流水线的入口,入口堵了,后面全是白搭。
这一章咱们就聊聊数据加载常见的几类错误。我按自己的排查习惯,把它们分成了四个大类:路径与文件、Dataset 实现、DataLoader 配置、以及数据增强的维度问题。你想想看,是不是每次报错都逃不出这几个坑?
核心原则:数据加载错误通常发生在训练启动的前 5 秒内。排查时先看报错栈的最后一层,是 IO 错误、类型错误还是形状错误——这决定了你该往哪个方向查。
3.1 路径不存在与文件格式不支持
先聊最基础也最气人的错误——路径不存在。我刚开始带项目那会儿,经常在服务器上跑脚本,结果报 FileNotFoundError。查了半天,原来是数据集放在 /data/train,但代码里写的是 ./data/train。相对路径和路径的坑,谁踩谁知道。
注意:在分布式训练场景下,不同节点的数据挂载点可能不同。我曾经遇到过主节点能读到数据,但其他 worker 节点路径不一致,导致训练到一半才报错。建议在 Dataset 初始化时就做一次路径校验。
文件格式不支持也是个常见问题。PyTorch 的 ImageFolder 默认只认常见的图片格式(jpg、png、bmp 等),如果你数据集里混了 .tiff 或 .webp,它直接罢工。我个人的习惯是,在数据预处理阶段统一转成 jpg 或 png,省得后面折腾。
# 路径校验的推荐写法
import os
from torch.utils.data import Dataset
class MyDataset(Dataset):
def __init__(self, root_dir):
if not os.path.exists(root_dir):
raise FileNotFoundError(f"数据路径不存在: {root_dir}")
self.file_list = [f for f in os.listdir(root_dir)
if f.endswith(('.jpg', '.png', '.jpeg'))]
if len(self.file_list) == 0:
raise RuntimeError(f"目录 {root_dir} 下没有找到支持的图片文件")
3.2 Dataset 实现中的常见错误
Dataset 的 __getitem__ 方法,说白了就是每次返回一个样本。但这里有个很容易忽略的点——返回的数据类型必须一致。我见过有人有的样本返回 torch.Tensor,有的返回 numpy.ndarray,结果 DataLoader 在拼 batch 时直接崩了。
另一个坑是 __len__ 和实际索引范围不一致。比如你 __len__ 返回 1000,但实际只有 999 个有效样本,DataLoader 遍历到第 1000 个时就会报 IndexError。嗯,这里要注意,__len__ 必须和 __getitem__ 能处理的索引范围严格对齐。
| 错误类型 | 典型报错信息 | 排查方向 |
|---|---|---|
| 返回类型不一致 | RuntimeError: stack expects each tensor to be equal size |
检查 __getitem__ 中所有返回值的 dtype 和 shape |
| 索引越界 | IndexError: list index out of range |
核对 __len__ 与实际数据量 |
| 图像读取失败 | PIL.UnidentifiedImageError |
检查文件是否损坏,或格式是否被支持 |
小技巧:调试 Dataset 时,我习惯先手动遍历前几个样本:for i in range(5): print(dataset[i])。这样能快速发现返回值的形状、类型问题,不用每次都跑完整训练循环。
3.3 DataLoader 配置错误
DataLoader 的配置,看起来就几个参数,但组合起来能玩出各种花样。先说 batch_size,这个一般不会错,但 num_workers 就经常出问题。我建议 num_workers 不要超过 CPU 核心数,否则多进程之间会有资源竞争,反而更慢。
更隐蔽的错误在 collate_fn 上。默认的 collate_fn 会把一批样本堆叠成 batch,但如果你的样本形状不一样——比如图像尺寸不同——它就报错了。这时候你需要自己写 collate_fn,做 padding 或者 resize。
# 自定义 collate_fn 处理变长序列
def custom_collate(batch):
images = []
labels = []
for img, label in batch:
# 假设图像已经 resize 到统一尺寸
images.append(img)
labels.append(label)
return torch.stack(images), torch.tensor(labels)
dataloader = DataLoader(dataset, batch_size=32,
collate_fn=custom_collate,
num_workers=4)
3.4 数据增强导致的维度不匹配
数据增强这块,我踩过的坑最多。你以为加个随机裁剪、翻转就完事了?其实维度问题往往就出在这里。
举个例子,你用 torchvision.transforms.RandomCrop(224),但输入图像是灰度图(单通道),裁剪后形状是 (1, 224, 224)。而你的模型第一层卷积期望输入是 (3, 224, 224)——维度不匹配,直接报错。
为什么会这样?因为很多数据增强操作默认输入是 RGB 三通道。如果你用了灰度图,要么在增强前转成三通道,要么调整增强参数。
排查建议:在数据增强流水线后加一个 print 或断点,打印增强后的张量形状。我一般会在 __getitem__ 里临时加一行 print(img.shape),确认每个样本的维度都符合预期。
还有一个容易忽略的点——标签和图像不同步。比如你做了随机水平翻转,图像翻转了,但对应的分割掩膜或关键点坐标没跟着翻转。这会导致模型学到的映射关系是错的。我曾经在目标检测项目里犯过这个错,模型训练了 20 个 epoch 才发现 mAP 一直上不去,排查了三天才找到原因。
# 确保数据增强同时作用于图像和标签
import torchvision.transforms as T
class TrainTransform:
def __init__(self):
self.flip = T.RandomHorizontalFlip(p=0.5)
def __call__(self, img, mask):
# 使用相同的随机种子保证图像和掩膜同步变换
seed = torch.randint(0, 2**32, (1,)).item()
torch.manual_seed(seed)
img = self.flip(img)
torch.manual_seed(seed)
mask = self.flip(mask)
return img, mask
3.5 排查流程总结
说了这么多,其实排查数据加载错误有个固定的套路。我个人习惯按这个顺序来:
- 看报错栈——定位到是 IO 错误、形状错误还是类型错误
- 检查路径——确认数据集路径存在,且有读取权限
- 验证 Dataset——手动遍历前几个样本,看返回值的形状和类型
- 测试 DataLoader——用
next(iter(dataloader))取一个 batch,看能否正常堆叠 - 检查数据增强——确认增强后的图像和标签维度一致,且同步变换
你想想看,是不是每次数据加载报错,都能归到上面某一步?把这五步走一遍,90% 的问题都能定位到。剩下的 10%,嗯,可能是硬件问题或者 PyTorch 版本兼容性——那又是另一章的内容了。