Pytorch通常使用Dataset和DataLoader这两个工具类来构建数据管道。

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

Dataset定义了数据集的内容,它相当于一个类似列表的数据结构,具有确定的长度,能够用索引获取数据集中的元素。

而DataLoader定义了按batch加载数据集的方法,它是一个实现了__iter__方法的可迭代对象,每次迭代输出一个batch的数据。

DataLoader能够控制batch的大小,batch中元素的采样方法,以及将batch结果整理成模型所需输入形式的方法,并且能够使用多进程读取数据。

在绝大部分情况下,用户只需实现Dataset的__len__方法和__getitem__方法,就可以轻松构建自己的数据集,并用默认数据管道进行加载。

1.利用Dataset创建数据集

Dataset创建数据集常用的方法有:

  • 使用 torch.utils.data.TensorDataset 根据Tensor创建数据集(numpy的array,Pandas的DataFrame需要先转换成Tensor)。
  • 使用 torchvision.datasets.ImageFolder 根据图片目录创建图片数据集。
  • 继承 torch.utils.data.Dataset 创建自定义数据集。

此外,还可以通过

  • torch.utils.data.random_split 将一个数据集分割成多份,常用于分割训练集,验证集和测试集。
  • 调用Dataset的加法运算符(+)将多个数据集合并成一个数据集。

2. 使用DataLoader加载数据集

DataLoader能够控制batch的大小,batch中元素的采样方法,以及将batch结果整理成模型所需输入形式的方法,并且能够使用多进程读取数据。

DataLoader的函数签名如下。

  1. DataLoader(
  2. dataset,
  3. batch_size=1,
  4. shuffle=False,
  5. sampler=None,
  6. batch_sampler=None,
  7. num_workers=0,
  8. collate_fn=None,
  9. pin_memory=False,
  10. drop_last=False,
  11. timeout=0,
  12. worker_init_fn=None,
  13. multiprocessing_context=None,
  14. )

一般情况下,我们仅仅会配置 dataset, batch_size, shuffle, num_workers, drop_last这五个参数,其他参数使用默认值即可。

DataLoader除了可以加载我们前面讲的 torch.utils.data.Dataset 外,还能够加载另外一种数据集 torch.utils.data.IterableDataset。

和Dataset数据集相当于一种列表结构不同,IterableDataset相当于一种迭代器结构。 它更加复杂,一般较少使用。

  • dataset : 数据集
  • batch_size: 批次大小
  • shuffle: 是否乱序
  • sampler: 样本采样函数,一般无需设置。
  • batch_sampler: 批次采样函数,一般无需设置。
  • num_workers: 使用多进程读取数据,设置的进程数。
  • collate_fn: 整理一个批次数据的函数。
  • pin_memory: 是否设置为锁业内存。默认为False,锁业内存不会使用虚拟内存(硬盘),从锁业内存拷贝到GPU上速度会更快。
  • drop_last: 是否丢弃最后一个样本数量不足batch_size批次数据。
  • timeout: 加载一个数据批次的最长等待时间,一般无需设置。
  • worker_init_fn: 每个worker中dataset的初始化函数,常用于 IterableDataset。一般不使用。
#构建输入数据管道
ds = TensorDataset(torch.arange(1,50))
dl = DataLoader(ds,
                batch_size = 10,
                shuffle= True,
                num_workers=2,
                drop_last = True)
#迭代数据
for batch, in dl:
    print(batch)
tensor([43, 44, 21, 36,  9,  5, 28, 16, 20, 14])
tensor([23, 49, 35, 38,  2, 34, 45, 18, 15, 40])
tensor([26,  6, 27, 39,  8,  4, 24, 19, 32, 17])
tensor([ 1, 29, 11, 47, 12, 22, 48, 42, 10,  7])

3. 调用dataloader 并 分割数据集

import numpy as np 
import torch 
from torch.utils.data import TensorDataset,Dataset,DataLoader,random_split 

# 根据Tensor创建数据集

from sklearn import datasets 
iris = datasets.load_iris()
ds_iris = TensorDataset(torch.tensor(iris.data),torch.tensor(iris.target))

# 分割成训练集和预测集
n_train = int(len(ds_iris)*0.8)
n_valid = len(ds_iris) - n_train
ds_train,ds_valid = random_split(ds_iris,[n_train,n_valid])

print(type(ds_iris))
print(type(ds_train))


# 使用DataLoader加载数据集
dl_train,dl_valid = DataLoader(ds_train,batch_size = 8),DataLoader(ds_valid,batch_size = 8)

for features,labels in dl_train:
    print(features,labels)
    break

# 演示加法运算符(`+`)的合并作用
ds_data = ds_train + ds_valid

4. 实际案例一 :加载VOC数据集

4.1 Dataset类模板

任何自定义的数据集类都必须继承自PyTorch的数据集类。自定义的类必须实现两个函数:__len__(self),__getitem__任何和Dataset类表现类似的自定义类都应和下面的代码类似

class FirstDataset(data.Dataset):#需要继承data.Dataset
    def __init__(self,root_dir,size=(16,16)):
        # TODO
        # 1. 初始化文件路径或文件名列表。
        # 2. 设置一些基本参数
        #也就是在这个模块里,我们所做的工作就是初始化该类的一些基本参数。
        self.files = os.listdir(root_dir)
        self.size = size
    def __getitem__(self, index):
        #1。从文件中读取一个数据(例如,使用numpy.fromfile,PIL.Image.open)。
        #2。预处理数据(例如torchvision.Transform)。
        #3。返回数据对(例如图像和标签)。
        #这里需要注意的是,使用index索引
        img = self.files[index][0]
        label = self.files[index][1]
        return img,label

    def __len__(self):
        # 将0更改为数据集的总大小。
        return len(self.files)

定义了数据集类之后就可以创建对象并在其上进行迭代,例如:

datasets = FirstDataset('../data/')
for image,label in datasets:
  pass

4.2 Dataloader 模板

  1. Dataloader
    Dataset类一般用于调用单个数据实例,现代的GPU都对批数据的执行进行了性能优化,DataLoader类通过多进程迭代器,为我们提供批量图片。
    train_loader = DataLoader(dataset=train_data, batch_size=6, shuffle=True ,num_workers=4)
    test_loader = DataLoader(dataset=test_data, batch_size=6, shuffle=False,num_workers=4)
    
  1. torchvision
  • dataset 一些基本的,常用的数据集
  • models 图像分类,图像分割,图像检测,视频分类的一些常用网络模型都有官方代码
  • transforms 对图片进行基础处理,切割,转换通道,归一化等。详细的’torchvision.transforms’的全部细节操作可以看这里
  • io/utils/ops 提供一些处理视频或一些特殊操作的接口,用到了在详细查询即可。

4.3 梳理基本流程

  1. 先将图片分成三个文件夹,分别是训练验证测试
  2. 然后将对应文件夹的图片和标签的路径读出来,写入txt,保证读取顺序
  3. 读取txt路径,创建DATASET类,用DataLoader读取

这是图片的读取方式,一些小细节要注意,图片的读取方式,一般为RGB,如果不是要转换一下。如果是调用现成的网络结构最好根据网络输入transform里切割或者resize一下,减少调整shape的工作量。

4.4 VOC数据集实例

要根据自己的数据格式来具体调整导入数据的方式,如果原始数据不是图片,只需要把数据导入成图片格式的矩阵维度即可,如果是语义分割任务,label也是一张图片,这里要注意一些细节,label的切割,transform会把类别变成小数。

import os
from torch.utils.data import Dataset, DataLoader
import torchvision
import torchvision.transforms as transforms
from torch import nn



class CustomDataset(Dataset):
    def __init__(self,data_root,NUM_CLASSES):

        self.train_data = np.load(os.path.join(data_root,'trainAVISO-SSH_2000-2010.npy'))
        self.train_label = np.load(os.path.join(data_root,'trainSegmentation_2000-2010.npy'))
        self.data_transform = transforms.Compose([
                                transforms.ToPILImage(), \
                                transforms.CenterCrop(10), \
                                transforms.ToTensor(), \
                                transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                                     std=[0.229, 0.224, 0.225])
                                ])

    def __len__(self):
        return self.train_data.shape[0]

    def __getitem__(self, index):

        images = Image.fromarray(self.train_data[index,:,:])
        if images.mode != 'RGB':
            images = images.convert('RGB')
        image = self.data_transform(images)

        # ----------label--------------

        labels = Image.fromarray(self.train_label[index,:,:])
        self.train_labels = self.data_transform_label(labels)

        mask_each_classes = torch.zeros(NUM_CLASSES, image.shape[1], image.shape[2])
        for i in range(NUM_CLASSES):
            class_value = np.unique(self.train_labels.cpu().numpy()) # 类别经过归一化不再是 0,1,2
            mask_each_classes[i][self.train_labels[0,:,:] == class_value[i]] = 1

        batch = {'input': image, 'target': mask_each_classes}
        return batch
DATA_ROOT = 'data/data_origin/'    
train_dataset = CustomDataset(DATA_ROOT,NUM_CLASSES = 3)
train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=0)

5. 实际案例二:加载大型CSV文件

这里我们下载了kaggle的一个数据集,train.csv文件大小为18G,显然对于一般的计算机是很难一次性读入内存的。

这样就要设定一个读入流,训练的时候迭代加入一个个batch,不一次性读入内存,实现模型的大数据集训练。

import os
from torch.utils.data import Dataset, DataLoader
import torchvision
import torchvision.transforms as transforms
from torch import nn
import torch
import pandas as pd
import numpy as np

class CustomDataset(Dataset):
    '''
        读取一个18G的csv
        并不是一次性读入,实现一个读入流,迭代器

    '''
    def __init__(self,csv_file:str,chunksize:int): 
        # 可以用这个chunksize 替代 batch_size
        self.chunksize = chunksize

        self.csv_reader = pd.read_csv(csv_file, iterator=True, chunksize=chunksize)

    def __len__(self):
        # 因为csv文件太大,只读取一列,获得其长度
        # rows_all = len(pd.read_csv(self.csv_path, usecols=['time_id']))

        # 有多少个chunk
        return int(942961/self.chunksize) #rows_all

    def __getitem__(self, index):

        chunk_data = self.csv_reader.get_chunk()
        x = chunk_data.iloc[:,4:]
        y = chunk_data.iloc[:,3]

        # 转换为tensor
        x_tensor = torch.FloatTensor(np.array(x))
        y_tensor = torch.FloatTensor(np.array(y))


        batch = {'input': x_tensor, 'target': y_tensor}
        return batch
DATA_ROOT = "/data/Chenjq/kaggle/data/train.csv"
train_dataset = CustomDataset(DATA_ROOT,chunksize=1000)
train_dataloader = DataLoader(train_dataset,shuffle=True, num_workers=0)

for batch in train_dataloader:
    print(batch['input'].shape)
    print(batch['target'].shape)
    break