Классы Dataset и Dataloader

Смотреть материал на YouTube | RuTube

На предыдущем занятии мы с вами разобрали несколько способов хранения выборок на внешнем носителе. Теперь возникает вопрос, как считывать эти данные и подавать на вход нейронной сети для ее обучения и последующего тестирования? Конечно, для этого можно было бы написать свою функцию или даже класс, который бы брал на себя этот функционал. Но, так как это типовая задача, то в PyTorch такие классы уже существуют и, как вы уже догадались, они называются:

  • Dataset – универсальное представление обучающих и тестовых данных;
  • Dataloader – универсальный итератор для извлечения данных, например, в процессе обучения или тестирования.

Все эти классы расположены в ветке:

torch.utils.data

и работают в паре друг с другом. Начнем с класса Dataset.

Класс Dataset

Как мы уже знаем, структура выборок и способы их представления могут быть самыми разными. Поэтому чтобы работать с конкретной выборкой необходимо прописать свой собственный класс, унаследованный от класса Dataset. При этом сам класс должен иметь следующий минимальный функционал:

import torch.utils.data as data
 
class MyDataset(data.Dataset): # имя класса MyDataset может быть любым
    def __init__(self): # инициализация переменных объекта класса
        pass
 
    def __getitem__(self, item): # возвращение образа выборки по индексу item
        pass
 
    def __len__(self): # возвращение размера выборки
        pass

То есть, назначение класса MyDataset возвращать образы выборки по указанному индексу item, и общий размер выборки. Для этого создается объект класса:

d_train = MyDataset([набор аргументов])

с которым возможны следующие команды:

x_j, y_j = d_train[j] # входной вектор x_j и целевое значение y_j
data_sz = len(d_train) # размер выборки

Все эти команды отрабатывают благодаря переопределению магических методов __getitem__ и __len__ в классе MyDataset.

Для больших выборок метод __getitem__ загружает в память нужный образ по его индексу и возвращает в виде пары: входной вектор, целевое значение. В результате происходит экономия памяти устройства, в котором хранятся только нужные на данный момент образы выборки, а не вся выборка сразу. Позже мы с вами реализуем такой класс для считывания изображений цифр и обучим НС их классифицировать. Вы увидите во всех деталях, как это может быть сделано.

Класс Dataloader

Итак, класс Dataset позволяет создавать унифицированный (стандартизированный) интерфейс извлечения образов из выборки. Но этого недостаточно. При обучении нейронных сетей (и не только) часто требуется вначале перемешать всю выборку, а затем, извлекать из нее группы наблюдений, которые называются батчами (batch) или мини-батчами (mini-batch). Затем, вся эта группа в виде тензора размером:

(batch_size, x_size)

подается на вход модели (нейронной сети), которая на выходе формирует набор данных (прогнозов) в виде тензора размерностью:

(batch_size, y_size)

После этого корректируются веса нейронной сети, берется следующий mini-batch и процедура обучения повторяется для нового пакета данных.

Так вот, класс Dataloader как раз автоматизирует процесс перемешивания и формирования пакетов образов. Для этого достаточно создать объект этого класса следующей командой:

train_data = data.DataLoader(d_train, batch_size, shuffle=True, drop_last=False)

Здесь d_train – объект класса MyDataset; batch_size – размер батча; shuffle=True – выполнить перемешивание образов выборки; drop_last – отбрасывать или нет последний batch. Аргумент drop_last иногда бывает полезен для отбрасывания последнего батча, так как он может содержать меньшее число образов, указанное в batch_size. Например, если у нас выборка в 100 элементов и batch_size=32, то размер последнего пакета составит:

100 %  32 = 4 образа

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

Значение параметра batch_size мы выбираем самостоятельно. Часто он принимает одно из значений:

16, 32, 64, 128

Хотя, мы можем указать и любое другое разумное значение. Строгого правила здесь нет.

Извлечение пакетов (batch) из выборки

Итак, у нас имеется объект train_data класса Dataloader. И этот объект связан с набором данных, описываемых классом MyDataset. Как теперь, используя объект train_data выбирать последовательно пакеты образов из обучающей выборки? Делается все очень просто. Сам по себе объект train_data можно воспринимать как генератор и перебирать его элементы с помощью стандартного цикла for языка Python:

for x_train, y_train in train_data:
    …

На каждой итерации будем получать входной тензор x_train размерностью:

(batch_size, x_size)

и выходной тензор y_train размерностью:

(batch_size, y_size)

Далее эти тензоры могут быть использованы для одного шага обучения НС по алгоритму back propagation. Как видите, все достаточно просто.

В действительности классы Dataset и Dataloader образуют связку, показанную на рисунке выше. При формировании итератора происходит перемешивание образов выборки (на самом деле перемешиваются лишь индексы, а не сами образы, т.к. они все сразу недоступны в классе Dataloader). А магический метод __next__ вызывается при извлечении текущего пакета (batch). Этот метод обращается к объекту класса Dataset и по индексам формирует текущий mini-batch. Сохраняет его в тензорах и возвращает результат, который мы получаем в цикле for в виде переменных x_train, y_train.

На следующем занятии мы с вами сформируем класс Dataset для работы с выборкой изображений цифр с последующей их классификацией с помощью нейронной сети.

Видео по теме