PyTorch: Могу ли я группировать партии по длине?
Я работаю над проектом ASR, где использую модель из HuggingFace (
Одна из этих утилит - это возможность группировать пакеты по длине и комбинировать это с динамическим заполнением (через сборщик данных). Однако, честно говоря, я не уверен, с чего начать это в PyTorch.
В моем случае входными данными являются одномерные массивы, которые представляют необработанную форму волны файла .wav. Поэтому перед обучением мне нужно убедиться, что массивы одинакового размера будут объединены вместе. Нужно ли мне создавать собственный класс Dataloader и изменять его так, чтобы каждый раз он давал мне размер партии как можно ближе к длине?
У меня была идея как-то отсортировать данные от самых коротких к самым длинным (или наоборот) и каждый раз извлекать из них образцы batch_size. Таким образом, первая партия будет состоять из образцов с наибольшей длиной, вторая партия будет иметь вторую по величине длину и т. Д.
Тем не менее, я не уверен, как подойти к этой реализации. Мы будем благодарны за любые советы.
Заранее спасибо.
2 ответа
Один из возможных способов сделать это - использовать пакетный сэмплер и реализовать для загрузчика данных, который будет выполнять динамическое заполнение ваших пакетных элементов.
Возьмите этот базовый набор данных:
class DS(Dataset):
def __init__(self, files):
super().__init__()
self.len = len(files)
self.files = files
def __getitem__(self, index):
return self.files[index]
def __len__(self):
return self.len
Инициализируется случайными данными:
>>> file_len = np.random.randint(0, 100, (16*6))
>>> files = [np.random.rand(s) for s in file_len]
>>> ds = DS(files)
Начните с определения вашего пакетного сэмплера, по сути, это повторяющиеся возвращаемые пакеты индексов, которые будут использоваться загрузчиком данных для извлечения элементов из набора данных. Как вы объяснили, мы можем просто отсортировать длины и построить разные партии из этого вида:
>>> batch_size = 16
>>> batches = np.split(file_len.argsort()[::-1], batch_size)
У нас должны получиться близкие по длине элементы.
Мы можем реализовать collate_fn
функция для сборки элементов пакета и интеграции динамического заполнения. По сути, это помещает дополнительный определяемый пользователем слой прямо между набором данных и загрузчиком данных. Цель состоит в том, чтобы найти самый длинный элемент в пакете и заполнить все остальные элементы правильным количеством
0
s:
def collate_fn(batch):
longest = max([len(x) for x in batch])
s = np.stack([np.pad(x, (0, longest - len(x))) for x in batch])
return torch.from_numpy(s)
Затем вы можете инициализировать загрузчик данных:
>>> dl = DataLoader(dataset=ds, batch_sampler=batches, collate_fn=collate_fn)
И попробуйте повторить итерацию, как видите, мы получаем партии уменьшающейся длины:
>>> for x in dl:
... print(x.shape)
torch.Size([6, 99])
torch.Size([6, 93])
torch.Size([6, 83])
torch.Size([6, 76])
torch.Size([6, 71])
torch.Size([6, 66])
torch.Size([6, 57])
...
У этого метода есть недостатки, например, распределение элементов всегда будет одинаковым. Это означает, что вы всегда будете получать одни и те же партии в одном и том же порядке появления. Это связано с тем, что этот метод основан на сортировке элементов в наборе данных по их длине, при создании пакетов нет изменений. Вы можете уменьшить этот эффект, перетасовывая пакеты (например, обернув
batches
внутри RandomSampler
). Однако, как я уже сказал, содержимое пакетов будет оставаться неизменным на протяжении всего обучения, что может привести к некоторым проблемам.
Обратите внимание на использование
batch_sampler
в вашем загрузчике данных есть взаимоисключающие опции
batch_size
,
shuffle
, и
sampler
!
Улучшая ответ @Ivan, создание DataLoader состоит из двух шагов:
- Вам необходимо предоставить
BatchSampler
объект, который предоставляет индексы образцов в каждой партии. (Я думаю, что это работает только с наборами данных в стиле карты). - Вам необходимо предоставить
collate_fn
функция, которая будет выполнять заполнение на лету, поэтому вам не нужно заранее фиксировать определенную длину последовательности.
Для пакетного пробоотборника на самом деле есть такой хитрый трюк: скажем, вы хотите иметь партию с размеромB
и иметь последовательности относительно одинаковой длины. Вы рисуете случайные супер-партии размера100*B
, вы сортируете эти супер-пакеты, а затем получаете 100 пакетов с запрошенным свойством. Используя эту процедуру, вы будете иметь разные пакеты при каждом запуске, поскольку вы сортируете не весь набор данных, а только небольшие его фрагменты, которые извлекаются случайным образом. Вот пример кода:
class BatchSampler:
def __init__(self, lengths, batch_size):
# ...
def __iter__(self):
size, step = len(self.lengths), 100*self.batch_size
indices = list(range(size))
random.shuffle(indices)
for i in range(0, size, step):
pool = sorted(indices[i:i+step], key=lambda x: self.lengths[x])
for j in range(0, len(pool), self.batch_size):
yield pool[j:j+self.batch_size]
Для collate_fn вы можете использовать служебную функцию, предоставляемую библиотекой torch: torch.nn.utils.rnn.pad_sequence .
Полный рабочий пример см. в этой публикации в блоге или в этом проекте на github .