Чтение файла .h5 происходит очень медленно

avatar
Dushi Fdz
9 августа 2021 в 01:28
596
1
0

Мои данные хранятся в формате .h5. Я использую генератор данных, чтобы соответствовать модели, и он очень медленный. Фрагмент моего кода приведен ниже.

def open_data_file(filename, readwrite="r"):
    return tables.open_file(filename, readwrite)

data_file_opened = open_data_file(os.path.abspath("../data/data.h5"))

train_generator, validation_generator, n_train_steps, n_validation_steps = get_training_and_validation_generators(
        data_file_opened,
        ......)

где:

def get_training_and_validation_generators(data_file, batch_size, ...):
    training_generator = data_generator(data_file, training_list,....)

функция генератора данных выглядит следующим образом:

def data_generator(data_file, index_list,....):
      orig_index_list = index_list
    while True:
        x_list = list()
        y_list = list()
        if patch_shape:
            index_list = create_patch_index_list(orig_index_list, data_file, patch_shape,
                                                 patch_overlap, patch_start_offset,pred_specific=pred_specific)
        else:
            index_list = copy.copy(orig_index_list)

        while len(index_list) > 0:
            index = index_list.pop()
            add_data(x_list, y_list, data_file, index, augment=augment, augment_flip=augment_flip,
                     augment_distortion_factor=augment_distortion_factor, patch_shape=patch_shape,
                     skip_blank=skip_blank, permute=permute)
            if len(x_list) == batch_size or (len(index_list) == 0 and len(x_list) > 0):
                yield convert_data(x_list, y_list, n_labels=n_labels, labels=labels, num_model=num_model,overlap_label=overlap_label)
                x_list = list()
                y_list = list()

add_data() выглядит следующим образом:

def add_data(x_list, y_list, data_file, index, augment=False, augment_flip=False, augment_distortion_factor=0.25,
             patch_shape=False, skip_blank=True, permute=False):
    '''
    add qualified x,y to the generator list
    '''
#     pdb.set_trace()
    data, truth = get_data_from_file(data_file, index, patch_shape=patch_shape)
    
    if np.sum(truth) == 0:
        return
    if augment:
        affine = np.load('affine.npy')
        data, truth = augment_data(data, truth, affine, flip=augment_flip, scale_deviation=augment_distortion_factor)

    if permute:
        if data.shape[-3] != data.shape[-2] or data.shape[-2] != data.shape[-1]:
            raise ValueError("To utilize permutations, data array must be in 3D cube shape with all dimensions having "
                             "the same length.")
        data, truth = random_permutation_x_y(data, truth[np.newaxis])
    else:
        truth = truth[np.newaxis]

    if not skip_blank or np.any(truth != 0):
        x_list.append(data)
        y_list.append(truth)

Обучение модели:

def train_model(model, model_file,....):
    model.fit(training_generator,
                        steps_per_epoch=steps_per_epoch,
                        epochs=n_epochs,
                        verbose = 2,
                        validation_data=validation_generator,
                        validation_steps=validation_steps)

Мой набор данных большой: data.h5 — 55 ГБ. Для завершения одной эпохи требуется около 7000 с. И я получаю ошибку ошибки сегментации примерно через 6 эпох. Размер пакета установлен равным 1, потому что в противном случае я получаю сообщение об исчерпании ресурсов. Есть ли эффективный способ чтения data.h5 в генераторе, чтобы обучение проходило быстрее и не приводило к ошибкам нехватки памяти?

Источник
Tim Roberts
9 августа 2021 в 01:46
1

Насколько велик файл .h5?

hpaulj
9 августа 2021 в 01:47
1

Похоже, вы используете pytables, а не h5py.

Dushi Fdz
9 августа 2021 в 02:12
0

Размер набора данных составляет 55 ГБ. Данные хранятся в формате .h5 как data.h5. Я использую pytables, чтобы открыть файл.

kcw78
9 августа 2021 в 23:38
0

Сколько раз вы читаете данные из файла .h5 за 1 эпоху? (сколько вызовов для чтения функций?) Скорость уменьшается с увеличением количества операций ввода/вывода. Кроме того, вы используете причудливую индексацию? Это медленнее, чем простые срезы.

Dushi Fdz
9 августа 2021 в 23:54
0

@ kcw78 Количество шагов обучения в каждой эпохе — 2268. Размер моего пакета — 1. Если я увеличу размер пакета, я получу ошибку исчерпания ресурсов. Даже при размере пакета 1 я получаю ошибку сегментации примерно через 6 эпох. Я не использую какую-либо причудливую индексацию. Моя функция генератора данных представлена ​​выше.

kcw78
11 августа 2021 в 18:57
0

За исключением функции open_data_file(), я не вижу никакого кода tables в вашем сообщении. (Это в функции add_data()?) Узкие места в производительности трудно выявить и устранить, не видя код и не понимая схему файла .h5. Если вы не хотите делиться этой информацией, вам нужно написать код, имитирующий то, как add_data() читает ваш файл .h5. Затем вы можете протестировать производительность чтения файлов, чтобы определить, не является ли это причиной проблем с производительностью и стабильностью.

Dushi Fdz
11 августа 2021 в 19:04
0

Я отредактировал вопрос с помощью функции add_data(). Я использовал таблицы при создании данных как data.h5.

kcw78
11 августа 2021 в 22:09
0

Хорошо, я думаю, я понял. data_generator() зацикливается на while len(index_list) > 0:, вызывая add_data(), который вызывает get_data_from_file(). Я предполагаю, что эта функция вызывает функции tables для чтения ваших данных .h5. Насколько велик index_list? Это количество раз, когда вы обращаетесь к файлу в каждую эпоху. Умножьте len(index_list) X эпох (2268), чтобы получить общее количество эпох. Это может быть очень большое число, которое объясняет, почему ваш процесс такой медленный. Чтобы повысить производительность, вам необходимо сократить количество вызовов чтения, считывая больше данных за один раз.

Dushi Fdz
11 августа 2021 в 23:52
0

Длина списка индексов — 3325. Количество шагов обучения в каждой эпохе — 2268. Скажите, пожалуйста, что нужно изменить, чтобы читать больше данных за один раз? Если я увеличу размер пакета, я получу ошибку исчерпания ресурсов.

kcw78
12 августа 2021 в 14:10
0

Цель состоит в том, чтобы уменьшить количество вызовов функции tables для чтения данных. Трудно дать конкретный совет без источника для get_data_from_file(). Что ты читаешь? Данные изображения? Вы читаете по 1 картинке за раз? Если это так, вам нужно реорганизовать свой код, чтобы считывать все нужные изображения для 1 эпохи за 1 вызов. Есть аналогичные вопросы на SO. Дополнительные идеи читайте в комментариях: coderhelper.com/a/67655331/10462884 и coderhelper.com/a/66681133/10462884.

Dushi Fdz
12 августа 2021 в 15:18
0

Спасибо за ссылки. Я читаю данные изображения. Это репозиторий, который я использую для создания данных: github.com/woodywff/brats_2019/blob/…

Salmonstrikes
15 августа 2021 в 12:08
0

Как отмечалось выше, основная проблема, вероятно, связана с неэффективными моделями доступа к данным. HDF5 поддерживает сжатие. Ваш файл данных сильно сжат? Это может быть одним из потенциально многих факторов, которые могут способствовать замедлению ввода-вывода. Кроме того, на аппаратном уровне: spinny или SSD? Объем оперативной памяти? Если у вас большой объем ОЗУ (например, 256 ГБ), а размер несжатых изображений составляет примерно 60 ГБ, рассмотрите возможность загрузки всего ввода в память для быстрого доступа. Если это все еще медленно, то структуры данных/алгоритмы неэффективны или изображения слишком велики для того, для чего был написан код - возможно, даунсэмплинг.

Dushi Fdz
15 августа 2021 в 16:52
0

@Salmonstrikes Да, мои данные сильно сжаты (уровень сжатия равен 5 по шкале от 0 до 9). Должен ли я уменьшить или увеличить уровень сжатия? В моей оперативной памяти 32 ГБ памяти, в моем графическом процессоре — 10 ГБ, а мои данные — 55 ГБ (хранятся как data.h5). Данные создаются как: data_storage = hdf5_file.create_earray(hdf5_file.root, 'data', tables.Float32Atom(), shape=data_shape, filters=filters, expectedrows=n_samples)

kcw78
15 августа 2021 в 17:15
1

@Salmonstrikes делает хорошее замечание о сжатии - оно замедляет ввод-вывод. Иногда это может быть существенно (особенно при более высоких уровнях сжатия — я использую только level=1). Достаточно просто распаковать файл и сравнить производительность. В PyTables есть утилита ptrepack, которая может это сделать. Вот как распаковать файл данных в новый файл: ptrepack --complevel 0 data.h5 data_unc.h5. Измените имя файла данных в коде на data_unc.h5.

Ответы (1)

avatar
kcw78
15 августа 2021 в 17:08
3

Это начало моего ответа. Я посмотрел на ваш код, и у вас много вызовов для чтения данных .h5. По моим подсчетам, генератор делает 6 вызовов чтения для каждого цикла на training_list и validation_list. Итак, это почти 20 тысяч вызовов в ОДНОМ обучающем цикле. Непонятно (мне), вызываются ли генераторы в каждом тренировочном цикле. Если они есть, умножьте на 2268 петель.

Эффективность чтения файла HDF5 зависит от количества вызовов для чтения данных (а не только от объема данных). Другими словами, быстрее прочитать 1 ГБ данных за один вызов, чем прочитать те же данные с 1000 вызовов x 1 МБ за раз. Итак, первое, что нам нужно определить, это количество времени, потраченное на чтение данных из файла HDF5 (для сравнения с вашими 7000).

Я изолировал вызовы PyTables, которые читали файл данных. Исходя из этого, я создал простую программу, которая имитирует поведение вашей функции-генератора. В настоящее время он делает один цикл обучения для всего списка образцов. Увеличьте значения n_train и n_epoch, если вы хотите, чтобы тест выполнялся дольше. (Примечание: синтаксис кода правильный. Однако без файла, поэтому невозможно проверить логику. Я думаю, что это правильно, но вам, возможно, придется исправить небольшие ошибки.)

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

import tables as tb
import numpy as np
from random import shuffle 
import time

with tb.open_file('../data/data.h5', 'r') as data_file:

    n_train = 1
    n_epochs = 1
    loops = n_train*n_epochs
    
    for e_cnt in range(loops):  
        nb_samples = data_file.root.truth.shape[0]
        sample_list = list(range(nb_samples))
        shuffle(sample_list)
        split = 0.80
        n_training = int(len(sample_list) * split)
        training_list = sample_list[:n_training]
        validation_list = sample_list[n_training:]
        
        start = time.time()
        for index_list in [ training_list, validation_list ]:
            shuffle(index_list)
            x_list = list()
            y_list = list()
            
            while len(index_list) > 0:
                index = index_list.pop() 
                
                brain_width = data_file.root.brain_width[index]
                x = np.array([modality_img[index,0,
                                           brain_width[0,0]:brain_width[1,0]+1,
                                           brain_width[0,1]:brain_width[1,1]+1,
                                           brain_width[0,2]:brain_width[1,2]+1] 
                              for modality_img in [data_file.root.t1,
                                                   data_file.root.t1ce,
                                                   data_file.root.flair,
                                                   data_file.root.t2]])
                y = data_file.root.truth[index, 0,
                                         brain_width[0,0]:brain_width[1,0]+1,
                                         brain_width[0,1]:brain_width[1,1]+1,
                                         brain_width[0,2]:brain_width[1,2]+1]    
                
                x_list.append(data)
                y_list.append(truth)
    
        print(f'For loop:{e_cnt}')
        print(f'Time to read all data={time.time()-start:.2f}')
Dushi Fdz
16 августа 2021 в 15:48
0

Большое спасибо за подробный ответ. Я проверю это и посмотрю, есть ли у меня какие-либо ошибки. Не могли бы вы немного объяснить настройку n_train = 1 и n_epochs = 1. Итак, когда вы сказали, что он создает один цикл обучения для всего списка образцов, означает ли это, что он вызывает данные только один раз? Если я тренируюсь (model.fit) в течение 10 эпох, мне не нужно здесь менять n_epochs, не так ли?

kcw78
16 августа 2021 в 18:35
0

Верный. При использовании n_train = 1 и n_epochs = 1 весь список образцов считывается только один раз. Это даст вам представление о времени чтения данных. Я сделал это так, потому что не уверен, когда вызываются генераторы. Я не думаю, что генераторы вызываются для циклов эпох. Я не уверен насчет тренировочных циклов. Кроме того, вы можете сравнить время чтения сжатого и несжатого файла.

Dushi Fdz
17 августа 2021 в 00:36
0

Еще один вопрос, пожалуйста. Если размер пакета больше, чем память графического процессора (10 ГБ), идет ли он в ЦП? Может ли в этом случае произойти ошибка сегментации? Размер моего файла данных составляет 55 ГБ. Потому что, помимо проблемы с медленным обучением, примерно через 6 эпох я получаю ошибку сегментации. Я не уверен, связано ли это с нехваткой памяти.

kcw78
17 августа 2021 в 14:19
1

Сколько времени требуется, чтобы прочитать ваши данные для 1 цикла? Если это «достаточно быстро», ваши проблемы где-то в другом месте. Ваш вопрос выходит за рамки моих знаний об алгоритмах и использовании памяти. Я на 99% уверен, что PyTables использует ЦП (систему) ОЗУ (только). Ошибка сегментации на 6 эпохах звучит как проблема с памятью в TF. Я знаю, что он может использовать память графического процессора, но не знаю, как контролировать использование памяти графического процессора и процессора. Вот интересный SO-вопрос от 2018 года: coderhelper.com/q/51343169/10462884. Дополнительные вопросы и ответы по теме можно найти по тегу [tensorflow] [gpu]. Удачи.