Как реализовать настраиваемые генераторы данных для включения динамического потока данных в модели Keras

Генераторы данных - одна из самых полезных функций Keras API. Рассмотрим сценарий, в котором у вас много данных, так много, что вы не можете хранить их все сразу в ОЗУ. Вид? Очевидно, что покупка дополнительной оперативной памяти - не вариант.

Что ж, решением этой проблемы может быть загрузка мини-пакетов, загружаемых в модель динамически. Именно этим и занимаются генераторы данных. Они могут динамически генерировать входные данные модели, таким образом формируя конвейер от хранилища к ОЗУ для загрузки данных по мере необходимости. Еще одним преимуществом этого конвейера является то, что можно легко применять процедуры предварительной обработки к этим минипакетам данных, поскольку они подготовлены для подачи в модель.

В этой статье мы увидим, как создать подкласс от класса tf.keras.utils.Sequence для реализации настраиваемых генераторов данных.

ImageDataGenerator

Перво-наперво, теперь мы увидим, как использовать API ImageDataGenerator для конвейерной обработки динамических изображений и, следовательно, решить необходимость реализации пользовательских.

datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True
)

API ImageDataGenerator предоставляет функции конвейерной обработки данных изображений из каталогов, а также из путей, упомянутых в кадре данных. Один из них может включать в себя этапы предварительной обработки, такие как масштабирование, увеличение изображений, которые будут непосредственно применяться к изображениям в реальном времени.

Итак, почему именно кастомные?

Обучение модели не ограничивается одним типом входных данных и целей. Бывают случаи, когда модель загружается сразу несколькими типами входных данных. Например, предположим, что вы работаете над проблемой мультимодальной классификации, когда вам необходимо одновременно обрабатывать текст и данные изображения. Здесь явно нельзя использовать ImageDataGenerator. К тому же загрузка всех данных сразу недоступна. Следовательно, мы решаем эту проблему, реализуя собственный генератор данных.

Реализация настраиваемого генератора данных

Наконец-то начнем с реализации.

Это будет очень общая реализация, и поэтому ее можно будет напрямую скопировать. Вам просто нужно заполнить пробелы / заменить определенные переменные своей собственной логикой.

Как упоминалось ранее, мы создадим подкласс API tf.keras.utils.Sequence.

def __init__(
     self, 
     df, 
     x_col, 
     y_col=None, 
     batch_size=32, 
     num_classes=None,
     shuffle=True
):
     self.batch_size = batch_size
     self.df = dataframe
     self.indices = self.df.index.tolist()
     self.num_classes = num_classes
     self.shuffle = shuffle
     self.x_col = x_col
     self.y_col = y_col
     self.on_epoch_end()

Сначала мы определяем конструктор для инициализации конфигурации генератора. Обратите внимание, что здесь мы предполагаем, что путь к данным находится в столбце фрейма данных. Следовательно, мы определяем параметры x_col и y_col. Это также может быть имя каталога, из которого вы можете загрузить данные.

Метод on_epoch_end - это метод, который вызывается после каждой эпохи. Здесь мы можем добавить такие процедуры, как перемешивание.

def on_epoch_end(self):
     self.index = np.arange(len(self.indices))
     if self.shuffle == True:
          np.random.shuffle(self.index)

По сути, мы изменили порядок строк фрейма данных в этом фрагменте.

Еще один служебный метод, который у нас есть, - это __len__. По сути, он возвращает количество шагов в эпоху, используя образцы и размер пакета.

def __len__(self):
     # Denotes the number of batches per epoch
     return len(self.indices) // self.batch_size

Затем следует метод __getitem__, который вызывается с номером пакета в качестве аргумента для получения заданного пакета данных.

def __getitem__(self, index):
     # Generate one batch of data
     # Generate indices of the batch
     index = self.index[index * self.batch_size:(index + 1) * self.batch_size]
     # Find list of IDs
     batch = [self.indices[k] for k in index]
     # Generate data
     X, y = self.__get_data(batch)
     return X, y

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

Наконец, мы пишем логику генерации данных в методе __ get_data. Поскольку этот метод будет вызван нами, мы можем назвать его как угодно. Более того, нет причин для того, чтобы этот метод был общедоступным, поэтому мы определяем его как частный.

def __get_data(self, batch):
     # X.shape : (batch_size, *dim)
     # We can have multiple Xs and can return them as a list
     X = # logic to load the data from storage
     y = # logic for the target variables
     # Generate data
     for i, id in enumerate(batch):
     # Store sample
          X[i,] = # logic
     # Store class
     y[i] = # labels
     return X, y

Кроме того, мы можем добавить процедуры предварительной обработки / дополнения, чтобы включить их в режиме реального времени. В приведенном выше фрагменте кода X и y загружаются из источников данных в соответствии с аргументом индексов пакета, переданным в метод. Это может быть что угодно, от загрузки изображений до загрузки текстов или и того, и другого одновременно, или любых других данных.

После включения всех методов полный генератор выглядит так:

Заключение

В этой статье мы увидели полезность генераторов данных при обучении моделей с огромным объемом данных. Мы заглянули в API ImageDataGenerator, чтобы узнать, что это такое, и удовлетворить потребность в пользовательских. Затем мы наконец узнали, как реализовать собственный генератор данных, создав подкласс API tf.keras.utils.Sequence.

Не стесняйтесь копировать этот код и добавлять к нему свою собственную логику генератора.

использованная литература