PyTorch dataloader показывает странное поведение со строковым набором данных

#python #pytorch #dataloader

#python #pytorch #загрузчик данных

Вопрос:

Я работаю над проблемой NLP и использую PyTorch. По какой-то причине мой dataloader возвращает неверно сформированные пакеты.У меня есть входные данные, которые содержат предложения и целочисленные метки. Предложения могут быть либо списком предложений, либо списком списков токенов. Позже я преобразоваю токены в целые числа в нисходящем компоненте.

 list_labels = [ 0, 1, 0]

# List of sentences.
list_sentences = [ 'the movie is terrible',
                   'The Film was great.',
                   'It was just awful.']

# Or list of list of tokens.
list_sentences = [['the', 'movie', 'is', 'terrible'],
                  ['The', 'Film', 'was', 'great.'],
                  ['It', 'was', 'just', 'awful.']]
  

Я создал следующий пользовательский набор данных:

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

class MyDataset(torch.utils.data.Dataset):

    def __init__(self, sentences, labels):

        self.sentences = sentences
        self.labels = labels

    def __getitem__(self, i):
        result = {}
        result['sentences'] = self.sentences[i]
        result['label'] = self.labels[i]
        return result

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

Когда я предоставляю входные данные в виде списка предложений, загрузчик данных корректно возвращает пакеты полных предложений. Обратите внимание, что batch_size=2 :

 list_sentences = [ 'the movie is terrible', 'The Film was great.', 'It was just awful.']
list_labels = [ 0, 1, 0]


dataset = MyDataset(list_sentences, list_labels)
dataloader = DataLoader(dataset, batch_size=2)

batch = next(iter(dataloader))
print(batch)
# {'sentences': ['the movie is terrible', 'The Film was great.'], <-- Great! 2 sentences in batch!
#  'label': tensor([0, 1])}
  

Пакет правильно содержит два предложения и две метки, потому batch_size=2 что .

Однако, когда я вместо этого ввожу предложения в виде предварительно маркированного списка списка токенов, я получаю странные результаты:

 list_sentences = [['the', 'movie', 'is', 'terrible'], ['The', 'Film', 'was', 'great.'], ['It', 'was', 'just', 'awful.']]
list_labels = [ 0, 1, 0]


dataset = MyDataset(list_sentences, list_labels)
dataloader = DataLoader(dataset, batch_size=2)

batch = next(iter(dataloader))
print(batch)
# {'sentences': [('the', 'The'), ('movie', 'Film'), ('is', 'was'), ('terrible', 'great.')], <-- WHAT?
#  'label': tensor([0, 1])}
  

Обратите внимание, что этот пакет sentences представляет собой один список с кортежами пар слов. Я ожидал sentences , что это будет список из двух списков, например:

 {'sentences': [['the', 'movie', 'is', 'terrible'], ['The', 'Film', 'was', 'great.']
  

Что происходит?

Комментарии:

1. Я также столкнулся с этой проблемой. это похоже на реальную проблему — pytorch должен иметь возможность сопоставлять пакеты строк. Я вижу много случаев, когда вы можете захотеть обработать строки после шага загрузки данных.

Ответ №1:

Это поведение связано с тем, что по умолчанию collate_fn выполняется следующее, когда ему нужно сопоставить list s (что имеет место для ['sentences'] ):

 # [...]
elif isinstance(elem, container_abcs.Sequence):
    # check to make sure that the elements in batch have consistent size
    it = iter(batch)
    elem_size = len(next(it))
    if not all(len(elem) == elem_size for elem in it):
        raise RuntimeError('each element in list of batch should be of equal size')
    transposed = zip(*batch)
    return [default_collate(samples) for samples in transposed]
  

«Проблема» возникает из-за того, что в последних двух строках он будет рекурсивно вызываться zip(*batch) , пока пакет является a container_abcs.Sequence list is ), и zip ведет себя следующим образом.

Как вы можете видеть:

 batch = [['the', 'movie', 'is', 'terrible'], ['The', 'Film', 'was', 'great.']]
list(zip(*batch))

# [('the', 'The'), ('movie', 'Film'), ('is', 'was'), ('terrible', 'great.')]
  

Я не вижу обходного пути в вашем случае, кроме реализации нового средства сортировки и передачи его DataLoader(..., collate_fn=mycollator) . Например, простой уродливый может быть:

 def mycollator(batch):
    assert all('sentences' in x for x in batch)
    assert all('label' in x for x in batch)
    return {
        'sentences': [x['sentences'] for x in batch],
        'label': torch.tensor([x['label'] for x in batch])
    }
  

Комментарии:

1. Спасибо. Я должен был углубиться в пакетный генератор, как вы это сделали.

2. Я должен был также признать, что когда вы видите пары объектов с одинаковым индексом в двух списках, таких как ( 'the', 'The') , это, вероятно, результат a zip() .

Ответ №2:

Альтернативным решением является кодирование строк в виде байтов и в вашем Dataset , а затем декодирование их в вашем прямом проходе. Это полезно, если вы хотите включить строки для метаданных (например, путь к файлу, из которого были получены данные), но на самом деле не нужно передавать данные в вашу модель.

Например:

 class MyDataset(torch.utils.data.Dataset):
    def __next__(self):
        return np.array("this is a sentence").bytes()
  

И затем в вашем прямом проходе вы бы сделали:

 sentences: List[str] = []
for sentence in batch:
    sentences.append(sentence.decode("ascii"))