#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')
, это, вероятно, результат azip()
.
Ответ №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"))