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 提供了一系列用于图像预处理和数据增强的工具,能够帮助我们将原始图像数据转换为适合模型输入的格式,并通过数据增强技术增加数据的多样性,提高模型的泛化能力。
常见的变换操作
- 图像尺寸调整
- 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 像素。
- 图像裁剪
- 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)
- 图像翻转
- RandomHorizontalFlip:以一定的概率(默认为 0.5)对图像进行水平翻转。
horizontal_flip_transform = transforms.RandomHorizontalFlip() flipped_image = horizontal_flip_transform(image)
- RandomVerticalFlip:以一定的概率(默认为 0.5)对图像进行垂直翻转。
- 图像归一化
- 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)
结果: