《深度学习》—— DataLoader数据处理、transforms

DataLoader简介

在 PyTorch 里,DataLoader 是一个极为重要的工具,位于** torch.utils.data** 模块中,它为数据的批量加载、打乱和并行处理提供了便捷的方式,能够显著提升数据处理的效率,尤其是在大规模数据集的训练过程中。

基本使用

以下是一个简单的使用示例,展示了如何使用 DataLoader 来加载自定义的数据集:

import torch
from torch.utils.data import Dataset, DataLoader

# 自定义数据集类
class CustomDataset(Dataset):
    def __init__(self):
        # 假设我们有 10 个数据样本
        self.data = torch.arange(10).float()

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

# 创建数据集实例
dataset = CustomDataset()

# 创建 DataLoader 实例
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)

# 遍历 DataLoader
for batch in dataloader:
    print(batch)
  • 在上述代码中:
    • 1.首先定义了一个自定义的数据集类 CustomDataset,它继承自 torch.utils.data.Dataset,并重写了 __len __和 __getitem__方法。
    • 2.然后创建了该数据集的一个实例 dataset。
    • 3.接着使用 DataLoader 对数据集进行封装,设置了批量大小为 2,并且开启了数据打乱功能。
    • 4.最后通过 for 循环遍历 DataLoader,每次迭代都会返回一个批量的数据。
参数详解

DataLoader 类有多个重要的参数,下面详细解释这些参数的作用:

DataLoader(
    dataset,
    batch_size=1,
    shuffle=False,
    sampler=None,
    batch_sampler=None,
    num_workers=0,
    collate_fn=None,
    pin_memory=False,
    drop_last=False,
    timeout=0,
    worker_init_fn=None,
    multiprocessing_context=None
)
  • dataset:必须传入的参数,代表要加载的数据集,通常是 torch.utils.data.Dataset 的子类实例。
  • batch_size:每个批量加载的数据样本数量,默认为 1。
  • shuffle:布尔值,若设置为 True,则在每个 epoch 开始时打乱数据集,有助于模型更好地学习数据特征,默认为 False。
  • sampler:自定义的采样器,用于指定数据加载的顺序。如果指定了该参数,shuffle 参数将被忽略。
  • batch_sampler:类似于 sampler,但它返回的是批量的索引,而不是单个样本的索引。
  • num_workers:用于数据加载的子进程数量。设置为 0 表示在主进程中进行数据加载,大于 0 则会开启多个子进程并行加载数据,可提高数据加载速度,特别是在处理大规模数据集时。
  • collate_fn:用于将多个样本合并成一个批量的函数。默认情况下,DataLoader 会使用一个简单的合并函数,但在处理复杂的数据结构时,可能需要自定义该函数。
  • pin_memory:布尔值,若设置为 True,会将数据加载到 CUDA 固定内存中,这样可以加快数据从 CPU 到 GPU 的传输速度,适用于使用 GPU 进行训练的场景。
  • drop_last:布尔值,若设置为 True,当数据集的样本数量不能被 batch_size 整除时,会丢弃最后一个不完整的批量,默认为 False。
  • timeout:如果使用了多进程数据加载,该参数指定了从队列中获取数据的超时时间,默认为 0。
  • worker_init_fn:每个工作进程启动时调用的函数,可用于初始化工作进程的状态。
  • multiprocessing_context:用于指定多进程的上下文,通常不需要手动设置。
结合 GPU 使用

在使用 GPU 进行训练时,可以通过设置 pin_memory=True 来加速数据从 CPU 到 GPU 的传输,示例如下:

import torch
from torch.utils.data import Dataset, DataLoader

class CustomDataset(Dataset):
    def __init__(self):
        self.data = torch.arange(10).float()

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

dataset = CustomDataset()
dataloader = DataLoader(dataset, batch_size=2, shuffle=True, pin_memory=True)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

for batch in dataloader:
    batch = batch.to(device)
    print(batch)

在这个示例中,我们将 pin_memory 设置为 True,并将每个批量的数据移动到 GPU 设备上进行处理。

PyTorch 的 DataLoader 提供了强大而灵活的数据加载功能,通过合理设置其参数,可以有效地提高数据处理效率,从而加速模型的训练过程。无论是处理小型数据集还是大规模的图像、文本等数据,DataLoader 都是一个不可或缺的工具。

transforms简介

在深度学习中,尤其是在使用 PyTorch 进行计算机视觉任务时,transforms 是一个非常重要的模块,它位于 torchvision.transforms 中。transforms 提供了一系列用于图像预处理和数据增强的工具,能够帮助我们将原始图像数据转换为适合模型输入的格式,并通过数据增强技术增加数据的多样性,提高模型的泛化能力。

常见的变换操作
  1. 图像尺寸调整
    • Resize:用于将图像的尺寸调整为指定的大小。
    import torchvision.transforms as transforms
    from PIL import Image
    
    # 打开一张图像
    image = Image.open('example.jpg')
    
    # 定义尺寸调整变换
    resize_transform = transforms.Resize((224, 224))
    resized_image = resize_transform(image)
    
     上述代码将图像的尺寸调整为 224x224 像素。
    
  2. 图像裁剪
    • CenterCrop:从图像的中心位置裁剪出指定大小的区域。
    center_crop_transform = transforms.CenterCrop(224)
    cropped_image = center_crop_transform(image)
    
     此代码从图像中心裁剪出一个 224x224 像素的区域。
    
    • RandomCrop:随机从图像中裁剪出指定大小的区域,可用于数据增强。
    random_crop_transform = transforms.RandomCrop(224)
    random_cropped_image = random_crop_transform(image)
    
  3. 图像翻转
    • RandomHorizontalFlip:以一定的概率(默认为 0.5)对图像进行水平翻转。
    horizontal_flip_transform = transforms.RandomHorizontalFlip()
    flipped_image = horizontal_flip_transform(image)
    
    • RandomVerticalFlip:以一定的概率(默认为 0.5)对图像进行垂直翻转。
  4. 图像归一化
    • Normalize:对图像的每个通道进行归一化处理,使图像数据具有零均值和单位方差,有助于模型的训练。
    # 假设图像的通道均值和标准差分别为 [0.5, 0.5, 0.5] 和 [0.5, 0.5, 0.5]
    normalize_transform = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
    # 通常需要先将 PIL 图像转换为 Tensor
    to_tensor = transforms.ToTensor()
    tensor_image = to_tensor(image)
    normalized_image = normalize_transform(tensor_image)
    
组合变换操作

在实际应用中,我们通常会将多个变换操作组合在一起,使用 transforms.Compose 函数可以方便地实现这一点。

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

transformed_image = transform(image)

上述代码将尺寸调整、随机水平翻转、转换为 Tensor 和归一化操作组合在一起,对图像进行了一系列的预处理。

在数据集上应用变换

在使用 torchvision.datasets 加载数据集时,可以直接将定义好的变换应用到数据集中。

import torchvision
from torchvision import transforms

# 定义变换
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 加载 CIFAR-10 数据集,并应用变换
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                             download=True, transform=transform)

这样,在加载数据集时,每个图像都会自动经过定义好的变换处理。

实例

对20种食物图片进行识别,如下图:
在这里插入图片描述
每个类别如下:

在这里插入图片描述
数据包含数据集和测试集:
在这里插入图片描述

实例过程
生成训练集和测试集的文本文件
# 该函数用于生成训练集或测试集的文本文件,文件中记录了图像路径及其对应的标签
def train_test_file(root, dir):
    # 打开一个以 dir 命名的文本文件,用于写入图像路径和标签信息
    file_txt = open(dir + '.txt', 'w')
    # 拼接根目录和当前目录,得到完整的路径
    path = os.path.join(root, dir)
    # 遍历指定路径下的所有文件和文件夹
    for roots, directories, files in os.walk(path):
        # 如果当前目录下有子文件夹,将子文件夹名称存储在 dirs 列表中
        if len(directories) != 0:
            dirs = directories
        else:
            # 获取当前目录的名称
            now_dir = roots.split('\\')
            # 遍历当前目录下的所有文件
            for file in files:
                # 拼接当前文件的完整路径
                path_1 = os.path.join(roots, file)
                print(path_1)
                # 将文件路径和对应的标签(标签为当前目录在 dirs 列表中的索引)写入文本文件
                file_txt.write(path_1 + ' ' + str(dirs.index(now_dir[-1])) + '\n')
    # 关闭文件
    file_txt.close()

# 数据集的根目录
root = r'.\食物分类\food_dataset2'
# 训练集目录名称
train_dir = 'train'
# 测试集目录名称
test_dir = 'test'
# 生成训练集的文本文件
train_test_file(root, train_dir)
# 生成测试集的文本文件
train_test_file(root, test_dir)

# 自定义类,用于演示 __getitem__ 和 __len__ 方法
class USE_getitem():
    def __init__(self, text):
        # 初始化时传入一个字符串
        self.text = text

    def __getitem__(self, index):
        # 根据索引获取字符串中的字符,并将其转换为大写
        result = self.text[index].upper()
        return result

    def __len__(self):
        # 返回字符串的长度
        return len(self.text)

# 创建 USE_getitem 类的实例
p = USE_getitem('pytorch')
# 打印索引为 0 和 1 的字符
print(p[0], p[1])
# 打印字符串的长度
len(p)
定义训练集和验证集的数据预处理转换
data_transforms = {
    'train':
        transforms.Compose([
            # 将图像大小调整为 300x300
            transforms.Resize([300, 300]),
            # 随机旋转图像,旋转角度范围为 -45 到 45 度
            transforms.RandomRotation(45),
            # 从图像中心裁剪出 256x256 的区域
            transforms.CenterCrop(256),
            # 以 0.5 的概率随机水平翻转图像
            transforms.RandomHorizontalFlip(p=0.5),
            # 以 0.5 的概率随机垂直翻转图像
            transforms.RandomVerticalFlip(p=0.5),
            # 随机调整图像的亮度、对比度、饱和度和色相
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            # 以 0.1 的概率将图像转换为灰度图
            transforms.RandomGrayscale(p=0.1),
            # 将图像转换为张量
            transforms.ToTensor(),
            # 对图像进行归一化处理
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    'valid':
        transforms.Compose([
            # 将图像大小调整为 256x256
            transforms.Resize([256, 256]),
            # 将图像转换为张量
            transforms.ToTensor(),
            # 对图像进行归一化处理
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
}

自定义数据集类
# 自定义数据集类,继承自 torch.utils.data.Dataset
class food_dataset(Dataset):
    def __init__(self, file_path, transform=None):
        # 初始化时传入文本文件路径和数据预处理转换
        self.file_path = file_path
        # 存储图像路径的列表
        self.imgs = []
        # 存储图像标签的列表
        self.labels = []
        # 数据预处理转换
        self.transform = transform
        # 打开文本文件
        with open(self.file_path) as f:
            # 读取文本文件中的每一行,并按空格分割
            samples = [x.strip().split(' ') for x in f.readlines()]
            # 遍历每一行数据
            for img_path, label in samples:
                # 将图像路径添加到 imgs 列表中
                self.imgs.append(img_path)
                # 将图像标签添加到 labels 列表中
                self.labels.append(label)

    def __len__(self):
        # 返回图像的数量
        return len(self.imgs)

    def __getitem__(self, idx):
        # 根据索引打开图像
        image = Image.open(self.imgs[idx])
        # 如果存在数据预处理转换,对图像进行处理
        if self.transform:
            image = self.transform(image)
        # 获取图像的标签
        label = self.labels[idx]
        # 将标签转换为 torch.Tensor 类型
        label = torch.from_numpy(np.array(label, dtype=np.int64))
        return image, label
创建训练集和测试集
# 创建训练集数据集对象
training_data = food_dataset(file_path='./train.txt', transform=data_transforms['train'])
# 创建测试集数据集对象
test_data = food_dataset(file_path='./test.txt', transform=data_transforms['valid'])

# 创建训练集数据加载器,设置批量大小为 64,打乱数据顺序
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
# 创建测试集数据加载器,设置批量大小为 64,打乱数据顺序
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
判断是否使用GPU
# 判断是否支持 GPU 或 MPS 加速,若都不支持则使用 CPU
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(f'Using {device} device')
定义卷积神经网络
# 定义卷积神经网络类,继承自 torch.nn.Module
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第一个卷积块,包含卷积层、批量归一化层、ReLU 激活函数和最大池化层
        self.conv_block1 = nn.Sequential(
            # 输入通道数为 3,输出通道数为 32,卷积核大小为 3,填充为 1
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第二个卷积块
        self.conv_block2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第三个卷积块
        self.conv_block3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第四个卷积块
        self.conv_block4 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第五个卷积块
        self.conv_block5 = nn.Sequential(
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 全连接层,输入维度为 512 * 8 * 8,输出维度为 2048
        self.fc1 = nn.Linear(512 * 8 * 8, 2048)
        # ReLU 激活函数
        self.relu = nn.ReLU(inplace=True)
        # Dropout 层,防止过拟合
        self.dropout = nn.Dropout(0.5)
        # 输出层,输入维度为 2048,输出维度为 20
        self.fc2 = nn.Linear(2048, 20)

    def forward(self, x):
        # 依次通过各个卷积块
        x = self.conv_block1(x)
        x = self.conv_block2(x)
        x = self.conv_block3(x)
        x = self.conv_block4(x)
        x = self.conv_block5(x)
        # 将多维的特征图展平为一维向量
        x = x.view(-1, 512 * 8 * 8)
        # 通过第一个全连接层
        x = self.fc1(x)
        # 通过 ReLU 激活函数
        x = self.relu(x)
        # 通过 Dropout 层
        x = self.dropout(x)
        # 通过输出层
        output = self.fc2(x)
        return output

# 创建 CNN 模型实例,并将其移动到指定设备上
model = CNN().to(device)
训练函数
# 训练函数,用于训练模型
def train(dataloader, model, loss_fn, optimizer):
    # 将模型设置为训练模式
    model.train()
    # 记录当前批次的编号
    batch_size_num = 1
    # 遍历数据加载器中的每一个批次
    for x, y in dataloader:
        # 将输入数据和标签移动到指定设备上
        x, y = x.to(device), y.to(device)
        # 前向传播,计算模型的预测结果
        pred = model.forward(x)
        # 计算损失值
        loss = loss_fn(pred, y)
        # 梯度清零
        optimizer.zero_grad()
        # 反向传播,计算梯度
        loss.backward()
        # 更新模型参数
        optimizer.step()
        # 获取损失值
        loss_value = loss.item()
        # 每 10 个批次打印一次损失值
        if batch_size_num % 10 == 0:
            print(f'loss:{loss_value:7f}  [number:{batch_size_num}]')
        # 批次编号加 1
        batch_size_num += 1

测试函数
# 测试函数,用于评估模型的性能
def test(dataloader, model, loss_fn):
    # 获取数据集的大小
    size = len(dataloader.dataset)
    # 获取数据加载器中的批次数量
    num_batches = len(dataloader)
    # 将模型设置为评估模式
    model.eval()
    # 初始化测试损失和正确预测的数量
    test_loss, correct = 0, 0
    # 关闭梯度计算
    with torch.no_grad():
        # 遍历数据加载器中的每一个批次
        for x, y in dataloader:
            # 将输入数据和标签移动到指定设备上
            x, y = x.to(device), y.to(device)
            # 前向传播,计算模型的预测结果
            pred = model.forward(x)
            # 累加测试损失
            test_loss += loss_fn(pred, y).item()
            # 计算正确预测的数量
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    # 计算平均测试损失
    test_loss /= num_batches
    # 计算平均正确率
    correct /= size
    # 打印测试结果
    print(f'Test result: \n Accuracy:{(100 * correct)}%,Avg loss:{test_loss}')

创建损失函数和优化器
# 创建交叉熵损失函数对象
loss_fn = nn.CrossEntropyLoss()
# 创建 Adam 优化器,用于更新模型参数
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
多轮训练得到结果
# 训练的轮数
epochs = 30
# 循环训练 epochs 轮
for t in range(epochs):
    print(f'epoch{t + 1}\n--------------------')
    # 调用训练函数进行训练
    train(train_dataloader, model, loss_fn, optimizer)
print('Done!')
# 调用测试函数进行测试
test(test_dataloader, model, loss_fn)

结果:
在这里插入图片描述

### 数据集划分的重要性 在机器学习和深度学习实践中,数据集的合理划分对于模型的有效性和可靠性至关重要[^1]。无论是分类、回归还是其他复杂任务,如图像识别或自然语言处理,恰当的数据分割策略都是不可或缺的一部分。 ### 使用sklearn进行简单随机划分 `sklearn.model_selection.train_test_split()` 是一种简便的方法来进行初始的数据拆分: ```python from sklearn.model_selection import train_test_split X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.4, random_state=42) X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42) ``` 这段代码首先将原始数据按照70%训练集和30%临时集的比例分开;接着再把这30%进一步均等地分配给验证集和测试集。 ### PyTorch中的自定义Dataset与DataLoader实现更灵活控制 为了适应不同应用场景的需求,可以利用 `torch.utils.data.Dataset` 类来自定义数据加载器,并配合 `torch.utils.data.DataLoader` 来创建迭代器访问批次样本。这种方式允许更加精细地管理各个子集之间的关系以及它们各自的预处理逻辑[^3]。 ```python import torch from torch.utils.data import Dataset, DataLoader, SubsetRandomSampler from torchvision.datasets import MNIST from torchvision.transforms import ToTensor dataset = MNIST(root='./data', download=True, transform=ToTensor()) indices = list(range(len(dataset))) split = int(np.floor(0.2 * len(indices))) # 假设取80%-20% np.random.shuffle(indices) train_indices, val_indices = indices[split:], indices[:split] train_sampler = SubsetRandomSampler(train_indices) val_sampler = SubsetRandomSampler(val_indices) train_loader = DataLoader(dataset, batch_size=64, sampler=train_sampler) val_loader = DataLoader(dataset, batch_size=64, sampler=val_sampler) ``` 上述例子展示了如何基于MNIST手写数字图片库构建一个简单的二元分割方案——即只设置了训练集和验证集。实际应用中可能还需要额外引入独立于两者之外的一个测试集用于最终评估。 ### 正确使用各阶段数据集 值得注意的是,在整个开发周期内应当严格遵循如下原则:仅用训练集调整参数权重;借助验证集挑选最优超参配置而不泄露任何关于其分布的信息至模型本身;最后依靠完全未知的测试集衡量整体表现并报告结果。违反此流程可能导致过拟合现象发生,使得实验结论失去意义[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值