首页 > 其他分享 >Pytorch建模过程中的DataLoader与Dataset

Pytorch建模过程中的DataLoader与Dataset

时间:2023-01-04 21:23:05浏览次数:49  
标签:__ target img self DataLoader transform Dataset Pytorch data

   

处理数据样本的代码会因为处理过程繁杂而变得混乱且难以维护,在理想情况下,我们希望数据预处理过程代码与我们的模型训练代码分离,以获得更好的可读性和模块化,为此,PyTorch提供了torch.utils.data.DataLoadertorch.utils.data.Dataset两个类用于数据处理。其中torch.utils.data.DataLoader用于将数据集进行打包封装成一个可迭代对象,torch.utils.data.Dataset存储有一些常用的数据集示例以及相关标签。

同时PyTorch针对不同的专业领域,也提供有不同的模块,例如 TorchText(自然语言处理), TorchVision(计算机视觉), TorchAudio(音频),这些模块中也都包含一些真实数据集示例。例如TorchVision模块中提供了CIFAR, COCO, FashionMNIST 数据集。

   

1 定义数据集

pytorch中提供两种风格的数据集定义方式:

  • 字典映射风格。之所以称为映射风格,是因为在后续加载数据迭代时,pytorch将自动使用迭代索引作为key,通过字典索引的方式获取value,本质就是将数据集定义为一个字典,使用这种风格时,需要继承Dataset类。
  In [54]:
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
  In [56]:
dataset = {0: '张三', 1:'李四', 2:'王五', 3:'赵六', 4:'陈七'}

dataloader = DataLoader(dataset, batch_size=2)

for i, value in enumerate(dataloader):
    print(i, value)
   
0 ['张三', '李四']
1 ['王五', '赵六']
2 ['陈七']
   
  • 迭代器风格。在自定义数据集类中,实现__iter____next__方法,即定义为迭代器,在后续加载数据迭代时,pytorch将依次获取value,使用这种风格时,需要继承IterableDataset类。这种方法在数据量巨大,无法一下全部加载到内存时非常实用。
  In [57]:
from torch.utils.data import DataLoader
from torch.utils.data import IterableDataset
  In [58]:
dataset = [i for i in range(10)]

dataloader = DataLoader(dataset=dataset, batch_size=3, shuffle=True) 

for i, item in enumerate(dataloader): # 迭代输出
    print(i, item)
   
0 tensor([3, 1, 2])
1 tensor([9, 7, 5])
2 tensor([0, 8, 4])
3 tensor([6])
   

如下所示,我们有一个蚂蚁蜜蜂图像分类数据集,目录结构如下所示,下面我们结合这个数据集,分别介绍如何使用这两个类定义真实数据集。

data
└── hymenoptera_data
    ├── train
    │   ├── ants
    │   │   ├── 0013035.jpg
    │   │   ……
    │   └── bees
    │       ├── 1092977343_cb42b38d62.jpg
    │       ……
    └── val
        ├── ants
        │   ├── 10308379_1b6c72e180.jpg
        │   ……
        └── bees
            ├── 1032546534_06907fe3b3.jpg
            ……
   

1.2 Dataset类

自定义一个Dataset类,继承torch.utils.data.Dataset,且必须实现下面三个方法:

  1. Dataset类里面的__init__函数初始化一些参数,如读取外部数据源文件。

  2. Dataset类里面的__getitem__函数,映射取值是调用的方法,获取单个的数据,训练迭代时将会调用这个方法。

  3. Dataset类里面的__len__函数获取数据的总量。

  In [211]:
import os
import pandas as pd
from PIL import Image
from torchvision.transforms import ToTensor, Lambda
from torchvision import transforms
import torchvision
class AntBeeDataset(Dataset):
    # 把图片所在的文件夹路径分成两个部分,一部分是根目录,一部分是标签目录,这是因为标签目录的名称我们需要用到
    def __init__(self, root_dir, transform=None, target_transform=None):
        """
        root_dir:存放数据的根目录,即:data/hymenoptera_data
        transform: 对图像数据进行处理,例如,将图片转换为Tensor、图片的维度可能不一致需要进行resize
        target_transform:对标签数据进行处理,例如,将文本标签转换为数值
        """
        self.root_dir = root_dir
        self.transform = transform
        self.target_transform = target_transform
        
        # 获取文件夹下所有图片的名称和对应的标签
        self.img_lst = []
        for label in ['ants', 'bees']:
            path = os.path.join(root_dir, label)
            for img_name in os.listdir(path):
                self.img_lst.append((os.path.join(root_dir, label, img_name), label))
        

    def __getitem__(self, idx):
        img_path, label = self.img_lst[idx]
        img = Image.open(img_path).convert('RGB')
        
        if self.transform:
            img = self.transform(img)
        if self.target_transform:
            label = self.target_transform(label)
        # 这个地方要注意,我们在计算loss的时候用交叉熵nn.CrossEntropyLoss()
        # 交叉熵的输入有两个,一个是模型的输出outputs,一个是标签targets,注意targets是一维tensor
        # 例如batchsize如果是2,ants的targets的应该[0,0],而不是[[0][0]]
        # 因此label要返回0,而不是[0]
        return img, label

    def __len__(self):
        return len(self.img_lst)
  In [310]:
train_transform = transforms.Compose([
    
    transforms.RandomResizedCrop(224),  # 将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小
    transforms.RandomHorizontalFlip(),  # 以给定的概率随机水平旋转给定的PIL的图像,默认为0.5
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])

# 验证集并不需要做与训练集相同的处理,所有,通常使用更加简单的transformer
val_transform = transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])

# 根据标签目录的名称来确定图片是哪一类,如果是"ants",标签设置为0,如果是"bees",标签设置为1
target_transform = transforms.Lambda(lambda y: 0 if y == "ants" else 1)
  In [311]:
train_dataset = AntBeeDataset('data/hymenoptera_data/train', transform=train_transform, target_transform=target_transform)
val_dataset = AntBeeDataset('data/hymenoptera_data/val', transform=val_transform, target_transform=target_transform)
   

1.2 Dataset数据集常用操作

   

1. 查看数据集大小:

  In [221]:
len(train_dataset), len(val_dataset)
  Out[221]:
(245, 153)
   

2. 合并数据集

  In [222]:
dataset = train_dataset + val_dataset
  In [223]:
len(dataset)
  Out[223]:
398
   

3. 划分训练集、测试集

  In [224]:
from torch.utils.data import random_split
# random_split 不能直接使用百分比划分,必须指定具体数字
train_size = int( len(dataset) * 0.8)
test_size = len(dataset) - train_size
  In [225]:
train_dataset, val_dataset = random_split(dataset, [train_size, test_size])
  In [226]:
len(train_dataset), len(val_dataset)
  Out[226]:
(318, 80)
   

1.3 IterableDataset类

使用迭代器风格时,必须继承IterableDataset类,且实现下面两个方法:

  1. __init__,函数初始化一些参数,如读取外部数据源文件,在数据量过大时,通常只是获取操作句柄、数据库连接。

  2. __iter__,获取迭代器。

虽然只需要实现这两个方法,但是通常还需要在迭代过程中对数据进行处理。IterableDataset类实现自定义数据集,本质就是创建一个数据集类,且实现__iter__返回一个迭代器。一下提供两种方法通过IterableDataset类自定义数据集:

   

方法一:

  In [289]:
class AntBeeIterableDataset(IterableDataset):
    # 把图片所在的文件夹路径分成两个部分,一部分是根目录,一部分是标签目录,这是因为标签目录的名称我们需要用到
    def __init__(self, root_dir, transform=None, target_transform=None):
        """
        root_dir:存放数据的根目录,即:data/hymenoptera_data
        transform: 对图像数据进行处理,例如,将图片转换为Tensor、图片的维度可能不一致需要进行resize
        target_transform:对标签数据进行处理,例如,将文本标签转换为数值
        """
        self.root_dir = root_dir
        self.transform = transform
        self.target_transform = target_transform
        
        # 获取文件夹下所有图片的名称和对应的标签
        self.img_lst = []
        for label in ['ants', 'bees']:
            path = os.path.join(root_dir, label)
            for img_name in os.listdir(path):
                self.img_lst.append((os.path.join(root_dir, label, img_name), label))
                
    def __iter__(self):
        for img_path, label in self.img_lst:
            img = Image.open(img_path).convert('RGB')
            if self.transform:
                img = self.transform(img)
            if self.target_transform:
                label = self.target_transform(label)
            yield img, label
   

方法二:

  In [285]:
class AntBeeIterableDataset(IterableDataset):
    # 把图片所在的文件夹路径分成两个部分,一部分是根目录,一部分是标签目录,这是因为标签目录的名称我们需要用到
    def __init__(self, root_dir, transform=None, target_transform=None):
        """
        root_dir:存放数据的根目录,即:data/hymenoptera_data
        transform: 对图像数据进行处理,例如,将图片转换为Tensor、图片的维度可能不一致需要进行resize
        target_transform:对标签数据进行处理,例如,将文本标签转换为数值
        """
        self.root_dir = root_dir
        self.transform = transform
        self.target_transform = target_transform
        
        # 获取文件夹下所有图片的名称和对应的标签
        self.img_lst = []
        for label in ['ants', 'bees']:
            path = os.path.join(root_dir, label)
            for img_name in os.listdir(path):
                self.img_lst.append((os.path.join(root_dir, label, img_name), label))
        self.index = 0
                
    def __iter__(self):
        return self
    
    def __next__(self):
        try:
            img_path, label = self.img_lst[self.index]
            self.index += 1
            img = Image.open(img_path).convert('RGB')
            if self.transform:
                img = self.transform(img)
            if self.target_transform:
                label = self.target_transform(label)
            return img, label
        except IndexError:
            raise StopIteration()
  In [290]:
train_dataset = AntBeeIterableDataset('data/hymenoptera_data/train', transform=train_transform, target_transform=target_transform)
val_dataset = AntBeeIterableDataset('data/hymenoptera_data/val', transform=val_transform, target_transform=target_transform)
   

在处理大数据集时,IterableDataset会比Dataset更有优势,例如数据存储在文件或者数据库中,只需要在自定义的IterableDataset之类中获取文件操作句柄或者数据库连接和游标惊喜迭代,每次只返回一条数据即可。我们把上文中蚂蚁蜜蜂数据集的所有图片、标签这里后写入hymenoptera_data.txt中,内容如下所示,假设有数亿行,那么,就不能直接将数据加载到内存了:

data/hymenoptera_data/train/ants/2288481644_83ff7e4572.jpg, ants
data/hymenoptera_data/train/ants/2278278459_6b99605e50.jpg, ants
data/hymenoptera_data/train/ants/543417860_b14237f569.jpg, ants
...
...

可以参考一下方式定义IterableDataset子类:

  In [299]:
class AntBeeIterableDataset(IterableDataset):
    # 把图片所在的文件夹路径分成两个部分,一部分是根目录,一部分是标签目录,这是因为标签目录的名称我们需要用到
    def __init__(self, filepath, transform=None, target_transform=None):
        """
        filepath:hymenoptera_data.txt完整路径
        transform: 对图像数据进行处理,例如,将图片转换为Tensor、图片的维度可能不一致需要进行resize
        target_transform:对标签数据进行处理,例如,将文本标签转换为数值
        """
        self.filepath = filepath
        self.transform = transform
        self.target_transform = target_transform

                
    def __iter__(self):
        with open(self.filepath, 'r') as f:
            for line in f:
                img_path, label = line.replace('\n', '').split(', ')
                img = Image.open(img_path).convert('RGB')
                if self.transform:
                    img = self.transform(img)
                if self.target_transform:
                    label = self.target_transform(label)
                yield img, label
  In [307]:
train_dataset = AntBeeIterableDataset('hymenoptera_data.txt', transform=train_transform, target_transform=target_transform)
   

注意,IterableDataset方法在处理大数据集时确实比Dataset更有优势,但是,IterableDataset在迭代过程中,样本输出顺序是固定的,在使用DataLoader进行加载时,无法使用shuffle进行打乱,同时,因为在IterableDataset中并未强制限定必须实现__len__()方法(很多时候确实也没法获取数据总量),不能通过len()方法获取数据总量。

   

2 DataLoad

DataLoader的功能是构建可迭代的数据装载器,在训练的时候,每一个for循环,每一次Iteration,就是从DataLoader中获取一个batch_size大小的数据,节省内存的同时,它还可以实现多进程、数据打乱等处理。我们通过一张图来了解DataLoader数据读取机制:

image.png

首先,在for循环中使用了DataLoader,进入DataLoader后,首先根据是否使用多进程DataLoaderIter,做出判断之后单线程还是多线程,接着使用Sampler得索引Index,然后将索引给到DatasetFetcher,在这里面调用Dataset,根据索引,通过getitem得到实际的数据和标签,得到一个batch size大小的数据后,通过collate_fn函数整理成一个Batch Data的形式输入到模型去训练。

在pytorch建模的数据处理、加载流程中,DataLoader应该算是最核心的一步操作DataLoader有很多参数,这里我们列出常用的几个:

  • dataset:表示Dataset类,它决定了数据从哪读取以及如何读取;
  • batch_size:表示批大小;
  • num_works:表示是否多进程读取数据;
  • shuffle:表示每个epoch是否乱序;
  • drop_last:表示当样本数不能被batch_size整除时,是否舍弃最后一批数据;
  • num_workers:启动多少个进程来加载数据。

我们重点说说多进程模式下使用DataLoader,在多进程模式下,每次 DataLoader 创建 iterator 时(遍历DataLoader时,例如,当调用时enumerate(dataloader)),都会创建 num_workers 工作进程。dataset, collate_fn, worker_init_fn 都会被传到每个worker中,每个worker都用独立的进程。

对于映射风格的数据集,即Dataset子类,主线程会用Sampler(采样器)产生indice,并将它们送到进程里。因此,shuffle是在主线程做的

对于迭代器风格的数据集,即IterableDataset子类,因为每个进程都有相同的data复制样本,并在各个进程里进行不同的操作,以防止每个进程输出的数据是重复的,所以一般用 torch.utils.data.get_worker_info() 来进行辅助处理。

这里,torch.utils.data.get_worker_info() 返回worker进程的一些信息(id, dataset, num_workers, seed),如果在主线程跑的话返回None

注意,通常不建议在多进程加载中返回CUDA张量,因为在使用CUDA和在多处理中共享CUDA张量时存在许多微妙之处(文档中提出:只要接收过程保留张量的副本,就需要发送过程来保留原始张量)。建议采用 pin_memory=True ,以将数据快速传输到支持CUDA的GPU。简而言之,不建议在使用多线程的情况下返回CUDA的tensor。

  In [313]:
dataload = DataLoader(train_dataset, batch_size=2)
  In [315]:
img, label = next(iter(dataload))
  In [316]:
img.shape, label
  Out[316]:
(torch.Size([2, 3, 224, 224]), tensor([0, 0]))

标签:__,target,img,self,DataLoader,transform,Dataset,Pytorch,data
From: https://www.cnblogs.com/chenhuabin/p/17026018.html

相关文章

  • dremio DatasetSaver 服务说明
    我以前简单写过关于元数据处理的说明(基于jprofiler+arthas工具)会依赖namespace服务实际对于数据的操作都是通过SourceMetadataManager执行的DatasetSaver服务提供的......
  • Pytorch基础-Tensor数据结构
    torch.TensorTensor数据类型Tensor的属性view和reshape的区别Tensor与ndarray创建Tensor传入维度的方法参考资料torch.Tensortorch.Tensor是一种......
  • transforms模块—PyTorch图像处理与数据增强方法
      计算机视觉任务中,对图像的变换(ImageTransform)往往是必不可少的操作,例如在迁移学习中,需要对图像尺寸进行变换以使用预训练网络的输入层,又如对数据进行增......
  • 从0开始pytorch--线性模型
    学习地址:https://www.bilibili.com/video/BV1Y7411d7Ys?p=2&vd_source=001ba1b001e88ca6d09a9b0de2a86d71colab链接:https://colab.research.google.com/gist/cyberangel......
  • torchviz进行pytorch神经网络可视化
    一、安装需要系统安装Graphviz工具,如果是自己电脑上可以用yum、apt等工具安装,但是如果在服务器上,没有root权限的那种就会比较困难。需要root权限,如果是虚拟的容器可以在do......
  • PyTorch1【Tensors张量基础】
     """Createdon2023/1/310:06.@Author:haifei"""from__future__importprint_function#必须放在第一行importtorchimporttime#构造一个5x3矩阵,不初始......
  • Pytorch入门实战(5):基于nn.Transformer实现机器翻译(英译汉)
    ​​使用GoogleColab运行(openInColab)​​​​源码地址​​文章目录​​本文涉及知识点​​​​本文内容​​​​环境配置​​​​数据预处理​​​​文本分词与构造词......
  • PyTorch 2.0 推理速度测试:与 TensorRT 、ONNX Runtime 进行对比
    PyTorch2.0于2022年12月上旬在NeurIPS2022上发布,它新增的torch.compile组件引起了广泛关注,因为该组件声称比PyTorch的先前版本带来更大的计算速度提升。这......
  • PyTorch多卡分布式训练DistributedDataParallel 使用方法
    PyTorch多卡分布式训练DistributedDataParallel 使用方法目录​​PyTorch多卡分布式训练DistributedDataParallel 使用方法​​​​1.DP模式和DP模式​​​​(1)单进程多G......
  • Linux上安装虚拟conda环境和神经网络学习框架pytorch
    学习目标:​​Linux系统安装Anaconda3​​;​​配置GPU,安装显卡toolkit​​;创建虚拟环境,安装深度学习框架;学习内容:创建虚拟环境,安装深度学习框架pytorch​​前面已经在anaco......