Как Pytorch Dataloader обрабатывает данные переменного размера?

У меня есть набор данных, который выглядит как показано ниже. Это первый элемент - это идентификатор пользователя, за которым следует набор элементов, по которым щелкнул пользователь.

0   24104   27359   6684
0   24104   27359
1   16742   31529   31485
1   16742   31529
2   6579    19316   13091   7181    6579    19316   13091
2   6579    19316   13091   7181    6579    19316
2   6579    19316   13091   7181    6579    19316   13091   6579
2   6579    19316   13091   7181    6579
4   19577   21608
4   19577   21608
4   19577   21608   18373
5   3541    9529
5   3541    9529
6   6832    19218   14144
6   6832    19218
7   9751    23424   25067   12606   26245   23083   12606

Я определяю настраиваемый набор данных для обработки данных журнала кликов.

import torch.utils.data as data
class ClickLogDataset(data.Dataset):
    def __init__(self, data_path):
        self.data_path = data_path
        self.uids = []
        self.streams = []

        with open(self.data_path, 'r') as fdata:
            for row in fdata:
                row = row.strip('\n').split('\t')
                self.uids.append(int(row[0]))
                self.streams.append(list(map(int, row[1:])))

    def __len__(self):
        return len(self.uids)

    def __getitem__(self, idx):
        uid, stream = self.uids[idx], self.streams[idx]
        return uid, stream

Затем я использую DataLoader для извлечения мини-пакетов из данных для обучения.

from torch.utils.data.dataloader import DataLoader
clicklog_dataset = ClickLogDataset(data_path)
clicklog_data_loader = DataLoader(dataset=clicklog_dataset, batch_size=16)

for uid_batch, stream_batch in stream_data_loader:
    print(uid_batch)
    print(stream_batch)

Приведенный выше код возвращается не так, как я ожидал. Я хочу, чтобы stream_batch был двумерным тензором целочисленного типа с длиной 16. Однако я получаю список одномерного тензора длины 16, и в этом списке только один элемент, как показано ниже. Это почему ?

#stream_batch
[tensor([24104, 24104, 16742, 16742,  6579,  6579,  6579,  6579, 19577, 19577,
        19577,  3541,  3541,  6832,  6832,  9751])]

person Trung Le    schedule 07.03.2019    source источник


Ответы (3)


Итак, как вы справляетесь с тем фактом, что ваши образцы имеют разную длину? torch.utils.data.DataLoader имеет параметр collate_fn, который используется для преобразования список образцов в партию. По по умолчанию это в списки. Вы можете написать свой собственный collate_fn, который, например, 0 дополняет ввод, усекает его до некоторой предопределенной длины или применяет любую другую операцию по вашему выбору.

person Jatentaki    schedule 07.03.2019
comment
Что делать, если я не хочу добавлять дополнительные числа? Я имею в виду, что если у меня есть полностью сверточная нейронная сеть, и мне не нужен ввод такого же размера, и, в частности, я не хочу изменять ввод, добавляя его (я провожу объяснимый эксперимент с ИИ)? - person Black Jack 21; 20.04.2020
comment
@RedFloyd все в порядке, за исключением того, что вам нужно будет внести некоторые изменения, и вы потеряете некоторую производительность. В PyTorch (и примерно во всех других фреймворках) операции CNN, такие как Conv2d, выполняются в векторизованном виде в 1-м измерении (обычно называемом пакетным измерением). В вашем случае вам просто нужно иметь это измерение, равное 1, и вызывать вашу сеть столько раз, сколько у вас есть изображений, вместо того, чтобы просто складывать их в один большой тензор и выполнять свою сеть один раз для всех из них. Это, вероятно, будет стоить вам производительности, но не более того. - person Jatentaki; 20.04.2020
comment
Спасибо за ответ. Чтобы уточнить, это, по сути, SGD, обучение которого было бы шумным и хлопотным (т. Е. Оно может не сходиться хорошо)? L - person Black Jack 21; 20.04.2020

Вот как я это делаю:

def collate_fn_padd(batch):
    '''
    Padds batch of variable length

    note: it converts things ToTensor manually here since the ToTensor transform
    assume it takes in images rather than arbitrary tensors.
    '''
    ## get sequence lengths
    lengths = torch.tensor([ t.shape[0] for t in batch ]).to(device)
    ## padd
    batch = [ torch.Tensor(t).to(device) for t in batch ]
    batch = torch.nn.utils.rnn.pad_sequence(batch)
    ## compute mask
    mask = (batch != 0).to(device)
    return batch, lengths, mask

затем я передаю это в класс загрузчика данных как collate_fn.


Кажется, на форуме pytorch существует огромный список разных сообщений. Позвольте мне связать их со всеми. У всех есть свои ответы и обсуждения. Мне не кажется, что существует один «стандартный способ сделать это», но если он есть из авторитетного источника, поделитесь.

Было бы хорошо, если бы в идеальном ответе упоминалось

  • эффективность, например если выполнять обработку в графическом процессоре с помощью torch в функции сопоставления vs numpy

такие вещи.

Список:

ведение: - https://discuss.pytorch.org/t/tensorflow-esque-bucket-by-sequence-length/41284

person Charlie Parker    schedule 25.07.2019
comment
Принято ли ставить тензоры на GPU в сопоставлении? У меня сложилось впечатление, что это означает, что вы не сможете использовать несколько воркеров в загрузчике данных, если сделаете это. Мне было бы интересно узнать, какой подход обычно имеет лучшую производительность. - person Tahlor; 29.05.2020
comment
@Pinocchio, почему вы вычисляете длину последовательности и маску? Если я правильно понимаю, как только пакет попадает в сеть, сеть не имеет возможности использовать маски или обрезать ввод, верно? - person financial_physician; 16.02.2021
comment
Если кто-то наткнется на это, я думаю, что ответ Дэвида Нг - лучший способ сделать это stackoverflow.com/questions/51030782/ - person financial_physician; 17.02.2021

Как предложил @Jatentaki, я написал свою собственную функцию сопоставления, и она отлично работала.

def get_max_length(x):
    return len(max(x, key=len))

def pad_sequence(seq):
    def _pad(_it, _max_len):
        return [0] * (_max_len - len(_it)) + _it
    return [_pad(it, get_max_length(seq)) for it in seq]

def custom_collate(batch):
    transposed = zip(*batch)
    lst = []
    for samples in transposed:
        if isinstance(samples[0], int):
            lst.append(torch.LongTensor(samples))
        elif isinstance(samples[0], float):
            lst.append(torch.DoubleTensor(samples))
        elif isinstance(samples[0], collections.Sequence):
            lst.append(torch.LongTensor(pad_sequence(samples)))
    return lst

stream_dataset = StreamDataset(data_path)
stream_data_loader = torch.utils.data.dataloader.DataLoader(dataset=stream_dataset,                                                         
                                                            batch_size=batch_size,                                            
                                                        collate_fn=custom_collate,
                                                        shuffle=False)
person Trung Le    schedule 08.03.2019