前言:作为深度学习小白,总是对"数据是如何喂给模型的"感到困惑直到理解了 DataSet 和 DataLoader 的设计哲学,才恍然大悟——原来数据加载也是一门艺术!

一、为什么要使用DataSet?

1.1 传统方式的痛点

假设要训练一个图像分类模型,最直观的想法是:

# ❌ 朴素做法:一次性加载所有数据
images = []
labels = []

for path in image_paths:
    img = cv2.imread(path)  # 读取图片
    images.append(img)
    labels.append(get_label(path))

# 转为 numpy 数组
X = np.array(images)  # 假设有 10 万张 224×224 的图片
y = np.array(labels)  # 这将占用约 15GB 内存!

# 手动分批
for i in range(0, len(X), batch_size):
    batch_X = X[i:i+batch_size]
    batch_y = y[i:i+batch_size]
    # 训练模型...

  可以发现这里存在了很多问题:

1、💥 内存爆炸:大规模数据集根本装不下

2、🐌 效率低下:无法并行读取数据

3、🔧 代码混乱:数据处理和模型训练耦合在一起

4、🔄 难以复用:换个数据集要重写大量代码

1.2 Dataset 的解决方案

为解决上述问题,PyTorch 提出了一个优雅的抽象:将数据封装成可索引的对象

# ✅ 使用 Dataset
class ImageDataset(Dataset):
    def __init__(self, image_paths, labels):
        self.paths = image_paths  # 只保存路径,不读取数据
        self.labels = labels
    
    def __len__(self):
        return len(self.paths)
    
    def __getitem__(self, idx):
        # 按需加载:只在需要时才读取图片
        image = cv2.imread(self.paths[idx])
        label = self.labels[idx]
        return torch.tensor(image), torch.tensor(label)

# 使用方式
dataset = ImageDataset(paths, labels)
loader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4)

for batch_images, batch_labels in loader:
    # 直接使用批次数据训练
    outputs = model(batch_images)
    loss = criterion(outputs, batch_labels)

DataSet所体现出来的优势:

按需加载:内存占用降低 90% 以上

自动批处理:DataLoader 自动组装 batch

并行加速num_workers=4 开启多进程读取

代码清晰:数据逻辑和训练逻辑完全解耦

二、Dataset 的核心机制

2.1 三大必备方法

由于PyTorch 的DataSet是一个抽象类,任何自定义数据集都必须实现以下三个方法:

from torch.utils.data import Dataset

class MyDataset(Dataset):
    def __init__(self, ...):
        """
        初始化方法:
        - 读取数据索引(文件路径、数据库查询结果等)
        - 设置数据增强策略
        - 不要在这里加载所有数据!
        """
        pass
    
    def __len__(self):
        """
        返回数据集大小
        DataLoader 需要这个信息来计算有多少个 batch
        """
        return len(self.data)
    
    def __getitem__(self, idx):
        """
        核心方法:根据索引返回单条数据
        - 这里才真正加载数据(按需加载)
        - 返回格式:(特征, 标签) 或只有特征(测试集)
        """
        return self.data[idx], self.label[idx]

2.2 工作流程图解

完整流程可分为三个阶段:

阶段1:创建 Dataset

dataset = MyDataset(...)

✅ 执行 __init__ 方法

✅ 准备数据索引(如文件路径列表)

不加载实际数据到内存

阶段2:创建 DataLoader

loader = DataLoader(dataset, batch_size=32, shuffle=True)

✅ 保存 dataset 引用

✅ 设置采样策略(批次大小、是否打乱等)

仍然不读取数据

阶段3:开始遍历(核心)

for batch_images, batch_labels in loader:
    # 使用数据...

DataLoader 在每次迭代时自动执行以下步骤:

  1. 采样索引
    根据 shuffle 策略生成本批次的索引列表
    例如:[0, 15, 23, 7, 31, ...](共 32 个,假设 batch_size=32)

  2. 调用 __getitem__(🔥 关键!)
    对每个索引调用一次:⚠️ 这里才真正读取数据(按需加载)

    dataset.__getitem__(0)   # 返回 (img0, label0)
    dataset.__getitem__(15)  # 返回 (img15, label15)
    dataset.__getitem__(23)  # 返回 (img23, label23)
    # ... 共调用 32 次
    
  3. 组装 batch
    将 32 条数动堆叠成张量:

    batch_images = torch.stack([img0, img15, img23, ...])  # [32, C, H, W]
    batch_labels = torch.stack([label0, label15, ...])     # [32]
    
  4. 返回循环:你在 for 循环中拿到的就是组装好的 batch_imagesbatch_labels

三、模式

DataSet里面有一个参数叫“mode”,意为模式,一般分为三种模式:train、val、test。

3.1 为什么需要不同的模式?

在机器学习中,我们通常会将数据集划分成三个部分:

完整数据集
    ├── 训练集 (Training Set)     - 用于训练模型
    ├── 验证集 (Validation Set)   - 用于调参和选择模型
    └── 测试集 (Test Set)         - 用于评估最终性能

我们可以将训练模型类比为备考:

数据集对应场景作用
训练集平时做的练习题学习知识,提升能力
验证集模拟考试检验学习效果,调整复习策略
测试集真正的考试最终评估真实水平

在训练时,只能看训练集(就像学习只能用练习题),验证集用于边学边测(模拟考),测试集只在最后用一次(真正考试)。

3.2 训练集(train)

作用:让模型学习数据中的规律

# 在训练集上:
# - 模型看到数据和标签
# - 根据预测错误来更新参数
for batch_x, batch_y in train_loader:
    prediction = model(batch_x)
    loss = criterion(prediction, batch_y)  # 计算预测和真实标签的差距
    loss.backward()  # 反向传播
    optimizer.step()  # 更新参数

3.3 验证集(val)

作用:检查模型是否"学偏了"(过拟合)

# 在验证集上:
# - 模型看到数据和标签
# - 只计算准确率,不更新参数
# - 用于调整超参数(学习率、网络层数等)
with torch.no_grad():  # 关闭梯度计算
    for batch_x, batch_y in val_loader:
        prediction = model(batch_x)
        accuracy = calculate_accuracy(prediction, batch_y)
        # 如果验证准确率下降 → 可能过拟合 → 早停

过拟合示例

Epoch 1:  Train Acc=60%, Val Acc=55%  ✅ 正常
Epoch 10: Train Acc=90%, Val Acc=85%  ✅ 正常
Epoch 20: Train Acc=99%, Val Acc=88%  ✅ 还可以
Epoch 30: Train Acc=100%, Val Acc=82% ⚠️ 过拟合!
         (训练集记住了,但验证集性能下降)

3.4 测试集 (test)

作用:评估模型在真实场景的表现

# 在测试集上:
# - 模型只能看到数据,看不到标签
# - 用于最终评估(比赛提交、论文实验)
# - 整个训练过程只用一次
for batch_x in test_loader:  # 没有 batch_y!
    prediction = model(batch_x)
    # 保存预测结果,提交到比赛平台

3.5 实际使用场景

举例:

训练流程:

# 步骤1:创建训练集和验证集
train_dataset = covidDataset('data.csv', mode='train', feature_dim=10)
val_dataset = covidDataset('data.csv', mode='val', feature_dim=10)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

# 步骤2:训练循环
for epoch in range(100):
    # ========== 训练阶段 (使用 train_loader) ==========
    model.train()  # 设置为训练模式
    for batch_x, batch_y in train_loader:
        outputs = model(batch_x)
        loss = criterion(outputs, batch_y)
        # 反向传播,更新参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
    # ========== 验证阶段 (使用 val_loader) ==========
    model.eval()  # 设置为评估模式
    with torch.no_grad():  # 不计算梯度
        for batch_x, batch_y in val_loader:
            outputs = model(batch_x)
            val_loss = criterion(outputs, batch_y)
            # 只计算损失,不更新参数
    
    print(f'Epoch {epoch}: Train Loss={loss:.4f}, Val Loss={val_loss:.4f}')

测试流程:

# 步骤3:训练完成后,用测试集预测
test_dataset = covidDataset('test.csv', mode='test', feature_dim=10)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

model.eval()
predictions = []
with torch.no_grad():
    for batch_x in test_loader:  # 注意:测试集没有标签
        outputs = model(batch_x)
        predictions.append(outputs)

# 保存预测结果
predictions = torch.cat(predictions)

更多推荐