Низкая точность проверки с Keras `fit_generator`, но не с` fit` - PullRequest
0 голосов
/ 14 мая 2019

У меня есть набор данных для задачи двоичной классификации, в которой оба класса представлены одинаково. Поскольку набор данных не помещается в память (4 миллиона точек данных), я сохраняю его в виде файла HDF5, который считывается и постепенно добавляется в простую модель Keras с помощью fit_generator. Проблема в том, что я получаю низкую точность проверки с fit_generator, тогда как все в порядке, если я просто использую fit. Я упоминал, что набор данных не умещается в памяти, но для целей отладки и для остальной части этого поста я использую только 100 КБ точек данных 4M.

Поскольку цель состоит в том, чтобы сделать многослойное 10-кратное резюме для полного набора данных, я вручную делю индексы набора данных на индексы для обучающих, проверочных и оценочных наборов. Я вызываю fit_generator с функцией генератора, дающей партии обучающих (или проверочных) образцов и меток, охватывающих указанные индексы из первой четверти файла HDF5, затем из второго квартала и т. Д.

Я знаю, что часть проверки fit_generator использует test_on_batch под капотом, как и evaluate_generator. Я также попробовал решение, используя подходы train_on_batch и test_on_batch, но с тем же результатом: точность проверки низкая с fit_generator и т.п., но высокая с fit если набор данных загружается в память одновременно. Модель одинакова в обоих случаях (fit против fit_generator).

Набор данных и модель

Мой набор данных отладки содержит ~ 100 тыс. Выборок и меток (~ 50 тыс. В классе 0 и ~ 50 тыс. В классе 1). Обучение и проверка выполняется на 75% данных (у меня примерно 60 тысяч образцов для обучения и 15 тысяч для проверки). Эти два класса в равной степени распределены между обучающими и проверочными образцами.

Вот очень простая модель, которую я использую:

input_layer = Input(shape=(2581,), dtype='float32')
hidden_layer = Dense(512, activation='relu', input_shape=(2581, 1))(input_layer)
output_layer = Dense(1, activation='sigmoid')(hidden_layer)

model = Model(inputs=[input_layer], outputs=[output_layer])
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

fit прекрасно работает ...

Поскольку этот небольшой набор данных легко помещается в память, вот как я использую fit напрямую, используя модель, созданную выше; train_idx - индексы для обучающего набора, а valid_idx - индексы для проверочного набора:

model.fit(features[train_idx], labels[train_idx],
          batch_size=128, epochs=5,
          shuffle=True,
          validation_data=(features[valid_idx], labels[valid_idx]))

Вот val_acc, которое я получаю с fit:

58847/58847 [==============================] - 4s 70us/step - loss: 0.4075 - acc: 0.8334 - val_loss: 0.3259 - val_acc: 0.8828
Epoch 2/5
58847/58847 [==============================] - 4s 61us/step - loss: 0.2757 - acc: 0.8960 - val_loss: 0.2686 - val_acc: 0.9039
Epoch 3/5
58847/58847 [==============================] - 4s 61us/step - loss: 0.2219 - acc: 0.9212 - val_loss: 0.2162 - val_acc: 0.9227
Epoch 4/5
58847/58847 [==============================] - 4s 61us/step - loss: 0.1855 - acc: 0.9353 - val_loss: 0.1992 - val_acc: 0.9314
Epoch 5/5
58847/58847 [==============================] - 4s 60us/step - loss: 0.1583 - acc: 0.9456 - val_loss: 0.1763 - val_acc: 0.9390

... но fit_generator не

Я ожидал бы таких же результатов с fit_generator:

model.fit_generator(generate_data(hdf5_file, train_idx, batch_size),
                    steps_per_epoch=len(train_idx) // batch_size,
                    epochs=5,
                    shuffle=False,
                    validation_data=generate_data(hdf5_file, valid_idx, batch_size),
                    validation_steps=len(valid_idx) // batch_size)

То, что я получаю, одинаково val_acc для каждой эпохи, как если бы постоянно предсказывался только один класс:

460/460 [==============================] - 8s 17ms/step - loss: 0.3230 - acc: 0.9447 - val_loss: 6.9277 - val_acc: 0.4941
Epoch 2/5
460/460 [==============================] - 6s 14ms/step - loss: 0.9536 - acc: 0.8627 - val_loss: 7.1385 - val_acc: 0.4941
Epoch 3/5
460/460 [==============================] - 6s 14ms/step - loss: 0.8764 - acc: 0.8839 - val_loss: 7.0521 - val_acc: 0.4941
Epoch 4/5
460/460 [==============================] - 6s 13ms/step - loss: 0.9005 - acc: 0.8885 - val_loss: 7.0459 - val_acc: 0.4941
Epoch 5/5
460/460 [==============================] - 6s 14ms/step - loss: 0.9259 - acc: 0.8907 - val_loss: 7.0880 - val_acc: 0.4941

Обратите внимание:

  • Генератор generate_data используется как для обучения, так и для проверки.
  • fit_generator вызывается с shuffle=False, потому что именно генератор обрабатывает тасование (в любом случае, указание shuffle=True не меняет val_acc).

Генератор метода

Последний кусочек головоломки: генератор. Здесь n_parts - это количество частей, на которые файл HDF5 разделен для загрузки. Затем я сохраняю только те строки - в загруженном в настоящее время part файла HDF5 - которые фактически попадают в выбранную indexes. Сохраненные элементы (partial_features) и метки (partial_labels) на самом деле являются строками с индексами partial_indexes в файле HDF5.

def generate_data(hdf5_file, indexes, batch_size, n_parts=4):
    part = 0
    with h5py.File(hdf5_file, 'r') as h5:
        dset = h5.get('features')
        part_size = dset.shape[0] // n_parts

    while True:
        with h5py.File(hdf5_file, 'r') as h5:
            dset = h5.get('features')
            dset_start = part * part_size
            dset_end = (part + 1) * part_size if part < n_parts - 1 else dset.shape[0]
            partial_features = dset[dset_start:dset_end, :-1]
            partial_labels = dset[dset_start:dset_end, -1]

        partial_indexes = list()
        for index in indexes:
            if dset_start <= index < dset_end:
                partial_indexes.append(index)
        partial_indexes = np.asarray(partial_indexes)

        offset = part * part_size
        part = part + 1 if part < n_parts - 1 else 0
        if not len(partial_indexes):
            continue

        partial_features = partial_features[partial_indexes - offset]
        partial_labels = partial_labels[partial_indexes - offset]

        batch_indexes = [idx for idx in range(0, len(partial_features), batch_size)]

        random.shuffle(batch_indexes)
        for idx in batch_indexes:
            yield np.asarray(partial_features[idx:idx + batch_size, :]), \
                  np.asarray(partial_labels[idx:idx + batch_size])

Я попробовал перетасовать только для тренировочного набора, только для проверочного набора и того и другого. Я попробовал эти комбинации с shuffle=True и shuffle=False в fit_generator. Помимо того, что val_acc может немного измениться, по-прежнему, по сути, он равен ~ 0,5, если я использую fit_generator, и ~ 0,9, если я использую fit.

Вы видите что-то не так с моим подходом? С моим генератором? Любая помощь приветствуется!

Я застрял на этой проблеме уже 10 дней. В качестве альтернативы, какой другой вариант (Keras или другая библиотека) мне нужно обучить модели на наборе данных, который не помещается в память?

1 Ответ

0 голосов
/ 05 июня 2019

Я, наконец, понял это, и я буду публиковать свои результаты для дальнейшего использования в случае, если кто-то еще наткнется на аналогичную проблему: проблема была не в генераторах, а в порядке образцов вФайл HDF5 был.

Модель используется для задачи двоичной классификации, где метки в наборе данных являются либо нулями, либо единицами.Проблема в том, что файл HDF5 изначально содержал все образцы, обозначенные 1, а затем все образцы, отмеченные 0 (где количество положительных и отрицательных образцов примерно одинаково).Это означает, что когда функция генератора разбивает файл HDF5 на 4 части, первые две части содержат только положительные выборки, а последние две части содержат только отрицательные выборки.

Это можно исправить, если выборки записаны в случайном порядкев файл HDF5, так что любая смежная часть файла примерно содержит одинаковое количество положительных и отрицательных выборок.Таким образом, модель представлена ​​с положительными и отрицательными данными в примерно равной пропорции в любой момент времени во время тренировки .

...