如何使用pytorch同时迭代两个数据加载器?

Posted

技术标签:

【中文标题】如何使用pytorch同时迭代两个数据加载器?【英文标题】:How to iterate over two dataloaders simultaneously using pytorch? 【发布时间】:2018-12-28 20:31:49 【问题描述】:

我正在尝试实现一个接收两个图像的连体网络。我加载这些图像并创建两个单独的数据加载器。

在我的循环中,我想同时通过两个数据加载器,以便我可以在两个图像上训练网络。

for i, data in enumerate(zip(dataloaders1, dataloaders2)):

    # get the inputs
    inputs1 = data[0][0].cuda(async=True);
    labels1 = data[0][1].cuda(async=True);

    inputs2 = data[1][0].cuda(async=True);
    labels2 = data[1][1].cuda(async=True);

    labels1 = labels1.view(batchSize,1)
    labels2 = labels2.view(batchSize,1)

    # zero the parameter gradients
    optimizer.zero_grad()

    # forward + backward + optimize
    outputs1 = alexnet(inputs1)
    outputs2 = alexnet(inputs2)

数据加载器的返回值是一个元组。 但是,当我尝试使用 zip 对其进行迭代时,出现以下错误:

OSError: [Errno 24] Too many open files
Exception NameError: "global name 'FileNotFoundError' is not defined" in <bound method _DataLoaderIter.__del__ of <torch.utils.data.dataloader._DataLoaderIter object at 0x7f2d3c00c190>> ignored                           

zip 不应该对所有可迭代项都有效吗?但似乎在这里我不能在数据加载器上使用它。

还有其他方法可以解决这个问题吗?还是我错误地实现了连体网络?

【问题讨论】:

与其制作两个数据加载器,我认为如果你只从一个数据加载器返回两个图像会容易得多。 由于数据加载器将图像分成批次,我是否只需将批次大小设为 2? 【参考方案1】:

除了已经提到的,cycle()zip() 可能会造成内存泄漏问题 - 尤其是在使用图像数据集时!为了解决这个问题,而不是像这样迭代:

dataloaders1 = DataLoader(DummyDataset(0, 100), batch_size=10, shuffle=True)
dataloaders2 = DataLoader(DummyDataset(0, 200), batch_size=10, shuffle=True)
num_epochs = 10

for epoch in range(num_epochs):

    for i, (data1, data2) in enumerate(zip(cycle(dataloaders1), dataloaders2)):
        
        do_cool_things()

你可以使用:

dataloaders1 = DataLoader(DummyDataset(0, 100), batch_size=10, shuffle=True)
dataloaders2 = DataLoader(DummyDataset(0, 200), batch_size=10, shuffle=True)
num_epochs = 10

for epoch in range(num_epochs):
    dataloader_iterator = iter(dataloaders1)
    
    for i, data1 in enumerate(dataloaders2)):

        try:
            data2 = next(dataloader_iterator)
        except StopIteration:
            dataloader_iterator = iter(dataloaders1)
            data2 = next(dataloader_iterator)

        do_cool_things()

请记住,如果您也使用标签,则应在此示例中将 data1 替换为 (inputs1,targets1),并将 data2 替换为 inputs2,targets2,正如 @Sajad Norouzi 所说。

感谢这个人:https://github.com/pytorch/pytorch/issues/1917#issuecomment-433698337

【讨论】:

当您需要每个数据集的不同批次大小时的最佳答案 @afroditi 如果我使用来自第一个数据加载器的图像和标签以及来自第二个数据加载器的掩码和标签,我该如何处理它们?【参考方案2】:

我看到您正在努力制作正确的 dataloder 功能。我会这样做:

class Siamese(Dataset):


    def __init__(self, transform=None):
    
       #init data here
    
    def __len__(self):
        return   #length of the data

    def __getitem__(self, idx):
        #get images and labels here 
        #returned images must be tensor
        #labels should be int 
        return img1, img2 , label1, label2 

【讨论】:

因为这个类的 __getitem__member 是用参数 idx 调用的,所以有两个图像需要用两个索引来调用吗? (自我,idx1,idx2)?还是我只是返回两个随机图像?例如,img1 = os.path.join(self.root_dir,imgNames[idx]) img2 = os.path.join(self.root_dir,imgNames[idx + 1]) ? 我猜你有相同数量的相同图像(在两个数组中)。所以使用一个索引你可以从两个图像数组中加载图像。 ...或者您可以创建另一个仅包含相同图像索引的数组,并使用数据加载器的 getitem 的 idx 加载这些图像【参考方案3】:

如果您想同时迭代两个数据集,则无需定义自己的数据集类,只需使用 TensorDataset,如下所示:

dataset = torch.utils.data.TensorDataset(dataset1, dataset2)
dataloader = DataLoader(dataset, batch_size=128, shuffle=True)
for index, (xb1, xb2) in enumerate(dataloader):
    ....

如果您想要标签或迭代两个以上的数据集,只需将它们作为参数提供给数据集 2 之后的 TensorDataset。

【讨论】:

谢谢,这是最相关且易于使用的答案【参考方案4】:

添加@Aldream 的解决方案,以解决我们有不同长度的数据集的情况,如果我们想在同一时期通过它们,那么我们可以使用来自itertoolscycle(),这是一个Python 标准库。使用@Aldrem 的代码sn-p,更新后的代码如下:

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

class DummyDataset(Dataset):
    """
    Dataset of numbers in [a,b] inclusive
    """

    def __init__(self, a=0, b=100):
        super(DummyDataset, self).__init__()
        self.a = a
        self.b = b

    def __len__(self):
        return self.b - self.a + 1

    def __getitem__(self, index):
        return index

dataloaders1 = DataLoader(DummyDataset(0, 100), batch_size=10, shuffle=True)
dataloaders2 = DataLoader(DummyDataset(0, 200), batch_size=10, shuffle=True)
num_epochs = 10

for epoch in range(num_epochs):
    for i, data in enumerate(zip(cycle(dataloaders1), dataloaders2)):
        print(data)

只有zip(),当长度等于最小数据集的长度(此处为 100)时,迭代器将被耗尽。但是使用cycle(),我们将再次重复最小的数据集,除非我们的迭代器查看最大数据集(此处为 200)中的所有样本。

附:人们总是可以争辩说,只要随机抽样,这种方法可能不需要实现收敛,但使用这种方法,评估可能更容易。

【讨论】:

我使用的是相同的方法,但是您如何计算 dataloaders1 的数据集长度以计算损失?谢谢!【参考方案5】:

完成@ManojAcharya 的回答:

您得到的错误既不是来自zip(),也不是直接来自DataLoader()。 Python 试图告诉您它找不到您要求的数据文件之一(参见异常跟踪中的FileNotFoundError),可能在您的Dataset 中。

在下面找到一个同时使用DataLoaderzip 的工作示例。请注意,如果要对数据进行混洗,则很难保持 2 个数据集之间的对应关系。这证明了@ManojAcharya 的解决方案。

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

class DummyDataset(Dataset):
    """
    Dataset of numbers in [a,b] inclusive
    """

    def __init__(self, a=0, b=100):
        super(DummyDataset, self).__init__()
        self.a = a
        self.b = b

    def __len__(self):
        return self.b - self.a + 1

    def __getitem__(self, index):
        return index, "label_".format(index)

dataloaders1 = DataLoader(DummyDataset(0, 9), batch_size=2, shuffle=True)
dataloaders2 = DataLoader(DummyDataset(0, 9), batch_size=2, shuffle=True)

for i, data in enumerate(zip(dataloaders1, dataloaders2)):
    print(data)
# ([tensor([ 4,  7]), ('label_4', 'label_7')], [tensor([ 8,  5]), ('label_8', 'label_5')])
# ([tensor([ 1,  9]), ('label_1', 'label_9')], [tensor([ 6,  9]), ('label_6', 'label_9')])
# ([tensor([ 6,  5]), ('label_6', 'label_5')], [tensor([ 0,  4]), ('label_0', 'label_4')])
# ([tensor([ 8,  2]), ('label_8', 'label_2')], [tensor([ 2,  7]), ('label_2', 'label_7')])
# ([tensor([ 0,  3]), ('label_0', 'label_3')], [tensor([ 3,  1]), ('label_3', 'label_1')])

【讨论】:

如果我错了,请纠正我,但我认为在许多情况下使用 zip 会非常低效。一些数据集有大量在运行时加载的图像。 Zip 将遍历整个数据集,使其一次加载图像。

以上是关于如何使用pytorch同时迭代两个数据加载器?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 pytorch 数据加载器中获取批迭代的总数?

如何在 PYTorch 中定义数据加载器

如何将 numpy 数组列表加载到 pytorch 数据集加载器?

Pytorch数据加载

PyTorch 数据加载器中的“工人数量”参数实际上是如何工作的?

Pytorch中如何使用DataLoader对数据集进行批训练