PyTorch数据加载实战:从torchvision.datasets到DataLoader的完整流程(以CIFAR10和MNIST为例)

📅 2026/7/14 12:58:49
PyTorch数据加载实战:从torchvision.datasets到DataLoader的完整流程(以CIFAR10和MNIST为例)
1. PyTorch数据加载基础认识torchvision.datasets当你刚开始接触PyTorch时数据加载可能是最让人头疼的部分之一。别担心torchvision.datasets模块就是专门为解决这个问题而生的。这个模块内置了多种常用的计算机视觉数据集比如CIFAR10和MNIST让你可以轻松地加载和使用这些数据。torchvision.datasets中的每个数据集都是torch.utils.data.Dataset的子类这意味着它们都遵循相同的接口规范。你只需要几行代码就能把数据集加载到内存中完全不需要自己写复杂的文件解析逻辑。我刚开始用PyTorch时就特别喜欢这个设计——它把繁琐的数据预处理工作都封装好了让我们可以专注于模型构建和训练。以CIFAR10数据集为例它包含了6万张32x32像素的彩色图片分为10个类别每个类别有6000张图片。其中5万张用于训练1万张用于测试。这些图片涵盖了日常生活中常见的物体比如飞机、汽车、鸟类等非常适合用来练习图像分类任务。import torchvision # 加载CIFAR10训练集 train_set torchvision.datasets.CIFAR10( root./data, # 数据集保存路径 trainTrue, # 加载训练集 downloadTrue # 如果本地没有就自动下载 ) # 加载CIFAR10测试集 test_set torchvision.datasets.CIFAR10( root./data, trainFalse, # 加载测试集 downloadTrue )2. 数据预处理transform参数详解原始数据通常不能直接喂给模型我们需要进行一些预处理操作。在torchvision.datasets中transform参数就是用来定义这些预处理步骤的。这个参数接受一个可调用对象通常是torchvision.transforms中的各种变换组合。最常用的变换包括ToTensor将PIL图像转换为PyTorch张量和Normalize标准化数据。我建议初学者至少使用ToTensor因为PyTorch模型处理的是张量而不是PIL图像。下面是一个典型的transform设置示例from torchvision import transforms # 定义数据预处理流程 transform transforms.Compose([ transforms.ToTensor(), # 转换为张量 transforms.Normalize( # 标准化 mean[0.5, 0.5, 0.5], # RGB三通道的均值 std[0.5, 0.5, 0.5] # RGB三通道的标准差 ) ]) # 应用transform加载数据集 train_set torchvision.datasets.CIFAR10( root./data, trainTrue, transformtransform, # 应用预处理 downloadTrue )在实际项目中你可能还需要添加其他变换比如随机裁剪RandomCrop、随机水平翻转RandomHorizontalFlip等数据增强操作。这些技巧可以有效防止模型过拟合我在多个项目中都验证过它们的效果。3. DataLoader高效加载批量数据有了数据集对象后我们还需要一个数据加载器来批量读取数据。这就是torch.utils.data.DataLoader的用武之地。DataLoader负责管理数据的批量加载、打乱顺序和多进程读取可以显著提高数据加载效率。from torch.utils.data import DataLoader # 创建DataLoader train_loader DataLoader( train_set, batch_size32, # 每批加载32个样本 shuffleTrue, # 每个epoch打乱数据顺序 num_workers2 # 使用2个子进程加载数据 ) test_loader DataLoader( test_set, batch_size32, shuffleFalse, # 测试集不需要打乱 num_workers2 )batch_size的选择很有讲究。太小会导致训练不稳定太大又可能超出显存容量。根据我的经验对于CIFAR10这样的数据集32或64是比较合适的起点。num_workers参数控制用于数据加载的子进程数量通常设置为CPU核心数的1/2到2/3比较合适。4. 完整实战从数据加载到模型训练现在我们把所有部分组合起来实现一个完整的图像分类流程。这个例子会使用CIFAR10数据集训练一个简单的卷积神经网络。import torch import torch.nn as nn import torch.optim as optim # 定义简单的CNN模型 class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 nn.Conv2d(3, 6, 5) # 输入通道3输出通道6卷积核5x5 self.pool nn.MaxPool2d(2, 2) # 2x2最大池化 self.conv2 nn.Conv2d(6, 16, 5) self.fc1 nn.Linear(16 * 5 * 5, 120) self.fc2 nn.Linear(120, 84) self.fc3 nn.Linear(84, 10) # 输出10个类别 def forward(self, x): x self.pool(nn.functional.relu(self.conv1(x))) x self.pool(nn.functional.relu(self.conv2(x))) x x.view(-1, 16 * 5 * 5) # 展平 x nn.functional.relu(self.fc1(x)) x nn.functional.relu(self.fc2(x)) x self.fc3(x) return x # 初始化模型、损失函数和优化器 model SimpleCNN() criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.001, momentum0.9) # 训练循环 for epoch in range(5): # 训练5个epoch running_loss 0.0 for i, data in enumerate(train_loader, 0): inputs, labels data optimizer.zero_grad() # 梯度清零 outputs model(inputs) # 前向传播 loss criterion(outputs, labels) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 更新参数 running_loss loss.item() if i % 500 499: # 每500个batch打印一次 print(fEpoch {epoch1}, Batch {i1}, Loss: {running_loss/500:.3f}) running_loss 0.0 print(Finished Training)5. MNIST数据集实战另一个经典示例MNIST是另一个常用的手写数字识别数据集包含6万张28x28的灰度图像。虽然比CIFAR10简单但它仍然是测试新模型的好选择。下面我们看看如何加载和处理MNIST数据。# 定义MNIST专用的transform mnist_transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) # MNIST的均值和标准差 ]) # 加载MNIST数据集 mnist_train torchvision.datasets.MNIST( root./data, trainTrue, transformmnist_transform, downloadTrue ) mnist_test torchvision.datasets.MNIST( root./data, trainFalse, transformmnist_transform ) # 创建DataLoader mnist_train_loader DataLoader( mnist_train, batch_size64, shuffleTrue ) # 可视化部分数据 import matplotlib.pyplot as plt dataiter iter(mnist_train_loader) images, labels dataiter.next() # 创建子图网格 fig, axes plt.subplots(4, 4, figsize(8,8)) for i, ax in enumerate(axes.flat): ax.imshow(images[i][0], cmapgray) # MNIST是单通道取[0] ax.set_title(fLabel: {labels[i]}) ax.axis(off) plt.tight_layout() plt.show()6. 常见问题与解决方案在实际使用中你可能会遇到各种问题。下面分享几个我踩过的坑和解决方法下载速度慢torchvision会自动从国外服务器下载数据集国内用户可能会很慢。解决方法是在代码运行前手动下载数据集放到指定的root目录下。比如CIFAR10的数据可以手动下载后解压到./data/cifar-10-batches-py/目录。内存不足处理大型数据集时可能出现内存不足。这时可以考虑减小batch_size使用DataLoader的pin_memoryTrue参数在GPU上训练时使用更高效的数据格式如HDF5数据不均衡如果某些类别的样本数量远多于其他类别可以考虑使用WeightedRandomSamplerfrom torch.utils.data.sampler import WeightedRandomSampler # 计算每个样本的权重 class_counts torch.bincount(torch.tensor(train_set.targets)) weights 1. / class_counts.float() samples_weights weights[train_set.targets] # 创建采样器 sampler WeightedRandomSampler( weightssamples_weights, num_sampleslen(samples_weights), replacementTrue ) # 在DataLoader中使用采样器 balanced_loader DataLoader( train_set, batch_size32, samplersampler, num_workers2 )7. 自定义数据集超越torchvision.datasets虽然torchvision.datasets提供了许多常用数据集但在实际项目中我们经常需要处理自己的数据。这时可以继承torch.utils.data.Dataset类创建自定义数据集。from torch.utils.data import Dataset from PIL import Image import os class CustomDataset(Dataset): def __init__(self, root_dir, transformNone): self.root_dir root_dir self.transform transform self.image_paths [] # 存储所有图片路径 self.labels [] # 存储对应标签 # 遍历目录收集图片路径和标签 for label, class_name in enumerate(os.listdir(root_dir)): class_dir os.path.join(root_dir, class_name) if os.path.isdir(class_dir): for img_name in os.listdir(class_dir): self.image_paths.append(os.path.join(class_dir, img_name)) self.labels.append(label) def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img_path self.image_paths[idx] image Image.open(img_path).convert(RGB) # 确保是RGB格式 label self.labels[idx] if self.transform: image self.transform(image) return image, label # 使用示例 custom_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) custom_dataset CustomDataset( root_dir./custom_data, transformcustom_transform ) custom_loader DataLoader( custom_dataset, batch_size32, shuffleTrue )这个自定义数据集类假设你的数据按照以下结构组织custom_data/ class1/ img1.jpg img2.jpg ... class2/ img1.jpg ... ...8. 高级技巧数据加载性能优化当数据集很大时数据加载可能成为训练过程的瓶颈。以下是一些提升性能的技巧预加载数据如果数据集能完全放入内存可以在__init__中加载所有数据避免每次__getitem__都要读文件。使用内存映射文件对于特别大的数据集可以使用torch.load(..., mmapTrue)进行内存映射。调整num_workersDataLoader的num_workers参数需要根据你的CPU核心数合理设置。通常4-8是个不错的起点但需要实际测试。使用prefetch_generator这个第三方库可以实现数据预取减少GPU等待时间from prefetch_generator import BackgroundGenerator class DataLoaderX(DataLoader): def __iter__(self): return BackgroundGenerator(super().__iter__()) fast_loader DataLoaderX( train_set, batch_size32, shuffleTrue, num_workers4 )禁用梯度计算进行验证在验证或测试时使用torch.no_grad()上下文管理器可以显著减少内存使用correct 0 total 0 with torch.no_grad(): for data in test_loader: images, labels data outputs model(images) _, predicted torch.max(outputs.data, 1) total labels.size(0) correct (predicted labels).sum().item() print(fAccuracy: {100 * correct / total}%)