如何将自己的数据集包装成dataloader? 4种方法包你会!

前言

我们经常会遇到这样的情况:使用的数据并不是库自带的,这样的话,不管是训练还是测试,都需要我们来自行实现索引我们的data,并且是每一次按照一个batch_size的规格来索引。我刚开始想的是(假设我有100个样本),自己建一个index表,如idx = np.arange(1,100,1),先将它shuffle(idx)一下,然后每一次取batch_size个下标进行索引,但总觉得有一丢丢不自然。

方法一:继承Dataset & DataLoader

from torch.utils.data import Dataset,DataLoader
import torch
train_X = torch.randn(100,28,28)
train_Y = torch.randn(100)
class Dataset(Dataset):
    def __init__(self,x,y):
        # self.x = x.unsqueeze(dim=1)
        self.y = y
    
    def __len__(self):
        return len(self.x)
    
    def __getitem__(self,idx):
        return self.x[idx],self.y[idx]
train_DL = DataLoader(Dataset(train_X,train_Y),batch_size=10,shuffle=True)
for idx,(xb,yb) in enumerate(train_DL):
    print(idx,xb.shape)

官网解释如下图:在这里插入图片描述

  • 在上面的样例中,我创建了Dataset的同名子类
  • 并且按照要求,复写了getitem以及len
  • 接着,把该类传入DataLoader,并设定参数:batch_size 和 shuffle

方法二:继承Dataset & 封装DataLoader

class DataLoader:
    def __init__(self, dl, func):
        self.dl = dl # 我们的数据集
        self.func = func # 我们的操作对象
    def __len__(self):
        return len(self.dl)
    def __iter__(self):
        batches = iter(self.dl)
        for b in batches:
            yield (self.func(*b))

在方法一的基础上,我们自行封装DataLoader,其中的func可以是我们自定义的函数,用于在加载数据时就把数据处理好,例如是:def preprocess(x,y): return x.view(...),y这样的函数。

方法三:TensorDataset & DataLoader

我基本用这种方式,就两行代码:

from torch.utils.data import TensorDataset,DataLoader
Train_DS = TensorDataset(train_x,train_y)
Train_DL = DataLoader(Train_DS,shuffle=True,batch_size = 10)

进阶:DataLoader的collate_fn参数设置

看到的例子都是图象方面的,那我说说我在情感分类时遇到的问题。其实跟每个图片的label长度不同的情况一样,每一个句子的单词个数也是不一样的。在一个batch中,如何保证它们的维度相同呢?句子的维度 = 单词数 * 词向量维度,后者不需要担心。我们要做的就是保证每个句子的单词数都一样。而策略就是,找到一个batch中,单词数最多的句子作为基准,然后对其它的较短的句子进行补充(补充全零向量即可)。 那么。DataLoader中的collate_fn就可以实现这样的"padding"过程。

由于暂时用的是"笨"办法,就先把大致思路用代码表示一下,完整的代码以后再补充:

size = 100 # 表示每一个单词表示的向量维度
def collate_fn(batch):
    batch.sort(key = lambda x:len(x[0]),reverse=True)
    x, y = zip(*batch)
    pad_x = []
    nums = []
    max_num = len(x[0])  
    for i in range(len(x)):
        temp = x[i]  # num_of_words * size
        for j in range(max_num - len(temp)):
            temp.append([0] * size)
        pad_x.append(temp_x)
        nums.append(len(x[i]))
    return pad_x,y,lens
# Then
Train_DS = TensorDataset(train_x,train_y)
Train_DL = DataLoader(Train_DS,shuffle=True,batch_size = 10,collate_fn = collate_fn)

明白了吗? 不明白的话,我尽力解释一下(毕竟自己水平也有限):

首先,collate_fn传入的batch是一个列表!列表的长度是batch_size,也就是说里面有batch_size个元素。每一个元素也是一个列表!这个列表里有两个元素,分别是train_x中的一个样本和train_y中的一个分类标签。我们要解决的就是train_x中的这一个样本,它的第0维(代表着单词数),与其他样本的单词数会有所不同。
那么,我们先把batch中的各个列表,按照列表中第一个元素(也就是train_x的一个样本)的0维进行排序(降序)。
接着就很好理解了,max_num代表最大的单词数,每一个train_x中的样本,如果小于这个单词数。就先计算一下max_num - len(temp),也就是缺了多少单词(n),然后补充n个size大小的0向量。

以上参考简书:PyTorch实现自由的数据读取

另外,使用collate_fn参数,还可以解决"被batch_size整除,而余下的样本"该怎么处理的问题:
具体查看博客:Pytorch技巧1:DataLoader的collate_fn参数

更新

好吧,有用到dataloader了,collate_fn不是进阶,是基础(特别在NLP)。

总结

总的来说,方法三 再 加上collate_fn参数,应该就够用了。虽然方法二可以自定义func,但是如何实现batch_size个样本一起读取,如何实现shuffle,又是一个问题。而相比之下,方法一中,也可以自定义func,在设置dataset的步骤,就可以进行一些操作,比如用unsqueeze(dim=1)增加1维等。

如果觉得哪里讲错了,或者讲得不清楚,请在评论区批评或指出,谢谢!!

猜你喜欢

转载自blog.csdn.net/jokerxsy/article/details/106504932